[libgdata] core: Remove the old authentication API in favour of a new GDataAuthorizer API



commit 2957118536be2c51c9e4c8c14b52c95db7717725
Author: Philip Withnall <philip tecnocode co uk>
Date:   Thu May 5 23:29:14 2011 +0100

    core: Remove the old authentication API in favour of a new GDataAuthorizer API
    
    This separates authentication and authorisation from the services themselves,
    allowing authorisation schemes to be plugged in as they pop into and out of
    fashion.
    
    This removes the old gdata_service_authenticate() methods, replacing
    them with an equivalent GDataClientLoginAuthorizer.
    
    Full test cases are provided, covering everything except parsing of error
    responses from the server by GDataClientLoginAuthorizer.
    
    This makes the following API changes:
     â?¢ Rename GDataAuthenticationError to GDataClientLoginAuthorizerError
       Rename GDATA_AUTHENTICATION_ERROR and gdata_authentication_error_quark()
       similarly.
     â?¢ Remove gdata_service_authenticate() in favour of
       using GDataClientLoginAuthorizer with GDataService::authorizer:
        - Remove gdata_service_authenticate(),
          gdata_service_authenticate_async() and
          gdata_service_authenticate_finish().
        - Replace gdata_service_is_authenticated() by
          gdata_service_is_authorized() with much the same functionality.
        - Add GDataService::authorizer, gdata_service_get_authorizer(),
          gdata_service_set_authorizer() and
          gdata_service_get_authorization_domains().
        - Remove gdata_service_get_client_id() in favour of
          GDataClientLoginAuthorizer::client-id.
        - Remove gdata_service_get_username() in favour of
          GDataClientLoginAuthorizer::username.
        - Remove gdata_service_get_password() in favour of
          GDataClientLoginAuthorizer::password.
        - Remove GDataServiceClass->service_name in favour of
          GDataAuthorizationDomain::service-name.
        - Remove GDataServiceClass->authentication_uri and
          GDataServiceClass->parse_authentication_response in favour of
          different GDataAuthorizer implementations.
        - Add GDataAuthorizer parameters to and remove client_id parameters from:
          gdata_calendar_service_new(), gdata_contacts_service_new(),
          gdata_documents_service_new(), gdata_picasaweb_service_new() and
          gdata_youtube_service_new().
     â?¢ Add GDataAuthorizationDomain.
        - Add GDataServiceClass->get_authorization_domains and
          gdata_service_get_authorization_domains().
        - Add auth. domain getters to various GDataService subclasses:
          gdata_youtube_service_get_primary_authorization_domain(),
          gdata_contacts_service_get_primary_authorization_domain(),
          gdata_calendar_service_get_primary_authorization_domain(),
          gdata_picasaweb_service_get_primary_authorization_domain(),
          gdata_documents_service_get_primary_authorization_domain() and
          gdata_documents_service_get_spreadsheet_authorization_domain().
        - Add auth. domain properties to various standalone request objects:
          GDataDownloadStream::authorization-domain with
          gdata_download_stream_get_authorization_domain(),
          GDataUploadStream::authorization-domain with
          gdata_upload_stream_get_authorization_domain() and
          GDataBatchOperation::authorization-domain with
          gdata_batch_operation_get_authorization_domain().
        - Add GDataAccessHandlerIface->get_authorization_domain. This doesn't
          have to be implemented by existing GDataAccessHandlers, but it's
          highly recommended.
        - Add a GDataAuthorizationDomain parameter to
          GDataServiceClass->append_query_headers,
          gdata_service_query(), gdata_service_query_async(),
          gdata_service_query_single_entry(),
          gdata_service_query_single_entry_async(), gdata_service_insert_entry(),
          gdata_service_insert_entry_async(), gdata_service_update_entry(),
          gdata_service_update_entry_async(), gdata_service_delete_entry(),
          gdata_service_delete_entry_async(),
          gdata_batchable_create_operation(),
          gdata_download_stream_new() and gdata_upload_stream_new().
     â?¢ Add GDataAuthorizer as described above, implemented by
       GDataClientLoginAuthorizer.
    
    Helps: bgo#646285

 Makefile.am                                        |   10 +-
 docs/reference/gdata-docs.xml                      |    7 +
 docs/reference/gdata-sections.txt                  |   94 ++-
 gdata/gdata-access-handler.c                       |   14 +-
 gdata/gdata-access-handler.h                       |    9 +-
 gdata/gdata-authorization-domain.c                 |  197 ++++
 gdata/gdata-authorization-domain.h                 |   69 ++
 gdata/gdata-authorizer.c                           |  310 +++++
 gdata/gdata-authorizer.h                           |   90 ++
 gdata/gdata-batch-operation.c                      |   66 +-
 gdata/gdata-batch-operation.h                      |    2 +
 gdata/gdata-batchable.c                            |   18 +-
 gdata/gdata-batchable.h                            |    4 +-
 gdata/gdata-client-login-authorizer.c              | 1218 ++++++++++++++++++++
 gdata/gdata-client-login-authorizer.h              |  130 +++
 gdata/gdata-download-stream.c                      |   79 ++-
 gdata/gdata-download-stream.h                      |    3 +-
 gdata/gdata-private.h                              |   49 +-
 gdata/gdata-service.c                              | 1048 ++++++-----------
 gdata/gdata-service.h                              |  101 +--
 gdata/gdata-upload-stream.c                        |   90 ++-
 gdata/gdata-upload-stream.h                        |    6 +-
 gdata/gdata.h                                      |    3 +
 gdata/gdata.symbols                                |   46 +-
 gdata/media/gdata-media-content.c                  |    2 +-
 gdata/media/gdata-media-thumbnail.c                |    2 +-
 gdata/services/calendar/gdata-calendar-calendar.c  |   11 +-
 gdata/services/calendar/gdata-calendar-service.c   |   88 +-
 gdata/services/calendar/gdata-calendar-service.h   |    4 +-
 gdata/services/contacts/gdata-contacts-contact.c   |    6 +-
 gdata/services/contacts/gdata-contacts-service.c   |   81 +-
 gdata/services/contacts/gdata-contacts-service.h   |    4 +-
 .../services/documents/gdata-documents-document.c  |   18 +-
 gdata/services/documents/gdata-documents-entry.c   |    8 +
 gdata/services/documents/gdata-documents-service.c |  205 ++--
 gdata/services/documents/gdata-documents-service.h |    6 +-
 .../documents/gdata-documents-spreadsheet.c        |    3 +-
 gdata/services/picasaweb/gdata-picasaweb-service.c |  109 ++-
 gdata/services/picasaweb/gdata-picasaweb-service.h |    4 +-
 gdata/services/youtube/gdata-youtube-service.c     |  109 ++-
 gdata/services/youtube/gdata-youtube-service.h     |    4 +-
 gdata/services/youtube/gdata-youtube-video.c       |    2 +-
 gdata/tests/Makefile.am                            |    6 +
 gdata/tests/authorization.c                        |  795 +++++++++++++
 gdata/tests/calendar.c                             |   90 +-
 gdata/tests/client-login-authorizer.c              |  745 ++++++++++++
 gdata/tests/common.h                               |   11 +-
 gdata/tests/contacts.c                             |  110 ++-
 gdata/tests/documents.c                            |  167 ++-
 gdata/tests/general.c                              |    7 +-
 gdata/tests/memory.c                               |    7 +-
 gdata/tests/picasaweb.c                            |   84 +-
 gdata/tests/streams.c                              |    8 +-
 gdata/tests/youtube.c                              |  104 ++-
 po/POTFILES.in                                     |    1 +
 55 files changed, 5178 insertions(+), 1286 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 6280229..f64cf7d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -40,7 +40,7 @@ gdata/gdata-enums.h: $(gdata_headers) Makefile
 
 gdata/gdata-enums.c: $(gdata_headers) Makefile gdata/gdata-enums.h
 	$(AM_V_GEN)($(GLIB_MKENUMS) \
-			--fhead "#include \"gdata-service.h\"\n#include \"gdata-parsable.h\"\n#include \"gdata-batch-operation.h\"\n#include \"gdata-enums.h\"" \
+			--fhead "#include \"gdata-service.h\"\n#include \"gdata-parsable.h\"\n#include \"gdata-batch-operation.h\"\n#include \"gdata-enums.h\"\n#include \"gdata-client-login-authorizer.h\"" \
 			--fprod "\n/* enumerations from \"@filename \" */" \
 			--vhead "GType\n enum_name@_get_type (void)\n{\n  static GType etype = 0;\n  if (etype == 0) {\n    static const G Type@Value values[] = {" \
 			--vprod "      { @VALUENAME@, \"@VALUENAME \", \"@valuenick \" }," \
@@ -158,7 +158,10 @@ gdata_headers = \
 	gdata/gdata-upload-stream.h	\
 	gdata/gdata-comparable.h	\
 	gdata/gdata-batch-operation.h	\
-	gdata/gdata-batchable.h
+	gdata/gdata-batchable.h		\
+	gdata/gdata-authorizer.h	\
+	gdata/gdata-authorization-domain.h	\
+	gdata/gdata-client-login-authorizer.h
 # The following headers are private, and shouldn't be installed:
 private_headers = \
 	gdata/gdata-private.h		\
@@ -293,6 +296,9 @@ gdata_sources = \
 	gdata/gdata-batch-operation.c	\
 	gdata/gdata-batchable.c		\
 	gdata/gdata-batch-feed.c	\
+	gdata/gdata-authorizer.c	\
+	gdata/gdata-authorization-domain.c	\
+	gdata/gdata-client-login-authorizer.c	\
 	\
 	gdata/atom/gdata-author.c	\
 	gdata/atom/gdata-category.c	\
diff --git a/docs/reference/gdata-docs.xml b/docs/reference/gdata-docs.xml
index 4d5070f..5b0e350 100644
--- a/docs/reference/gdata-docs.xml
+++ b/docs/reference/gdata-docs.xml
@@ -47,6 +47,13 @@
 			<xi:include href="xml/gdata-batchable.xml"/>
 			<xi:include href="xml/gdata-batch-operation.xml"/>
 		</chapter>
+
+		<chapter>
+			<title>Authentication/Authorization API</title>
+			<xi:include href="xml/gdata-authorizer.xml"/>
+			<xi:include href="xml/gdata-authorization-domain.xml"/>
+			<xi:include href="xml/gdata-client-login-authorizer.xml"/>
+		</chapter>
 	</part>
 
 	<part>
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index 7490525..715f6ce 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -4,14 +4,13 @@
 GDataService
 GDataServiceClass
 GDataServiceError
-GDataAuthenticationError
 GDataParserError
 GDataOperationType
 GDataQueryProgressCallback
-gdata_service_authenticate
-gdata_service_authenticate_async
-gdata_service_authenticate_finish
-gdata_service_is_authenticated
+gdata_service_is_authorized
+gdata_service_get_authorizer
+gdata_service_set_authorizer
+gdata_service_get_authorization_domains
 gdata_service_query
 gdata_service_query_async
 gdata_service_query_finish
@@ -27,9 +26,6 @@ gdata_service_update_entry_finish
 gdata_service_delete_entry
 gdata_service_delete_entry_async
 gdata_service_delete_entry_finish
-gdata_service_get_client_id
-gdata_service_get_username
-gdata_service_get_password
 gdata_service_get_proxy_uri
 gdata_service_set_proxy_uri
 gdata_service_get_timeout
@@ -48,8 +44,6 @@ gdata_service_error_quark
 GDATA_SERVICE_ERROR
 GDATA_PARSER_ERROR
 gdata_parser_error_quark
-GDATA_AUTHENTICATION_ERROR
-gdata_authentication_error_quark
 <SUBSECTION Private>
 GDataServicePrivate
 </SECTION>
@@ -181,6 +175,7 @@ GDataYouTubeServiceClass
 GDataYouTubeServiceError
 GDataYouTubeStandardFeedType
 gdata_youtube_service_new
+gdata_youtube_service_get_primary_authorization_domain
 gdata_youtube_service_query_videos
 gdata_youtube_service_query_videos_async
 gdata_youtube_service_query_related
@@ -278,6 +273,7 @@ GDataYouTubeVideoPrivate
 GDataContactsService
 GDataContactsServiceClass
 gdata_contacts_service_new
+gdata_contacts_service_get_primary_authorization_domain
 gdata_contacts_service_query_contacts
 gdata_contacts_service_query_contacts_async
 gdata_contacts_service_insert_contact
@@ -451,6 +447,7 @@ GDataContactsContactPrivate
 GDataCalendarService
 GDataCalendarServiceClass
 gdata_calendar_service_new
+gdata_calendar_service_get_primary_authorization_domain
 gdata_calendar_service_query_all_calendars
 gdata_calendar_service_query_all_calendars_async
 gdata_calendar_service_query_own_calendars
@@ -1336,6 +1333,7 @@ GDataYouTubeStatePrivate
 GDataPicasaWebService
 GDataPicasaWebServiceClass
 gdata_picasaweb_service_new
+gdata_picasaweb_service_get_primary_authorization_domain
 gdata_picasaweb_service_get_user
 gdata_picasaweb_service_query_all_albums
 gdata_picasaweb_service_query_all_albums_async
@@ -1718,6 +1716,8 @@ GDataDocumentsService
 GDataDocumentsServiceClass
 GDataDocumentsServiceError
 gdata_documents_service_new
+gdata_documents_service_get_primary_authorization_domain
+gdata_documents_service_get_spreadsheet_authorization_domain
 gdata_documents_service_query_documents
 gdata_documents_service_query_documents_async
 gdata_documents_service_upload_document
@@ -1753,6 +1753,7 @@ GDataDownloadStream
 GDataDownloadStreamClass
 gdata_download_stream_new
 gdata_download_stream_get_service
+gdata_download_stream_get_authorization_domain
 gdata_download_stream_get_cancellable
 gdata_download_stream_get_download_uri
 gdata_download_stream_get_content_type
@@ -1777,6 +1778,7 @@ GDataUploadStreamClass
 gdata_upload_stream_new
 gdata_upload_stream_get_response
 gdata_upload_stream_get_service
+gdata_upload_stream_get_authorization_domain
 gdata_upload_stream_get_cancellable
 gdata_upload_stream_get_method
 gdata_upload_stream_get_upload_uri
@@ -2097,6 +2099,7 @@ gdata_batch_operation_run
 gdata_batch_operation_run_async
 gdata_batch_operation_run_finish
 gdata_batch_operation_get_service
+gdata_batch_operation_get_authorization_domain
 gdata_batch_operation_get_feed_uri
 <SUBSECTION Standard>
 GDATA_BATCH_OPERATION
@@ -2152,3 +2155,74 @@ GDATA_IS_CONTACTS_GROUP_CLASS
 <SUBSECTION Private>
 GDataContactsGroupPrivate
 </SECTION>
+
+<SECTION>
+<FILE>gdata-authorizer</FILE>
+<TITLE>GDataAuthorizer</TITLE>
+GDataAuthorizer
+GDataAuthorizerInterface
+gdata_authorizer_process_request
+gdata_authorizer_is_authorized_for_domain
+gdata_authorizer_refresh_authorization
+gdata_authorizer_refresh_authorization_async
+gdata_authorizer_refresh_authorization_finish
+<SUBSECTION Standard>
+GDATA_TYPE_AUTHORIZER
+GDATA_AUTHORIZER
+GDATA_AUTHORIZER_CLASS
+GDATA_IS_AUTHORIZER
+GDATA_AUTHORIZER_GET_IFACE
+gdata_authorizer_get_type
+</SECTION>
+
+<SECTION>
+<FILE>gdata-authorization-domain</FILE>
+<TITLE>GDataAuthorizationDomain</TITLE>
+GDataAuthorizationDomain
+GDataAuthorizationDomainClass
+gdata_authorization_domain_get_service_name
+gdata_authorization_domain_get_scope
+<SUBSECTION Standard>
+GDATA_AUTHORIZATION_DOMAIN
+GDATA_IS_AUTHORIZATION_DOMAIN
+GDATA_TYPE_AUTHORIZATION_DOMAIN
+gdata_authorization_domain_get_type
+GDATA_AUTHORIZATION_DOMAIN_GET_CLASS
+GDATA_AUTHORIZATION_DOMAIN_CLASS
+GDATA_IS_AUTHORIZATION_DOMAIN_CLASS
+<SUBSECTION Private>
+GDataAuthorizationDomainPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-client-login-authorizer</FILE>
+<TITLE>GDataClientLoginAuthorizer</TITLE>
+GDataClientLoginAuthorizer
+GDataClientLoginAuthorizerClass
+GDataClientLoginAuthorizerError
+gdata_client_login_authorizer_new
+gdata_client_login_authorizer_new_for_authorization_domains
+gdata_client_login_authorizer_authenticate
+gdata_client_login_authorizer_authenticate_async
+gdata_client_login_authorizer_authenticate_finish
+gdata_client_login_authorizer_get_client_id
+gdata_client_login_authorizer_get_username
+gdata_client_login_authorizer_get_password
+gdata_client_login_authorizer_get_proxy_uri
+gdata_client_login_authorizer_set_proxy_uri
+gdata_client_login_authorizer_get_timeout
+gdata_client_login_authorizer_set_timeout
+<SUBSECTION Standard>
+GDATA_TYPE_CLIENT_LOGIN_AUTHORIZER
+GDATA_CLIENT_LOGIN_AUTHORIZER
+GDATA_CLIENT_LOGIN_AUTHORIZER_CLASS
+GDATA_IS_CLIENT_LOGIN_AUTHORIZER
+GDATA_IS_CLIENT_LOGIN_AUTHORIZER_CLASS
+GDATA_CLIENT_LOGIN_AUTHORIZER_GET_CLASS
+gdata_client_login_authorizer_get_type
+GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR
+gdata_client_login_authorizer_error_quark
+gdata_client_login_authorizer_error_get_type
+<SUBSECTION Private>
+GDataClientLoginAuthorizerPrivate
+</SECTION>
diff --git a/gdata/gdata-access-handler.c b/gdata/gdata-access-handler.c
index 3323b04..0646393 100644
--- a/gdata/gdata-access-handler.c
+++ b/gdata/gdata-access-handler.c
@@ -27,7 +27,9 @@
  * access control list (ACL). It has a set of methods which allow the #GDataAccessRule<!-- -->s for the access handler/entry to be retrieved,
  * added, modified and deleted, with immediate effect.
  *
- * When implementing the interface, classes must implement an <function>is_owner_rule</function> function.
+ * When implementing the interface, classes must implement an <function>is_owner_rule</function> function. It's optional to implement a
+ * <function>get_authorization_domain</function> function, but if it's not implemented, any operations on the access handler's
+ * #GDataAccessRule<!-- -->s will be performed unauthorized (i.e. as if by a non-logged-in user). This will not usually work.
  *
  * Since: 0.3.0
  **/
@@ -162,6 +164,8 @@ GDataFeed *
 gdata_access_handler_get_rules (GDataAccessHandler *self, GDataService *service, GCancellable *cancellable,
                                 GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
 {
+	GDataAccessHandlerIface *iface;
+	GDataAuthorizationDomain *domain = NULL;
 	GDataFeed *feed;
 	GDataLink *_link;
 	SoupMessage *message;
@@ -173,7 +177,13 @@ gdata_access_handler_get_rules (GDataAccessHandler *self, GDataService *service,
 
 	_link = gdata_entry_look_up_link (GDATA_ENTRY (self), GDATA_LINK_ACCESS_CONTROL_LIST);
 	g_assert (_link != NULL);
-	message = _gdata_service_query (service, gdata_link_get_uri (_link), NULL, cancellable, error);
+
+	iface = GDATA_ACCESS_HANDLER_GET_IFACE (self);
+	if (iface->get_authorization_domain != NULL) {
+		domain = iface->get_authorization_domain (self);
+	}
+
+	message = _gdata_service_query (service, domain, gdata_link_get_uri (_link), NULL, cancellable, error);
 	if (message == NULL)
 		return NULL;
 
diff --git a/gdata/gdata-access-handler.h b/gdata/gdata-access-handler.h
index 17e4f92..d1b8a2a 100644
--- a/gdata/gdata-access-handler.h
+++ b/gdata/gdata-access-handler.h
@@ -26,6 +26,7 @@
 #include <gdata/gdata-feed.h>
 #include <gdata/gdata-service.h>
 #include <gdata/gdata-access-rule.h>
+#include <gdata/gdata-authorization-domain.h>
 
 G_BEGIN_DECLS
 
@@ -60,15 +61,19 @@ typedef struct _GDataAccessHandler		GDataAccessHandler; /* dummy typedef */
  * GDataAccessHandlerIface:
  * @parent: the parent type
  * @is_owner_rule: a function to return whether the given #GDataAccessRule has the role of an owner (of a #GDataAccessHandler).
+ * @get_authorization_domain: (allow-none): a function to return the #GDataAuthorizationDomain to be used for all operations on the access rules
+ * belonging to this access handler; not implementing this function is equivalent to returning %NULL from it, which signifies that operations on the
+ * access rules don't require authorization; new in version 0.9.0
  *
  * The class structure for the #GDataAccessHandler interface.
  *
- * Since: 0.3.0
- **/
+ * Since: 0.9.0
+ */
 typedef struct {
 	GTypeInterface parent;
 
 	gboolean (*is_owner_rule) (GDataAccessRule *rule);
+	GDataAuthorizationDomain *(*get_authorization_domain) (GDataAccessHandler *self);
 } GDataAccessHandlerIface;
 
 GType gdata_access_handler_get_type (void) G_GNUC_CONST;
diff --git a/gdata/gdata-authorization-domain.c b/gdata/gdata-authorization-domain.c
new file mode 100644
index 0000000..af067be
--- /dev/null
+++ b/gdata/gdata-authorization-domain.c
@@ -0,0 +1,197 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011 <philip tecnocode co uk>
+ *
+ * GData Client is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-authorization-domain
+ * @short_description: GData authorization domain
+ * @stability: Unstable
+ * @include: gdata/gdata-authorization-domain.h
+ *
+ * A #GDataAuthorizationDomain represents a single data domain which a user can authorize libgdata to access. This might be a domain covering the
+ * whole of the user's Google Contacts account, for example. Typically, #GDataService<!-- -->s map to #GDataAuthorizationDomains in a one-to-one
+ * fashion, though some services (such as #GDataDocumentsService) use multiple authorization domains.
+ *
+ * The #GDataAuthorizationDomains<!-- -->s used by a service can be retrieved using gdata_service_get_authorization_domains(). The set of domains
+ * used by a given service is static and will never change at runtime.
+ *
+ * #GDataAuthorizationDomain<!-- -->s are used by a #GDataAuthorizer instance to request authorization to interact with data in those domains when
+ * first authenticating and authorizing with the online service. Typically, a given #GDataAuthorizer will be passed a set of domains (or a service
+ * type, from which it can retrieve the service's set of domains) at construction time, and will use those domains when initially asking the user for
+ * authorization and whenever the authorization is refreshed afterwards. It's not expected that the set of domains used by a #GDataAuthorizer will
+ * change after construction time.
+ *
+ * Note that it's not expected that #GDataAuthorizationDomain<!-- -->s will have to be constructed manually. All #GDataService<!-- -->s should provide
+ * accessor functions to return instances of all the authorization domains they support.
+ *
+ * Since: 0.9.0
+ */
+
+#include <glib.h>
+
+#include "gdata-authorization-domain.h"
+
+static void finalize (GObject *object);
+static void get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+
+struct _GDataAuthorizationDomainPrivate {
+	gchar *service_name;
+	gchar *scope;
+};
+
+enum {
+	PROP_SERVICE_NAME = 1,
+	PROP_SCOPE,
+};
+
+G_DEFINE_TYPE (GDataAuthorizationDomain, gdata_authorization_domain, G_TYPE_OBJECT)
+
+static void
+gdata_authorization_domain_class_init (GDataAuthorizationDomainClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (GDataAuthorizationDomainPrivate));
+
+	gobject_class->get_property = get_property;
+	gobject_class->set_property = set_property;
+	gobject_class->finalize = finalize;
+
+	/**
+	 * GDataAuthorizationDomain:service-name:
+	 *
+	 * The name of the service which contains the authorization domain, as enumerated in the
+	 * <ulink type="http" url="http://code.google.com/apis/documents/faq_gdata.html#clientlogin";>online documentation</ulink>.
+	 *
+	 * Since: 0.9.0
+	 */
+	g_object_class_install_property (gobject_class, PROP_SERVICE_NAME,
+	                                 g_param_spec_string ("service-name",
+	                                                      "Service name", "The name of the service which contains the authorization domain.",
+	                                                      NULL,
+	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataAuthorizationDomain:scope:
+	 *
+	 * A URI detailing the scope of the authorization domain, as enumerated in the
+	 * <ulink type="http" url="http://code.google.com/apis/documents/faq_gdata.html#AuthScopes";>online documentation</ulink>.
+	 *
+	 * Since: 0.9.0
+	 */
+	g_object_class_install_property (gobject_class, PROP_SCOPE,
+	                                 g_param_spec_string ("scope",
+	                                                      "Scope", "A URI detailing the scope of the authorization domain.",
+	                                                      NULL,
+	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_authorization_domain_init (GDataAuthorizationDomain *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_AUTHORIZATION_DOMAIN, GDataAuthorizationDomainPrivate);
+}
+
+static void
+finalize (GObject *object)
+{
+	GDataAuthorizationDomainPrivate *priv = GDATA_AUTHORIZATION_DOMAIN (object)->priv;
+
+	g_free (priv->service_name);
+	g_free (priv->scope);
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_authorization_domain_parent_class)->finalize (object);
+}
+
+static void
+get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+	GDataAuthorizationDomainPrivate *priv = GDATA_AUTHORIZATION_DOMAIN (object)->priv;
+
+	switch (property_id) {
+		case PROP_SERVICE_NAME:
+			g_value_set_string (value, priv->service_name);
+			break;
+		case PROP_SCOPE:
+			g_value_set_string (value, priv->scope);
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static void
+set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+	GDataAuthorizationDomainPrivate *priv = GDATA_AUTHORIZATION_DOMAIN (object)->priv;
+
+	switch (property_id) {
+		/* Construct only */
+		case PROP_SERVICE_NAME:
+			priv->service_name = g_value_dup_string (value);
+			break;
+		/* Construct only */
+		case PROP_SCOPE:
+			priv->scope = g_value_dup_string (value);
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+/**
+ * gdata_authorization_domain_get_service_name:
+ * @self: a #GDataAuthorizationDomain
+ *
+ * Returns the name of the service containing the authorization domain. See #GDataAuthorizationDomain:service-name for more details.
+ *
+ * Return value: name of the service containing the authorization domain
+ *
+ * Since: 0.9.0
+ */
+const gchar *
+gdata_authorization_domain_get_service_name (GDataAuthorizationDomain *self)
+{
+	g_return_val_if_fail (GDATA_IS_AUTHORIZATION_DOMAIN (self), NULL);
+
+	return self->priv->service_name;
+}
+
+/**
+ * gdata_authorization_domain_get_scope:
+ * @self: a #GDataAuthorizationDomain
+ *
+ * Returns a URI detailing the scope of the authorization domain. See #GDataAuthorizationDomain:scope for more details.
+ *
+ * Return value: URI detailing the scope of the authorization domain
+ *
+ * Since: 0.9.0
+ */
+const gchar *
+gdata_authorization_domain_get_scope (GDataAuthorizationDomain *self)
+{
+	g_return_val_if_fail (GDATA_IS_AUTHORIZATION_DOMAIN (self), NULL);
+
+	return self->priv->scope;
+}
diff --git a/gdata/gdata-authorization-domain.h b/gdata/gdata-authorization-domain.h
new file mode 100644
index 0000000..1f7ca1c
--- /dev/null
+++ b/gdata/gdata-authorization-domain.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011 <philip tecnocode co uk>
+ *
+ * GData Client is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_AUTHORIZATION_DOMAIN_H
+#define GDATA_AUTHORIZATION_DOMAIN_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_AUTHORIZATION_DOMAIN		(gdata_authorization_domain_get_type ())
+#define GDATA_AUTHORIZATION_DOMAIN(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_AUTHORIZATION_DOMAIN, GDataAuthorizationDomain))
+#define GDATA_AUTHORIZATION_DOMAIN_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_AUTHORIZATION_DOMAIN, GDataAuthorizationDomainClass))
+#define GDATA_IS_AUTHORIZATION_DOMAIN(o)	(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_AUTHORIZATION_DOMAIN))
+#define GDATA_IS_AUTHORIZATION_DOMAIN_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_AUTHORIZATION_DOMAIN))
+#define GDATA_AUTHORIZATION_DOMAIN_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_AUTHORIZATION_DOMAIN, GDataAuthorizationDomainClass))
+
+typedef struct _GDataAuthorizationDomainPrivate	GDataAuthorizationDomainPrivate;
+
+/**
+ * GDataAuthorizationDomain:
+ *
+ * All the fields in the #GDataAuthorizationDomain structure are private and should never be accessed directly.
+ *
+ * Since: 0.9.0
+ */
+typedef struct {
+	/*< private >*/
+	GObject parent;
+	GDataAuthorizationDomainPrivate *priv;
+} GDataAuthorizationDomain;
+
+/**
+ * GDataAuthorizationDomainClass:
+ *
+ * All the fields in the #GDataAuthorizationDomainClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.9.0
+ */
+typedef struct {
+	/*< private >*/
+	GObjectClass parent;
+} GDataAuthorizationDomainClass;
+
+GType gdata_authorization_domain_get_type (void) G_GNUC_CONST;
+
+const gchar *gdata_authorization_domain_get_service_name (GDataAuthorizationDomain *self) G_GNUC_PURE;
+const gchar *gdata_authorization_domain_get_scope (GDataAuthorizationDomain *self) G_GNUC_PURE;
+
+G_END_DECLS
+
+#endif /* !GDATA_AUTHORIZATION_DOMAIN_H */
diff --git a/gdata/gdata-authorizer.c b/gdata/gdata-authorizer.c
new file mode 100644
index 0000000..413dc5f
--- /dev/null
+++ b/gdata/gdata-authorizer.c
@@ -0,0 +1,310 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011 <philip tecnocode co uk>
+ *
+ * GData Client is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-authorizer
+ * @short_description: GData authorization interface
+ * @stability: Unstable
+ * @include: gdata/gdata-authorizer.h
+ *
+ * The #GDataAuthorizer interface provides a uniform way to implement authentication and authorization processes for use by #GDataService<!-- -->s.
+ * Client code will construct a new #GDataAuthorizer instance of their choosing, such as #GDataClientLoginAuthorizer, for the #GDataService<!-- -->s
+ * which will be used by the client, then authenticates and authorizes with the #GDataAuthorizer instead of the #GDataService. The #GDataService then
+ * uses the #GDataAuthorizer to authorize individual network requests using whatever authorization token was returned to the #GDataAuthorizer by the
+ * Google Accounts service.
+ *
+ * All #GDataAuthorizer implementations are expected to operate against a set of #GDataAuthorizationDomain<!-- -->s which are provided to the
+ * authorizer at construction time. These domains specify which data domains the client expects to access using the #GDataService<!-- -->s they
+ * have using the #GDataAuthorizer instance. Following the principle of least privilege, the set of domains should be the minimum such set of domains
+ * which still allows the client to operate normally. Note that implementations of #GDataAuthorizationDomain may display the list of requested
+ * authorization domains to the user for verification before authorization is granted.
+ *
+ * #GDataAuthorizer implementations are provided for some of the standard authorization processes supported by Google for installed applications, as
+ * listed in their <ulink type="http" url="http://code.google.com/apis/accounts/docs/GettingStarted.html";>online documentation</ulink>:
+ * <itemizedlist>
+ *  <listitem>#GDataClientLoginAuthorizer for
+ *    <ulink type="http" url="http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html";>ClientLogin</ulink></listitem>
+ * </itemizedlist>
+ *
+ * It is quite possible for clients to write their own #GDataAuthorizer implementation. For example, if a client uses OAuth 2.0 and handles
+ * authentication itself, it may want to use its own #GDataAuthorizer implementation which simply exposes the client's existing access token to
+ * libgdata and does nothing more.
+ *
+ * It must be noted that all #GDataAuthorizer implementations must be thread safe, as methods such as gdata_authorizer_refresh_authorization() may be
+ * called from any thread (such as the thread performing an asynchronous query operation) at any time.
+ *
+ * Examples of code using #GDataAuthorizer can be found in the documentation for the various implementations of the #GDataAuthorizer interface.
+ *
+ * Since: 0.9.0
+ */
+
+#include <glib.h>
+
+#include "gdata-authorizer.h"
+
+G_DEFINE_INTERFACE (GDataAuthorizer, gdata_authorizer, G_TYPE_OBJECT)
+
+static void
+gdata_authorizer_default_init (GDataAuthorizerInterface *iface)
+{
+	/* Nothing to see here */
+}
+
+/**
+ * gdata_authorizer_process_request:
+ * @self: a #GDataAuthorizer
+ * @domain: (allow-none): the #GDataAuthorizationDomain the query falls under, or %NULL
+ * @message: the query to process
+ *
+ * Processes @message, adding all the necessary extra headers and parameters to ensure that it's correctly authenticated and authorized under the
+ * given @domain for the online service. Basically, if a query is not processed by calling this method on it, it will be sent to the online service as
+ * if it's a query from a non-logged-in user. Similarly, if the #GDataAuthorizer isn't authenticated or authorized (for @domain), no changes will
+ * be made to the @message.
+ *
+ * @domain may be %NULL if the request doesn't require authorization.
+ *
+ * This modifies @message in place.
+ *
+ * This method is thread safe.
+ *
+ * Since: 0.9.0
+ */
+void
+gdata_authorizer_process_request (GDataAuthorizer *self, GDataAuthorizationDomain *domain, SoupMessage *message)
+{
+	GDataAuthorizerInterface *iface;
+
+	g_return_if_fail (GDATA_IS_AUTHORIZER (self));
+	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
+	g_return_if_fail (SOUP_IS_MESSAGE (message));
+
+	iface = GDATA_AUTHORIZER_GET_IFACE (self);
+	g_assert (iface->process_request != NULL);
+
+	iface->process_request (self, domain, message);
+}
+
+/**
+ * gdata_authorizer_is_authorized_for_domain:
+ * @self: (allow-none): a #GDataAuthorizer, or %NULL
+ * @domain: the #GDataAuthorizationDomain to check against
+ *
+ * Returns whether the #GDataAuthorizer instance believes it's currently authorized to access the given @domain. Note that this will not perform any
+ * network requests, and will just look up the result in the #GDataAuthorizer's local cache of authorizations. This means that the result may be out
+ * of date, as the server may have since invalidated the authorization. If the #GDataAuthorizer class supports timeouts and TTLs on authorizations,
+ * they will not be taken into account; this method effectively returns whether the last successful authorization operation performed on the
+ * #GDataAuthorizer included @domain in the list of requested authorization domains.
+ *
+ * Note that %NULL may be passed as the #GDataAuthorizer, in which case %FALSE will always be returned, regardless of the @domain. This is for
+ * convenience of checking whether a domain is authorized by the #GDataAuthorizer returned by gdata_service_get_authorizer(), which may be %NULL.
+ * For example:
+ * |[
+ * if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (my_service), my_domain) == TRUE) {
+ * 	/<!-- -->* Code to execute only if we're authorized for the given domain *<!-- -->/
+ * }
+ * ]|
+ *
+ * This method is thread safe.
+ *
+ * Return value: %TRUE if the #GDataAuthorizer has been authorized to access @domain, %FALSE otherwise
+ *
+ * Since: 0.9.0
+ */
+gboolean
+gdata_authorizer_is_authorized_for_domain (GDataAuthorizer *self, GDataAuthorizationDomain *domain)
+{
+	GDataAuthorizerInterface *iface;
+
+	g_return_val_if_fail (self == NULL || GDATA_IS_AUTHORIZER (self), FALSE);
+	g_return_val_if_fail (GDATA_IS_AUTHORIZATION_DOMAIN (domain), FALSE);
+
+	if (self == NULL) {
+		return FALSE;
+	}
+
+	iface = GDATA_AUTHORIZER_GET_IFACE (self);
+	g_assert (iface->is_authorized_for_domain != NULL);
+
+	return iface->is_authorized_for_domain (self, domain);
+}
+
+/**
+ * gdata_authorizer_refresh_authorization:
+ * @self: a #GDataAuthorizer
+ * @cancellable: (allow-none): optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Forces the #GDataAuthorizer to refresh any authorization tokens it holds with the online service. This should typically be called when a
+ * #GDataService query returns %GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, and is already called transparently by methods such as
+ * gdata_service_query() and gdata_service_insert_entry() (see their documentation for more details).
+ *
+ * If re-authorization is successful, it's guaranteed that by the time this method returns, the properties containing the relevant authorization
+ * tokens on the #GDataAuthorizer instance will have been updated.
+ *
+ * If %FALSE is returned, @error will be set if (and only if) it's due to a refresh being attempted and failing. If a refresh is not attempted, %FALSE
+ * will be returned but @error will not be set.
+ *
+ * If the #GDataAuthorizer has not been previously authenticated or authorized (using the class' specific methods), no authorization will be
+ * attempted, %FALSE will be returned immediately and @error will not be set.
+ *
+ * Some #GDataAuthorizer implementations may not support refreshing authorization tokens at all; for example if doing so requires user interaction.
+ * %FALSE will be returned immediately in that case and @error will not be set.
+ *
+ * This method is thread safe.
+ *
+ * Return value: %TRUE if an authorization refresh was attempted and was successful, %FALSE if a refresh wasn't attempted or was unsuccessful
+ *
+ * Since: 0.9.0
+ */
+gboolean
+gdata_authorizer_refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, GError **error)
+{
+	GDataAuthorizerInterface *iface;
+
+	g_return_val_if_fail (GDATA_IS_AUTHORIZER (self), FALSE);
+	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	iface = GDATA_AUTHORIZER_GET_IFACE (self);
+
+	/* Return FALSE with no error if the method isn't implemented */
+	if (iface->refresh_authorization == NULL) {
+		return FALSE;
+	}
+
+	return iface->refresh_authorization (self, cancellable, error);
+}
+
+static void
+refresh_authorization_thread (GSimpleAsyncResult *result, GDataAuthorizer *authorizer, GCancellable *cancellable)
+{
+	GError *error = NULL;
+
+	/* Refresh the authorisation and return */
+	gdata_authorizer_refresh_authorization (authorizer, cancellable, &error);
+
+	if (error != NULL) {
+		g_simple_async_result_set_from_error (result, error);
+		g_error_free (error);
+	}
+}
+
+/**
+ * gdata_authorizer_refresh_authorization_async:
+ * @self: a #GDataAuthorizer
+ * @cancellable: (allow-none): optional #GCancellable object, or %NULL
+ * @callback: (allow-none) (scope async): a #GAsyncReadyCallback to call when the authorization refresh operation is finished, or %NULL
+ * @user_data: (closure): data to pass to the @callback function
+ *
+ * Forces the #GDataAuthorizer to refresh any authorization tokens it holds with the online service. @self and @cancellable are reffed when this
+ * method is called, so can safely be freed after this method returns.
+ *
+ * For more details, see gdata_authorizer_refresh_authorization(), which is the synchronous version of this method. If the #GDataAuthorizer class
+ * doesn't implement #GDataAuthorizerInterface.refresh_authorization_async but does implement #GDataAuthorizerInterface.refresh_authorization, the
+ * latter will be called from a new thread to make it asynchronous.
+ *
+ * When the authorization refresh operation is finished, @callback will be called. You can then call gdata_authorizer_refresh_authorization_finish()
+ * to get the results of the operation.
+ *
+ * This method is thread safe.
+ *
+ * Since: 0.9.0
+ */
+void
+gdata_authorizer_refresh_authorization_async (GDataAuthorizer *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+{
+	GDataAuthorizerInterface *iface;
+
+	g_return_if_fail (GDATA_IS_AUTHORIZER (self));
+	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+	iface = GDATA_AUTHORIZER_GET_IFACE (self);
+
+	/* Either both _async() and _finish() must be defined, or they must both be undefined. */
+	g_assert ((iface->refresh_authorization_async == NULL && iface->refresh_authorization_finish == NULL) ||
+	          (iface->refresh_authorization_async != NULL && iface->refresh_authorization_finish != NULL));
+
+	if (iface->refresh_authorization_async != NULL) {
+		/* Call the method */
+		iface->refresh_authorization_async (self, cancellable, callback, user_data);
+	} else if (iface->refresh_authorization != NULL) {
+		/* If the _async() method isn't implemented, fall back to running the sync method in a thread */
+		GSimpleAsyncResult *result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+		                                                        gdata_authorizer_refresh_authorization_async);
+		g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) refresh_authorization_thread, G_PRIORITY_DEFAULT, cancellable);
+		g_object_unref (result);
+
+		return;
+	} else {
+		/* If neither are implemented, immediately return FALSE with no error in a callback */
+		GSimpleAsyncResult *result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+		                                                        gdata_authorizer_refresh_authorization_async);
+		g_simple_async_result_complete_in_idle (result);
+		g_object_unref (result);
+
+		return;
+	}
+}
+
+/**
+ * gdata_authorizer_refresh_authorization_finish:
+ * @self: a #GDataAuthorizer
+ * @async_result: a #GAsyncResult
+ * @error: a #GError, or %NULL
+ *
+ * Finishes an asynchronous authorization refresh operation for the #GDataAuthorizer, as started with gdata_authorizer_refresh_authorization_async().
+ *
+ * This method is thread safe.
+ *
+ * Return value: %TRUE if an authorization refresh was attempted and was successful, %FALSE if a refresh wasn't attempted or was unsuccessful
+ *
+ * Since: 0.9.0
+ */
+gboolean
+gdata_authorizer_refresh_authorization_finish (GDataAuthorizer *self, GAsyncResult *async_result, GError **error)
+{
+	GDataAuthorizerInterface *iface;
+
+	g_return_val_if_fail (GDATA_IS_AUTHORIZER (self), FALSE);
+	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	iface = GDATA_AUTHORIZER_GET_IFACE (self);
+
+	/* Either both _async() and _finish() must be defined, or they must both be undefined. */
+	g_assert ((iface->refresh_authorization_async == NULL && iface->refresh_authorization_finish == NULL) ||
+	          (iface->refresh_authorization_async != NULL && iface->refresh_authorization_finish != NULL));
+
+	if (iface->refresh_authorization_finish != NULL) {
+		/* Call the method */
+		return iface->refresh_authorization_finish (self, async_result, error);
+	} else if (iface->refresh_authorization != NULL) {
+		/* If the _async() method isn't implemented, fall back to finishing off running the sync method in a thread */
+		g_warn_if_fail (g_simple_async_result_is_valid (async_result, G_OBJECT (self), gdata_authorizer_refresh_authorization_async) == TRUE);
+
+		if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (async_result), error) == TRUE) {
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	/* Fall back to just returning FALSE if none of the methods are implemented */
+	return FALSE;
+}
diff --git a/gdata/gdata-authorizer.h b/gdata/gdata-authorizer.h
new file mode 100644
index 0000000..5898348
--- /dev/null
+++ b/gdata/gdata-authorizer.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011 <philip tecnocode co uk>
+ *
+ * GData Client is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_AUTHORIZER_H
+#define GDATA_AUTHORIZER_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <libsoup/soup.h>
+
+#include <gdata/gdata-authorization-domain.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_AUTHORIZER		(gdata_authorizer_get_type ())
+#define GDATA_AUTHORIZER(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_AUTHORIZER, GDataAuthorizer))
+#define GDATA_AUTHORIZER_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_AUTHORIZER, GDataAuthorizerInterface))
+#define GDATA_IS_AUTHORIZER(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_AUTHORIZER))
+#define GDATA_AUTHORIZER_GET_IFACE(o)	(G_TYPE_INSTANCE_GET_INTERFACE ((o), GDATA_TYPE_AUTHORIZER, GDataAuthorizerInterface))
+
+/**
+ * GDataAuthorizer:
+ *
+ * All the fields in the #GDataAuthorizer structure are private and should never be accessed directly.
+ *
+ * Since: 0.9.0
+ */
+typedef struct _GDataAuthorizer		GDataAuthorizer; /* dummy typedef */
+
+/**
+ * GDataAuthorizerInterface:
+ * @parent: the parent type
+ * @process_request: a function to append authorization headers to queries before they are submitted to the online service under the given
+ * authorization domain (which may be %NULL); this must be implemented and must be thread safe
+ * @is_authorized_for_domain: a function to check whether the authorizer is authorized against the given domain; this must be implemented and must
+ * be thread safe
+ * @refresh_authorization: (allow-none): a function to force a refresh of any authorization tokens the authorizer holds, returning %TRUE if a refresh
+ * was attempted and was successful, or %FALSE if a refresh wasn't attempted or was unsuccessful; if this isn't implemented it's assumed %FALSE
+ * would've been returned, if it is implemented it must be thread safe
+ * @refresh_authorization_async: (allow-none): an asynchronous version of @refresh_authorization; if this isn't implemented and @refresh_authorization
+ * is, @refresh_authorization will be called in a thread to simulate this function, whereas if this is implemented @refresh_authorization_finish must
+ * also be implemented and both functions must be thread safe
+ * @refresh_authorization_finish: (allow-none): a finish function for the asynchronous version of @refresh_authorization; this must be implemented
+ * exactly if @refresh_authorization_async is implemented, and must be thread safe if it is implemented
+ *
+ * The class structure for the #GDataAuthorizer interface.
+ *
+ * Since: 0.9.0
+ */
+typedef struct {
+	GTypeInterface parent;
+
+	void (*process_request) (GDataAuthorizer *self, GDataAuthorizationDomain *domain, SoupMessage *message);
+	gboolean (*is_authorized_for_domain) (GDataAuthorizer *self, GDataAuthorizationDomain *domain);
+	gboolean (*refresh_authorization) (GDataAuthorizer *self, GCancellable *cancellable, GError **error);
+	void (*refresh_authorization_async) (GDataAuthorizer *self, GCancellable *cancellable,
+	                                     GAsyncReadyCallback callback, gpointer user_data);
+	gboolean (*refresh_authorization_finish) (GDataAuthorizer *self, GAsyncResult *async_result, GError **error);
+} GDataAuthorizerInterface;
+
+GType gdata_authorizer_get_type (void) G_GNUC_CONST;
+
+void gdata_authorizer_process_request (GDataAuthorizer *self, GDataAuthorizationDomain *domain, SoupMessage *message);
+gboolean gdata_authorizer_is_authorized_for_domain (GDataAuthorizer *self, GDataAuthorizationDomain *domain);
+
+gboolean gdata_authorizer_refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, GError **error);
+void gdata_authorizer_refresh_authorization_async (GDataAuthorizer *self, GCancellable *cancellable,
+                                                   GAsyncReadyCallback callback, gpointer user_data);
+gboolean gdata_authorizer_refresh_authorization_finish (GDataAuthorizer *self, GAsyncResult *async_result, GError **error);
+
+G_END_DECLS
+
+#endif /* !GDATA_AUTHORIZER_H */
diff --git a/gdata/gdata-batch-operation.c b/gdata/gdata-batch-operation.c
index 5492eb7..3bada33 100644
--- a/gdata/gdata-batch-operation.c
+++ b/gdata/gdata-batch-operation.c
@@ -29,6 +29,15 @@
  * gdata_batch_operation_add_deletion(), respectively; run the request with gdata_batch_operation_run() or gdata_batch_operation_run_async(); and
  * handle the results in the callback functions which are invoked by the operation as the results are received and parsed.
  *
+ * If authorization is required for any of the requests in the batch operation, the #GDataService set in #GDataBatchOperation:service must have
+ * a #GDataAuthorizer set as its #GDataService:authorizer property, and that authorizer must be authorized for the #GDataAuthorizationDomain set
+ * in #GDataBatchOperation:authorization-domain. It's not possible for requests in a single batch operation to be authorized under multiple domains;
+ * in that case, the requests must be split up across several batch operations using different authorization domains.
+ *
+ * If all of the requests in the batch operation don't require authorization (i.e. they all operate on public data; see the documentation for the
+ * #GDataService subclass in question's operations for details of which require authorization), #GDataBatchOperation:authorization-domain can be set
+ * to %NULL to save the overhead of sending authorization data to the online service.
+ *
  * <example>
  * 	<title>Running a Synchronous Operation</title>
  * 	<programlisting>
@@ -36,18 +45,21 @@
  *	GDataBatchOperation *operation;
  *	GDataContactsContact *contact;
  *	GDataService *service;
+ *	GDataAuthorizationDomain *domain;
  *
  *	service = create_contacts_service ();
+ *	domain = get_authorization_domain_from_service (service);
  *	contact = create_new_contact ();
  *	batch_link = gdata_feed_look_up_link (contacts_feed, GDATA_LINK_BATCH);
  *
- *	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_link_get_uri (batch_link));
+ *	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), domain, gdata_link_get_uri (batch_link));
  *
  *	/<!-- -->* Add to the operation to insert a new contact and query for another one *<!-- -->/
  *	op_id = gdata_batch_operation_add_insertion (operation, GDATA_ENTRY (contact), insertion_cb, user_data);
  *	op_id2 = gdata_batch_operation_add_query (operation, gdata_entry_get_id (other_contact), GDATA_TYPE_CONTACTS_CONTACT, query_cb, user_data);
  *
  *	g_object_unref (contact);
+ *	g_object_unref (domain);
  *	g_object_unref (service);
  *
  *	/<!-- -->* Run the operations in a blocking fashion. Ideally, check and free the error as appropriate after running the operation. *<!-- -->/
@@ -98,6 +110,7 @@ static void gdata_batch_operation_set_property (GObject *object, guint property_
 
 struct _GDataBatchOperationPrivate {
 	GDataService *service;
+	GDataAuthorizationDomain *authorization_domain;
 	gchar *feed_uri;
 	GHashTable *operations;
 	guint next_id; /* next available operation ID */
@@ -107,7 +120,8 @@ struct _GDataBatchOperationPrivate {
 
 enum {
 	PROP_SERVICE = 1,
-	PROP_FEED_URI
+	PROP_FEED_URI,
+	PROP_AUTHORIZATION_DOMAIN,
 };
 
 G_DEFINE_TYPE (GDataBatchOperation, gdata_batch_operation, G_TYPE_OBJECT)
@@ -139,6 +153,23 @@ gdata_batch_operation_class_init (GDataBatchOperationClass *klass)
 	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
 	/**
+	 * GDataBatchOperation:authorization-domain:
+	 *
+	 * The authorization domain for the batch operation, against which the #GDataService:authorizer for the #GDataBatchOperation:service should be
+	 * authorized. This may be %NULL if authorization is not needed for any of the requests in the batch operation.
+	 *
+	 * All requests in the batch operation must be authorizable under this single authorization domain. If requests need different authorization
+	 * domains, they must be performed in different batch operations.
+	 *
+	 * Since: 0.9.0
+	 */
+	g_object_class_install_property (gobject_class, PROP_AUTHORIZATION_DOMAIN,
+	                                 g_param_spec_object ("authorization-domain",
+	                                                      "Authorization domain", "The authorization domain for the batch operation.",
+	                                                      GDATA_TYPE_AUTHORIZATION_DOMAIN,
+	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
 	 * GDataBatchOperation:feed-uri:
 	 *
 	 * The feed URI that this batch operation will be sent to.
@@ -161,6 +192,9 @@ gdata_batch_operation_get_property (GObject *object, guint property_id, GValue *
 		case PROP_SERVICE:
 			g_value_set_object (value, priv->service);
 			break;
+		case PROP_AUTHORIZATION_DOMAIN:
+			g_value_set_object (value, priv->authorization_domain);
+			break;
 		case PROP_FEED_URI:
 			g_value_set_string (value, priv->feed_uri);
 			break;
@@ -180,6 +214,10 @@ gdata_batch_operation_set_property (GObject *object, guint property_id, const GV
 		case PROP_SERVICE:
 			priv->service = g_value_dup_object (value);
 			break;
+		/* Construct only */
+		case PROP_AUTHORIZATION_DOMAIN:
+			priv->authorization_domain = g_value_dup_object (value);
+			break;
 		case PROP_FEED_URI:
 			priv->feed_uri = g_value_dup_string (value);
 			break;
@@ -203,6 +241,10 @@ gdata_batch_operation_dispose (GObject *object)
 {
 	GDataBatchOperationPrivate *priv = GDATA_BATCH_OPERATION_GET_PRIVATE (object);
 
+	if (priv->authorization_domain != NULL)
+		g_object_unref (priv->authorization_domain);
+	priv->authorization_domain = NULL;
+
 	if (priv->service != NULL)
 		g_object_unref (priv->service);
 	priv->service = NULL;
@@ -241,6 +283,24 @@ gdata_batch_operation_get_service (GDataBatchOperation *self)
 }
 
 /**
+ * gdata_batch_operation_get_authorization_domain:
+ * @self: a #GDataBatchOperation
+ *
+ * Gets the #GDataBatchOperation:authorization-domain property.
+ *
+ * Return value: (transfer none) (allow-none): the #GDataAuthorizationDomain used to authorize the batch operation, or %NULL
+ *
+ * Since: 0.9.0
+ */
+GDataAuthorizationDomain *
+gdata_batch_operation_get_authorization_domain (GDataBatchOperation *self)
+{
+	g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), NULL);
+
+	return self->priv->authorization_domain;
+}
+
+/**
  * gdata_batch_operation_get_feed_uri:
  * @self: a #GDataBatchOperation
  *
@@ -549,7 +609,7 @@ gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable,
 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 	g_return_val_if_fail (priv->has_run == FALSE, FALSE);
 
-	message = _gdata_service_build_message (priv->service, SOUP_METHOD_POST, priv->feed_uri, NULL, TRUE);
+	message = _gdata_service_build_message (priv->service, priv->authorization_domain, SOUP_METHOD_POST, priv->feed_uri, NULL, TRUE);
 
 	/* Build the request */
 	g_get_current_time (&updated);
diff --git a/gdata/gdata-batch-operation.h b/gdata/gdata-batch-operation.h
index 76a05e8..5b9f85e 100644
--- a/gdata/gdata-batch-operation.h
+++ b/gdata/gdata-batch-operation.h
@@ -25,6 +25,7 @@
 
 #include <gdata/gdata-service.h>
 #include <gdata/gdata-entry.h>
+#include <gdata/gdata-authorization-domain.h>
 
 G_BEGIN_DECLS
 
@@ -122,6 +123,7 @@ typedef struct {
 GType gdata_batch_operation_get_type (void) G_GNUC_CONST;
 
 GDataService *gdata_batch_operation_get_service (GDataBatchOperation *self) G_GNUC_PURE;
+GDataAuthorizationDomain *gdata_batch_operation_get_authorization_domain (GDataBatchOperation *self) G_GNUC_PURE;
 const gchar *gdata_batch_operation_get_feed_uri (GDataBatchOperation *self) G_GNUC_PURE;
 
 guint gdata_batch_operation_add_query (GDataBatchOperation *self, const gchar *id, GType entry_type,
diff --git a/gdata/gdata-batchable.c b/gdata/gdata-batchable.c
index ee29a15..df053fa 100644
--- a/gdata/gdata-batchable.c
+++ b/gdata/gdata-batchable.c
@@ -54,20 +54,28 @@ gdata_batchable_get_type (void)
 /**
  * gdata_batchable_create_operation:
  * @self: a #GDataBatchable
+ * @domain: (allow-none): the #GDataAuthorizationDomain to authorize the operation, or %NULL
  * @feed_uri: the URI to send the batch operation request to
  *
  * Creates a new #GDataBatchOperation for the given #GDataBatchable service, and with the given @feed_uri. @feed_uri is normally the %GDATA_LINK_BATCH
- * link URI in the appropriate #GDataFeed from the service.
+ * link URI in the appropriate #GDataFeed from the service. If authorization will be required to perform any of the requests in the batch operation,
+ * @domain must be non-%NULL, and must be an authorization domain which covers all of the requests. Otherwise, @domain may be %NULL if authorization
+ * is not required.
  *
  * Return value: (transfer full): a new #GDataBatchOperation; unref with g_object_unref()
  *
- * Since: 0.7.0
- **/
+ * Since: 0.9.0
+ */
 GDataBatchOperation *
-gdata_batchable_create_operation (GDataBatchable *self, const gchar *feed_uri)
+gdata_batchable_create_operation (GDataBatchable *self, GDataAuthorizationDomain *domain, const gchar *feed_uri)
 {
 	g_return_val_if_fail (GDATA_IS_BATCHABLE (self), NULL);
+	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
 	g_return_val_if_fail (feed_uri != NULL, NULL);
 
-	return g_object_new (GDATA_TYPE_BATCH_OPERATION, "service", self, "feed-uri", feed_uri, NULL);
+	return g_object_new (GDATA_TYPE_BATCH_OPERATION,
+	                     "service", self,
+	                     "authorization-domain", domain,
+	                     "feed-uri", feed_uri,
+	                     NULL);
 }
diff --git a/gdata/gdata-batchable.h b/gdata/gdata-batchable.h
index e67969a..f886760 100644
--- a/gdata/gdata-batchable.h
+++ b/gdata/gdata-batchable.h
@@ -25,6 +25,7 @@
 
 #include <gdata/gdata-service.h>
 #include <gdata/gdata-batch-operation.h>
+#include <gdata/gdata-authorization-domain.h>
 
 G_BEGIN_DECLS
 
@@ -57,7 +58,8 @@ typedef struct {
 
 GType gdata_batchable_get_type (void) G_GNUC_CONST;
 
-GDataBatchOperation *gdata_batchable_create_operation (GDataBatchable *self, const gchar *feed_uri) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+GDataBatchOperation *gdata_batchable_create_operation (GDataBatchable *self, GDataAuthorizationDomain *domain,
+                                                       const gchar *feed_uri) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 
 G_END_DECLS
 
diff --git a/gdata/gdata-client-login-authorizer.c b/gdata/gdata-client-login-authorizer.c
new file mode 100644
index 0000000..842d406
--- /dev/null
+++ b/gdata/gdata-client-login-authorizer.c
@@ -0,0 +1,1218 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011 <philip tecnocode co uk>
+ *
+ * GData Client is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-client-login-authorizer
+ * @short_description: GData ClientLogin authorization interface
+ * @stability: Unstable
+ * @include: gdata/gdata-client-login-authorizer.h
+ *
+ * #GDataClientLoginAuthorizer provides an implementation of the #GDataAuthorizer interface for authentication and authorization using the deprecated
+ * <ulink type="http" url="http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html";>ClientLogin</ulink> process.
+ *
+ * As noted, the ClientLogin process is being deprecated in favour of OAuth 2.0. This API is not (yet) deprecated, however. One of the main reasons
+ * for ClientLogin being deprecated is that it cannot support two-factor authentication as now available to Google Accounts. Any account which has
+ * two-factor authentication enabled has to use a service-specific one-time password instead if a client is authenticating with
+ * #GDataClientLoginAuthorizer. More documentation about this is
+ * <ulink type="http" url="http://www.google.com/support/accounts/bin/static.py?page=guide.cs&guide=1056283&topic=1056286";>available online</ulink>.
+ *
+ * The ClientLogin process is a simple one whereby the user's Google Account username and password are sent over an HTTPS connection to the Google
+ * Account servers (when gdata_client_login_authorizer_authenticate() is called), which return an authorization token. This token is then attached to
+ * all future requests to the online service. A slight complication is that the Google Accounts service may return a CAPTCHA challenge instead of
+ * immediately returning an authorization token. In this case, the #GDataClientLoginAuthorizer::captcha-challenge signal will be emitted, and the
+ * user's response to the CAPTCHA should be returned by the handler.
+ *
+ * ClientLogin does not natively support authorization against multiple authorization domains concurrently with a single authorization token, so it
+ * has to be simulated by maintaining multiple authorization tokens if multiple authorization domains are used. This means that proportionally more
+ * network requests are made when gdata_client_login_authorizer_authenticate() is called, which will be proportionally slower. Handling of the
+ * multiple authorization tokens is otherwise transparent to the client.
+ *
+ * Each authorization token is long lived, so reauthorization is rarely necessary with #GDataClientLoginAuthorizer. Consequently, refreshing
+ * authorization using gdata_authorizer_refresh_authorization() is not supported by #GDataClientLoginAuthorizer, and will immediately return %FALSE
+ * with no error set.
+ *
+ * <example>
+ * 	<title>Authenticating Asynchronously Using ClientLogin</title>
+ * 	<programlisting>
+ *	GDataSomeService *service;
+ *	GDataClientLoginAuthorizer *authorizer;
+ *
+ *	/<!-- -->* Create an authorizer and authenticate and authorize the service we're using, asynchronously. *<!-- -->/
+ *	authorizer = gdata_client_login_authorizer_new ("companyName-applicationName-versionID", GDATA_TYPE_SOME_SERVICE);
+ *	gdata_client_login_authorizer_authenticate_async (authorizer, username, password, cancellable,
+ *	                                                  (GAsyncReadyCallback) authenticate_cb, user_data);
+ *
+ *	/<!-- -->* Create a service object and link it with the authorizer *<!-- -->/
+ *	service = gdata_some_service_new (GDATA_AUTHORIZER (authorizer));
+ *
+ *	static void
+ *	authenticate_cb (GDataClientLoginAuthorizer *authorizer, GAsyncResult *async_result, gpointer user_data)
+ *	{
+ *		GError *error = NULL;
+ *
+ *		if (gdata_client_login_authorizer_authenticate_finish (authorizer, async_result, &error) == FALSE) {
+ *			/<!-- -->* Notify the user of all errors except cancellation errors *<!-- -->/
+ *			if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ *				g_error ("Authentication failed: %s", error->message);
+ *			}
+ *			g_error_free (error);
+ *			return;
+ *		}
+ *
+ *		/<!-- -->* (The client is now authenticated and authorized against the service.
+ *		 * It can now proceed to execute queries on the service object which require the user to be authenticated.) *<!-- -->/
+ *	}
+ *
+ *	g_object_unref (service);
+ *	g_object_unref (authorizer);
+ *	</programlisting>
+ * </example>
+ *
+ * Since: 0.9.0
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <string.h>
+
+#include "gdata-service.h"
+#include "gdata-private.h"
+#include "gdata-marshal.h"
+#include "gdata-client-login-authorizer.h"
+
+/* The default e-mail domain to use for usernames */
+#define EMAIL_DOMAIN "gmail.com"
+
+GQuark
+gdata_client_login_authorizer_error_quark (void)
+{
+	return g_quark_from_static_string ("gdata-client-login-authorizer-error-quark");
+}
+
+static void authorizer_init (GDataAuthorizerInterface *iface);
+static void dispose (GObject *object);
+static void finalize (GObject *object);
+static void get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+
+static void process_request (GDataAuthorizer *self, GDataAuthorizationDomain *domain, SoupMessage *message);
+static gboolean is_authorized_for_domain (GDataAuthorizer *self, GDataAuthorizationDomain *domain);
+
+static void notify_proxy_uri_cb (GObject *gobject, GParamSpec *pspec, GDataClientLoginAuthorizer *self);
+static void notify_timeout_cb (GObject *gobject, GParamSpec *pspec, GObject *self);
+
+struct _GDataClientLoginAuthorizerPrivate {
+	SoupSession *session;
+	SoupURI *proxy_uri; /* cached version only set if gdata_client_login_authorizer_get_proxy_uri() is called */
+
+	gchar *client_id;
+
+	/* Mutex for username, password and auth_tokens. It has to be recursive as the top-level authentication functions need to hold a lock on
+	 * auth_tokens while looping over it, but lower-level functions also need to modify auth_tokens to add the auth_tokens themselves once they're
+	 * returned by the online service. */
+	GStaticRecMutex mutex;
+
+	gchar *username;
+	gchar *password;
+
+	/* Mapping from GDataAuthorizationDomain to string? auth_token; auth_token is NULL for domains which aren't authorised at the moment */
+	GHashTable *auth_tokens;
+};
+
+enum {
+	PROP_CLIENT_ID = 1,
+	PROP_USERNAME,
+	PROP_PASSWORD,
+	PROP_PROXY_URI,
+	PROP_TIMEOUT,
+};
+
+enum {
+	SIGNAL_CAPTCHA_CHALLENGE,
+	LAST_SIGNAL
+};
+
+static guint authorizer_signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE_WITH_CODE (GDataClientLoginAuthorizer, gdata_client_login_authorizer, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDATA_TYPE_AUTHORIZER, authorizer_init))
+
+static void
+gdata_client_login_authorizer_class_init (GDataClientLoginAuthorizerClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (GDataClientLoginAuthorizerPrivate));
+
+	gobject_class->get_property = get_property;
+	gobject_class->set_property = set_property;
+	gobject_class->dispose = dispose;
+	gobject_class->finalize = finalize;
+
+	/**
+	 * GDataClientLoginAuthorizer:client-id:
+	 *
+	 * A client ID for your application (see the
+	 * <ulink url="http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html#Request"; type="http">reference documentation</ulink>).
+	 *
+	 * It is recommended that the ID is of the form <literal><replaceable>company name</replaceable>-<replaceable>application name</replaceable>-
+	 * <replaceable>version ID</replaceable></literal>.
+	 *
+	 * Since: 0.9.0
+	 */
+	g_object_class_install_property (gobject_class, PROP_CLIENT_ID,
+	                                 g_param_spec_string ("client-id",
+	                                                      "Client ID", "A client ID for your application.",
+	                                                      NULL,
+	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataClientLoginAuthorizer:username:
+	 *
+	 * The user's Google username for authentication. This will always be a full e-mail address.
+	 *
+	 * This will only be set after authentication using gdata_client_login_authorizer_authenticate() is completed successfully. It will
+	 * then be set to the username passed to gdata_client_login_authorizer_authenticate(), and a #GObject::notify signal will be emitted. If
+	 * authentication fails, it will be set to %NULL.
+	 *
+	 * Since: 0.9.0
+	 */
+	g_object_class_install_property (gobject_class, PROP_USERNAME,
+	                                 g_param_spec_string ("username",
+	                                                      "Username", "The user's Google username for authentication.",
+	                                                      NULL,
+	                                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataClientLoginAuthorizer:password:
+	 *
+	 * The user's account password for authentication.
+	 *
+	 * This will only be set after authentication using gdata_client_login_authorizer_authenticate() is completed successfully. It will
+	 * then be set to the password passed to gdata_client_login_authorizer_authenticate(), and a #GObject::notify signal will be emitted. If
+	 * authentication fails, it will be set to %NULL.
+	 *
+	 * Since: 0.9.0
+	 */
+	g_object_class_install_property (gobject_class, PROP_PASSWORD,
+	                                 g_param_spec_string ("password",
+	                                                      "Password", "The user's account password for authentication.",
+	                                                      NULL,
+	                                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataClientLoginAuthorizer:proxy-uri:
+	 *
+	 * The proxy URI used internally for all network requests.
+	 *
+	 * Since: 0.9.0
+	 */
+	g_object_class_install_property (gobject_class, PROP_PROXY_URI,
+	                                 g_param_spec_boxed ("proxy-uri",
+	                                                     "Proxy URI", "The proxy URI used internally for all network requests.",
+	                                                     SOUP_TYPE_URI,
+	                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataClientLoginAuthorizer:timeout:
+	 *
+	 * A timeout, in seconds, for network operations. If the timeout is exceeded, the operation will be cancelled and
+	 * %GDATA_SERVICE_ERROR_NETWORK_ERROR will be returned.
+	 *
+	 * If the timeout is <code class="literal">0</code>, operations will never time out.
+	 *
+	 * Since: 0.9.0
+	 */
+	g_object_class_install_property (gobject_class, PROP_TIMEOUT,
+	                                 g_param_spec_uint ("timeout",
+	                                                    "Timeout", "A timeout, in seconds, for network operations.",
+	                                                    0, G_MAXUINT, 0,
+	                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataClientLoginAuthorizer::captcha-challenge:
+	 * @authorizer: the #GDataClientLoginAuthorizer which received the challenge
+	 * @uri: the URI of the CAPTCHA image to be used
+	 *
+	 * The #GDataClientLoginAuthorizer::captcha-challenge signal is emitted during the authentication process if the authorizer requires a CAPTCHA
+	 * to be completed. The URI of a CAPTCHA image is given, and the program should display this to the user, and return their response (the text
+	 * displayed in the image). There is no timeout imposed by the library for the response.
+	 *
+	 * Return value: a newly allocated string containing the text in the CAPTCHA image
+	 *
+	 * Since: 0.9.0
+	 */
+	authorizer_signals[SIGNAL_CAPTCHA_CHALLENGE] = g_signal_new ("captcha-challenge",
+	                                                             G_TYPE_FROM_CLASS (klass),
+	                                                             G_SIGNAL_RUN_LAST,
+	                                                             0, NULL, NULL,
+	                                                             gdata_marshal_STRING__OBJECT_STRING,
+	                                                             G_TYPE_STRING, 1, G_TYPE_STRING);
+}
+
+static void
+authorizer_init (GDataAuthorizerInterface *iface)
+{
+	iface->process_request = process_request;
+	iface->is_authorized_for_domain = is_authorized_for_domain;
+}
+
+static void
+gdata_client_login_authorizer_init (GDataClientLoginAuthorizer *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_CLIENT_LOGIN_AUTHORIZER, GDataClientLoginAuthorizerPrivate);
+
+	/* Set up the authentication mutex */
+	g_static_rec_mutex_init (&(self->priv->mutex));
+	self->priv->auth_tokens = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, g_free);
+
+	/* Set up the session */
+	self->priv->session = _gdata_service_build_session ();
+
+	/* Proxy the SoupSession's proxy-uri and timeout properties */
+	g_signal_connect (self->priv->session, "notify::proxy-uri", (GCallback) notify_proxy_uri_cb, self);
+	g_signal_connect (self->priv->session, "notify::timeout", (GCallback) notify_timeout_cb, self);
+}
+
+static void
+dispose (GObject *object)
+{
+	GDataClientLoginAuthorizerPrivate *priv = GDATA_CLIENT_LOGIN_AUTHORIZER (object)->priv;
+
+	if (priv->session != NULL) {
+		g_object_unref (priv->session);
+	}
+	priv->session = NULL;
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_client_login_authorizer_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	GDataClientLoginAuthorizerPrivate *priv = GDATA_CLIENT_LOGIN_AUTHORIZER (object)->priv;
+
+	g_free (priv->username);
+	g_free (priv->password);
+	g_free (priv->client_id);
+	g_hash_table_destroy (priv->auth_tokens);
+	g_static_rec_mutex_free (&(priv->mutex));
+
+	if (priv->proxy_uri != NULL) {
+		soup_uri_free (priv->proxy_uri);
+	}
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_client_login_authorizer_parent_class)->finalize (object);
+}
+
+static void
+get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+	GDataClientLoginAuthorizerPrivate *priv = GDATA_CLIENT_LOGIN_AUTHORIZER (object)->priv;
+
+	switch (property_id) {
+		case PROP_CLIENT_ID:
+			g_value_set_string (value, priv->client_id);
+			break;
+		case PROP_USERNAME:
+			g_static_rec_mutex_lock (&(priv->mutex));
+			g_value_set_string (value, priv->username);
+			g_static_rec_mutex_unlock (&(priv->mutex));
+			break;
+		case PROP_PASSWORD:
+			g_static_rec_mutex_lock (&(priv->mutex));
+			g_value_set_string (value, priv->password);
+			g_static_rec_mutex_unlock (&(priv->mutex));
+			break;
+		case PROP_PROXY_URI:
+			g_value_set_boxed (value, gdata_client_login_authorizer_get_proxy_uri (GDATA_CLIENT_LOGIN_AUTHORIZER (object)));
+			break;
+		case PROP_TIMEOUT:
+			g_value_set_uint (value, gdata_client_login_authorizer_get_timeout (GDATA_CLIENT_LOGIN_AUTHORIZER (object)));
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static void
+set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+	GDataClientLoginAuthorizerPrivate *priv = GDATA_CLIENT_LOGIN_AUTHORIZER (object)->priv;
+
+	switch (property_id) {
+		case PROP_CLIENT_ID:
+			priv->client_id = g_value_dup_string (value);
+			break;
+		case PROP_PROXY_URI:
+			gdata_client_login_authorizer_set_proxy_uri (GDATA_CLIENT_LOGIN_AUTHORIZER (object), g_value_get_boxed (value));
+			break;
+		case PROP_TIMEOUT:
+			gdata_client_login_authorizer_set_timeout (GDATA_CLIENT_LOGIN_AUTHORIZER (object), g_value_get_uint (value));
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static void
+process_request (GDataAuthorizer *self, GDataAuthorizationDomain *domain, SoupMessage *message)
+{
+	const gchar *auth_token;
+	GDataClientLoginAuthorizerPrivate *priv = GDATA_CLIENT_LOGIN_AUTHORIZER (self)->priv;
+
+	/* If the domain's NULL, return immediately */
+	if (domain == NULL) {
+		return;
+	}
+
+	/* Set the authorisation header */
+	g_static_rec_mutex_lock (&(priv->mutex));
+
+	auth_token = (const gchar*) g_hash_table_lookup (priv->auth_tokens, domain);
+
+	if (auth_token != NULL) {
+		/* Ensure that we're using HTTPS: if not, we shouldn't set the Authorization header or we could be revealing the auth token to
+		 * anyone snooping the connection, which would give them the same rights as us on the user's data. Generally a bad thing to happen. */
+		if (soup_message_get_uri (message)->scheme != SOUP_URI_SCHEME_HTTPS) {
+			g_warning ("Not authorizing a non-HTTPS message with the user's ClientLogin auth token as the connection isn't secure.");
+		} else {
+			gchar *authorisation_header = g_strdup_printf ("GoogleLogin auth=%s", auth_token);
+			soup_message_headers_append (message->request_headers, "Authorization", authorisation_header);
+			g_free (authorisation_header);
+		}
+	}
+
+	g_static_rec_mutex_unlock (&(priv->mutex));
+}
+
+static gboolean
+is_authorized_for_domain (GDataAuthorizer *self, GDataAuthorizationDomain *domain)
+{
+	GDataClientLoginAuthorizerPrivate *priv = GDATA_CLIENT_LOGIN_AUTHORIZER (self)->priv;
+	gpointer result;
+
+	g_static_rec_mutex_lock (&(priv->mutex));
+	result = g_hash_table_lookup (priv->auth_tokens, domain);
+	g_static_rec_mutex_unlock (&(priv->mutex));
+
+	return (result != NULL) ? TRUE : FALSE;
+}
+
+/**
+ * gdata_client_login_authorizer_new:
+ * @client_id: your application's client ID
+ * @service_type: the #GType of a #GDataService subclass which the #GDataClientLoginAuthorizer will be used with
+ *
+ * Creates a new #GDataClientLoginAuthorizer. The @client_id must be unique for your application, and as registered with Google.
+ *
+ * The #GDataAuthorizationDomain<!-- -->s for the given @service_type (i.e. as returned by gdata_service_get_authorization_domains()) are the ones the
+ * user will be logged in to using the provided username and password when gdata_client_login_authorizer_authenticate() is called. Note that the same
+ * username and password will be used for all domains.
+ *
+ * Return value: (transfer full): a new #GDataClientLoginAuthorizer, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.9.0
+ */
+GDataClientLoginAuthorizer *
+gdata_client_login_authorizer_new (const gchar *client_id, GType service_type)
+{
+	g_return_val_if_fail (client_id != NULL && *client_id != '\0', NULL);
+	g_return_val_if_fail (g_type_is_a (service_type, GDATA_TYPE_SERVICE), NULL);
+
+	return gdata_client_login_authorizer_new_for_authorization_domains (client_id,
+	                                                                    gdata_service_get_authorization_domains (service_type));
+}
+
+/**
+ * gdata_client_login_authorizer_new_for_authorization_domains:
+ * @client_id: your application's client ID
+ * @authorization_domains: (element-type GDataAuthorizationDomain) (transfer none): a non-empty list of #GDataAuthorizationDomain<!-- -->s to be
+ * authorized against by the #GDataClientLoginAuthorizer
+ *
+ * Creates a new #GDataClientLoginAuthorizer. The @client_id must be unique for your application, and as registered with Google. This function is
+ * intended to be used only when the default authorization domain list for a single #GDataService, as used by gdata_client_login_authorizer_new(),
+ * isn't suitable. For example, this could be because the #GDataClientLoginAuthorizer will be used with multiple #GDataService subclasses, or because
+ * the client requires a specific set of authorization domains.
+ *
+ * The specified #GDataAuthorizationDomain<!-- -->s are the ones the user will be logged in to using the provided username and password when
+ * gdata_client_login_authorizer_authenticate() is called. Note that the same username and password will be used for all domains.
+ *
+ * Return value: (transfer full): a new #GDataClientLoginAuthorizer, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.9.0
+ */
+GDataClientLoginAuthorizer *
+gdata_client_login_authorizer_new_for_authorization_domains (const gchar *client_id, GList *authorization_domains)
+{
+	GList *i;
+	GDataClientLoginAuthorizer *authorizer;
+
+	g_return_val_if_fail (client_id != NULL && *client_id != '\0', NULL);
+	g_return_val_if_fail (authorization_domains != NULL, NULL);
+
+	authorizer = GDATA_CLIENT_LOGIN_AUTHORIZER (g_object_new (GDATA_TYPE_CLIENT_LOGIN_AUTHORIZER,
+	                                                          "client-id", client_id,
+	                                                          NULL));
+
+	/* Register all the domains with the authorizer */
+	for (i = authorization_domains; i != NULL; i = i->next) {
+		g_return_val_if_fail (GDATA_IS_AUTHORIZATION_DOMAIN (i->data), NULL);
+
+		/* We don't have to lock the authoriser's mutex here as no other code has seen the authoriser yet */
+		g_hash_table_insert (authorizer->priv->auth_tokens, g_object_ref (GDATA_AUTHORIZATION_DOMAIN (i->data)), NULL);
+	}
+
+	return authorizer;
+}
+
+/* Called in the main thread to notify of changes to the username and password properties from the authentication thread */
+static gboolean
+notify_authentication_details_cb (GDataClientLoginAuthorizer *self)
+{
+	GObject *authorizer = G_OBJECT (self);
+
+	g_object_freeze_notify (authorizer);
+	g_object_notify (authorizer, "username");
+	g_object_notify (authorizer, "password");
+	g_object_thaw_notify (authorizer);
+
+	/* Only execute once */
+	return FALSE;
+}
+
+static void
+set_authentication_details (GDataClientLoginAuthorizer *self, const gchar *username, const gchar *password, gboolean is_async)
+{
+	GDataClientLoginAuthorizerPrivate *priv = self->priv;
+
+	g_static_rec_mutex_lock (&(priv->mutex));
+
+	/* Ensure the username is always a full e-mail address */
+	g_free (priv->username);
+	if (username != NULL && strchr (username, '@') == NULL) {
+		priv->username = g_strdup_printf ("%s@" EMAIL_DOMAIN, username);
+	} else {
+		priv->username = g_strdup (username);
+	}
+
+	g_free (priv->password);
+	priv->password = g_strdup (password);
+
+	g_static_rec_mutex_unlock (&(priv->mutex));
+
+	/* Notify of the property changes in the main thread; i.e. if we're running an async operation, schedule the notification in an idle
+	 * callback; but if we're running a sync operation, emit them immediately.
+	 * This guarantees that:
+	 *  â?¢ notifications will always be emitted before gdata_client_login_authorizer_authenticate() returns; and
+	 *  â?¢ notifications will always be emitted in the main thread for calls to gdata_client_login_authorizer_authenticate_async(). */
+	if (is_async == TRUE) {
+		g_idle_add ((GSourceFunc) notify_authentication_details_cb, self);
+	} else {
+		notify_authentication_details_cb (self);
+	}
+}
+
+static gboolean
+parse_authentication_response (GDataClientLoginAuthorizer *self, GDataAuthorizationDomain *domain, guint status,
+                               const gchar *response_body, gint length, GError **error)
+{
+	GDataClientLoginAuthorizerPrivate *priv = self->priv;
+	gchar *auth_start, *auth_end, *auth_token;
+
+	/* Parse the response */
+	auth_start = strstr (response_body, "Auth=");
+	if (auth_start == NULL) {
+		goto protocol_error;
+	}
+	auth_start += strlen ("Auth=");
+
+	auth_end = strstr (auth_start, "\n");
+	if (auth_end == NULL) {
+		goto protocol_error;
+	}
+
+	auth_token = g_strndup (auth_start, auth_end - auth_start);
+	if (auth_token == NULL || strlen (auth_token) == 0) {
+		g_free (auth_token);
+		goto protocol_error;
+	}
+
+	g_static_rec_mutex_lock (&(priv->mutex));
+	g_hash_table_insert (priv->auth_tokens, g_object_ref (domain), auth_token);
+	g_static_rec_mutex_unlock (&(priv->mutex));
+
+	return TRUE;
+
+protocol_error:
+	g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
+	                     _("The server returned a malformed response."));
+	return FALSE;
+}
+
+static void
+parse_error_response (GDataClientLoginAuthorizer *self, guint status, const gchar *reason_phrase, const gchar *response_body, gint length,
+                      GError **error)
+{
+	/* We prefer to include the @response_body in the error message, but if it's empty, fall back to the @reason_phrase */
+	if (response_body == NULL || *response_body == '\0') {
+		response_body = reason_phrase;
+	}
+
+	/* See: http://code.google.com/apis/gdata/docs/2.0/reference.html#HTTPStatusCodes */
+	switch (status) {
+		case SOUP_STATUS_CANT_RESOLVE:
+		case SOUP_STATUS_CANT_CONNECT:
+		case SOUP_STATUS_SSL_FAILED:
+		case SOUP_STATUS_IO_ERROR:
+			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NETWORK_ERROR,
+			             _("Cannot connect to the service's server."));
+			return;
+		case SOUP_STATUS_CANT_RESOLVE_PROXY:
+		case SOUP_STATUS_CANT_CONNECT_PROXY:
+			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROXY_ERROR,
+			             _("Cannot connect to the proxy server."));
+			return;
+		case SOUP_STATUS_MALFORMED:
+		case SOUP_STATUS_BAD_REQUEST: /* 400 */
+			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
+			             /* Translators: the parameter is an error message returned by the server. */
+			             _("Invalid request URI or header, or unsupported nonstandard parameter: %s"), response_body);
+			return;
+		case SOUP_STATUS_UNAUTHORIZED: /* 401 */
+		case SOUP_STATUS_FORBIDDEN: /* 403 */
+			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+			             /* Translators: the parameter is an error message returned by the server. */
+			             _("Authentication required: %s"), response_body);
+			return;
+		case SOUP_STATUS_NOT_FOUND: /* 404 */
+			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NOT_FOUND,
+			             /* Translators: the parameter is an error message returned by the server. */
+			             _("The requested resource was not found: %s"), response_body);
+			return;
+		case SOUP_STATUS_CONFLICT: /* 409 */
+		case SOUP_STATUS_PRECONDITION_FAILED: /* 412 */
+			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_CONFLICT,
+			             /* Translators: the parameter is an error message returned by the server. */
+			             _("The entry has been modified since it was downloaded: %s"), response_body);
+			return;
+		case SOUP_STATUS_INTERNAL_SERVER_ERROR: /* 500 */
+		default:
+			/* We'll fall back to a generic error, below */
+			break;
+	}
+
+	/* If the error hasn't been handled already, throw a generic error */
+	g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
+	             /* Translators: the first parameter is an HTTP status,
+	              * and the second is an error message returned by the server. */
+	             _("Error code %u when authenticating: %s"), status, response_body);
+}
+
+static gboolean
+authenticate (GDataClientLoginAuthorizer *self, GDataAuthorizationDomain *domain, const gchar *username, const gchar *password,
+              gchar *captcha_token, gchar *captcha_answer, GCancellable *cancellable, GError **error)
+{
+	GDataClientLoginAuthorizerPrivate *priv = self->priv;
+	SoupMessage *message;
+	gchar *request_body;
+	const gchar *service_name;
+	guint status;
+	gboolean retval;
+
+	/* Prepare the request */
+	service_name = gdata_authorization_domain_get_service_name (domain);
+	request_body = soup_form_encode ("accountType", "HOSTED_OR_GOOGLE",
+	                                 "Email", username,
+	                                 "Passwd", password,
+	                                 "service", service_name,
+	                                 "source", priv->client_id,
+	                                 (captcha_token == NULL) ? NULL : "logintoken", captcha_token,
+	                                 "loginanswer", captcha_answer,
+	                                 NULL);
+
+	/* Free the CAPTCHA token and answer if necessary */
+	g_free (captcha_token);
+	g_free (captcha_answer);
+
+	/* Build the message */
+	message = soup_message_new (SOUP_METHOD_POST, "https://www.google.com/accounts/ClientLogin";);
+	soup_message_set_request (message, "application/x-www-form-urlencoded", SOUP_MEMORY_TAKE, request_body, strlen (request_body));
+
+	/* Send the message */
+	_gdata_service_actually_send_message (priv->session, message, cancellable, error);
+	status = message->status_code;
+
+	if (status == SOUP_STATUS_CANCELLED) {
+		/* Cancelled (the error has already been set) */
+		g_object_unref (message);
+		return FALSE;
+	} else if (status != SOUP_STATUS_OK) {
+		const gchar *response_body = message->response_body->data;
+		gchar *error_start, *error_end, *uri_start, *uri_end, *uri = NULL;
+
+		/* Parse the error response; see: http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html#Errors */
+		if (response_body == NULL) {
+			goto protocol_error;
+		}
+
+		/* Error */
+		error_start = strstr (response_body, "Error=");
+		if (error_start == NULL) {
+			goto protocol_error;
+		}
+		error_start += strlen ("Error=");
+
+		error_end = strstr (error_start, "\n");
+		if (error_end == NULL) {
+			goto protocol_error;
+		}
+
+		if (strncmp (error_start, "CaptchaRequired", error_end - error_start) == 0) {
+			const gchar *captcha_base_uri = "http://www.google.com/accounts/";;
+			gchar *captcha_start, *captcha_end, *captcha_uri, *new_captcha_answer;
+			guint captcha_base_uri_length;
+
+			/* CAPTCHA required to log in */
+			captcha_start = strstr (response_body, "CaptchaUrl=");
+			if (captcha_start == NULL) {
+				goto protocol_error;
+			}
+			captcha_start += strlen ("CaptchaUrl=");
+
+			captcha_end = strstr (captcha_start, "\n");
+			if (captcha_end == NULL) {
+				goto protocol_error;
+			}
+
+			/* Do some fancy memory stuff to save ourselves another alloc */
+			captcha_base_uri_length = strlen (captcha_base_uri);
+			captcha_uri = g_malloc (captcha_base_uri_length + (captcha_end - captcha_start) + 1);
+			memcpy (captcha_uri, captcha_base_uri, captcha_base_uri_length);
+			memcpy (captcha_uri + captcha_base_uri_length, captcha_start, (captcha_end - captcha_start));
+			captcha_uri[captcha_base_uri_length + (captcha_end - captcha_start)] = '\0';
+
+			/* Request a CAPTCHA answer from the application */
+			g_signal_emit (self, authorizer_signals[SIGNAL_CAPTCHA_CHALLENGE], 0, captcha_uri, &new_captcha_answer);
+			g_free (captcha_uri);
+
+			if (new_captcha_answer == NULL || *new_captcha_answer == '\0') {
+				/* Translators: see http://en.wikipedia.org/wiki/CAPTCHA for information about CAPTCHAs */
+				g_set_error_literal (error, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_CAPTCHA_REQUIRED,
+				                     _("A CAPTCHA must be filled out to log in."));
+				goto login_error;
+			}
+
+			/* Get the CAPTCHA token */
+			captcha_start = strstr (response_body, "CaptchaToken=");
+			if (captcha_start == NULL) {
+				goto protocol_error;
+			}
+			captcha_start += strlen ("CaptchaToken=");
+
+			captcha_end = strstr (captcha_start, "\n");
+			if (captcha_end == NULL) {
+				goto protocol_error;
+			}
+
+			/* Save the CAPTCHA token and answer, and attempt to log in with them */
+			g_object_unref (message);
+
+			return authenticate (self, domain, username, password,
+			                     g_strndup (captcha_start, captcha_end - captcha_start), new_captcha_answer,
+			                     cancellable, error);
+		} else if (strncmp (error_start, "Unknown", error_end - error_start) == 0) {
+			goto protocol_error;
+		} else if (strncmp (error_start, "BadAuthentication", error_end - error_start) == 0) {
+			/* Looks like Error=BadAuthentication errors don't return a URI */
+			gchar *info_start, *info_end;
+
+			info_start = strstr (response_body, "Info=");
+			if (info_start != NULL) {
+				info_start += strlen ("Info=");
+				info_end = strstr (info_start, "\n");
+			}
+
+			/* If Info=InvalidSecondFactor, the user needs to generate an application-specific password and use that instead */
+			if (info_start != NULL && info_end != NULL && strncmp (info_start, "InvalidSecondFactor", info_end - info_start) == 0) {
+				g_set_error (error, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_INVALID_SECOND_FACTOR,
+				             /* Translators: the parameter is a URI for further information. */
+				             _("This account requires an application-specific password. (%s)"),
+				             "http://www.google.com/support/accounts/bin/static.py?page=guide.cs&guide=1056283&topic=1056286";);
+				goto login_error;
+			}
+
+			/* Fall back to a generic "bad authentication details" message */
+			g_set_error_literal (error, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_BAD_AUTHENTICATION,
+			                     _("Your username or password were incorrect."));
+			goto login_error;
+		}
+
+		/* Get the information URI */
+		uri_start = strstr (response_body, "Url=");
+		if (uri_start == NULL) {
+			goto protocol_error;
+		}
+		uri_start += strlen ("Url=");
+
+		uri_end = strstr (uri_start, "\n");
+		if (uri_end == NULL) {
+			goto protocol_error;
+		}
+
+		uri = g_strndup (uri_start, uri_end - uri_start);
+
+		if (strncmp (error_start, "NotVerified", error_end - error_start) == 0) {
+			g_set_error (error, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_NOT_VERIFIED,
+			             /* Translators: the parameter is a URI for further information. */
+			             _("Your account's e-mail address has not been verified. (%s)"), uri);
+			goto login_error;
+		} else if (strncmp (error_start, "TermsNotAgreed", error_end - error_start) == 0) {
+			g_set_error (error, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_TERMS_NOT_AGREED,
+			             /* Translators: the parameter is a URI for further information. */
+			             _("You have not agreed to the service's terms and conditions. (%s)"), uri);
+			goto login_error;
+		} else if (strncmp (error_start, "AccountMigrated", error_end - error_start) == 0) {
+			/* This is non-standard, and used by YouTube since it's got messed-up accounts */
+			g_set_error (error, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_ACCOUNT_MIGRATED,
+			             /* Translators: the parameter is a URI for further information. */
+			             _("This account has been migrated. Please log in online to receive your new username and password. (%s)"), uri);
+			goto login_error;
+		} else if (strncmp (error_start, "AccountDeleted", error_end - error_start) == 0) {
+			g_set_error (error, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_ACCOUNT_DELETED,
+			             /* Translators: the parameter is a URI for further information. */
+			             _("This account has been deleted. (%s)"), uri);
+			goto login_error;
+		} else if (strncmp (error_start, "AccountDisabled", error_end - error_start) == 0) {
+			g_set_error (error, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_ACCOUNT_DISABLED,
+			             /* Translators: the parameter is a URI for further information. */
+			             _("This account has been disabled. (%s)"), uri);
+			goto login_error;
+		} else if (strncmp (error_start, "ServiceDisabled", error_end - error_start) == 0) {
+			g_set_error (error, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_SERVICE_DISABLED,
+			             /* Translators: the parameter is a URI for further information. */
+			             _("This account's access to this service has been disabled. (%s)"), uri);
+			goto login_error;
+		} else if (strncmp (error_start, "ServiceUnavailable", error_end - error_start) == 0) {
+			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_UNAVAILABLE,
+			             /* Translators: the parameter is a URI for further information. */
+			             _("This service is not available at the moment. (%s)"), uri);
+			goto login_error;
+		}
+
+		/* Unknown error type! */
+		goto protocol_error;
+
+login_error:
+		g_free (uri);
+		g_object_unref (message);
+
+		return FALSE;
+	}
+
+	g_assert (message->response_body->data != NULL);
+
+	retval = parse_authentication_response (self, domain, status, message->response_body->data, message->response_body->length, error);
+
+	g_object_unref (message);
+
+	return retval;
+
+protocol_error:
+	parse_error_response (self, status, message->reason_phrase, message->response_body->data, message->response_body->length, error);
+
+	g_object_unref (message);
+
+	return FALSE;
+}
+
+typedef struct {
+	gchar *username;
+	gchar *password;
+} AuthenticateAsyncData;
+
+static void
+authenticate_async_data_free (AuthenticateAsyncData *self)
+{
+	g_free (self->username);
+	g_free (self->password);
+
+	g_slice_free (AuthenticateAsyncData, self);
+}
+
+static void
+authenticate_thread (GSimpleAsyncResult *result, GDataClientLoginAuthorizer *authorizer, GCancellable *cancellable)
+{
+	GDataClientLoginAuthorizerPrivate *priv = authorizer->priv;
+	GError *error = NULL;
+	gboolean success = TRUE;
+	GHashTableIter iter;
+	GDataAuthorizationDomain *domain;
+	AuthenticateAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);
+
+	g_static_rec_mutex_lock (&(priv->mutex));
+
+	/* Authenticate and authorize against each of the services registered with the authorizer */
+	g_hash_table_iter_init (&iter, priv->auth_tokens);
+
+	while (g_hash_table_iter_next (&iter, (gpointer*) &domain, NULL) == TRUE) {
+		GError *authenticate_error = NULL;
+
+		success = authenticate (authorizer, domain, data->username, data->password, NULL, NULL, cancellable, &authenticate_error) && success;
+
+		if (success == FALSE) {
+			g_propagate_error (&error, authenticate_error);
+		}
+	}
+
+	g_static_rec_mutex_unlock (&(priv->mutex));
+
+	/* Set or clear the authentication details and return now that we're done */
+	if (success == TRUE) {
+		set_authentication_details (authorizer, data->username, data->password, TRUE);
+	} else {
+		set_authentication_details (authorizer, NULL, NULL, TRUE);
+	}
+
+	g_simple_async_result_set_op_res_gboolean (result, success);
+
+	if (success == FALSE) {
+		g_simple_async_result_set_from_error (result, error);
+		g_error_free (error);
+	}
+}
+
+/**
+ * gdata_client_login_authorizer_authenticate_async:
+ * @self: a #GDataClientLoginAuthorizer
+ * @username: the user's username
+ * @password: the user's password
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when authentication is finished
+ * @user_data: (closure): data to pass to the @callback function
+ *
+ * Authenticates the #GDataClientLoginAuthorizer with the Google accounts service using the given @username and @password. @self, @username and
+ * @password are all reffed/copied when this function is called, so can safely be freed after this function returns.
+ *
+ * For more details, see gdata_client_login_authorizer_authenticate(), which is the synchronous version of this function.
+ *
+ * When the operation is finished, @callback will be called. You can then call gdata_client_login_authorizer_authenticate_finish()
+ * to get the results of the operation.
+ *
+ * Since: 0.9.0
+ */
+void
+gdata_client_login_authorizer_authenticate_async (GDataClientLoginAuthorizer *self, const gchar *username, const gchar *password,
+                                                  GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+{
+	GSimpleAsyncResult *result;
+	AuthenticateAsyncData *data;
+
+	g_return_if_fail (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (self));
+	g_return_if_fail (username != NULL);
+	g_return_if_fail (password != NULL);
+	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+	data = g_slice_new (AuthenticateAsyncData);
+	data->username = g_strdup (username);
+	data->password = g_strdup (password);
+
+	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_client_login_authorizer_authenticate_async);
+	g_simple_async_result_set_handle_cancellation (result, FALSE); /* we handle our own cancellation so we can set ::username and ::password */
+	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) authenticate_async_data_free);
+	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) authenticate_thread, G_PRIORITY_DEFAULT, cancellable);
+	g_object_unref (result);
+}
+
+/**
+ * gdata_client_login_authorizer_authenticate_finish:
+ * @self: a #GDataClientLoginAuthorizer
+ * @async_result: a #GAsyncResult
+ * @error: a #GError, or %NULL
+ *
+ * Finishes an asynchronous authentication operation started with gdata_client_login_authorizer_authenticate_async().
+ *
+ * Return value: %TRUE if authentication was successful, %FALSE otherwise
+ *
+ * Since: 0.9.0
+ */
+gboolean
+gdata_client_login_authorizer_authenticate_finish (GDataClientLoginAuthorizer *self, GAsyncResult *async_result, GError **error)
+{
+	g_return_val_if_fail (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (self), FALSE);
+	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	g_warn_if_fail (g_simple_async_result_is_valid (async_result, G_OBJECT (self), gdata_client_login_authorizer_authenticate_async));
+
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (async_result), error) == TRUE) {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/**
+ * gdata_client_login_authorizer_authenticate:
+ * @self: a #GDataClientLoginAuthorizer
+ * @username: the user's username
+ * @password: the user's password
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Authenticates the #GDataClientLoginAuthorizer with the Google Accounts service using @username and @password and authorizes it against all the
+ * service types passed to gdata_client_login_authorizer_new(); i.e. logs into the service with the given user account. @username should be a full
+ * e-mail address (e.g. <literal>john smith\ gmail com</literal>). If a full e-mail address is not given, @username will have
+ * <literal>\ gmail com</literal> appended to create an e-mail address
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
+ * If the operation was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ *
+ * A %GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_BAD_AUTHENTICATION will be returned if authentication failed due to an incorrect username or password.
+ * Other #GDataClientLoginAuthorizerError errors can be returned for other conditions.
+ *
+ * If the service requires a CAPTCHA to be completed, the #GDataClientLoginAuthorizer::captcha-challenge signal will be emitted.
+ * The return value from a signal handler for the signal should be a newly allocated string containing the text from the image. If the text is %NULL
+ * or empty, authentication will fail with a %GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_CAPTCHA_REQUIRED error. Otherwise, authentication will be
+ * automatically and transparently restarted with the new CAPTCHA details.
+ *
+ * A %GDATA_SERVICE_ERROR_PROTOCOL_ERROR will be returned if the server's responses were invalid.
+ *
+ * Return value: %TRUE if authentication and authorization was successful against all the services, %FALSE otherwise
+ *
+ * Since: 0.9.0
+ */
+gboolean
+gdata_client_login_authorizer_authenticate (GDataClientLoginAuthorizer *self, const gchar *username, const gchar *password,
+                                            GCancellable *cancellable, GError **error)
+{
+	GDataClientLoginAuthorizerPrivate *priv;
+	gboolean retval = TRUE;
+	GHashTableIter iter;
+	GDataAuthorizationDomain *domain;
+
+	g_return_val_if_fail (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (self), FALSE);
+	g_return_val_if_fail (username != NULL, FALSE);
+	g_return_val_if_fail (password != NULL, FALSE);
+	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	priv = self->priv;
+
+	g_static_rec_mutex_lock (&(priv->mutex));
+
+	/* Authenticate and authorize against each of the services registered with the authorizer */
+	g_hash_table_iter_init (&iter, self->priv->auth_tokens);
+
+	while (g_hash_table_iter_next (&iter, (gpointer*) &domain, NULL) == TRUE) {
+		retval = authenticate (self, domain, username, password, NULL, NULL, cancellable, error) && retval;
+	}
+
+	g_static_rec_mutex_unlock (&(priv->mutex));
+
+	/* Set or clear the authentication details */
+	if (retval == TRUE) {
+		set_authentication_details (self, username, password, FALSE);
+	} else {
+		set_authentication_details (self, NULL, NULL, FALSE);
+	}
+
+	return retval;
+}
+
+static void
+notify_proxy_uri_cb (GObject *object, GParamSpec *pspec, GDataClientLoginAuthorizer *self)
+{
+	/* Flush our cached version */
+	if (self->priv->proxy_uri != NULL) {
+		soup_uri_free (self->priv->proxy_uri);
+		self->priv->proxy_uri = NULL;
+	}
+
+	g_object_notify (G_OBJECT (self), "proxy-uri");
+}
+
+/**
+ * gdata_client_login_authorizer_get_proxy_uri:
+ * @self: a #GDataClientLoginAuthorizer
+ *
+ * Gets the proxy URI on the #GDataClientLoginAuthorizer's #SoupSession.
+ *
+ * Return value: (transfer full): the proxy URI, or %NULL; free with soup_uri_free()
+ *
+ * Since: 0.9.0
+ */
+SoupURI *
+gdata_client_login_authorizer_get_proxy_uri (GDataClientLoginAuthorizer *self)
+{
+	SoupURI *proxy_uri;
+
+	g_return_val_if_fail (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (self), NULL);
+
+	/* If we have a cached version, return that */
+	if (self->priv->proxy_uri != NULL) {
+		return self->priv->proxy_uri;
+	}
+
+	g_object_get (self->priv->session, SOUP_SESSION_PROXY_URI, &proxy_uri, NULL);
+
+	/* Update the cache; it takes ownership of the URI */
+	self->priv->proxy_uri = proxy_uri;
+
+	return proxy_uri;
+}
+
+/**
+ * gdata_client_login_authorizer_set_proxy_uri:
+ * @self: a #GDataClientLoginAuthorizer
+ * @proxy_uri: (allow-none): the proxy URI, or %NULL
+ *
+ * Sets the proxy URI on the #SoupSession used internally by the #GDataClientLoginAuthorizer. This forces all requests through the given proxy.
+ *
+ * If @proxy_uri is %NULL, no proxy will be used.
+ *
+ * Since: 0.9.0
+ */
+void
+gdata_client_login_authorizer_set_proxy_uri (GDataClientLoginAuthorizer *self, SoupURI *proxy_uri)
+{
+	g_return_if_fail (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (self));
+
+	g_object_set (self->priv->session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
+
+	/* Notification is handled in notify_proxy_uri_cb() which is called as a result of setting the property on the session */
+}
+
+static void
+notify_timeout_cb (GObject *gobject, GParamSpec *pspec, GObject *self)
+{
+	g_object_notify (self, "timeout");
+}
+
+/**
+ * gdata_client_login_authorizer_get_timeout:
+ * @self: a #GDataClientLoginAuthorizer
+ *
+ * Gets the #GDataClientLoginAuthorizer:timeout property; the network timeout, in seconds.
+ *
+ * Return value: the timeout, or <code class="literal">0</code>
+ *
+ * Since: 0.9.0
+ */
+guint
+gdata_client_login_authorizer_get_timeout (GDataClientLoginAuthorizer *self)
+{
+	guint timeout;
+
+	g_return_val_if_fail (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (self), 0);
+
+	g_object_get (self->priv->session, SOUP_SESSION_TIMEOUT, &timeout, NULL);
+
+	return timeout;
+}
+
+/**
+ * gdata_client_login_authorizer_set_timeout:
+ * @self: a #GDataClientLoginAuthorizer
+ * @timeout: the timeout, or <code class="literal">0</code>
+ *
+ * Sets the #GDataClientLoginAuthorizer:timeout property; the network timeout, in seconds.
+ *
+ * If @timeout is <code class="literal">0</code>, network operations will never time out.
+ *
+ * Since: 0.9.0
+ */
+void
+gdata_client_login_authorizer_set_timeout (GDataClientLoginAuthorizer *self, guint timeout)
+{
+	g_return_if_fail (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (self));
+
+	g_object_set (self->priv->session, SOUP_SESSION_TIMEOUT, timeout, NULL);
+
+	/* Notification is handled in notify_proxy_uri_cb() which is called as a result of setting the property on the session */
+}
+
+/**
+ * gdata_client_login_authorizer_get_client_id:
+ * @self: a #GDataClientLoginAuthorizer
+ *
+ * Returns the authorizer's client ID, as specified on constructing the #GDataClientLoginAuthorizer.
+ *
+ * Return value: the authorizer's client ID
+ *
+ * Since: 0.9.0
+ */
+const gchar *
+gdata_client_login_authorizer_get_client_id (GDataClientLoginAuthorizer *self)
+{
+	g_return_val_if_fail (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (self), NULL);
+
+	return self->priv->client_id;
+}
+
+/**
+ * gdata_client_login_authorizer_get_username:
+ * @self: a #GDataClientLoginAuthorizer
+ *
+ * Returns the username of the currently authenticated user, or %NULL if nobody is authenticated.
+ *
+ * It is not safe to call this while an authentication operation is ongoing.
+ *
+ * Return value: the username of the currently authenticated user, or %NULL
+ *
+ * Since: 0.9.0
+ */
+const gchar *
+gdata_client_login_authorizer_get_username (GDataClientLoginAuthorizer *self)
+{
+	g_return_val_if_fail (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (self), NULL);
+
+	/* There's little point protecting this with ->mutex, as the data's meaningless if accessed during an authentication operation,
+	 * and not being accessed concurrently otherwise. */
+	return self->priv->username;
+}
+
+/**
+ * gdata_client_login_authorizer_get_password:
+ * @self: a #GDataClientLoginAuthorizer
+ *
+ * Returns the password of the currently authenticated user, or %NULL if nobody is authenticated.
+ *
+ * It is not safe to call this while an authentication operation is ongoing.
+ *
+ * Return value: the password of the currently authenticated user, or %NULL
+ *
+ * Since: 0.9.0
+ */
+const gchar *
+gdata_client_login_authorizer_get_password (GDataClientLoginAuthorizer *self)
+{
+	g_return_val_if_fail (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (self), NULL);
+
+	/* There's little point protecting this with ->mutex, as the data's meaningless if accessed during an authentication operation,
+	 * and not being accessed concurrently otherwise. */
+	return self->priv->password;
+}
diff --git a/gdata/gdata-client-login-authorizer.h b/gdata/gdata-client-login-authorizer.h
new file mode 100644
index 0000000..7cb51a1
--- /dev/null
+++ b/gdata/gdata-client-login-authorizer.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011 <philip tecnocode co uk>
+ *
+ * GData Client is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_CLIENT_LOGIN_AUTHORIZER_H
+#define GDATA_CLIENT_LOGIN_AUTHORIZER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "gdata-authorizer.h"
+
+G_BEGIN_DECLS
+
+/**
+ * GDataClientLoginAuthorizerError:
+ * @GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_BAD_AUTHENTICATION: The login request used a username or password that is not recognized.
+ * @GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_NOT_VERIFIED: The account email address has not been verified. The user will need to access their Google
+ * account directly to resolve the issue before logging in using a non-Google application.
+ * @GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_TERMS_NOT_AGREED: The user has not agreed to terms. The user will need to access their Google account directly
+ * to resolve the issue before logging in using a non-Google application.
+ * @GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_CAPTCHA_REQUIRED: A CAPTCHA is required. (A response with this error code will also contain an image URI and a
+ * CAPTCHA token.)
+ * @GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_ACCOUNT_DELETED: The user account has been deleted.
+ * @GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_ACCOUNT_DISABLED: The user account has been disabled.
+ * @GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_SERVICE_DISABLED: The user's access to the specified service has been disabled. (The user account may still be
+ * valid.)
+ * @GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_ACCOUNT_MIGRATED: The user's account login details have been migrated to a new system. (This is used for the
+ * transition from the old YouTube login details to the new ones.)
+ * @GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_INVALID_SECOND_FACTOR: The user's account requires an application-specific password to be used.
+ *
+ * Error codes for authentication and authorization operations on #GDataClientLoginAuthorizer. See the
+ * <ulink type="http" url="http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html#Errors";>online ClientLogin documentation</ulink> for
+ * more information.
+ *
+ * Since: 0.9.0
+ */
+typedef enum {
+	GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_BAD_AUTHENTICATION = 1,
+	GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_NOT_VERIFIED,
+	GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_TERMS_NOT_AGREED,
+	GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_CAPTCHA_REQUIRED,
+	GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_ACCOUNT_DELETED,
+	GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_ACCOUNT_DISABLED,
+	GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_SERVICE_DISABLED,
+	GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_ACCOUNT_MIGRATED,
+	GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_INVALID_SECOND_FACTOR
+} GDataClientLoginAuthorizerError;
+
+GQuark gdata_client_login_authorizer_error_quark (void) G_GNUC_CONST;
+
+#define GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR		gdata_client_login_authorizer_error_quark ()
+
+#define GDATA_TYPE_CLIENT_LOGIN_AUTHORIZER		(gdata_client_login_authorizer_get_type ())
+#define GDATA_CLIENT_LOGIN_AUTHORIZER(o) \
+	(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_CLIENT_LOGIN_AUTHORIZER, GDataClientLoginAuthorizer))
+#define GDATA_CLIENT_LOGIN_AUTHORIZER_CLASS(k) \
+	(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_CLIENT_LOGIN_AUTHORIZER, GDataClientLoginAuthorizerClass))
+#define GDATA_IS_CLIENT_LOGIN_AUTHORIZER(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_CLIENT_LOGIN_AUTHORIZER))
+#define GDATA_IS_CLIENT_LOGIN_AUTHORIZER_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_CLIENT_LOGIN_AUTHORIZER))
+#define GDATA_CLIENT_LOGIN_AUTHORIZER_GET_CLASS(o) \
+	(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_CLIENT_LOGIN_AUTHORIZER, GDataClientLoginAuthorizerClass))
+
+typedef struct _GDataClientLoginAuthorizerPrivate	GDataClientLoginAuthorizerPrivate;
+
+/**
+ * GDataClientLoginAuthorizer:
+ *
+ * All the fields in the #GDataClientLoginAuthorizer structure are private and should never be accessed directly.
+ *
+ * Since: 0.9.0
+ */
+typedef struct {
+	/*< private >*/
+	GObject parent;
+	GDataClientLoginAuthorizerPrivate *priv;
+} GDataClientLoginAuthorizer;
+
+/**
+ * GDataClientLoginAuthorizerClass:
+ *
+ * All the fields in the #GDataClientLoginAuthorizerClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.9.0
+ */
+typedef struct {
+	/*< private >*/
+	GObjectClass parent;
+} GDataClientLoginAuthorizerClass;
+
+GType gdata_client_login_authorizer_get_type (void) G_GNUC_CONST;
+
+GDataClientLoginAuthorizer *gdata_client_login_authorizer_new (const gchar *client_id, GType service_type) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+GDataClientLoginAuthorizer *gdata_client_login_authorizer_new_for_authorization_domains (const gchar *client_id, GList *authorization_domains)
+                                                                                        G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
+gboolean gdata_client_login_authorizer_authenticate (GDataClientLoginAuthorizer *self, const gchar *username, const gchar *password,
+                                                     GCancellable *cancellable, GError **error);
+void gdata_client_login_authorizer_authenticate_async (GDataClientLoginAuthorizer *self, const gchar *username, const gchar *password,
+                                                       GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
+gboolean gdata_client_login_authorizer_authenticate_finish (GDataClientLoginAuthorizer *self, GAsyncResult *async_result, GError **error);
+
+const gchar *gdata_client_login_authorizer_get_client_id (GDataClientLoginAuthorizer *self) G_GNUC_PURE;
+const gchar *gdata_client_login_authorizer_get_username (GDataClientLoginAuthorizer *self) G_GNUC_PURE;
+const gchar *gdata_client_login_authorizer_get_password (GDataClientLoginAuthorizer *self) G_GNUC_PURE;
+
+SoupURI *gdata_client_login_authorizer_get_proxy_uri (GDataClientLoginAuthorizer *self) G_GNUC_PURE;
+void gdata_client_login_authorizer_set_proxy_uri (GDataClientLoginAuthorizer *self, SoupURI *proxy_uri);
+
+guint gdata_client_login_authorizer_get_timeout (GDataClientLoginAuthorizer *self) G_GNUC_PURE;
+void gdata_client_login_authorizer_set_timeout (GDataClientLoginAuthorizer *self, guint timeout);
+
+G_END_DECLS
+
+#endif /* !GDATA_CLIENT_LOGIN_AUTHORIZER_H */
diff --git a/gdata/gdata-download-stream.c b/gdata/gdata-download-stream.c
index 31cfcd7..d19fcfb 100644
--- a/gdata/gdata-download-stream.c
+++ b/gdata/gdata-download-stream.c
@@ -23,7 +23,9 @@
  * @stability: Unstable
  * @include: gdata/gdata-download-stream.h
  *
- * #GDataDownloadStream is a #GInputStream subclass to allow downloading of files from GData services with authentication from a #GDataService.
+ * #GDataDownloadStream is a #GInputStream subclass to allow downloading of files from GData services with authorization from a #GDataService under
+ * the given #GDataAuthorizationDomain. If authorization is not required to perform the download, a #GDataAuthorizationDomain doesn't have to be
+ * specified.
  *
  * Once a #GDataDownloadStream is instantiated with gdata_download_stream_new(), the standard #GInputStream API can be used on the stream to download
  * the file. Network communication may not actually begin until the first call to g_input_stream_read(), so having a #GDataDownloadStream around is no
@@ -49,21 +51,23 @@
  * using the method's #GCancellable, or by cancelling the download stream as a whole) will cause it to stop waiting for the network activity to finish,
  * and return %G_IO_ERROR_CANCELLED immediately. Network activity will continue to be shut down in the background.
  *
- * If the server returns an error message (for example, if the user is not correctly authenticated or doesn't have suitable permissions to download
- * from the given URI), it will be returned as a #GDataServiceError by the first call to g_input_stream_read().
+ * If the server returns an error message (for example, if the user is not correctly authenticated/authorized or doesn't have suitable permissions to
+ * download from the given URI), it will be returned as a #GDataServiceError by the first call to g_input_stream_read().
  *
  * <example>
  * 	<title>Downloading to a File</title>
  * 	<programlisting>
  *	GDataService *service;
+ *	GDataAuthorizationDomain *domain;
  *	GCancellable *cancellable;
  *	GInputStream *download_stream;
  *	GOutputStream *output_stream;
  *
  *	/<!-- -->* Create the download stream *<!-- -->/
  *	service = create_my_service ();
+ *	domain = get_my_authorization_domain_from_service (service);
  *	cancellable = g_cancellable_new (); /<!-- -->* cancel this to cancel the entire download operation *<!-- -->/
- *	download_stream = gdata_download_stream_new (service, download_uri, cancellable);
+ *	download_stream = gdata_download_stream_new (service, domain, download_uri, cancellable);
  *	output_stream = create_file_and_return_output_stream ();
  *
  *	/<!-- -->* Perform the download asynchronously *<!-- -->/
@@ -73,6 +77,7 @@
  *	g_object_unref (output_stream);
  *	g_object_unref (download_stream);
  *	g_object_unref (cancellable);
+ *	g_object_unref (domain);
  *	g_object_unref (service);
  *
  *	static void
@@ -137,6 +142,7 @@ static void create_network_thread (GDataDownloadStream *self, GError **error);
 struct _GDataDownloadStreamPrivate {
 	gchar *download_uri;
 	GDataService *service;
+	GDataAuthorizationDomain *authorization_domain;
 	SoupSession *session;
 	SoupMessage *message;
 	GDataBuffer *buffer;
@@ -161,7 +167,8 @@ enum {
 	PROP_DOWNLOAD_URI,
 	PROP_CONTENT_TYPE,
 	PROP_CONTENT_LENGTH,
-	PROP_CANCELLABLE
+	PROP_CANCELLABLE,
+	PROP_AUTHORIZATION_DOMAIN,
 };
 
 G_DEFINE_TYPE_WITH_CODE (GDataDownloadStream, gdata_download_stream, G_TYPE_INPUT_STREAM,
@@ -189,17 +196,31 @@ gdata_download_stream_class_init (GDataDownloadStreamClass *klass)
 	/**
 	 * GDataDownloadStream:service:
 	 *
-	 * The service which is used to authenticate the download, and to which the download relates.
+	 * The service which is used to authorize the download, and to which the download relates.
 	 *
 	 * Since: 0.5.0
 	 **/
 	g_object_class_install_property (gobject_class, PROP_SERVICE,
 	                                 g_param_spec_object ("service",
-	                                                      "Service", "The service which is used to authenticate the download.",
+	                                                      "Service", "The service which is used to authorize the download.",
 	                                                      GDATA_TYPE_SERVICE,
 	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
 	/**
+	 * GDataDownloadStream:authorization-domain:
+	 *
+	 * The authorization domain for the download, against which the #GDataService:authorizer for the #GDataDownloadStream:service should be
+	 * authorized. This may be %NULL if authorization is not needed for the download.
+	 *
+	 * Since: 0.9.0
+	 */
+	g_object_class_install_property (gobject_class, PROP_AUTHORIZATION_DOMAIN,
+	                                 g_param_spec_object ("authorization-domain",
+	                                                      "Authorization domain", "The authorization domain for the download.",
+	                                                      GDATA_TYPE_AUTHORIZATION_DOMAIN,
+	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
 	 * GDataDownloadStream:download-uri:
 	 *
 	 * The URI of the file to download.
@@ -328,8 +349,9 @@ gdata_download_stream_constructor (GType type, guint n_construct_params, GObject
 
 	/* Make sure the headers are set */
 	klass = GDATA_SERVICE_GET_CLASS (priv->service);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (priv->service, priv->message);
+	if (klass->append_query_headers != NULL) {
+		klass->append_query_headers (priv->service, priv->authorization_domain, priv->message);
+	}
 
 	/* We don't want to accumulate chunks */
 	soup_message_body_set_accumulate (priv->message->request_body, FALSE);
@@ -355,6 +377,10 @@ gdata_download_stream_dispose (GObject *object)
 		g_object_unref (priv->network_cancellable);
 	priv->network_cancellable = NULL;
 
+	if (priv->authorization_domain != NULL)
+		g_object_unref (priv->authorization_domain);
+	priv->authorization_domain = NULL;
+
 	if (priv->service != NULL)
 		g_object_unref (priv->service);
 	priv->service = NULL;
@@ -393,6 +419,9 @@ gdata_download_stream_get_property (GObject *object, guint property_id, GValue *
 		case PROP_SERVICE:
 			g_value_set_object (value, priv->service);
 			break;
+		case PROP_AUTHORIZATION_DOMAIN:
+			g_value_set_object (value, priv->authorization_domain);
+			break;
 		case PROP_DOWNLOAD_URI:
 			g_value_set_string (value, priv->download_uri);
 			break;
@@ -426,6 +455,9 @@ gdata_download_stream_set_property (GObject *object, guint property_id, const GV
 			priv->service = g_value_dup_object (value);
 			priv->session = _gdata_service_get_session (priv->service);
 			break;
+		case PROP_AUTHORIZATION_DOMAIN:
+			priv->authorization_domain = g_value_dup_object (value);
+			break;
 		case PROP_DOWNLOAD_URI:
 			priv->download_uri = g_value_dup_string (value);
 			break;
@@ -773,6 +805,7 @@ create_network_thread (GDataDownloadStream *self, GError **error)
 /**
  * gdata_download_stream_new:
  * @service: a #GDataService
+ * @domain: (allow-none): the #GDataAuthorizationDomain to authorize the download, or %NULL
  * @download_uri: the URI to download
  * @cancellable: (allow-none): a #GCancellable for the entire download stream, or %NULL
  *
@@ -789,18 +822,20 @@ create_network_thread (GDataDownloadStream *self, GError **error)
  *
  * Return value: a new #GInputStream, or %NULL; unref with g_object_unref()
  *
- * Since: 0.8.0
+ * Since: 0.9.0
  **/
 GInputStream *
-gdata_download_stream_new (GDataService *service, const gchar *download_uri, GCancellable *cancellable)
+gdata_download_stream_new (GDataService *service, GDataAuthorizationDomain *domain, const gchar *download_uri, GCancellable *cancellable)
 {
 	g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL);
+	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
 	g_return_val_if_fail (download_uri != NULL, NULL);
 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
 
 	return G_INPUT_STREAM (g_object_new (GDATA_TYPE_DOWNLOAD_STREAM,
 	                                     "download-uri", download_uri,
 	                                     "service", service,
+	                                     "authorization-domain", domain,
 	                                     "cancellable", cancellable,
 	                                     NULL));
 }
@@ -809,9 +844,9 @@ gdata_download_stream_new (GDataService *service, const gchar *download_uri, GCa
  * gdata_download_stream_get_service:
  * @self: a #GDataDownloadStream
  *
- * Gets the service used to authenticate the download, as passed to gdata_download_stream_new().
+ * Gets the service used to authorize the download, as passed to gdata_download_stream_new().
  *
- * Return value: (transfer none): the #GDataService used to authenticate the download
+ * Return value: (transfer none): the #GDataService used to authorize the download
  *
  * Since: 0.5.0
  **/
@@ -823,6 +858,24 @@ gdata_download_stream_get_service (GDataDownloadStream *self)
 }
 
 /**
+ * gdata_download_stream_get_authorization_domain:
+ * @self: a #GDataDownloadStream
+ *
+ * Gets the authorization domain used to authorize the download, as passed to gdata_download_stream_new(). It may be %NULL if authorization is not
+ * needed for the download.
+ *
+ * Return value: (transfer none) (allow-none): the #GDataAuthorizationDomain used to authorize the download, or %NULL
+ *
+ * Since: 0.9.0
+ */
+GDataAuthorizationDomain *
+gdata_download_stream_get_authorization_domain (GDataDownloadStream *self)
+{
+	g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), NULL);
+	return self->priv->authorization_domain;
+}
+
+/**
  * gdata_download_stream_get_download_uri:
  * @self: a #GDataDownloadStream
  *
diff --git a/gdata/gdata-download-stream.h b/gdata/gdata-download-stream.h
index fc4a70b..d80bd26 100644
--- a/gdata/gdata-download-stream.h
+++ b/gdata/gdata-download-stream.h
@@ -63,10 +63,11 @@ typedef struct {
 
 GType gdata_download_stream_get_type (void) G_GNUC_CONST;
 
-GInputStream *gdata_download_stream_new (GDataService *service, const gchar *download_uri,
+GInputStream *gdata_download_stream_new (GDataService *service, GDataAuthorizationDomain *domain, const gchar *download_uri,
                                          GCancellable *cancellable) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 
 GDataService *gdata_download_stream_get_service (GDataDownloadStream *self) G_GNUC_PURE;
+GDataAuthorizationDomain *gdata_download_stream_get_authorization_domain (GDataDownloadStream *self) G_GNUC_PURE;
 const gchar *gdata_download_stream_get_download_uri (GDataDownloadStream *self) G_GNUC_PURE;
 const gchar *gdata_download_stream_get_content_type (GDataDownloadStream *self) G_GNUC_PURE;
 gssize gdata_download_stream_get_content_length (GDataDownloadStream *self) G_GNUC_PURE;
diff --git a/gdata/gdata-private.h b/gdata/gdata-private.h
index 61cd1ee..0c00aee 100644
--- a/gdata/gdata-private.h
+++ b/gdata/gdata-private.h
@@ -46,15 +46,17 @@ typedef enum {
 
 #include "gdata-service.h"
 G_GNUC_INTERNAL SoupSession *_gdata_service_get_session (GDataService *self) G_GNUC_PURE;
-G_GNUC_INTERNAL void _gdata_service_set_authenticated (GDataService *self, gboolean authenticated);
-G_GNUC_INTERNAL SoupMessage *_gdata_service_build_message (GDataService *self, const gchar *method, const gchar *uri, const gchar *etag, gboolean etag_if_match);
+G_GNUC_INTERNAL SoupMessage *_gdata_service_build_message (GDataService *self, GDataAuthorizationDomain *domain, const gchar *method, const gchar *uri,
+                                                           const gchar *etag, gboolean etag_if_match);
 G_GNUC_INTERNAL void _gdata_service_actually_send_message (SoupSession *session, SoupMessage *message, GCancellable *cancellable, GError **error);
 G_GNUC_INTERNAL guint _gdata_service_send_message (GDataService *self, SoupMessage *message, GCancellable *cancellable, GError **error);
-G_GNUC_INTERNAL SoupMessage *_gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *query, GCancellable *cancellable,
-                                                   GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+G_GNUC_INTERNAL SoupMessage *_gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query,
+                                                   GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 G_GNUC_INTERNAL const gchar *_gdata_service_get_scheme (void) G_GNUC_CONST;
 G_GNUC_INTERNAL gchar *_gdata_service_build_uri (const gchar *format, ...) G_GNUC_PRINTF (1, 2) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+G_GNUC_INTERNAL gchar *_gdata_service_fix_uri_scheme (const gchar *uri) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 G_GNUC_INTERNAL GDataLogLevel _gdata_service_get_log_level (void) G_GNUC_CONST;
+G_GNUC_INTERNAL SoupSession *_gdata_service_build_session (void) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 
 #include "gdata-query.h"
 G_GNUC_INTERNAL void _gdata_query_set_next_uri (GDataQuery *self, const gchar *next_uri);
@@ -84,12 +86,43 @@ G_GNUC_INTERNAL void _gdata_feed_call_progress_callback (GDataFeed *self, gpoint
 G_GNUC_INTERNAL void _gdata_entry_set_updated (GDataEntry *self, gint64 updated);
 G_GNUC_INTERNAL void _gdata_entry_set_batch_data (GDataEntry *self, guint id, GDataBatchOperationType type);
 
-#include "gdata/services/documents/gdata-documents-service.h"
-
-G_GNUC_INTERNAL GDataService *_gdata_documents_service_get_spreadsheet_service (GDataDocumentsService *self) G_GNUC_PURE;
-
 #include "gdata-parser.h"
 
+/**
+ * _GDATA_DEFINE_AUTHORIZATION_DOMAIN:
+ * @l_n: lowercase name for the authorization domain, separated by underscores
+ * @SERVICE_NAME: the service name, as listed here: http://code.google.com/apis/documents/faq_gdata.html#clientlogin
+ * @SCOPE: the scope URI, as listed here: http://code.google.com/apis/documents/faq_gdata.html#AuthScopes
+ *
+ * Defines a static function to return an interned singleton #GDataAuthorizationDomain instance for the given parameters. Every time it's called, the
+ * function will return the same instance.
+ *
+ * The function will be named <code class="literal">get_(l_n)_authorization_domain</code>.
+ *
+ * Return value: (transfer none): a #GDataAuthorizationDomain instance for the given parameters
+ *
+ * Since: 0.9.0
+ */
+#define _GDATA_DEFINE_AUTHORIZATION_DOMAIN(l_n, SERVICE_NAME, SCOPE) \
+static GDataAuthorizationDomain * \
+get_##l_n##_authorization_domain (void) \
+{ \
+	static volatile GDataAuthorizationDomain *domain__volatile = NULL; \
+ \
+	if (g_once_init_enter ((volatile gsize *) &domain__volatile) == TRUE) { \
+		GDataAuthorizationDomain *domain; \
+ \
+		domain = g_object_new (GDATA_TYPE_AUTHORIZATION_DOMAIN, \
+		                       "service-name", SERVICE_NAME, \
+		                       "scope", SCOPE, \
+		                       NULL); \
+ \
+		g_once_init_leave ((volatile gsize *) &domain__volatile, (gsize) domain); \
+	} \
+ \
+	return GDATA_AUTHORIZATION_DOMAIN (domain__volatile); \
+}
+
 G_END_DECLS
 
 #endif /* !GDATA_PRIVATE_H */
diff --git a/gdata/gdata-service.c b/gdata/gdata-service.c
index 6f93931..f704cc7 100644
--- a/gdata/gdata-service.c
+++ b/gdata/gdata-service.c
@@ -27,36 +27,15 @@
  * #GDataService instance is required to issue queries to the service, handle insertions, updates and deletions, and generally
  * communicate with the online service.
  *
- * <example>
- * 	<title>Authenticating Asynchronously</title>
- * 	<programlisting>
- *	GDataSomeService *service;
- *
- *	/<!-- -->* Create a service object and authenticate with the service's server asynchronously *<!-- -->/
- *	service = gdata_some_service_new ("companyName-applicationName-versionID");
- *	gdata_service_authenticate_async (GDATA_SERVICE (service), username, password, cancellable, (GAsyncReadyCallback) authenticate_cb, user_data);
- *
- *	static void
- *	authenticate_cb (GDataSomeService *service, GAsyncResult *async_result, gpointer user_data)
- *	{
- *		GError *error = NULL;
- *
- *		if (gdata_service_authenticate_finish (GDATA_SERVICE (service), async_result, &error) == FALSE) {
- *			/<!-- -->* Notify the user of all errors except cancellation errors *<!-- -->/
- *			if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- *				g_error ("Authentication failed: %s", error->message);
- *			g_error_free (error);
- *			return;
- *		}
- *
- *		/<!-- -->* (The client is now authenticated. It can now proceed to execute queries on the service object which require the user
- *		 * to be authenticated. *<!-- -->/
- *	}
- *
- *	g_object_unref (service);
- *	</programlisting>
- * </example>
- **/
+ * If operations performed on a #GDataService need authorization (such as uploading a video to YouTube or querying the user's personal calendar on
+ * Google Calendar), the service needs a #GDataAuthorizer instance set as #GDataService:authorizer. Once the user is appropriately authenticated and
+ * authorized by the #GDataAuthorizer implementation (see the documentation for #GDataAuthorizer for details on how this is achieved for specific
+ * implementations), all operations will be automatically authorized.
+ *
+ * Note that it's not always necessary to supply a #GDataAuthorizer instance to a #GDataService. If the only operations to be performed on the
+ * #GDataService don't need authorization (e.g. they only query public information), setting up a #GDataAuthorizer is just extra overhead. See the
+ * documentation for the operations on individual #GDataService subclasses to see which need authorization and which don't.
+ */
 
 #include <config.h>
 #include <glib.h>
@@ -71,30 +50,21 @@
 
 #include "gdata-service.h"
 #include "gdata-private.h"
+#include "gdata-client-login-authorizer.h"
 #include "gdata-marshal.h"
 #include "gdata-types.h"
 
-/* The default e-mail domain to use for usernames */
-#define EMAIL_DOMAIN "gmail.com"
-
 GQuark
 gdata_service_error_quark (void)
 {
 	return g_quark_from_static_string ("gdata-service-error-quark");
 }
 
-GQuark
-gdata_authentication_error_quark (void)
-{
-	return g_quark_from_static_string ("gdata-authentication-error-quark");
-}
-
 static void gdata_service_dispose (GObject *object);
 static void gdata_service_finalize (GObject *object);
 static void gdata_service_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
 static void gdata_service_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
-static gboolean real_parse_authentication_response (GDataService *self, guint status, const gchar *response_body, gint length, GError **error);
-static void real_append_query_headers (GDataService *self, SoupMessage *message);
+static void real_append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, SoupMessage *message);
 static void real_parse_error_response (GDataService *self, GDataOperationType operation_type, guint status, const gchar *reason_phrase,
                                        const gchar *response_body, gint length, GError **error);
 static void notify_proxy_uri_cb (GObject *gobject, GParamSpec *pspec, GObject *self);
@@ -104,33 +74,17 @@ static void soup_log_printer (SoupLogger *logger, SoupLoggerLogLevel level, char
 
 struct _GDataServicePrivate {
 	SoupSession *session;
-
-	GStaticMutex authentication_mutex; /* mutex for username, password, auth_token and authenticated */
-	gchar *username;
-	gchar *password;
-	gchar *auth_token;
-	gchar *client_id;
-	gboolean authenticated;
 	gchar *locale;
+	GDataAuthorizer *authorizer;
 };
 
 enum {
-	PROP_CLIENT_ID = 1,
-	PROP_USERNAME,
-	PROP_PASSWORD,
-	PROP_AUTHENTICATED,
-	PROP_PROXY_URI,
+	PROP_PROXY_URI = 1,
 	PROP_TIMEOUT,
-	PROP_LOCALE
-};
-
-enum {
-	SIGNAL_CAPTCHA_CHALLENGE,
-	LAST_SIGNAL
+	PROP_LOCALE,
+	PROP_AUTHORIZER,
 };
 
-static guint service_signals[LAST_SIGNAL] = { 0, };
-
 G_DEFINE_TYPE (GDataService, gdata_service, G_TYPE_OBJECT)
 
 static void
@@ -145,67 +99,19 @@ gdata_service_class_init (GDataServiceClass *klass)
 	gobject_class->dispose = gdata_service_dispose;
 	gobject_class->finalize = gdata_service_finalize;
 
-	klass->service_name = "xapi";
-	klass->authentication_uri = "https://www.google.com/accounts/ClientLogin";;
 	klass->api_version = "2";
 	klass->feed_type = GDATA_TYPE_FEED;
-	klass->parse_authentication_response = real_parse_authentication_response;
 	klass->append_query_headers = real_append_query_headers;
 	klass->parse_error_response = real_parse_error_response;
-
-	/**
-	 * GDataService:client-id:
-	 *
-	 * A client ID for your application (see the
-	 * <ulink url="http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html#Request"; type="http">reference documentation</ulink>).
-	 *
-	 * It is recommended that the ID is of the form <literal><replaceable>company name</replaceable>-<replaceable>application name</replaceable>-
-	 * <replaceable>version ID</replaceable></literal>.
-	 **/
-	g_object_class_install_property (gobject_class, PROP_CLIENT_ID,
-	                                 g_param_spec_string ("client-id",
-	                                                      "Client ID", "A client ID for your application.",
-	                                                      NULL,
-	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-	/**
-	 * GDataService:username:
-	 *
-	 * The user's Google username for authentication. This will always be a full e-mail address.
-	 **/
-	g_object_class_install_property (gobject_class, PROP_USERNAME,
-	                                 g_param_spec_string ("username",
-	                                                      "Username", "The user's Google username for authentication.",
-	                                                      NULL,
-	                                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-	/**
-	 * GDataService:password:
-	 *
-	 * The user's account password for authentication.
-	 **/
-	g_object_class_install_property (gobject_class, PROP_PASSWORD,
-	                                 g_param_spec_string ("password",
-	                                                      "Password", "The user's account password for authentication.",
-	                                                      NULL,
-	                                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-	/**
-	 * GDataService:authenticated:
-	 *
-	 * Whether the user is authenticated (logged in) with the service.
-	 **/
-	g_object_class_install_property (gobject_class, PROP_AUTHENTICATED,
-	                                 g_param_spec_boolean ("authenticated",
-	                                                       "Authenticated", "Whether the user is authenticated (logged in) with the service.",
-	                                                       FALSE,
-	                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+	klass->get_authorization_domains = NULL; /* equivalent to returning an empty list of domains */
 
 	/**
 	 * GDataService:proxy-uri:
 	 *
 	 * The proxy URI used internally for all network requests.
 	 *
+	 * Note that if a #GDataAuthorizer is being used with this #GDataService, the authorizer might also need its proxy URI setting.
+	 *
 	 * Since: 0.2.0
 	 **/
 	g_object_class_install_property (gobject_class, PROP_PROXY_URI,
@@ -222,6 +128,8 @@ gdata_service_class_init (GDataServiceClass *klass)
 	 *
 	 * If the timeout is <code class="literal">0</code>, operations will never time out.
 	 *
+	 * Note that if a #GDataAuthorizer is being used with this #GDataService, the authorizer might also need its timeout setting.
+	 *
 	 * Since: 0.7.0
 	 **/
 	g_object_class_install_property (gobject_class, PROP_TIMEOUT,
@@ -250,67 +158,39 @@ gdata_service_class_init (GDataServiceClass *klass)
 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
 	/**
-	 * GDataService::captcha-challenge:
-	 * @service: the #GDataService which received the challenge
-	 * @uri: the URI of the CAPTCHA image to be used
+	 * GDataService:authorizer:
+	 *
+	 * An object which implements #GDataAuthorizer. This should have previously been authenticated authorized against this service type (and
+	 * potentially other service types). The service will use the authorizer to add an authorization token to each request it performs.
+	 *
+	 * Your application should call methods on the #GDataAuthorizer object itself in order to authenticate with the Google accounts service and
+	 * authorize against this service type. See the documentation for the particular #GDataAuthorizer implementation being used for more details.
 	 *
-	 * The #GDataService::captcha-challenge signal is emitted during the authentication process if
-	 * the service requires a CAPTCHA to be completed. The URI of a CAPTCHA image is given, and the
-	 * program should display this to the user, and return their response (the text displayed in the
-	 * image). There is no timeout imposed by the library for the response.
+	 * The authorizer for a service can be changed at runtime for a different #GDataAuthorizer object or %NULL without affecting ongoing requests
+	 * and operations.
 	 *
-	 * Return value: a newly allocated string containing the text in the CAPTCHA image
+	 * Note that it's only necessary to set an authorizer on the service if your application is going to make requests of the service which
+	 * require authorization. For example, listing the current most popular videos on YouTube does not require authorization, but uploading a
+	 * video to YouTube does. It's an unnecessary overhead to require the user to authorize against a service when not strictly required.
+	 *
+	 * Since: 0.9.0
 	 **/
-	service_signals[SIGNAL_CAPTCHA_CHALLENGE] = g_signal_new ("captcha-challenge",
-	                                                          G_TYPE_FROM_CLASS (klass),
-	                                                          G_SIGNAL_RUN_LAST,
-	                                                          0, NULL, NULL,
-	                                                          gdata_marshal_STRING__OBJECT_STRING,
-	                                                          G_TYPE_STRING, 1, G_TYPE_STRING);
+	g_object_class_install_property (gobject_class, PROP_AUTHORIZER,
+	                                 g_param_spec_object ("authorizer",
+	                                                      "Authorizer", "An authorizer object to provide an authorization token for each request.",
+	                                                      GDATA_TYPE_AUTHORIZER,
+	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 }
 
 static void
 gdata_service_init (GDataService *self)
 {
 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_SERVICE, GDataServicePrivate);
-	self->priv->session = soup_session_sync_new ();
-
-#ifdef HAVE_GNOME
-	soup_session_add_feature_by_type (self->priv->session, SOUP_TYPE_GNOME_FEATURES_2_26);
-#endif /* HAVE_GNOME */
+	self->priv->session = _gdata_service_build_session ();
 
 	/* Debug log handling */
 	g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, (GLogFunc) debug_handler, self);
 
-	/* Set up the authentication mutex */
-	g_static_mutex_init (&(self->priv->authentication_mutex));
-
-	/* Log all libsoup traffic if debugging's turned on */
-	if (_gdata_service_get_log_level () > GDATA_LOG_MESSAGES) {
-		SoupLoggerLogLevel level;
-		SoupLogger *logger;
-
-		switch (_gdata_service_get_log_level ()) {
-			case GDATA_LOG_FULL:
-				level = SOUP_LOGGER_LOG_BODY;
-				break;
-			case GDATA_LOG_HEADERS:
-				level = SOUP_LOGGER_LOG_HEADERS;
-				break;
-			case GDATA_LOG_MESSAGES:
-			case GDATA_LOG_NONE:
-			default:
-				g_assert_not_reached ();
-		}
-
-		logger = soup_logger_new (level, -1);
-		soup_logger_set_printer (logger, (SoupLoggerPrinter) soup_log_printer, self, NULL);
-
-		soup_session_add_feature (self->priv->session, SOUP_SESSION_FEATURE (logger));
-
-		g_object_unref (logger);
-	}
-
 	/* Proxy the SoupSession's proxy-uri and timeout properties */
 	g_signal_connect (self->priv->session, "notify::proxy-uri", (GCallback) notify_proxy_uri_cb, self);
 	g_signal_connect (self->priv->session, "notify::timeout", (GCallback) notify_timeout_cb, self);
@@ -321,6 +201,10 @@ gdata_service_dispose (GObject *object)
 {
 	GDataServicePrivate *priv = GDATA_SERVICE (object)->priv;
 
+	if (priv->authorizer != NULL)
+		g_object_unref (priv->authorizer);
+	priv->authorizer = NULL;
+
 	if (priv->session != NULL)
 		g_object_unref (priv->session);
 	priv->session = NULL;
@@ -334,12 +218,7 @@ gdata_service_finalize (GObject *object)
 {
 	GDataServicePrivate *priv = GDATA_SERVICE (object)->priv;
 
-	g_free (priv->username);
-	g_free (priv->password);
-	g_free (priv->auth_token);
-	g_free (priv->client_id);
 	g_free (priv->locale);
-	g_static_mutex_free (&(priv->authentication_mutex));
 
 	/* Chain up to the parent class */
 	G_OBJECT_CLASS (gdata_service_parent_class)->finalize (object);
@@ -351,18 +230,6 @@ gdata_service_get_property (GObject *object, guint property_id, GValue *value, G
 	GDataServicePrivate *priv = GDATA_SERVICE (object)->priv;
 
 	switch (property_id) {
-		case PROP_CLIENT_ID:
-			g_value_set_string (value, priv->client_id);
-			break;
-		case PROP_USERNAME:
-			g_value_set_string (value, priv->username);
-			break;
-		case PROP_PASSWORD:
-			g_value_set_string (value, priv->password);
-			break;
-		case PROP_AUTHENTICATED:
-			g_value_set_boolean (value, priv->authenticated);
-			break;
 		case PROP_PROXY_URI:
 			g_value_set_boxed (value, gdata_service_get_proxy_uri (GDATA_SERVICE (object)));
 			break;
@@ -372,6 +239,9 @@ gdata_service_get_property (GObject *object, guint property_id, GValue *value, G
 		case PROP_LOCALE:
 			g_value_set_string (value, priv->locale);
 			break;
+		case PROP_AUTHORIZER:
+			g_value_set_object (value, priv->authorizer);
+			break;
 		default:
 			/* We don't have any other property... */
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -382,12 +252,7 @@ gdata_service_get_property (GObject *object, guint property_id, GValue *value, G
 static void
 gdata_service_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
 {
-	GDataServicePrivate *priv = GDATA_SERVICE (object)->priv;
-
 	switch (property_id) {
-		case PROP_CLIENT_ID:
-			priv->client_id = g_value_dup_string (value);
-			break;
 		case PROP_PROXY_URI:
 			gdata_service_set_proxy_uri (GDATA_SERVICE (object), g_value_get_boxed (value));
 			break;
@@ -397,6 +262,9 @@ gdata_service_set_property (GObject *object, guint property_id, const GValue *va
 		case PROP_LOCALE:
 			gdata_service_set_locale (GDATA_SERVICE (object), g_value_get_string (value));
 			break;
+		case PROP_AUTHORIZER:
+			gdata_service_set_authorizer (GDATA_SERVICE (object), g_value_get_object (value));
+			break;
 		default:
 			/* We don't have any other property... */
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -404,48 +272,15 @@ gdata_service_set_property (GObject *object, guint property_id, const GValue *va
 	}
 }
 
-static gboolean
-real_parse_authentication_response (GDataService *self, guint status, const gchar *response_body, gint length, GError **error)
-{
-	gchar *auth_start, *auth_end;
-
-	/* Parse the response */
-	auth_start = strstr (response_body, "Auth=");
-	if (auth_start == NULL)
-		goto protocol_error;
-	auth_start += strlen ("Auth=");
-
-	auth_end = strstr (auth_start, "\n");
-	if (auth_end == NULL)
-		goto protocol_error;
-
-	self->priv->auth_token = g_strndup (auth_start, auth_end - auth_start);
-	if (self->priv->auth_token == NULL || strlen (self->priv->auth_token) == 0)
-		goto protocol_error;
-
-	return TRUE;
-
-protocol_error:
-	g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
-	                     _("The server returned a malformed response."));
-	return FALSE;
-}
-
 static void
-real_append_query_headers (GDataService *self, SoupMessage *message)
+real_append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, SoupMessage *message)
 {
-	gchar *authorisation_header;
-
 	g_assert (message != NULL);
 
 	/* Set the authorisation header */
-	g_static_mutex_lock (&(self->priv->authentication_mutex));
-	if (self->priv->auth_token != NULL) {
-		authorisation_header = g_strdup_printf ("GoogleLogin auth=%s", self->priv->auth_token);
-		soup_message_headers_append (message->request_headers, "Authorization", authorisation_header);
-		g_free (authorisation_header);
+	if (self->priv->authorizer != NULL) {
+		gdata_authorizer_process_request (self->priv->authorizer, domain, message);
 	}
-	g_static_mutex_unlock (&(self->priv->authentication_mutex));
 
 	/* Set the GData-Version header to tell it we want to use the v2 API */
 	soup_message_headers_append (message->request_headers, "GData-Version", GDATA_SERVICE_GET_CLASS (self)->api_version);
@@ -562,402 +397,132 @@ real_parse_error_response (GDataService *self, GDataOperationType operation_type
 	}
 }
 
-static void
-set_authentication_details (GDataService *self, const gchar *username, const gchar *password, gboolean authenticated)
-{
-	GObject *service = G_OBJECT (self);
-	GDataServicePrivate *priv = self->priv;
-
-	g_static_mutex_lock (&(priv->authentication_mutex));
-
-	g_object_freeze_notify (service);
-
-	if (authenticated == TRUE) {
-		/* Update several properties the service holds */
-		g_free (priv->username);
-
-		/* Ensure the username is always a full e-mail address */
-		if (strchr (username, '@') == NULL)
-			priv->username = g_strdup_printf ("%s@" EMAIL_DOMAIN, username);
-		else
-			priv->username = g_strdup (username);
-
-		g_free (priv->password);
-		priv->password = g_strdup (password);
-
-		g_object_notify (service, "username");
-		g_object_notify (service, "password");
-	}
-
-	if (priv->authenticated != authenticated) {
-		priv->authenticated = authenticated;
-		g_object_notify (service, "authenticated");
-	}
-
-	g_object_thaw_notify (service);
-
-	g_static_mutex_unlock (&(priv->authentication_mutex));
-}
-
-static gboolean
-authenticate (GDataService *self, const gchar *username, const gchar *password, gchar *captcha_token, gchar *captcha_answer,
-              GCancellable *cancellable, GError **error)
+/**
+ * gdata_service_is_authorized:
+ * @self: a #GDataService
+ *
+ * Determines whether the service is authorized for all the #GDataAuthorizationDomain<!-- -->s it belongs to (as returned by
+ * gdata_service_get_authorization_domains()). If the service's #GDataService:authorizer is %NULL, %FALSE is always returned.
+ *
+ * This is basically a convenience method for checking that the service's #GDataAuthorizer is authorized for all the service's
+ * #GDataAuthorizationDomain<!-- -->s.
+ *
+ * Return value: %TRUE if the service is authorized for all its domains, %FALSE otherwise
+ *
+ * Since: 0.9.0
+ */
+gboolean
+gdata_service_is_authorized (GDataService *self)
 {
-	GDataServicePrivate *priv = self->priv;
-	GDataServiceClass *klass;
-	SoupMessage *message;
-	gchar *request_body;
-	guint status;
-	gboolean retval;
+	GList *domains, *i;
+	gboolean authorised = TRUE;
 
-	/* Prepare the request */
-	klass = GDATA_SERVICE_GET_CLASS (self);
-	request_body = soup_form_encode ("accountType", "HOSTED_OR_GOOGLE",
-	                                 "Email", username,
-	                                 "Passwd", password,
-	                                 "service", klass->service_name,
-	                                 "source", priv->client_id,
-	                                 (captcha_token == NULL) ? NULL : "logintoken", captcha_token,
-	                                 "loginanswer", captcha_answer,
-	                                 NULL);
-
-	/* Free the CAPTCHA token and answer if necessary */
-	g_free (captcha_token);
-	g_free (captcha_answer);
-
-	/* Build the message */
-	message = soup_message_new (SOUP_METHOD_POST, klass->authentication_uri);
-	soup_message_set_request (message, "application/x-www-form-urlencoded", SOUP_MEMORY_TAKE, request_body, strlen (request_body));
-
-	/* Send the message */
-	status = _gdata_service_send_message (self, message, cancellable, error);
+	g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE);
 
-	if (status == SOUP_STATUS_CANCELLED) {
-		/* Cancelled (the error has already been set) */
-		g_object_unref (message);
+	/* If we don't have an authoriser set, we can't be authorised */
+	if (self->priv->authorizer == NULL) {
 		return FALSE;
-	} else if (status != SOUP_STATUS_OK) {
-		const gchar *response_body = message->response_body->data;
-		gchar *error_start, *error_end, *uri_start, *uri_end, *uri = NULL;
-
-		/* Parse the error response; see: http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html#Errors */
-		if (response_body == NULL)
-			goto protocol_error;
+	}
 
-		/* Error */
-		error_start = strstr (response_body, "Error=");
-		if (error_start == NULL)
-			goto protocol_error;
-		error_start += strlen ("Error=");
-
-		error_end = strstr (error_start, "\n");
-		if (error_end == NULL)
-			goto protocol_error;
-
-		if (strncmp (error_start, "CaptchaRequired", error_end - error_start) == 0) {
-			const gchar *captcha_base_uri = "http://www.google.com/accounts/";;
-			gchar *captcha_start, *captcha_end, *captcha_uri, *new_captcha_answer;
-			guint captcha_base_uri_length;
-
-			/* CAPTCHA required to log in */
-			captcha_start = strstr (response_body, "CaptchaUrl=");
-			if (captcha_start == NULL)
-				goto protocol_error;
-			captcha_start += strlen ("CaptchaUrl=");
-
-			captcha_end = strstr (captcha_start, "\n");
-			if (captcha_end == NULL)
-				goto protocol_error;
-
-			/* Do some fancy memory stuff to save ourselves another alloc */
-			captcha_base_uri_length = strlen (captcha_base_uri);
-			captcha_uri = g_malloc (captcha_base_uri_length + (captcha_end - captcha_start) + 1);
-			memcpy (captcha_uri, captcha_base_uri, captcha_base_uri_length);
-			memcpy (captcha_uri + captcha_base_uri_length, captcha_start, (captcha_end - captcha_start));
-			captcha_uri[captcha_base_uri_length + (captcha_end - captcha_start)] = '\0';
-
-			/* Request a CAPTCHA answer from the application */
-			g_signal_emit (self, service_signals[SIGNAL_CAPTCHA_CHALLENGE], 0, captcha_uri, &new_captcha_answer);
-			g_free (captcha_uri);
-
-			if (new_captcha_answer == NULL || *new_captcha_answer == '\0') {
-				/* Translators: see http://en.wikipedia.org/wiki/CAPTCHA for information about CAPTCHAs */
-				g_set_error_literal (error, GDATA_AUTHENTICATION_ERROR, GDATA_AUTHENTICATION_ERROR_CAPTCHA_REQUIRED,
-				                     _("A CAPTCHA must be filled out to log in."));
-				goto login_error;
-			}
-
-			/* Get the CAPTCHA token */
-			captcha_start = strstr (response_body, "CaptchaToken=");
-			if (captcha_start == NULL)
-				goto protocol_error;
-			captcha_start += strlen ("CaptchaToken=");
-
-			captcha_end = strstr (captcha_start, "\n");
-			if (captcha_end == NULL)
-				goto protocol_error;
-
-			/* Save the CAPTCHA token and answer, and attempt to log in with them */
-			g_object_unref (message);
-
-			return authenticate (self, username, password, g_strndup (captcha_start, captcha_end - captcha_start), new_captcha_answer,
-			                     cancellable, error);
-		} else if (strncmp (error_start, "Unknown", error_end - error_start) == 0) {
-			goto protocol_error;
-		} else if (strncmp (error_start, "BadAuthentication", error_end - error_start) == 0) {
-			/* Looks like Error=BadAuthentication errors don't return a URI */
-			gchar *info_start, *info_end;
-
-			info_start = strstr (response_body, "Info=");
-			if (info_start != NULL) {
-				info_start += strlen ("Info=");
-				info_end = strstr (info_start, "\n");
-			}
-
-			/* If Info=InvalidSecondFactor, the user needs to generate an application-specific password and use that instead */
-			if (info_start != NULL && info_end != NULL && strncmp (info_start, "InvalidSecondFactor", info_end - info_start) == 0) {
-				g_set_error (error, GDATA_AUTHENTICATION_ERROR, GDATA_AUTHENTICATION_ERROR_INVALID_SECOND_FACTOR,
-				             /* Translators: the parameter is a URI for further information. */
-				             _("This account requires an application-specific password. (%s)"),
-				             "http://www.google.com/support/accounts/bin/static.py?page=guide.cs&guide=1056283&topic=1056286";);
-				goto login_error;
-			}
-
-			/* Fall back to a generic "bad authentication details" message */
-			g_set_error_literal (error, GDATA_AUTHENTICATION_ERROR, GDATA_AUTHENTICATION_ERROR_BAD_AUTHENTICATION,
-			                     _("Your username or password were incorrect."));
-			goto login_error;
-		}
+	domains = gdata_service_get_authorization_domains (G_OBJECT_TYPE (self));
 
-		/* Get the information URI */
-		uri_start = strstr (response_body, "Url=");
-		if (uri_start == NULL)
-			goto protocol_error;
-		uri_start += strlen ("Url=");
-
-		uri_end = strstr (uri_start, "\n");
-		if (uri_end == NULL)
-			goto protocol_error;
-
-		uri = g_strndup (uri_start, uri_end - uri_start);
-
-		if (strncmp (error_start, "NotVerified", error_end - error_start) == 0) {
-			g_set_error (error, GDATA_AUTHENTICATION_ERROR, GDATA_AUTHENTICATION_ERROR_NOT_VERIFIED,
-			             /* Translators: the parameter is a URI for further information. */
-			             _("Your account's e-mail address has not been verified. (%s)"), uri);
-			goto login_error;
-		} else if (strncmp (error_start, "TermsNotAgreed", error_end - error_start) == 0) {
-			g_set_error (error, GDATA_AUTHENTICATION_ERROR, GDATA_AUTHENTICATION_ERROR_TERMS_NOT_AGREED,
-			             /* Translators: the parameter is a URI for further information. */
-			             _("You have not agreed to the service's terms and conditions. (%s)"), uri);
-			goto login_error;
-		} else if (strncmp (error_start, "AccountMigrated", error_end - error_start) == 0) {
-			/* This is non-standard, and used by YouTube since it's got messed-up accounts */
-			g_set_error (error, GDATA_AUTHENTICATION_ERROR, GDATA_AUTHENTICATION_ERROR_ACCOUNT_MIGRATED,
-			             /* Translators: the parameter is a URI for further information. */
-			             _("This account has been migrated. Please log in online to receive your new username and password. (%s)"), uri);
-			goto login_error;
-		} else if (strncmp (error_start, "AccountDeleted", error_end - error_start) == 0) {
-			g_set_error (error, GDATA_AUTHENTICATION_ERROR, GDATA_AUTHENTICATION_ERROR_ACCOUNT_DELETED,
-			             /* Translators: the parameter is a URI for further information. */
-			             _("This account has been deleted. (%s)"), uri);
-			goto login_error;
-		} else if (strncmp (error_start, "AccountDisabled", error_end - error_start) == 0) {
-			g_set_error (error, GDATA_AUTHENTICATION_ERROR, GDATA_AUTHENTICATION_ERROR_ACCOUNT_DISABLED,
-			             /* Translators: the parameter is a URI for further information. */
-			             _("This account has been disabled. (%s)"), uri);
-			goto login_error;
-		} else if (strncmp (error_start, "ServiceDisabled", error_end - error_start) == 0) {
-			g_set_error (error, GDATA_AUTHENTICATION_ERROR, GDATA_AUTHENTICATION_ERROR_SERVICE_DISABLED,
-			             /* Translators: the parameter is a URI for further information. */
-			             _("This account's access to this service has been disabled. (%s)"), uri);
-			goto login_error;
-		} else if (strncmp (error_start, "ServiceUnavailable", error_end - error_start) == 0) {
-			g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_UNAVAILABLE,
-			             /* Translators: the parameter is a URI for further information. */
-			             _("This service is not available at the moment. (%s)"), uri);
-			goto login_error;
+	/* Find any domains which we're not authorised for */
+	for (i = domains; i != NULL; i = i->next) {
+		if (gdata_authorizer_is_authorized_for_domain (self->priv->authorizer, GDATA_AUTHORIZATION_DOMAIN (i->data)) == FALSE) {
+			authorised = FALSE;
+			break;
 		}
-
-		/* Unknown error type! */
-		goto protocol_error;
-
-login_error:
-		g_free (uri);
-		g_object_unref (message);
-
-		return FALSE;
 	}
 
-	g_assert (message->response_body->data != NULL);
-
-	g_static_mutex_lock (&(priv->authentication_mutex));
-	retval = klass->parse_authentication_response (self, status, message->response_body->data, message->response_body->length, error);
-	g_static_mutex_unlock (&(priv->authentication_mutex));
-
-	g_object_unref (message);
-
-	return retval;
-
-protocol_error:
-	g_assert (klass->parse_error_response != NULL);
-	klass->parse_error_response (self, GDATA_OPERATION_AUTHENTICATION, status, message->reason_phrase, message->response_body->data,
-	                             message->response_body->length, error);
-
-	g_object_unref (message);
-
-	return FALSE;
-}
-
-typedef struct {
-	gchar *username;
-	gchar *password;
-} AuthenticateAsyncData;
+	g_list_free (domains);
 
-static void
-authenticate_async_data_free (AuthenticateAsyncData *self)
-{
-	g_free (self->username);
-	g_free (self->password);
-
-	g_slice_free (AuthenticateAsyncData, self);
-}
-
-static void
-authenticate_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *cancellable)
-{
-	GError *error = NULL;
-	gboolean success;
-	AuthenticateAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);
-
-	/* Authenticate and return */
-	success = authenticate (service, data->username, data->password, NULL, NULL, cancellable, &error);
-	set_authentication_details (service, data->username, data->password, success);
-	g_simple_async_result_set_op_res_gboolean (result, success);
-
-	if (success == FALSE) {
-		g_simple_async_result_set_from_error (result, error);
-		g_error_free (error);
-	}
+	return authorised;
 }
 
 /**
- * gdata_service_authenticate_async:
+ * gdata_service_get_authorizer:
  * @self: a #GDataService
- * @username: the user's username
- * @password: the user's password
- * @cancellable: optional #GCancellable object, or %NULL
- * @callback: a #GAsyncReadyCallback to call when authentication is finished
- * @user_data: (closure): data to pass to the @callback function
  *
- * Authenticates the #GDataService with the online service using the given @username and @password. @self, @username and
- * @password are all reffed/copied when this function is called, so can safely be freed after this function returns.
+ * Gets the #GDataAuthorizer object currently in use by the service. See the documentation for #GDataService:authorizer for more details.
  *
- * For more details, see gdata_service_authenticate(), which is the synchronous version of this function.
+ * Return value: (transfer none): the authorizer object for this service, or %NULL
  *
- * When the operation is finished, @callback will be called. You can then call gdata_service_authenticate_finish()
- * to get the results of the operation.
- **/
-void
-gdata_service_authenticate_async (GDataService *self, const gchar *username, const gchar *password,
-				  GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+ * Since: 0.9.0
+ */
+GDataAuthorizer *
+gdata_service_get_authorizer (GDataService *self)
 {
-	GSimpleAsyncResult *result;
-	AuthenticateAsyncData *data;
-
-	g_return_if_fail (GDATA_IS_SERVICE (self));
-	g_return_if_fail (username != NULL);
-	g_return_if_fail (password != NULL);
-	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
-
-	data = g_slice_new (AuthenticateAsyncData);
-	data->username = g_strdup (username);
-	data->password = g_strdup (password);
+	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
 
-	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_service_authenticate_async);
-	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) authenticate_async_data_free);
-	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) authenticate_thread, G_PRIORITY_DEFAULT, cancellable);
-	g_object_unref (result);
+	return self->priv->authorizer;
 }
 
 /**
- * gdata_service_authenticate_finish:
+ * gdata_service_set_authorizer:
  * @self: a #GDataService
- * @async_result: a #GAsyncResult
- * @error: a #GError, or %NULL
+ * @authorizer: a new authorizer object for the service, or %NULL
  *
- * Finishes an asynchronous authentication operation started with gdata_service_authenticate_async().
+ * Sets #GDataService:authorizer to @authorizer. This may be %NULL if the service will only make requests in future which don't require authorization.
+ * See the documentation for #GDataService:authorizer for more information.
  *
- * Return value: %TRUE if authentication was successful, %FALSE otherwise
- **/
-gboolean
-gdata_service_authenticate_finish (GDataService *self, GAsyncResult *async_result, GError **error)
+ * Since: 0.9.0
+ */
+void
+gdata_service_set_authorizer (GDataService *self, GDataAuthorizer *authorizer)
 {
-	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
-	gboolean success;
+	GDataServicePrivate *priv = self->priv;
 
-	g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE);
-	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE);
-	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	g_return_if_fail (GDATA_IS_SERVICE (self));
+	g_return_if_fail (authorizer == NULL || GDATA_IS_AUTHORIZER (authorizer));
 
-	g_warn_if_fail (g_simple_async_result_get_source_tag (result) == gdata_service_authenticate_async);
+	if (priv->authorizer != NULL) {
+		g_object_unref (priv->authorizer);
+	}
 
-	if (g_simple_async_result_propagate_error (result, error) == TRUE)
-		return FALSE;
+	priv->authorizer = authorizer;
 
-	success = g_simple_async_result_get_op_res_gboolean (result);
-	g_assert (success == TRUE);
+	if (priv->authorizer != NULL) {
+		g_object_ref (priv->authorizer);
+	}
 
-	return success;
+	g_object_notify (G_OBJECT (self), "authorizer");
 }
 
 /**
- * gdata_service_authenticate:
- * @self: a #GDataService
- * @username: the user's username
- * @password: the user's password
- * @cancellable: optional #GCancellable object, or %NULL
- * @error: a #GError, or %NULL
+ * gdata_service_get_authorization_domains:
+ * @service_type: the #GType of the #GDataService subclass to retrieve the authorization domains for
  *
- * Authenticates the #GDataService with the online service using @username and @password; i.e. logs into the service with the given
- * user account. @username should be a full e-mail address (e.g. <literal>john smith\ gmail com</literal>). If a full e-mail address is
- * not given, @username will have <literal>\ gmail com</literal> appended to create an e-mail address
+ * Retrieves the full list of #GDataAuthorizationDomain<!-- -->s which relate to the specified @service_type. All the
+ * #GDataAuthorizationDomain<!-- -->s are unique and interned, so can be compared with other domains by simple pointer comparison.
  *
- * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
- * If the operation was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ * Note that in addition to this method, #GDataService subclasses may expose some or all of their authorization domains individually by means of
+ * individual accessor functions.
  *
- * A %GDATA_AUTHENTICATION_ERROR_BAD_AUTHENTICATION will be returned if authentication failed due to an incorrect username or password.
- * Other #GDataAuthenticationError errors can be returned for other conditions.
+ * Return value: (transfer container) (element-type GDataAuthorizationDomain): an unordered list of #GDataAuthorizationDomain<!-- -->s; free with
+ * g_list_free()
  *
- * If the service requires a CAPTCHA to be completed, the #GDataService::captcha-challenge signal will be emitted. The return value from
- * a signal handler for the signal should be a newly allocated string containing the text from the image. If the text is %NULL or empty,
- * authentication will fail with a %GDATA_AUTHENTICATION_ERROR_CAPTCHA_REQUIRED error. Otherwise, authentication will be automatically and
- * transparently restarted with the new CAPTCHA details.
- *
- * A %GDATA_SERVICE_ERROR_PROTOCOL_ERROR will be returned if the server's responses were invalid. Subclasses of #GDataService can override
- * parsing the authentication response, and may return their own error codes. See their documentation for more details.
- *
- * Return value: %TRUE if authentication was successful, %FALSE otherwise
- **/
-gboolean
-gdata_service_authenticate (GDataService *self, const gchar *username, const gchar *password, GCancellable *cancellable, GError **error)
+ * Since: 0.9.0
+ */
+GList *
+gdata_service_get_authorization_domains (GType service_type)
 {
-	gboolean retval;
+	GDataServiceClass *klass;
+	GList *domains = NULL;
 
-	g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE);
-	g_return_val_if_fail (username != NULL, FALSE);
-	g_return_val_if_fail (password != NULL, FALSE);
-	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
-	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	g_return_val_if_fail (g_type_is_a (service_type, GDATA_TYPE_SERVICE), NULL);
 
-	retval = authenticate (self, username, password, NULL, NULL, cancellable, error);
-	set_authentication_details (self, username, password, retval);
+	klass = GDATA_SERVICE_CLASS (g_type_class_ref (service_type));
+	if (klass->get_authorization_domains != NULL) {
+		domains = klass->get_authorization_domains ();
+	}
+	g_type_class_unref (klass);
 
-	return retval;
+	return domains;
 }
 
 SoupMessage *
-_gdata_service_build_message (GDataService *self, const gchar *method, const gchar *uri, const gchar *etag, gboolean etag_if_match)
+_gdata_service_build_message (GDataService *self, GDataAuthorizationDomain *domain, const gchar *method, const gchar *uri,
+                              const gchar *etag, gboolean etag_if_match)
 {
 	SoupMessage *message;
 	GDataServiceClass *klass;
@@ -968,7 +533,7 @@ _gdata_service_build_message (GDataService *self, const gchar *method, const gch
 	/* Make sure subclasses set their headers */
 	klass = GDATA_SERVICE_GET_CLASS (self);
 	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (self, message);
+		klass->append_query_headers (self, domain, message);
 
 	/* Append the ETag header if possible */
 	if (etag != NULL)
@@ -1110,11 +675,24 @@ _gdata_service_send_message (GDataService *self, SoupMessage *message, GCancella
 		_gdata_service_actually_send_message (self->priv->session, message, cancellable, error);
 	}
 
+	/* Not authorised, or authorisation has expired. If we were authorised in the first place, attempt to refresh the authorisation and
+	 * try sending the message again (but only once, so we don't get caught in an infinite loop of denied authorisation errors). */
+	if (message->status_code == SOUP_STATUS_UNAUTHORIZED) {
+		GDataAuthorizer *authorizer = self->priv->authorizer;
+
+		if (authorizer != NULL && gdata_authorizer_refresh_authorization (authorizer, cancellable, NULL) == TRUE) {
+			/* Send the message again */
+			g_clear_error (error);
+			_gdata_service_actually_send_message (self->priv->session, message, cancellable, error);
+		}
+	}
+
 	return message->status_code;
 }
 
 typedef struct {
 	/* Input */
+	GDataAuthorizationDomain *domain;
 	gchar *feed_uri;
 	GDataQuery *query;
 	GType entry_type;
@@ -1128,6 +706,9 @@ typedef struct {
 static void
 query_async_data_free (QueryAsyncData *self)
 {
+	if (self->domain != NULL)
+		g_object_unref (self->domain);
+
 	g_free (self->feed_uri);
 	if (self->query)
 		g_object_unref (self->query);
@@ -1144,7 +725,7 @@ query_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *c
 	QueryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);
 
 	/* Execute the query and return */
-	data->feed = gdata_service_query (service, data->feed_uri, data->query, data->entry_type, cancellable,
+	data->feed = gdata_service_query (service, data->domain, data->feed_uri, data->query, data->entry_type, cancellable,
 	                                  data->progress_callback, data->progress_user_data, &error);
 	if (data->feed == NULL && error != NULL) {
 		g_simple_async_result_set_from_error (result, error);
@@ -1155,6 +736,7 @@ query_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *c
 /**
  * gdata_service_query_async: (skip)
  * @self: a #GDataService
+ * @domain: (allow-none): the #GDataAuthorizationDomain the query falls under, or %NULL
  * @feed_uri: the feed URI to query, including the host name and protocol
  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
  * @entry_type: a #GType for the #GDataEntry<!-- -->s to build from the XML
@@ -1171,22 +753,26 @@ query_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *c
  *
  * When the operation is finished, @callback will be called. You can then call gdata_service_query_finish()
  * to get the results of the operation.
+ *
+ * Since: 0.9.0
  **/
 void
-gdata_service_query_async (GDataService *self, const gchar *feed_uri, GDataQuery *query, GType entry_type, GCancellable *cancellable,
-                           GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
+gdata_service_query_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GType entry_type,
+                           GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
                            GAsyncReadyCallback callback, gpointer user_data)
 {
 	GSimpleAsyncResult *result;
 	QueryAsyncData *data;
 
 	g_return_if_fail (GDATA_IS_SERVICE (self));
+	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
 	g_return_if_fail (feed_uri != NULL);
 	g_return_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY));
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 	g_return_if_fail (callback != NULL);
 
 	data = g_slice_new (QueryAsyncData);
+	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
 	data->feed_uri = g_strdup (feed_uri);
 	data->query = (query != NULL) ? g_object_ref (query) : NULL;
 	data->entry_type = entry_type;
@@ -1233,7 +819,8 @@ gdata_service_query_finish (GDataService *self, GAsyncResult *async_result, GErr
 /* Does the bulk of the work of gdata_service_query. Split out because certain queries (such as that done by
  * gdata_service_query_single_entry()) only return a single entry, and thus need special parsing code. */
 SoupMessage *
-_gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *query, GCancellable *cancellable, GError **error)
+_gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query,
+                      GCancellable *cancellable, GError **error)
 {
 	SoupMessage *message;
 	guint status;
@@ -1246,10 +833,10 @@ _gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *que
 	/* Build the message */
 	if (query != NULL) {
 		gchar *query_uri = gdata_query_get_query_uri (query, feed_uri);
-		message = _gdata_service_build_message (self, SOUP_METHOD_GET, query_uri, etag, FALSE);
+		message = _gdata_service_build_message (self, domain, SOUP_METHOD_GET, query_uri, etag, FALSE);
 		g_free (query_uri);
 	} else {
-		message = _gdata_service_build_message (self, SOUP_METHOD_GET, feed_uri, etag, FALSE);
+		message = _gdata_service_build_message (self, domain, SOUP_METHOD_GET, feed_uri, etag, FALSE);
 	}
 
 	/* Note that cancellation only applies to network activity; not to the processing done afterwards */
@@ -1275,6 +862,7 @@ _gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *que
 /**
  * gdata_service_query:
  * @self: a #GDataService
+ * @domain: (allow-none): the #GDataAuthorizationDomain the query falls under, or %NULL
  * @feed_uri: the feed URI to query, including the host name and protocol
  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
  * @entry_type: a #GType for the #GDataEntry<!-- -->s to build from the XML
@@ -1304,9 +892,11 @@ _gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *que
  * @query's ETag will be updated with the ETag from the returned feed, if available.
  *
  * Return value: (transfer full): a #GDataFeed of query results, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.9.0
  **/
 GDataFeed *
-gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *query, GType entry_type,
+gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GType entry_type,
                      GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
 {
 	GDataServiceClass *klass;
@@ -1314,12 +904,13 @@ gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *quer
 	SoupMessage *message;
 
 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
+	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
 	g_return_val_if_fail (feed_uri != NULL, NULL);
 	g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY), NULL);
 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
-	message = _gdata_service_query (self, feed_uri, query, cancellable, error);
+	message = _gdata_service_query (self, domain, feed_uri, query, cancellable, error);
 	if (message == NULL)
 		return NULL;
 
@@ -1354,6 +945,7 @@ gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *quer
 /**
  * gdata_service_query_single_entry:
  * @self: a #GDataService
+ * @domain: (allow-none): the #GDataAuthorizationDomain the query falls under, or %NULL
  * @entry_id: the entry ID of the desired entry
  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
  * @entry_type: a #GType for the #GDataEntry to build from the XML
@@ -1371,10 +963,10 @@ gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *quer
  *
  * Return value: (transfer full): a #GDataEntry, or %NULL; unref with g_object_unref()
  *
- * Since: 0.7.0
+ * Since: 0.9.0
  **/
 GDataEntry *
-gdata_service_query_single_entry (GDataService *self, const gchar *entry_id, GDataQuery *query, GType entry_type,
+gdata_service_query_single_entry (GDataService *self, GDataAuthorizationDomain *domain, const gchar *entry_id, GDataQuery *query, GType entry_type,
                                   GCancellable *cancellable, GError **error)
 {
 	GDataEntryClass *klass;
@@ -1383,6 +975,7 @@ gdata_service_query_single_entry (GDataService *self, const gchar *entry_id, GDa
 	SoupMessage *message;
 
 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
+	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
 	g_return_val_if_fail (entry_id != NULL, NULL);
 	g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL);
 	g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY) == TRUE, NULL);
@@ -1394,7 +987,7 @@ gdata_service_query_single_entry (GDataService *self, const gchar *entry_id, GDa
 	g_assert (klass->get_entry_uri != NULL);
 
 	entry_uri = klass->get_entry_uri (entry_id);
-	message = _gdata_service_query (GDATA_SERVICE (self), entry_uri, query, cancellable, error);
+	message = _gdata_service_query (GDATA_SERVICE (self), domain, entry_uri, query, cancellable, error);
 	g_free (entry_uri);
 
 	if (message == NULL) {
@@ -1411,6 +1004,7 @@ gdata_service_query_single_entry (GDataService *self, const gchar *entry_id, GDa
 }
 
 typedef struct {
+	GDataAuthorizationDomain *domain;
 	gchar *entry_id;
 	GDataQuery *query;
 	GType entry_type;
@@ -1419,6 +1013,9 @@ typedef struct {
 static void
 query_single_entry_async_data_free (QuerySingleEntryAsyncData *data)
 {
+	if (data->domain != NULL)
+		g_object_unref (data->domain);
+
 	g_free (data->entry_id);
 	if (data->query != NULL)
 		g_object_unref (data->query);
@@ -1433,7 +1030,7 @@ query_single_entry_thread (GSimpleAsyncResult *result, GDataService *service, GC
 	QuerySingleEntryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);
 
 	/* Execute the query and return */
-	entry = gdata_service_query_single_entry (service, data->entry_id, data->query, data->entry_type, cancellable, &error);
+	entry = gdata_service_query_single_entry (service, data->domain, data->entry_id, data->query, data->entry_type, cancellable, &error);
 	if (entry == NULL && error != NULL) {
 		g_simple_async_result_set_from_error (result, error);
 		g_error_free (error);
@@ -1445,6 +1042,7 @@ query_single_entry_thread (GSimpleAsyncResult *result, GDataService *service, GC
 /**
  * gdata_service_query_single_entry_async:
  * @self: a #GDataService
+ * @domain: (allow-none): the #GDataAuthorizationDomain the query falls under, or %NULL
  * @entry_id: the entry ID of the desired entry
  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
  * @entry_type: a #GType for the #GDataEntry to build from the XML
@@ -1461,16 +1059,17 @@ query_single_entry_thread (GSimpleAsyncResult *result, GDataService *service, GC
  * When the operation is finished, @callback will be called. You can then call gdata_service_query_single_entry_finish()
  * to get the results of the operation.
  *
- * Since: 0.7.0
+ * Since: 0.9.0
  **/
 void
-gdata_service_query_single_entry_async (GDataService *self, const gchar *entry_id, GDataQuery *query, GType entry_type,
-                                        GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+gdata_service_query_single_entry_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *entry_id, GDataQuery *query,
+                                        GType entry_type, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
 {
 	GSimpleAsyncResult *result;
 	QuerySingleEntryAsyncData *data;
 
 	g_return_if_fail (GDATA_IS_SERVICE (self));
+	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
 	g_return_if_fail (entry_id != NULL);
 	g_return_if_fail (query == NULL || GDATA_IS_QUERY (query));
 	g_return_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY) == TRUE);
@@ -1478,6 +1077,7 @@ gdata_service_query_single_entry_async (GDataService *self, const gchar *entry_i
 	g_return_if_fail (callback != NULL);
 
 	data = g_slice_new (QuerySingleEntryAsyncData);
+	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
 	data->query = (query != NULL) ? g_object_ref (query) : NULL;
 	data->entry_id = g_strdup (entry_id);
 	data->entry_type = entry_type;
@@ -1522,6 +1122,7 @@ gdata_service_query_single_entry_finish (GDataService *self, GAsyncResult *async
 }
 
 typedef struct {
+	GDataAuthorizationDomain *domain;
 	gchar *upload_uri;
 	GDataEntry *entry;
 } InsertEntryAsyncData;
@@ -1529,6 +1130,9 @@ typedef struct {
 static void
 insert_entry_async_data_free (InsertEntryAsyncData *self)
 {
+	if (self->domain != NULL)
+		g_object_unref (self->domain);
+
 	g_free (self->upload_uri);
 	if (self->entry)
 		g_object_unref (self->entry);
@@ -1544,7 +1148,7 @@ insert_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancell
 	InsertEntryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);
 
 	/* Insert the entry and return */
-	updated_entry = gdata_service_insert_entry (service, data->upload_uri, data->entry, cancellable, &error);
+	updated_entry = gdata_service_insert_entry (service, data->domain, data->upload_uri, data->entry, cancellable, &error);
 	if (updated_entry == NULL) {
 		g_simple_async_result_set_from_error (result, error);
 		g_error_free (error);
@@ -1558,6 +1162,7 @@ insert_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancell
 /**
  * gdata_service_insert_entry_async:
  * @self: a #GDataService
+ * @domain: (allow-none): the #GDataAuthorizationDomain the insertion operation falls under, or %NULL
  * @upload_uri: the URI to which the upload should be sent
  * @entry: the #GDataEntry to insert
  * @cancellable: optional #GCancellable object, or %NULL
@@ -1572,21 +1177,23 @@ insert_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancell
  * When the operation is finished, @callback will be called. You can then call gdata_service_insert_entry_finish()
  * to get the results of the operation.
  *
- * Since: 0.3.0
+ * Since: 0.9.0
  **/
 void
-gdata_service_insert_entry_async (GDataService *self, const gchar *upload_uri, GDataEntry *entry, GCancellable *cancellable,
-                                  GAsyncReadyCallback callback, gpointer user_data)
+gdata_service_insert_entry_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *upload_uri, GDataEntry *entry,
+                                  GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
 {
 	GSimpleAsyncResult *result;
 	InsertEntryAsyncData *data;
 
 	g_return_if_fail (GDATA_IS_SERVICE (self));
+	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
 	g_return_if_fail (upload_uri != NULL);
 	g_return_if_fail (GDATA_IS_ENTRY (entry));
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
 	data = g_slice_new (InsertEntryAsyncData);
+	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
 	data->upload_uri = g_strdup (upload_uri);
 	data->entry = g_object_ref (entry);
 
@@ -1633,6 +1240,7 @@ gdata_service_insert_entry_finish (GDataService *self, GAsyncResult *async_resul
 /**
  * gdata_service_insert_entry:
  * @self: a #GDataService
+ * @domain: (allow-none): the #GDataAuthorizationDomain the insertion operation falls under, or %NULL
  * @upload_uri: the URI to which the upload should be sent
  * @entry: the #GDataEntry to insert
  * @cancellable: optional #GCancellable object, or %NULL
@@ -1657,9 +1265,12 @@ gdata_service_insert_entry_finish (GDataService *self, GAsyncResult *async_resul
  * <emphasis>cannot</emphasis> cannot override this or provide more specific errors.
  *
  * Return value: (transfer full): an updated #GDataEntry, or %NULL; unref with g_object_unref()
- **/
+ *
+ * Since: 0.9.0
+ */
 GDataEntry *
-gdata_service_insert_entry (GDataService *self, const gchar *upload_uri, GDataEntry *entry, GCancellable *cancellable, GError **error)
+gdata_service_insert_entry (GDataService *self, GDataAuthorizationDomain *domain, const gchar *upload_uri, GDataEntry *entry,
+                            GCancellable *cancellable, GError **error)
 {
 	GDataEntry *updated_entry;
 	SoupMessage *message;
@@ -1667,6 +1278,7 @@ gdata_service_insert_entry (GDataService *self, const gchar *upload_uri, GDataEn
 	guint status;
 
 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
+	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
 	g_return_val_if_fail (upload_uri != NULL, NULL);
 	g_return_val_if_fail (GDATA_IS_ENTRY (entry), NULL);
 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
@@ -1678,7 +1290,7 @@ gdata_service_insert_entry (GDataService *self, const gchar *upload_uri, GDataEn
 		return NULL;
 	}
 
-	message = _gdata_service_build_message (self, SOUP_METHOD_POST, upload_uri, NULL, FALSE);
+	message = _gdata_service_build_message (self, domain, SOUP_METHOD_POST, upload_uri, NULL, FALSE);
 
 	/* Append the data */
 	upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
@@ -1710,14 +1322,32 @@ gdata_service_insert_entry (GDataService *self, const gchar *upload_uri, GDataEn
 	return updated_entry;
 }
 
+typedef struct {
+	GDataAuthorizationDomain *domain;
+	GDataEntry *entry;
+} UpdateEntryAsyncData;
+
+static void
+update_entry_async_data_free (UpdateEntryAsyncData *data)
+{
+	if (data->domain != NULL)
+		g_object_unref (data->domain);
+
+	if (data->entry != NULL)
+		g_object_unref (data->entry);
+
+	g_slice_free (UpdateEntryAsyncData, data);
+}
+
 static void
 update_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *cancellable)
 {
 	GDataEntry *updated_entry;
 	GError *error = NULL;
+	UpdateEntryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);
 
 	/* Update the entry and return */
-	updated_entry = gdata_service_update_entry (service, g_simple_async_result_get_op_res_gpointer (result), cancellable, &error);
+	updated_entry = gdata_service_update_entry (service, data->domain, data->entry, cancellable, &error);
 	if (updated_entry == NULL) {
 		g_simple_async_result_set_from_error (result, error);
 		g_error_free (error);
@@ -1731,6 +1361,7 @@ update_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancell
 /**
  * gdata_service_update_entry_async:
  * @self: a #GDataService
+ * @domain: (allow-none): the #GDataAuthorizationDomain the update operation falls under, or %NULL
  * @entry: the #GDataEntry to update
  * @cancellable: optional #GCancellable object, or %NULL
  * @callback: a #GAsyncReadyCallback to call when the update is finished, or %NULL
@@ -1744,19 +1375,26 @@ update_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancell
  * When the operation is finished, @callback will be called. You can then call gdata_service_update_entry_finish()
  * to get the results of the operation.
  *
- * Since: 0.3.0
+ * Since: 0.9.0
  **/
 void
-gdata_service_update_entry_async (GDataService *self, GDataEntry *entry, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+gdata_service_update_entry_async (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry,
+                                  GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
 {
 	GSimpleAsyncResult *result;
+	UpdateEntryAsyncData *data;
 
 	g_return_if_fail (GDATA_IS_SERVICE (self));
+	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
 	g_return_if_fail (GDATA_IS_ENTRY (entry));
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
+	data = g_slice_new (UpdateEntryAsyncData);
+	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
+	data->entry = g_object_ref (entry);
+
 	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_service_update_entry_async);
-	g_simple_async_result_set_op_res_gpointer (result, g_object_ref (entry), (GDestroyNotify) g_object_unref);
+	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) update_entry_async_data_free);
 	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) update_entry_thread, G_PRIORITY_DEFAULT, cancellable);
 	g_object_unref (result);
 }
@@ -1798,6 +1436,7 @@ gdata_service_update_entry_finish (GDataService *self, GAsyncResult *async_resul
 /**
  * gdata_service_update_entry:
  * @self: a #GDataService
+ * @domain: (allow-none): the #GDataAuthorizationDomain the update operation falls under, or %NULL
  * @entry: the #GDataEntry to update
  * @cancellable: optional #GCancellable object, or %NULL
  * @error: a #GError, or %NULL
@@ -1819,10 +1458,10 @@ gdata_service_update_entry_finish (GDataService *self, GAsyncResult *async_resul
  *
  * Return value: (transfer full): an updated #GDataEntry, or %NULL; unref with g_object_unref()
  *
- * Since: 0.2.0
+ * Since: 0.9.0
  **/
 GDataEntry *
-gdata_service_update_entry (GDataService *self, GDataEntry *entry, GCancellable *cancellable, GError **error)
+gdata_service_update_entry (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry, GCancellable *cancellable, GError **error)
 {
 	GDataEntry *updated_entry;
 	GDataLink *_link;
@@ -1831,6 +1470,7 @@ gdata_service_update_entry (GDataService *self, GDataEntry *entry, GCancellable
 	guint status;
 
 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
+	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
 	g_return_val_if_fail (GDATA_IS_ENTRY (entry), NULL);
 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
@@ -1838,7 +1478,7 @@ gdata_service_update_entry (GDataService *self, GDataEntry *entry, GCancellable
 	/* Get the edit URI */
 	_link = gdata_entry_look_up_link (entry, GDATA_LINK_EDIT);
 	g_assert (_link != NULL);
-	message = _gdata_service_build_message (self, SOUP_METHOD_PUT, gdata_link_get_uri (_link), gdata_entry_get_etag (entry), TRUE);
+	message = _gdata_service_build_message (self, domain, SOUP_METHOD_PUT, gdata_link_get_uri (_link), gdata_entry_get_etag (entry), TRUE);
 
 	/* Append the data */
 	upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
@@ -1870,14 +1510,32 @@ gdata_service_update_entry (GDataService *self, GDataEntry *entry, GCancellable
 	return updated_entry;
 }
 
+typedef struct {
+	GDataAuthorizationDomain *domain;
+	GDataEntry *entry;
+} DeleteEntryAsyncData;
+
+static void
+delete_entry_async_data_free (DeleteEntryAsyncData *data)
+{
+	if (data->domain != NULL)
+		g_object_unref (data->domain);
+
+	if (data->entry != NULL)
+		g_object_unref (data->entry);
+
+	g_slice_free (DeleteEntryAsyncData, data);
+}
+
 static void
 delete_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *cancellable)
 {
 	gboolean success;
 	GError *error = NULL;
+	DeleteEntryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);
 
 	/* Delete the entry and return */
-	success = gdata_service_delete_entry (service, g_simple_async_result_get_op_res_gpointer (result), cancellable, &error);
+	success = gdata_service_delete_entry (service, data->domain, data->entry, cancellable, &error);
 	if (success == FALSE) {
 		g_simple_async_result_set_from_error (result, error);
 		g_error_free (error);
@@ -1891,6 +1549,7 @@ delete_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancell
 /**
  * gdata_service_delete_entry_async:
  * @self: a #GDataService
+ * @domain: (allow-none): the #GDataAuthorizationDomain the deletion falls under, or %NULL
  * @entry: the #GDataEntry to delete
  * @cancellable: optional #GCancellable object, or %NULL
  * @callback: a #GAsyncReadyCallback to call when deletion is finished, or %NULL
@@ -1904,19 +1563,26 @@ delete_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancell
  * When the operation is finished, @callback will be called. You can then call gdata_service_delete_entry_finish()
  * to get the results of the operation.
  *
- * Since: 0.3.0
+ * Since: 0.9.0
  **/
 void
-gdata_service_delete_entry_async (GDataService *self, GDataEntry *entry, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+gdata_service_delete_entry_async (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry,
+                                  GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
 {
 	GSimpleAsyncResult *result;
+	DeleteEntryAsyncData *data;
 
 	g_return_if_fail (GDATA_IS_SERVICE (self));
+	g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
 	g_return_if_fail (GDATA_IS_ENTRY (entry));
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
+	data = g_slice_new (DeleteEntryAsyncData);
+	data->domain = (domain != NULL) ? g_object_ref (domain) : NULL;
+	data->entry = g_object_ref (entry);
+
 	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_service_delete_entry_async);
-	g_simple_async_result_set_op_res_gpointer (result, g_object_ref (entry), (GDestroyNotify) g_object_unref);
+	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) delete_entry_async_data_free);
 	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) delete_entry_thread, G_PRIORITY_DEFAULT, cancellable);
 	g_object_unref (result);
 }
@@ -1953,6 +1619,7 @@ gdata_service_delete_entry_finish (GDataService *self, GAsyncResult *async_resul
 /**
  * gdata_service_delete_entry:
  * @self: a #GDataService
+ * @domain: (allow-none): the #GDataAuthorizationDomain the deletion falls under, or %NULL
  * @entry: the #GDataEntry to delete
  * @cancellable: optional #GCancellable object, or %NULL
  * @error: a #GError, or %NULL
@@ -1972,16 +1639,17 @@ gdata_service_delete_entry_finish (GDataService *self, GAsyncResult *async_resul
  *
  * Return value: %TRUE on success, %FALSE otherwise
  *
- * Since: 0.2.0
+ * Since: 0.9.0
  **/
 gboolean
-gdata_service_delete_entry (GDataService *self, GDataEntry *entry, GCancellable *cancellable, GError **error)
+gdata_service_delete_entry (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry, GCancellable *cancellable, GError **error)
 {
 	GDataLink *_link;
 	SoupMessage *message;
 	guint status;
 
 	g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE);
+	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), FALSE);
 	g_return_val_if_fail (GDATA_IS_ENTRY (entry), FALSE);
 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
@@ -1989,7 +1657,7 @@ gdata_service_delete_entry (GDataService *self, GDataEntry *entry, GCancellable
 	/* Get the edit URI */
 	_link = gdata_entry_look_up_link (entry, GDATA_LINK_EDIT);
 	g_assert (_link != NULL);
-	message = _gdata_service_build_message (self, SOUP_METHOD_DELETE, gdata_link_get_uri (_link), gdata_entry_get_etag (entry), TRUE);
+	message = _gdata_service_build_message (self, domain, SOUP_METHOD_DELETE, gdata_link_get_uri (_link), gdata_entry_get_etag (entry), TRUE);
 
 	/* Send the message */
 	status = _gdata_service_send_message (self, message, cancellable, error);
@@ -2052,6 +1720,8 @@ gdata_service_get_proxy_uri (GDataService *self)
  *
  * If @proxy_uri is %NULL, no proxy will be used.
  *
+ * Note that if a #GDataAuthorizer is being used with this #GDataService, the authorizer might also need its proxy URI setting.
+ *
  * Since: 0.2.0
  **/
 void
@@ -2099,6 +1769,8 @@ gdata_service_get_timeout (GDataService *self)
  *
  * If @timeout is <code class="literal">0</code>, network operations will never time out.
  *
+ * Note that if a #GDataAuthorizer is being used with this #GDataService, the authorizer might also need its timeout setting.
+ *
  * Since: 0.7.0
  **/
 void
@@ -2109,86 +1781,6 @@ gdata_service_set_timeout (GDataService *self, guint timeout)
 	g_object_notify (G_OBJECT (self), "timeout");
 }
 
-/**
- * gdata_service_is_authenticated:
- * @self: a #GDataService
- *
- * Returns whether a user is authenticated with the online service through @self.
- * Authentication is performed by calling gdata_service_authenticate() or gdata_service_authenticate_async().
- *
- * Return value: %TRUE if a user is authenticated, %FALSE otherwise
- **/
-gboolean
-gdata_service_is_authenticated (GDataService *self)
-{
-	g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE);
-	return self->priv->authenticated;
-}
-
-/* This should only ever be called in the main thread */
-void
-_gdata_service_set_authenticated (GDataService *self, gboolean authenticated)
-{
-	g_return_if_fail (GDATA_IS_SERVICE (self));
-	self->priv->authenticated = authenticated;
-	g_object_notify (G_OBJECT (self), "authenticated");
-}
-
-/**
- * gdata_service_get_client_id:
- * @self: a #GDataService
- *
- * Returns the service's client ID, as specified on constructing the #GDataService.
- *
- * Return value: the service's client ID
- **/
-const gchar *
-gdata_service_get_client_id (GDataService *self)
-{
-	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
-	return self->priv->client_id;
-}
-
-/**
- * gdata_service_get_username:
- * @self: a #GDataService
- *
- * Returns the username of the currently-authenticated user, or %NULL if nobody is authenticated.
- *
- * It is not safe to call this while an authentication operation is ongoing.
- *
- * Return value: the username of the currently-authenticated user, or %NULL
- **/
-const gchar *
-gdata_service_get_username (GDataService *self)
-{
-	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
-
-	/* There's little point protecting this with authentication_mutex, as the data's meaningless if accessed during an authentication operation,
-	 * and not being accessed concurrently otherwise. */
-	return self->priv->username;
-}
-
-/**
- * gdata_service_get_password:
- * @self: a #GDataService
- *
- * Returns the password of the currently-authenticated user, or %NULL if nobody is authenticated.
- *
- * It is not safe to call this while an authentication operation is ongoing.
- *
- * Return value: the password of the currently-authenticated user, or %NULL
- **/
-const gchar *
-gdata_service_get_password (GDataService *self)
-{
-	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
-
-	/* There's little point protecting this with authentication_mutex, as the data's meaningless if accessed during an authentication operation,
-	 * and not being accessed concurrently otherwise. */
-	return self->priv->password;
-}
-
 SoupSession *
 _gdata_service_get_session (GDataService *self)
 {
@@ -2233,8 +1825,8 @@ _gdata_service_get_scheme (void)
 gchar *
 _gdata_service_build_uri (const gchar *format, ...)
 {
-	const gchar *p, *scheme;
-	gchar *built_uri;
+	const gchar *p;
+	gchar *fixed_uri;
 	GString *uri;
 	va_list args;
 
@@ -2273,25 +1865,43 @@ _gdata_service_build_uri (const gchar *format, ...)
 
 	va_end (args);
 
-	built_uri = g_string_free (uri, FALSE);
-	scheme = _gdata_service_get_scheme ();
+	/* Fix the scheme to always be HTTPS */
+	fixed_uri = _gdata_service_fix_uri_scheme (uri->str);
+	g_string_free (uri, TRUE);
+
+	return fixed_uri;
+}
+
+/**
+ * _gdata_service_fix_uri_scheme:
+ * @uri: an URI with either HTTP or HTTPS as the scheme
+ *
+ * Fixes the given URI to always have HTTPS as its scheme.
+ *
+ * Return value: (transfer full): the URI with HTTPS as its scheme
+ *
+ * Since: 0.9.0
+ */
+gchar *
+_gdata_service_fix_uri_scheme (const gchar *uri)
+{
+	g_return_val_if_fail (uri != NULL && *uri != '\0', NULL);
 
 	/* Ensure we're using the correct scheme (HTTP or HTTPS) */
-	if (g_str_has_prefix (built_uri, scheme) == FALSE) {
+	if (g_str_has_prefix (uri, "https") == FALSE) {
 		gchar *fixed_uri, **pieces;
 
-		pieces = g_strsplit (built_uri, ":", 2);
+		pieces = g_strsplit (uri, ":", 2);
 		g_assert (pieces[0] != NULL && pieces[1] != NULL && pieces[2] == NULL);
 
-		fixed_uri = g_strdup_printf ("%s:%s", scheme, pieces[1]);
+		fixed_uri = g_strconcat ("https:", pieces[1], NULL);
 
 		g_strfreev (pieces);
-		g_free (built_uri);
 
 		return fixed_uri;
 	}
 
-	return built_uri;
+	return g_strdup (uri);
 }
 
 /*
@@ -2343,6 +1953,54 @@ _gdata_service_get_log_level (void)
 }
 
 /**
+ * _gdata_service_build_session:
+ *
+ * Build a new #SoupSession, enabling GNOME features if support has been compiled for them, and adding a log printer which is hooked into
+ * libgdata's logging functionality.
+ *
+ * Return value: a new #SoupSession; unref with g_object_unref()
+ *
+ * Since: 0.9.0
+ */
+SoupSession *
+_gdata_service_build_session (void)
+{
+	SoupSession *session = soup_session_sync_new ();
+
+#ifdef HAVE_GNOME
+	soup_session_add_feature_by_type (session, SOUP_TYPE_GNOME_FEATURES_2_26);
+#endif /* HAVE_GNOME */
+
+	/* Log all libsoup traffic if debugging's turned on */
+	if (_gdata_service_get_log_level () > GDATA_LOG_MESSAGES) {
+		SoupLoggerLogLevel level;
+		SoupLogger *logger;
+
+		switch (_gdata_service_get_log_level ()) {
+			case GDATA_LOG_FULL:
+				level = SOUP_LOGGER_LOG_BODY;
+				break;
+			case GDATA_LOG_HEADERS:
+				level = SOUP_LOGGER_LOG_HEADERS;
+				break;
+			case GDATA_LOG_MESSAGES:
+			case GDATA_LOG_NONE:
+			default:
+				g_assert_not_reached ();
+		}
+
+		logger = soup_logger_new (level, -1);
+		soup_logger_set_printer (logger, (SoupLoggerPrinter) soup_log_printer, NULL, NULL);
+
+		soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
+
+		g_object_unref (logger);
+	}
+
+	return session;
+}
+
+/**
  * gdata_service_get_locale:
  * @self: a #GDataService
  *
diff --git a/gdata/gdata-service.h b/gdata/gdata-service.h
index 744d017..2527f88 100644
--- a/gdata/gdata-service.h
+++ b/gdata/gdata-service.h
@@ -24,6 +24,7 @@
 #include <glib-object.h>
 #include <libsoup/soup.h>
 
+#include <gdata/gdata-authorizer.h>
 #include <gdata/gdata-feed.h>
 
 G_BEGIN_DECLS
@@ -59,7 +60,8 @@ typedef enum {
  * @GDATA_SERVICE_ERROR_UNAVAILABLE: The service is unavailable due to maintainence or other reasons (e.g. network errors at the server end)
  * @GDATA_SERVICE_ERROR_PROTOCOL_ERROR: The client or server unexpectedly strayed from the protocol (fatal error)
  * @GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED: An entry has already been inserted, and cannot be re-inserted
- * @GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED: The user attempted to do something which required authentication, and they weren't authenticated
+ * @GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED: The user attempted to do something which required authentication, and they weren't authenticated or
+ * didn't have authorization for the operation
  * @GDATA_SERVICE_ERROR_NOT_FOUND: A requested resource (feed or entry) was not found on the server
  * @GDATA_SERVICE_ERROR_CONFLICT: There was a conflict when updating an entry on the server; the server-side copy was modified inbetween downloading
  * and uploading the modified entry
@@ -86,38 +88,6 @@ typedef enum {
 } GDataServiceError;
 
 /**
- * GDataAuthenticationError:
- * @GDATA_AUTHENTICATION_ERROR_BAD_AUTHENTICATION: The login request used a username or password that is not recognized.
- * @GDATA_AUTHENTICATION_ERROR_NOT_VERIFIED: The account email address has not been verified. The user will need to access their Google account
- * directly to resolve the issue before logging in using a non-Google application.
- * @GDATA_AUTHENTICATION_ERROR_TERMS_NOT_AGREED: The user has not agreed to terms. The user will need to access their Google account directly to
- * resolve the issue before logging in using a non-Google application.
- * @GDATA_AUTHENTICATION_ERROR_CAPTCHA_REQUIRED: A CAPTCHA is required. (A response with this error code will also contain an image URI and a
- * CAPTCHA token.)
- * @GDATA_AUTHENTICATION_ERROR_ACCOUNT_DELETED: The user account has been deleted.
- * @GDATA_AUTHENTICATION_ERROR_ACCOUNT_DISABLED: The user account has been disabled.
- * @GDATA_AUTHENTICATION_ERROR_SERVICE_DISABLED: The user's access to the specified service has been disabled. (The user account may still be valid.)
- * @GDATA_AUTHENTICATION_ERROR_ACCOUNT_MIGRATED: The user's account login details have been migrated to a new system. (This is used for the transition
- * from the old YouTube login details to the new ones.)
- * @GDATA_AUTHENTICATION_ERROR_INVALID_SECOND_FACTOR: The user's account requires an application-specific password to be used.
- *
- * Error codes for #GDataService authentication operations. See the
- * <ulink type="http" url="http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html#Errors";>Google accounts documentation</ulink> for
- * more information.
- **/
-typedef enum {
-	GDATA_AUTHENTICATION_ERROR_BAD_AUTHENTICATION = 1,
-	GDATA_AUTHENTICATION_ERROR_NOT_VERIFIED,
-	GDATA_AUTHENTICATION_ERROR_TERMS_NOT_AGREED,
-	GDATA_AUTHENTICATION_ERROR_CAPTCHA_REQUIRED,
-	GDATA_AUTHENTICATION_ERROR_ACCOUNT_DELETED,
-	GDATA_AUTHENTICATION_ERROR_ACCOUNT_DISABLED,
-	GDATA_AUTHENTICATION_ERROR_SERVICE_DISABLED,
-	GDATA_AUTHENTICATION_ERROR_ACCOUNT_MIGRATED,
-	GDATA_AUTHENTICATION_ERROR_INVALID_SECOND_FACTOR
-} GDataAuthenticationError;
-
-/**
  * GDataQueryProgressCallback:
  * @entry: a new #GDataEntry
  * @entry_key: the key of the entry (zero-based index of its position in the feed)
@@ -140,7 +110,6 @@ typedef void (*GDataQueryProgressCallback) (GDataEntry *entry, guint entry_key,
 #define GDATA_SERVICE_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_SERVICE, GDataServiceClass))
 
 #define GDATA_SERVICE_ERROR		gdata_service_error_quark ()
-#define GDATA_AUTHENTICATION_ERROR	gdata_authentication_error_quark ()
 
 typedef struct _GDataServicePrivate	GDataServicePrivate;
 
@@ -157,78 +126,77 @@ typedef struct {
 /**
  * GDataServiceClass:
  * @parent: the parent class
- * @service_name: the name of the service (for subclasses) as given in the service's GData API documentation
- * @authentication_uri: the authentication URI (for subclasses) if different from the Google ClientLogin default
  * @api_version: the version of the GData API used by the service (typically <code class="literal">2</code>)
  * @feed_type: the #GType of the feed class (subclass of #GDataFeed) to use for query results from this service
- * @parse_authentication_response: a function to parse the response from the online service to an authentication request as
- * issued by gdata_service_authenticate(). It should return %TRUE if authentication was successful, and %FALSE if there was
- * an error.
- * @append_query_headers: a function to allow subclasses to append their own headers to queries before they are submitted
- * to the online service
+ * @append_query_headers: a function to allow subclasses to append their own headers to queries before they are submitted to the online service,
+ * using the given authorization domain; new in version 0.9.0
  * @parse_error_response: a function to parse error responses to queries from the online service. It should set the error
  * from the status, reason phrase and response body it is passed.
+ * @get_authorization_domains: a function to return a newly-allocated list of all the #GDataAuthorizationDomain<!-- -->s the service makes use of;
+ * while the list should be newly-allocated, the individual domains should not be; not implementing this function is equivalent to returning an
+ * empty list; new in version 0.9.0
  *
  * The class structure for the #GDataService type.
- **/
+ *
+ * Since: 0.9.0
+ */
 typedef struct {
 	GObjectClass parent;
 
-	const gchar *service_name;
-	const gchar *authentication_uri;
 	const gchar *api_version;
 	GType feed_type;
 
-	gboolean (*parse_authentication_response) (GDataService *self, guint status, const gchar *response_body, gint length, GError **error);
-	void (*append_query_headers) (GDataService *self, SoupMessage *message);
+	void (*append_query_headers) (GDataService *self, GDataAuthorizationDomain *domain, SoupMessage *message);
 	void (*parse_error_response) (GDataService *self, GDataOperationType operation_type, guint status, const gchar *reason_phrase,
 	                              const gchar *response_body, gint length, GError **error);
+	GList *(*get_authorization_domains) (void);
 } GDataServiceClass;
 
 GType gdata_service_get_type (void) G_GNUC_CONST;
 GQuark gdata_service_error_quark (void) G_GNUC_CONST;
-GQuark gdata_authentication_error_quark (void) G_GNUC_CONST;
 
-gboolean gdata_service_authenticate (GDataService *self, const gchar *username, const gchar *password, GCancellable *cancellable, GError **error);
-void gdata_service_authenticate_async (GDataService *self, const gchar *username, const gchar *password,
-                                       GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
-gboolean gdata_service_authenticate_finish (GDataService *self, GAsyncResult *async_result, GError **error);
+GList *gdata_service_get_authorization_domains (GType service_type) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+gboolean gdata_service_is_authorized (GDataService *self);
+
+GDataAuthorizer *gdata_service_get_authorizer (GDataService *self) G_GNUC_PURE;
+void gdata_service_set_authorizer (GDataService *self, GDataAuthorizer *authorizer);
 
 #include <gdata/gdata-query.h>
 
-GDataFeed *gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *query, GType entry_type,
+GDataFeed *gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GType entry_type,
                                 GCancellable *cancellable,
                                 GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
                                 GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
-void gdata_service_query_async (GDataService *self, const gchar *feed_uri, GDataQuery *query, GType entry_type,
+void gdata_service_query_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GType entry_type,
                                 GCancellable *cancellable,
                                 GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
                                 GAsyncReadyCallback callback, gpointer user_data);
 GDataFeed *gdata_service_query_finish (GDataService *self, GAsyncResult *async_result, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 
-GDataEntry *gdata_service_query_single_entry (GDataService *self, const gchar *entry_id, GDataQuery *query, GType entry_type,
-                                              GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
-void gdata_service_query_single_entry_async (GDataService *self, const gchar *entry_id, GDataQuery *query, GType entry_type,
-                                             GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
+GDataEntry *gdata_service_query_single_entry (GDataService *self, GDataAuthorizationDomain *domain, const gchar *entry_id, GDataQuery *query,
+                                              GType entry_type, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+void gdata_service_query_single_entry_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *entry_id, GDataQuery *query,
+                                             GType entry_type, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
 GDataEntry *gdata_service_query_single_entry_finish (GDataService *self, GAsyncResult *async_result,
                                                      GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 
-GDataEntry *gdata_service_insert_entry (GDataService *self, const gchar *upload_uri, GDataEntry *entry,
+GDataEntry *gdata_service_insert_entry (GDataService *self, GDataAuthorizationDomain *domain, const gchar *upload_uri, GDataEntry *entry,
                                         GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
-void gdata_service_insert_entry_async (GDataService *self, const gchar *upload_uri, GDataEntry *entry, GCancellable *cancellable,
-                                       GAsyncReadyCallback callback, gpointer user_data);
+void gdata_service_insert_entry_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *upload_uri, GDataEntry *entry,
+                                       GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
 GDataEntry *gdata_service_insert_entry_finish (GDataService *self, GAsyncResult *async_result,
                                                GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 
-GDataEntry *gdata_service_update_entry (GDataService *self, GDataEntry *entry,
+GDataEntry *gdata_service_update_entry (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry,
                                         GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
-void gdata_service_update_entry_async (GDataService *self, GDataEntry *entry, GCancellable *cancellable,
+void gdata_service_update_entry_async (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry, GCancellable *cancellable,
                                        GAsyncReadyCallback callback, gpointer user_data);
 GDataEntry *gdata_service_update_entry_finish (GDataService *self, GAsyncResult *async_result,
                                                GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 
-gboolean gdata_service_delete_entry (GDataService *self, GDataEntry *entry, GCancellable *cancellable, GError **error);
-void gdata_service_delete_entry_async (GDataService *self, GDataEntry *entry, GCancellable *cancellable,
+gboolean gdata_service_delete_entry (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry,
+                                     GCancellable *cancellable, GError **error);
+void gdata_service_delete_entry_async (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry, GCancellable *cancellable,
                                        GAsyncReadyCallback callback, gpointer user_data);
 gboolean gdata_service_delete_entry_finish (GDataService *self, GAsyncResult *async_result, GError **error);
 
@@ -238,11 +206,6 @@ void gdata_service_set_proxy_uri (GDataService *self, SoupURI *proxy_uri);
 guint gdata_service_get_timeout (GDataService *self) G_GNUC_PURE;
 void gdata_service_set_timeout (GDataService *self, guint timeout);
 
-gboolean gdata_service_is_authenticated (GDataService *self) G_GNUC_PURE;
-const gchar *gdata_service_get_client_id (GDataService *self) G_GNUC_PURE;
-const gchar *gdata_service_get_username (GDataService *self) G_GNUC_PURE;
-const gchar *gdata_service_get_password (GDataService *self) G_GNUC_PURE;
-
 const gchar *gdata_service_get_locale (GDataService *self) G_GNUC_PURE;
 void gdata_service_set_locale (GDataService *self, const gchar *locale);
 
diff --git a/gdata/gdata-upload-stream.c b/gdata/gdata-upload-stream.c
index 8282d0d..e636bdf 100644
--- a/gdata/gdata-upload-stream.c
+++ b/gdata/gdata-upload-stream.c
@@ -23,7 +23,9 @@
  * @stability: Unstable
  * @include: gdata/gdata-upload-stream.h
  *
- * #GDataUploadStream is a #GOutputStream subclass to allow uploading of files from GData services with authentication from a #GDataService.
+ * #GDataUploadStream is a #GOutputStream subclass to allow uploading of files from GData services with authorization from a #GDataService under
+ * the given #GDataAuthorizationDomain. If authorization is not required to perform the upload, a #GDataAuthorizationDomain doesn't have to be
+ * specified.
  *
  * Once a #GDataUploadStream is instantiated with gdata_upload_stream_new(), the standard #GOutputStream API can be used on the stream to upload
  * the file. Network communication may not actually begin until the first call to g_output_stream_write(), so having a #GDataUploadStream around is no
@@ -59,13 +61,14 @@
  * connection will be closed immediately. i.e. #GDataUploadStream will do its best to instruct the server to cancel the upload and any associated
  * server-side changes of state.
  *
- * If the server returns an error message (for example, if the user is not correctly authenticated or doesn't have suitable permissions to upload
- * from the given URI), it will be returned as a #GDataServiceError by g_output_stream_close().
+ * If the server returns an error message (for example, if the user is not correctly authenticated/authorized or doesn't have suitable permissions
+ * to upload from the given URI), it will be returned as a #GDataServiceError by g_output_stream_close().
  *
  * <example>
  * 	<title>Uploading from a File</title>
  * 	<programlisting>
  *	GDataService *service;
+ *	GDataAuthorizationDomain *domain;
  *	GCancellable *cancellable;
  *	GInputStream *input_stream;
  *	GOutputStream *upload_stream;
@@ -97,8 +100,9 @@
  *
  *	/<!-- -->* Create the upload stream *<!-- -->/
  *	service = create_my_service ();
+ *	domain = get_my_authorization_domain_from_service (service);
  *	cancellable = g_cancellable_new (); /<!-- -->* cancel this to cancel the entire upload operation *<!-- -->/
- *	upload_stream = gdata_upload_stream_new (service, SOUP_METHOD_POST, upload_uri, NULL, g_file_info_get_display_name (file_info),
+ *	upload_stream = gdata_upload_stream_new (service, domain, SOUP_METHOD_POST, upload_uri, NULL, g_file_info_get_display_name (file_info),
  *	                                         g_file_info_get_content_type (file_info), cancellable);
  *	g_object_unref (file_info);
  *
@@ -109,6 +113,7 @@
  *	g_object_unref (upload_stream);
  *	g_object_unref (input_stream);
  *	g_object_unref (cancellable);
+ *	g_object_unref (domain);
  *	g_object_unref (service);
  *
  *	static void
@@ -186,6 +191,7 @@ struct _GDataUploadStreamPrivate {
 	gchar *method;
 	gchar *upload_uri;
 	GDataService *service;
+	GDataAuthorizationDomain *authorization_domain;
 	GDataEntry *entry;
 	gchar *slug;
 	gchar *content_type;
@@ -216,7 +222,8 @@ enum {
 	PROP_SLUG,
 	PROP_CONTENT_TYPE,
 	PROP_METHOD,
-	PROP_CANCELLABLE
+	PROP_CANCELLABLE,
+	PROP_AUTHORIZATION_DOMAIN,
 };
 
 G_DEFINE_TYPE (GDataUploadStream, gdata_upload_stream, G_TYPE_OUTPUT_STREAM)
@@ -244,17 +251,31 @@ gdata_upload_stream_class_init (GDataUploadStreamClass *klass)
 	/**
 	 * GDataUploadStream:service:
 	 *
-	 * The service which is used to authenticate the upload, and to which the upload relates.
+	 * The service which is used to authorize the upload, and to which the upload relates.
 	 *
 	 * Since: 0.5.0
 	 **/
 	g_object_class_install_property (gobject_class, PROP_SERVICE,
 	                                 g_param_spec_object ("service",
-	                                                      "Service", "The service which is used to authenticate the upload.",
+	                                                      "Service", "The service which is used to authorize the upload.",
 	                                                      GDATA_TYPE_SERVICE,
 	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
 	/**
+	 * GDataUploadStream:authorization-domain:
+	 *
+	 * The authorization domain for the upload, against which the #GDataService:authorizer for the #GDataDownloadStream:service should be
+	 * authorized. This may be %NULL if authorization is not needed for the upload.
+	 *
+	 * Since: 0.9.0
+	 */
+	g_object_class_install_property (gobject_class, PROP_AUTHORIZATION_DOMAIN,
+	                                 g_param_spec_object ("authorization-domain",
+	                                                      "Authorization domain", "The authorization domain for the upload.",
+	                                                      GDATA_TYPE_AUTHORIZATION_DOMAIN,
+	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
 	 * GDataUploadStream:method:
 	 *
 	 * The HTTP request method to use when uploading the file.
@@ -372,8 +393,9 @@ gdata_upload_stream_constructor (GType type, guint n_construct_params, GObjectCo
 
 	/* Make sure the headers are set */
 	klass = GDATA_SERVICE_GET_CLASS (priv->service);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (priv->service, priv->message);
+	if (klass->append_query_headers != NULL) {
+		klass->append_query_headers (priv->service, priv->authorization_domain, priv->message);
+	}
 
 	if (priv->slug != NULL)
 		soup_message_headers_append (priv->message->request_headers, "Slug", priv->slug);
@@ -416,6 +438,10 @@ gdata_upload_stream_dispose (GObject *object)
 		g_object_unref (priv->service);
 	priv->service = NULL;
 
+	if (priv->authorization_domain != NULL)
+		g_object_unref (priv->authorization_domain);
+	priv->authorization_domain = NULL;
+
 	if (priv->message != NULL)
 		g_object_unref (priv->message);
 	priv->message = NULL;
@@ -457,6 +483,9 @@ gdata_upload_stream_get_property (GObject *object, guint property_id, GValue *va
 		case PROP_SERVICE:
 			g_value_set_object (value, priv->service);
 			break;
+		case PROP_AUTHORIZATION_DOMAIN:
+			g_value_set_object (value, priv->authorization_domain);
+			break;
 		case PROP_METHOD:
 			g_value_set_string (value, priv->method);
 			break;
@@ -492,6 +521,9 @@ gdata_upload_stream_set_property (GObject *object, guint property_id, const GVal
 			priv->service = g_value_dup_object (value);
 			priv->session = _gdata_service_get_session (priv->service);
 			break;
+		case PROP_AUTHORIZATION_DOMAIN:
+			priv->authorization_domain = g_value_dup_object (value);
+			break;
 		case PROP_METHOD:
 			priv->method = g_value_dup_string (value);
 			break;
@@ -965,6 +997,7 @@ create_network_thread (GDataUploadStream *self, GError **error)
 /**
  * gdata_upload_stream_new:
  * @service: a #GDataService
+ * @domain: (allow-none): the #GDataAuthorizationDomain to authorize the upload, or %NULL
  * @method: the HTTP method to use
  * @upload_uri: the URI to upload
  * @entry: (allow-none): the entry to upload as metadata, or %NULL
@@ -997,13 +1030,14 @@ create_network_thread (GDataUploadStream *self, GError **error)
  *
  * Return value: a new #GOutputStream, or %NULL; unref with g_object_unref()
  *
- * Since: 0.8.0
+ * Since: 0.9.0
  **/
 GOutputStream *
-gdata_upload_stream_new (GDataService *service, const gchar *method, const gchar *upload_uri, GDataEntry *entry,
+gdata_upload_stream_new (GDataService *service, GDataAuthorizationDomain *domain, const gchar *method, const gchar *upload_uri, GDataEntry *entry,
                          const gchar *slug, const gchar *content_type, GCancellable *cancellable)
 {
 	g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL);
+	g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL);
 	g_return_val_if_fail (method != NULL, NULL);
 	g_return_val_if_fail (upload_uri != NULL, NULL);
 	g_return_val_if_fail (entry == NULL || GDATA_IS_ENTRY (entry), NULL);
@@ -1012,8 +1046,16 @@ gdata_upload_stream_new (GDataService *service, const gchar *method, const gchar
 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
 
 	/* Create the upload stream */
-	return G_OUTPUT_STREAM (g_object_new (GDATA_TYPE_UPLOAD_STREAM, "method", method, "upload-uri", upload_uri, "service", service,
-	                                      "entry", entry, "slug", slug, "content-type", content_type, "cancellable", cancellable, NULL));
+	return G_OUTPUT_STREAM (g_object_new (GDATA_TYPE_UPLOAD_STREAM,
+	                                      "method", method,
+	                                      "upload-uri", upload_uri,
+	                                      "service", service,
+	                                      "authorization-domain", domain,
+	                                      "entry", entry,
+	                                      "slug", slug,
+	                                      "content-type", content_type,
+	                                      "cancellable", cancellable,
+	                                      NULL));
 }
 
 /**
@@ -1073,9 +1115,9 @@ gdata_upload_stream_get_response (GDataUploadStream *self, gssize *length)
  * gdata_upload_stream_get_service:
  * @self: a #GDataUploadStream
  *
- * Gets the service used to authenticate the upload, as passed to gdata_upload_stream_new().
+ * Gets the service used to authorize the upload, as passed to gdata_upload_stream_new().
  *
- * Return value: (transfer none): the #GDataService used to authenticate the upload
+ * Return value: (transfer none): the #GDataService used to authorize the upload
  *
  * Since: 0.5.0
  **/
@@ -1087,6 +1129,24 @@ gdata_upload_stream_get_service (GDataUploadStream *self)
 }
 
 /**
+ * gdata_upload_stream_get_authorization_domain:
+ * @self: a #GDataUploadStream
+ *
+ * Gets the authorization domain used to authorize the upload, as passed to gdata_upload_stream_new(). It may be %NULL if authorization is not
+ * needed for the upload.
+ *
+ * Return value: (transfer none) (allow-none): the #GDataAuthorizationDomain used to authorize the upload, or %NULL
+ *
+ * Since: 0.9.0
+ */
+GDataAuthorizationDomain *
+gdata_upload_stream_get_authorization_domain (GDataUploadStream *self)
+{
+	g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL);
+	return self->priv->authorization_domain;
+}
+
+/**
  * gdata_upload_stream_get_method:
  * @self: a #GDataUploadStream
  *
diff --git a/gdata/gdata-upload-stream.h b/gdata/gdata-upload-stream.h
index 133b6f4..b7e5c69 100644
--- a/gdata/gdata-upload-stream.h
+++ b/gdata/gdata-upload-stream.h
@@ -64,12 +64,14 @@ typedef struct {
 
 GType gdata_upload_stream_get_type (void) G_GNUC_CONST;
 
-GOutputStream *gdata_upload_stream_new (GDataService *service, const gchar *method, const gchar *upload_uri, GDataEntry *entry,
-                                        const gchar *slug, const gchar *content_type, GCancellable *cancellable) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+GOutputStream *gdata_upload_stream_new (GDataService *service, GDataAuthorizationDomain *domain, const gchar *method, const gchar *upload_uri,
+                                        GDataEntry *entry, const gchar *slug, const gchar *content_type,
+                                        GCancellable *cancellable) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 
 const gchar *gdata_upload_stream_get_response (GDataUploadStream *self, gssize *length);
 
 GDataService *gdata_upload_stream_get_service (GDataUploadStream *self) G_GNUC_PURE;
+GDataAuthorizationDomain *gdata_upload_stream_get_authorization_domain (GDataUploadStream *self) G_GNUC_PURE;
 const gchar *gdata_upload_stream_get_method (GDataUploadStream *self) G_GNUC_PURE;
 const gchar *gdata_upload_stream_get_upload_uri (GDataUploadStream *self) G_GNUC_PURE;
 GDataEntry *gdata_upload_stream_get_entry (GDataUploadStream *self) G_GNUC_PURE;
diff --git a/gdata/gdata.h b/gdata/gdata.h
index 984777b..4980c2b 100644
--- a/gdata/gdata.h
+++ b/gdata/gdata.h
@@ -35,6 +35,9 @@
 #include <gdata/gdata-comparable.h>
 #include <gdata/gdata-batchable.h>
 #include <gdata/gdata-batch-operation.h>
+#include <gdata/gdata-authorizer.h>
+#include <gdata/gdata-authorization-domain.h>
+#include <gdata/gdata-client-login-authorizer.h>
 
 /* Namespaces */
 
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index 35e9d58..153c5b0 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -46,10 +46,6 @@ gdata_feed_get_start_index
 gdata_feed_get_total_results
 gdata_service_get_type
 gdata_service_error_quark
-gdata_authentication_error_quark
-gdata_service_authenticate
-gdata_service_authenticate_async
-gdata_service_authenticate_finish
 gdata_service_query
 gdata_service_query_async
 gdata_service_query_finish
@@ -67,10 +63,6 @@ gdata_service_delete_entry_async
 gdata_service_delete_entry_finish
 gdata_service_get_proxy_uri
 gdata_service_set_proxy_uri
-gdata_service_is_authenticated
-gdata_service_get_client_id
-gdata_service_get_username
-gdata_service_get_password
 gdata_query_get_type
 gdata_query_new
 gdata_query_new_with_limits
@@ -244,7 +236,6 @@ gdata_calendar_service_query_events
 gdata_calendar_service_insert_event
 gdata_operation_type_get_type
 gdata_service_error_get_type
-gdata_authentication_error_get_type
 gdata_media_expression_get_type
 gdata_media_medium_get_type
 gdata_parser_error_get_type
@@ -862,3 +853,40 @@ gdata_youtube_video_get_coordinates
 gdata_youtube_video_set_coordinates
 gdata_upload_stream_get_cancellable
 gdata_download_stream_get_cancellable
+gdata_service_is_authorized
+gdata_service_get_authorizer
+gdata_service_set_authorizer
+gdata_authorizer_get_type
+gdata_authorizer_process_request
+gdata_authorizer_is_authorized_for_domain
+gdata_authorizer_refresh_authorization
+gdata_authorizer_refresh_authorization_async
+gdata_authorizer_refresh_authorization_finish
+gdata_authorization_domain_get_type
+gdata_authorization_domain_get_service_name
+gdata_authorization_domain_get_scope
+gdata_client_login_authorizer_get_type
+gdata_client_login_authorizer_new
+gdata_client_login_authorizer_new_for_authorization_domains
+gdata_client_login_authorizer_authenticate
+gdata_client_login_authorizer_authenticate_async
+gdata_client_login_authorizer_authenticate_finish
+gdata_client_login_authorizer_get_client_id
+gdata_client_login_authorizer_get_username
+gdata_client_login_authorizer_get_password
+gdata_client_login_authorizer_get_proxy_uri
+gdata_client_login_authorizer_set_proxy_uri
+gdata_client_login_authorizer_get_timeout
+gdata_client_login_authorizer_set_timeout
+gdata_client_login_authorizer_error_quark
+gdata_client_login_authorizer_error_get_type
+gdata_download_stream_get_authorization_domain
+gdata_upload_stream_get_authorization_domain
+gdata_batch_operation_get_authorization_domain
+gdata_contacts_service_get_primary_authorization_domain
+gdata_calendar_service_get_primary_authorization_domain
+gdata_documents_service_get_primary_authorization_domain
+gdata_documents_service_get_spreadsheet_authorization_domain
+gdata_picasaweb_service_get_primary_authorization_domain
+gdata_youtube_service_get_primary_authorization_domain
+gdata_service_get_authorization_domains
diff --git a/gdata/media/gdata-media-content.c b/gdata/media/gdata-media-content.c
index 990f3c6..4dd936c 100644
--- a/gdata/media/gdata-media-content.c
+++ b/gdata/media/gdata-media-content.c
@@ -569,5 +569,5 @@ gdata_media_content_download (GDataMediaContent *self, GDataService *service, GC
 
 	/* Get the download URI and create a stream for it */
 	src_uri = gdata_media_content_get_uri (self);
-	return GDATA_DOWNLOAD_STREAM (gdata_download_stream_new (service, src_uri, cancellable));
+	return GDATA_DOWNLOAD_STREAM (gdata_download_stream_new (service, NULL, src_uri, cancellable));
 }
diff --git a/gdata/media/gdata-media-thumbnail.c b/gdata/media/gdata-media-thumbnail.c
index ac985c9..4008d9e 100644
--- a/gdata/media/gdata-media-thumbnail.c
+++ b/gdata/media/gdata-media-thumbnail.c
@@ -396,5 +396,5 @@ gdata_media_thumbnail_download (GDataMediaThumbnail *self, GDataService *service
 
 	/* Get the download URI and create a stream for it */
 	src_uri = gdata_media_thumbnail_get_uri (self);
-	return GDATA_DOWNLOAD_STREAM (gdata_download_stream_new (service, src_uri, cancellable));
+	return GDATA_DOWNLOAD_STREAM (gdata_download_stream_new (service, NULL, src_uri, cancellable));
 }
diff --git a/gdata/services/calendar/gdata-calendar-calendar.c b/gdata/services/calendar/gdata-calendar-calendar.c
index dc27aa8..8808e65 100644
--- a/gdata/services/calendar/gdata-calendar-calendar.c
+++ b/gdata/services/calendar/gdata-calendar-calendar.c
@@ -86,6 +86,7 @@
 #include "gdata-parser.h"
 #include "gdata-types.h"
 #include "gdata-access-handler.h"
+#include "gdata-calendar-service.h"
 
 static void gdata_calendar_calendar_access_handler_init (GDataAccessHandlerIface *iface);
 static GObject *gdata_calendar_calendar_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params);
@@ -204,7 +205,8 @@ gdata_calendar_calendar_class_init (GDataCalendarCalendarClass *klass)
 	 * GDataCalendarCalendar:access-level:
 	 *
 	 * Indicates the access level the current user has to the calendar. For example: %GDATA_CALENDAR_ACCESS_ROLE_READ or
-	 * %GDATA_CALENDAR_ACCESS_ROLE_FREE_BUSY. The "current user" is the one logged in with gdata_service_authenticate() or the guest user.
+	 * %GDATA_CALENDAR_ACCESS_ROLE_FREE_BUSY. The "current user" is the one authenticated against the service's #GDataService:authorizer,
+	 * or the guest user.
 	 **/
 	g_object_class_install_property (gobject_class, PROP_ACCESS_LEVEL,
 	                                 g_param_spec_string ("access-level",
@@ -233,10 +235,17 @@ is_owner_rule (GDataAccessRule *rule)
 	return (strcmp (gdata_access_rule_get_role (rule), GDATA_CALENDAR_ACCESS_ROLE_OWNER) == 0) ? TRUE : FALSE;
 }
 
+static GDataAuthorizationDomain *
+get_authorization_domain (GDataAccessHandler *self)
+{
+	return gdata_calendar_service_get_primary_authorization_domain ();
+}
+
 static void
 gdata_calendar_calendar_access_handler_init (GDataAccessHandlerIface *iface)
 {
 	iface->is_owner_rule = is_owner_rule;
+	iface->get_authorization_domain = get_authorization_domain;
 }
 
 static void
diff --git a/gdata/services/calendar/gdata-calendar-service.c b/gdata/services/calendar/gdata-calendar-service.c
index 6761ac5..4b9acd9 100644
--- a/gdata/services/calendar/gdata-calendar-service.c
+++ b/gdata/services/calendar/gdata-calendar-service.c
@@ -215,14 +215,17 @@
 
 /* Standards reference here: http://code.google.com/apis/calendar/docs/2.0/reference.html */
 
+static GList *get_authorization_domains (void);
+
+_GDATA_DEFINE_AUTHORIZATION_DOMAIN (calendar, "cl", "https://www.google.com/calendar/feeds/";)
 G_DEFINE_TYPE_WITH_CODE (GDataCalendarService, gdata_calendar_service, GDATA_TYPE_SERVICE, G_IMPLEMENT_INTERFACE (GDATA_TYPE_BATCHABLE, NULL))
 
 static void
 gdata_calendar_service_class_init (GDataCalendarServiceClass *klass)
 {
 	GDataServiceClass *service_class = GDATA_SERVICE_CLASS (klass);
-	service_class->service_name = "cl";
 	service_class->feed_type = GDATA_TYPE_CALENDAR_FEED;
+	service_class->get_authorization_domains = get_authorization_domains;
 }
 
 static void
@@ -231,19 +234,49 @@ gdata_calendar_service_init (GDataCalendarService *self)
 	/* Nothing to see here */
 }
 
+static GList *
+get_authorization_domains (void)
+{
+	return g_list_prepend (NULL, get_calendar_authorization_domain ());
+}
+
 /**
  * gdata_calendar_service_new:
- * @client_id: your application's client ID
+ * @authorizer: (allow-none): a #GDataAuthorizer to authorize the service's requests, or %NULL
  *
- * Creates a new #GDataCalendarService. The @client_id must be unique for your application, and as registered with Google.
+ * Creates a new #GDataCalendarService using the given #GDataAuthorizer. If @authorizer is %NULL, all requests are made as an unauthenticated user.
  *
- * Return value: a new #GDataCalendarService, or %NULL
- **/
+ * Return value: a new #GDataCalendarService, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.9.0
+ */
 GDataCalendarService *
-gdata_calendar_service_new (const gchar *client_id)
+gdata_calendar_service_new (GDataAuthorizer *authorizer)
 {
-	g_return_val_if_fail (client_id != NULL, NULL);
-	return g_object_new (GDATA_TYPE_CALENDAR_SERVICE, "client-id", client_id, NULL);
+	g_return_val_if_fail (authorizer == NULL || GDATA_IS_AUTHORIZER (authorizer), NULL);
+
+	return g_object_new (GDATA_TYPE_CALENDAR_SERVICE,
+	                     "authorizer", authorizer,
+	                     NULL);
+}
+
+/**
+ * gdata_calendar_service_get_primary_authorization_domain:
+ *
+ * The primary #GDataAuthorizationDomain for interacting with Google Calendar. This will not normally need to be used, as it's used internally
+ * by the #GDataCalendarService methods. However, if using the plain #GDataService methods to implement custom queries or requests which libgdata
+ * does not support natively, then this domain may be needed to authorize the requests.
+ *
+ * The domain never changes, and is interned so that pointer comparison can be used to differentiate it from other authorization domains.
+ *
+ * Return value: (transfer none): the service's authorization domain
+ *
+ * Since: 0.9.0
+ */
+GDataAuthorizationDomain *
+gdata_calendar_service_get_primary_authorization_domain (void)
+{
+	return get_calendar_authorization_domain ();
 }
 
 /**
@@ -276,14 +309,15 @@ gdata_calendar_service_query_all_calendars (GDataCalendarService *self, GDataQue
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_calendar_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to query all calendars."));
 		return NULL;
 	}
 
 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/calendar/feeds/default/allcalendars/full", NULL);
-	feed = gdata_service_query (GDATA_SERVICE (self), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR,
+	feed = gdata_service_query (GDATA_SERVICE (self), get_calendar_authorization_domain (), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR,
 	                            cancellable, progress_callback, progress_user_data, error);
 	g_free (request_uri);
 
@@ -320,7 +354,8 @@ gdata_calendar_service_query_all_calendars_async (GDataCalendarService *self, GD
 	g_return_if_fail (callback != NULL);
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_calendar_authorization_domain ()) == FALSE) {
 		g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
 		                                     GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                                     _("You must be authenticated to query all calendars."));
@@ -328,7 +363,7 @@ gdata_calendar_service_query_all_calendars_async (GDataCalendarService *self, GD
 	}
 
 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/calendar/feeds/default/allcalendars/full", NULL);
-	gdata_service_query_async (GDATA_SERVICE (self), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR,
+	gdata_service_query_async (GDATA_SERVICE (self), get_calendar_authorization_domain (), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR,
 	                           cancellable, progress_callback, progress_user_data, callback, user_data);
 	g_free (request_uri);
 }
@@ -363,15 +398,16 @@ gdata_calendar_service_query_own_calendars (GDataCalendarService *self, GDataQue
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_calendar_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to query your own calendars."));
 		return NULL;
 	}
 
 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/calendar/feeds/default/owncalendars/full", NULL);
-	feed = gdata_service_query (GDATA_SERVICE (self), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR, cancellable,
-	                            progress_callback, progress_user_data, error);
+	feed = gdata_service_query (GDATA_SERVICE (self), get_calendar_authorization_domain (), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR,
+	                            cancellable, progress_callback, progress_user_data, error);
 	g_free (request_uri);
 
 	return feed;
@@ -407,7 +443,8 @@ gdata_calendar_service_query_own_calendars_async (GDataCalendarService *self, GD
 	g_return_if_fail (callback != NULL);
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_calendar_authorization_domain ()) == FALSE) {
 		g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
 		                                     GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                                     _("You must be authenticated to query your own calendars."));
@@ -415,7 +452,7 @@ gdata_calendar_service_query_own_calendars_async (GDataCalendarService *self, GD
 	}
 
 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/calendar/feeds/default/owncalendars/full", NULL);
-	gdata_service_query_async (GDATA_SERVICE (self), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR,
+	gdata_service_query_async (GDATA_SERVICE (self), get_calendar_authorization_domain (), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR,
 	                           cancellable, progress_callback, progress_user_data, callback, user_data);
 	g_free (request_uri);
 }
@@ -449,7 +486,8 @@ gdata_calendar_service_query_events (GDataCalendarService *self, GDataCalendarCa
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_calendar_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to query your own calendars."));
 		return NULL;
@@ -465,7 +503,7 @@ gdata_calendar_service_query_events (GDataCalendarService *self, GDataCalendarCa
 	}
 
 	/* Execute the query */
-	return gdata_service_query (GDATA_SERVICE (self), uri, query, GDATA_TYPE_CALENDAR_EVENT, cancellable,
+	return gdata_service_query (GDATA_SERVICE (self), get_calendar_authorization_domain (), uri, query, GDATA_TYPE_CALENDAR_EVENT, cancellable,
 	                            progress_callback, progress_user_data, error);
 }
 
@@ -504,7 +542,8 @@ gdata_calendar_service_query_events_async (GDataCalendarService *self, GDataCale
 	g_return_if_fail (callback != NULL);
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_calendar_authorization_domain ()) == FALSE) {
 		g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
 		                                     GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                                     _("You must be authenticated to query your own calendars."));
@@ -521,8 +560,8 @@ gdata_calendar_service_query_events_async (GDataCalendarService *self, GDataCale
 	}
 
 	/* Execute the query */
-	gdata_service_query_async (GDATA_SERVICE (self), uri, query, GDATA_TYPE_CALENDAR_EVENT, cancellable, progress_callback, progress_user_data,
-	                           callback, user_data);
+	gdata_service_query_async (GDATA_SERVICE (self), get_calendar_authorization_domain (), uri, query, GDATA_TYPE_CALENDAR_EVENT, cancellable,
+	                           progress_callback, progress_user_data, callback, user_data);
 }
 
 /**
@@ -553,7 +592,7 @@ gdata_calendar_service_insert_event (GDataCalendarService *self, GDataCalendarEv
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/calendar/feeds/default/private/full", NULL);
-	entry = gdata_service_insert_entry (GDATA_SERVICE (self), uri, GDATA_ENTRY (event), cancellable, error);
+	entry = gdata_service_insert_entry (GDATA_SERVICE (self), get_calendar_authorization_domain (), uri, GDATA_ENTRY (event), cancellable, error);
 	g_free (uri);
 
 	return GDATA_CALENDAR_EVENT (entry);
@@ -589,6 +628,7 @@ gdata_calendar_service_insert_event_async (GDataCalendarService *self, GDataCale
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
 	uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/calendar/feeds/default/private/full", NULL);
-	gdata_service_insert_entry_async (GDATA_SERVICE (self), uri, GDATA_ENTRY (event), cancellable, callback, user_data);
+	gdata_service_insert_entry_async (GDATA_SERVICE (self), get_calendar_authorization_domain (), uri, GDATA_ENTRY (event), cancellable,
+	                                  callback, user_data);
 	g_free (uri);
 }
diff --git a/gdata/services/calendar/gdata-calendar-service.h b/gdata/services/calendar/gdata-calendar-service.h
index f7924f3..4c3ce4f 100644
--- a/gdata/services/calendar/gdata-calendar-service.h
+++ b/gdata/services/calendar/gdata-calendar-service.h
@@ -59,7 +59,9 @@ typedef struct {
 
 GType gdata_calendar_service_get_type (void) G_GNUC_CONST;
 
-GDataCalendarService *gdata_calendar_service_new (const gchar *client_id) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+GDataCalendarService *gdata_calendar_service_new (GDataAuthorizer *authorizer) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
+GDataAuthorizationDomain *gdata_calendar_service_get_primary_authorization_domain (void) G_GNUC_CONST;
 
 GDataFeed *gdata_calendar_service_query_all_calendars (GDataCalendarService *self, GDataQuery *query, GCancellable *cancellable,
                                                        GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
diff --git a/gdata/services/contacts/gdata-contacts-contact.c b/gdata/services/contacts/gdata-contacts-contact.c
index 6af9df4..1b233f6 100644
--- a/gdata/services/contacts/gdata-contacts-contact.c
+++ b/gdata/services/contacts/gdata-contacts-contact.c
@@ -3120,7 +3120,8 @@ gdata_contacts_contact_get_photo (GDataContactsContact *self, GDataContactsServi
 	/* TODO: ETag support */
 	_link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/contacts/2008/rel#photo";);
 	g_assert (_link != NULL);
-	message = _gdata_service_build_message (GDATA_SERVICE (service), SOUP_METHOD_GET, gdata_link_get_uri (_link), NULL, FALSE);
+	message = _gdata_service_build_message (GDATA_SERVICE (service), gdata_contacts_service_get_primary_authorization_domain (),
+	                                        SOUP_METHOD_GET, gdata_link_get_uri (_link), NULL, FALSE);
 
 	/* Send the message */
 	status = _gdata_service_send_message (GDATA_SERVICE (service), message, cancellable, error);
@@ -3326,7 +3327,8 @@ gdata_contacts_contact_set_photo (GDataContactsContact *self, GDataContactsServi
 	/* Get the photo URI */
 	_link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/contacts/2008/rel#photo";);
 	g_assert (_link != NULL);
-	message = _gdata_service_build_message (GDATA_SERVICE (service), (deleting_photo == TRUE) ? SOUP_METHOD_DELETE : SOUP_METHOD_PUT,
+	message = _gdata_service_build_message (GDATA_SERVICE (service), gdata_contacts_service_get_primary_authorization_domain (),
+	                                        (deleting_photo == TRUE) ? SOUP_METHOD_DELETE : SOUP_METHOD_PUT,
 	                                        gdata_link_get_uri (_link), self->priv->photo_etag, TRUE);
 
 	/* Append the data */
diff --git a/gdata/services/contacts/gdata-contacts-service.c b/gdata/services/contacts/gdata-contacts-service.c
index 12b2912..65ddac4 100644
--- a/gdata/services/contacts/gdata-contacts-service.c
+++ b/gdata/services/contacts/gdata-contacts-service.c
@@ -157,6 +157,9 @@
 #include "gdata-private.h"
 #include "gdata-query.h"
 
+static GList *get_authorization_domains (void);
+
+_GDATA_DEFINE_AUTHORIZATION_DOMAIN (contacts, "cp", "https://www.google.com/m8/feeds/";)
 G_DEFINE_TYPE_WITH_CODE (GDataContactsService, gdata_contacts_service, GDATA_TYPE_SERVICE,
                          G_IMPLEMENT_INTERFACE (GDATA_TYPE_BATCHABLE, NULL))
 
@@ -164,8 +167,8 @@ static void
 gdata_contacts_service_class_init (GDataContactsServiceClass *klass)
 {
 	GDataServiceClass *service_class = GDATA_SERVICE_CLASS (klass);
-	service_class->service_name = "cp";
 	service_class->api_version = "3";
+	service_class->get_authorization_domains = get_authorization_domains;
 }
 
 static void
@@ -174,27 +177,52 @@ gdata_contacts_service_init (GDataContactsService *self)
 	/* Nothing to see here */
 }
 
+static GList *
+get_authorization_domains (void)
+{
+	return g_list_prepend (NULL, get_contacts_authorization_domain ());
+}
+
 /**
  * gdata_contacts_service_new:
- * @client_id: your application's client ID
+ * @authorizer: (allow-none): a #GDataAuthorizer to authorize the service's requests, or %NULL
  *
- * Creates a new #GDataContactsService. The @client_id must be unique for your application, and as registered with Google.
+ * Creates a new #GDataContactsService using the given #GDataAuthorizer. If @authorizer is %NULL, all requests are made as an unauthenticated user.
  *
- * Return value: a new #GDataContactsService, or %NULL
+ * Return value: a new #GDataContactsService, or %NULL; unref with g_object_unref()
  *
- * Since: 0.2.0
- **/
+ * Since: 0.9.0
+ */
 GDataContactsService *
-gdata_contacts_service_new (const gchar *client_id)
+gdata_contacts_service_new (GDataAuthorizer *authorizer)
 {
-	g_return_val_if_fail (client_id != NULL, NULL);
+	g_return_val_if_fail (authorizer == NULL || GDATA_IS_AUTHORIZER (authorizer), NULL);
 
 	return g_object_new (GDATA_TYPE_CONTACTS_SERVICE,
-	                     "client-id", client_id,
+	                     "authorizer", authorizer,
 	                     NULL);
 }
 
 /**
+ * gdata_contacts_service_get_primary_authorization_domain:
+ *
+ * The primary #GDataAuthorizationDomain for interacting with Google Contacts. This will not normally need to be used, as it's used internally
+ * by the #GDataContactsService methods. However, if using the plain #GDataService methods to implement custom queries or requests which libgdata
+ * does not support natively, then this domain may be needed to authorize the requests.
+ *
+ * The domain never changes, and is interned so that pointer comparison can be used to differentiate it from other authorization domains.
+ *
+ * Return value: (transfer none): the service's authorization domain
+ *
+ * Since: 0.9.0
+ */
+GDataAuthorizationDomain *
+gdata_contacts_service_get_primary_authorization_domain (void)
+{
+	return get_contacts_authorization_domain ();
+}
+
+/**
  * gdata_contacts_service_query_contacts:
  * @self: a #GDataContactsService
  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
@@ -224,14 +252,15 @@ gdata_contacts_service_query_contacts (GDataContactsService *self, GDataQuery *q
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_contacts_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to query contacts."));
 		return NULL;
 	}
 
 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/m8/feeds/contacts/default/full", NULL);
-	feed = gdata_service_query (GDATA_SERVICE (self), request_uri, GDATA_QUERY (query),
+	feed = gdata_service_query (GDATA_SERVICE (self), get_contacts_authorization_domain (), request_uri, GDATA_QUERY (query),
 	                            GDATA_TYPE_CONTACTS_CONTACT, cancellable, progress_callback, progress_user_data, error);
 	g_free (request_uri);
 
@@ -269,7 +298,8 @@ gdata_contacts_service_query_contacts_async (GDataContactsService *self, GDataQu
 	g_return_if_fail (callback != NULL);
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_contacts_authorization_domain ()) == FALSE) {
 		g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
 		                                     GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                                     _("You must be authenticated to query contacts."));
@@ -277,7 +307,7 @@ gdata_contacts_service_query_contacts_async (GDataContactsService *self, GDataQu
 	}
 
 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/m8/feeds/contacts/default/full", NULL);
-	gdata_service_query_async (GDATA_SERVICE (self), request_uri, GDATA_QUERY (query),
+	gdata_service_query_async (GDATA_SERVICE (self), get_contacts_authorization_domain (), request_uri, GDATA_QUERY (query),
 	                           GDATA_TYPE_CONTACTS_CONTACT, cancellable, progress_callback, progress_user_data, callback, user_data);
 	g_free (request_uri);
 }
@@ -309,7 +339,8 @@ gdata_contacts_service_insert_contact (GDataContactsService *self, GDataContacts
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/m8/feeds/contacts/default/full", NULL);
-	entry = gdata_service_insert_entry (GDATA_SERVICE (self), uri, GDATA_ENTRY (contact), cancellable, error);
+	entry = gdata_service_insert_entry (GDATA_SERVICE (self), get_contacts_authorization_domain (), uri, GDATA_ENTRY (contact), cancellable,
+	                                    error);
 	g_free (uri);
 
 	return GDATA_CONTACTS_CONTACT (entry);
@@ -345,7 +376,8 @@ gdata_contacts_service_insert_contact_async (GDataContactsService *self, GDataCo
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
 	uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/m8/feeds/contacts/default/full", NULL);
-	gdata_service_insert_entry_async (GDATA_SERVICE (self), uri, GDATA_ENTRY (contact), cancellable, callback, user_data);
+	gdata_service_insert_entry_async (GDATA_SERVICE (self), get_contacts_authorization_domain (), uri, GDATA_ENTRY (contact), cancellable,
+	                                  callback, user_data);
 	g_free (uri);
 }
 
@@ -379,14 +411,15 @@ gdata_contacts_service_query_groups (GDataContactsService *self, GDataQuery *que
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_contacts_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to query contact groups."));
 		return NULL;
 	}
 
 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/m8/feeds/groups/default/full", NULL);
-	feed = gdata_service_query (GDATA_SERVICE (self), request_uri, GDATA_QUERY (query),
+	feed = gdata_service_query (GDATA_SERVICE (self), get_contacts_authorization_domain (), request_uri, GDATA_QUERY (query),
 	                            GDATA_TYPE_CONTACTS_GROUP, cancellable, progress_callback, progress_user_data, error);
 	g_free (request_uri);
 
@@ -424,7 +457,8 @@ gdata_contacts_service_query_groups_async (GDataContactsService *self, GDataQuer
 	g_return_if_fail (callback != NULL);
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_contacts_authorization_domain ()) == FALSE) {
 		g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
 		                                     GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                                     _("You must be authenticated to query contact groups."));
@@ -432,7 +466,7 @@ gdata_contacts_service_query_groups_async (GDataContactsService *self, GDataQuer
 	}
 
 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/m8/feeds/groups/default/full", NULL);
-	gdata_service_query_async (GDATA_SERVICE (self), request_uri, GDATA_QUERY (query),
+	gdata_service_query_async (GDATA_SERVICE (self), get_contacts_authorization_domain (), request_uri, GDATA_QUERY (query),
 	                           GDATA_TYPE_CONTACTS_GROUP, cancellable, progress_callback, progress_user_data, callback, user_data);
 	g_free (request_uri);
 }
@@ -467,14 +501,16 @@ gdata_contacts_service_insert_group (GDataContactsService *self, GDataContactsGr
 		return NULL;
 	}
 
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_contacts_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to insert a group."));
 		return NULL;
 	}
 
 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/m8/feeds/groups/default/full", NULL);
-	new_group = gdata_service_insert_entry (GDATA_SERVICE (self), request_uri, GDATA_ENTRY (group), cancellable, error);
+	new_group = gdata_service_insert_entry (GDATA_SERVICE (self), get_contacts_authorization_domain (), request_uri, GDATA_ENTRY (group),
+	                                        cancellable, error);
 	g_free (request_uri);
 
 	return GDATA_CONTACTS_GROUP (new_group);
@@ -510,6 +546,7 @@ gdata_contacts_service_insert_group_async (GDataContactsService *self, GDataCont
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/m8/feeds/groups/default/full", NULL);
-	gdata_service_insert_entry_async (GDATA_SERVICE (self), request_uri, GDATA_ENTRY (group), cancellable, callback, user_data);
+	gdata_service_insert_entry_async (GDATA_SERVICE (self), get_contacts_authorization_domain (), request_uri, GDATA_ENTRY (group), cancellable,
+	                                  callback, user_data);
 	g_free (request_uri);
 }
diff --git a/gdata/services/contacts/gdata-contacts-service.h b/gdata/services/contacts/gdata-contacts-service.h
index 613c8b4..36abbd3 100644
--- a/gdata/services/contacts/gdata-contacts-service.h
+++ b/gdata/services/contacts/gdata-contacts-service.h
@@ -62,7 +62,9 @@ typedef struct {
 
 GType gdata_contacts_service_get_type (void) G_GNUC_CONST;
 
-GDataContactsService *gdata_contacts_service_new (const gchar *client_id) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+GDataContactsService *gdata_contacts_service_new (GDataAuthorizer *authorizer) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
+GDataAuthorizationDomain *gdata_contacts_service_get_primary_authorization_domain (void) G_GNUC_CONST;
 
 GDataFeed *gdata_contacts_service_query_contacts (GDataContactsService *self, GDataQuery *query, GCancellable *cancellable,
                                                   GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
diff --git a/gdata/services/documents/gdata-documents-document.c b/gdata/services/documents/gdata-documents-document.c
index 6f8ef61..0ef4c78 100644
--- a/gdata/services/documents/gdata-documents-document.c
+++ b/gdata/services/documents/gdata-documents-document.c
@@ -252,7 +252,7 @@ gdata_documents_document_download (GDataDocumentsDocument *self, GDataDocumentsS
                                    GError **error)
 {
 	gchar *download_uri;
-	GDataService *_service;
+	GDataAuthorizationDomain *domain;
 	GDataDownloadStream *download_stream;
 
 	g_return_val_if_fail (GDATA_IS_DOCUMENTS_DOCUMENT (self), NULL);
@@ -261,15 +261,15 @@ gdata_documents_document_download (GDataDocumentsDocument *self, GDataDocumentsS
 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-	/* Horrible hack to force use of the spreadsheet service if the document we're downloading is a spreadsheet. This is necessary because it's
-	 * in a different authentication domain. */
-	if (GDATA_IS_DOCUMENTS_SPREADSHEET (self))
-		_service = _gdata_documents_service_get_spreadsheet_service (service);
-	else
-		_service = GDATA_SERVICE (service);
+	/* If we're downloading a spreadsheet we have to use a different authorization domain. */
+	if (GDATA_IS_DOCUMENTS_SPREADSHEET (self)) {
+		domain = gdata_documents_service_get_spreadsheet_authorization_domain ();
+	} else {
+		domain = gdata_documents_service_get_primary_authorization_domain ();
+	}
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (_service) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (service)), domain) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to download documents."));
 		return NULL;
@@ -277,7 +277,7 @@ gdata_documents_document_download (GDataDocumentsDocument *self, GDataDocumentsS
 
 	/* Get the download URI and create a stream for it */
 	download_uri = gdata_documents_document_get_download_uri (self, export_format);
-	download_stream = GDATA_DOWNLOAD_STREAM (gdata_download_stream_new (_service, download_uri, cancellable));
+	download_stream = GDATA_DOWNLOAD_STREAM (gdata_download_stream_new (GDATA_SERVICE (service), domain, download_uri, cancellable));
 	g_free (download_uri);
 
 	return download_stream;
diff --git a/gdata/services/documents/gdata-documents-entry.c b/gdata/services/documents/gdata-documents-entry.c
index cb4998c..32cb38c 100644
--- a/gdata/services/documents/gdata-documents-entry.c
+++ b/gdata/services/documents/gdata-documents-entry.c
@@ -99,6 +99,7 @@
 #include "gdata-types.h"
 #include "gdata-private.h"
 #include "gdata-access-handler.h"
+#include "gdata-documents-service.h"
 
 static void gdata_documents_entry_access_handler_init (GDataAccessHandlerIface *iface);
 static GObject *gdata_documents_entry_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params);
@@ -239,10 +240,17 @@ is_owner_rule (GDataAccessRule *rule)
 	return (strcmp (gdata_access_rule_get_role (rule), GDATA_DOCUMENTS_ACCESS_ROLE_OWNER) == 0) ? TRUE : FALSE;
 }
 
+static GDataAuthorizationDomain *
+get_authorization_domain (GDataAccessHandler *self)
+{
+	return gdata_documents_service_get_primary_authorization_domain ();
+}
+
 static void
 gdata_documents_entry_access_handler_init (GDataAccessHandlerIface *iface)
 {
 	iface->is_owner_rule = is_owner_rule;
+	iface->get_authorization_domain = get_authorization_domain;
 }
 
 static void
diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c
index 092bcb6..acef79d 100644
--- a/gdata/services/documents/gdata-documents-service.c
+++ b/gdata/services/documents/gdata-documents-service.c
@@ -229,110 +229,97 @@ gdata_documents_service_error_quark (void)
 	return g_quark_from_static_string ("gdata-documents-service-error-quark");
 }
 
-static void gdata_documents_service_dispose (GObject *object);
-static void gdata_documents_service_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
-static void notify_authenticated_cb (GObject *service, GParamSpec *pspec, GObject *self);
-static void notify_proxy_uri_cb (GObject *service, GParamSpec *pspec, GObject *self);
-
-struct _GDataDocumentsServicePrivate {
-	GDataService *spreadsheet_service;
-};
-
-enum {
-	PROP_SPREADSHEET_SERVICE = 1
-};
+static GList *get_authorization_domains (void);
 
+_GDATA_DEFINE_AUTHORIZATION_DOMAIN (documents, "writely", "https://docs.google.com/feeds/";)
+_GDATA_DEFINE_AUTHORIZATION_DOMAIN (spreadsheets, "wise", "https://spreadsheets.google.com/feeds/";)
 G_DEFINE_TYPE_WITH_CODE (GDataDocumentsService, gdata_documents_service, GDATA_TYPE_SERVICE, G_IMPLEMENT_INTERFACE (GDATA_TYPE_BATCHABLE, NULL))
 
 static void
 gdata_documents_service_class_init (GDataDocumentsServiceClass *klass)
 {
-	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 	GDataServiceClass *service_class = GDATA_SERVICE_CLASS (klass);
-
-	g_type_class_add_private (klass, sizeof (GDataDocumentsServicePrivate));
-
-	gobject_class->get_property = gdata_documents_service_get_property;
-	gobject_class->dispose = gdata_documents_service_dispose;
-
-	service_class->service_name = "writely";
 	service_class->feed_type = GDATA_TYPE_DOCUMENTS_FEED;
-
-	/**
-	 * GDataService:spreadsheet-service:
-	 *
-	 * Another service for spreadsheets, required to be able to handle downloads.
-	 *
-	 * For more details about the spreadsheet downloads handling, see the
-	 * <ulink type="http" url="http://groups.google.com/group/Google-Docs-Data-APIs/browse_thread/thread/bfc50e94e303a29a?pli=1";>
-	 * online explanation about the problem</ulink>.
-	 *
-	 * Since: 0.4.0
-	 **/
-	g_object_class_install_property (gobject_class, PROP_SPREADSHEET_SERVICE,
-	                                 g_param_spec_object ("spreadsheet-service",
-	                                                      "Spreadsheet service", "Another service for spreadsheets.",
-	                                                      GDATA_TYPE_SERVICE,
-	                                                      G_PARAM_READABLE));
+	service_class->get_authorization_domains = get_authorization_domains;
 }
 
 static void
 gdata_documents_service_init (GDataDocumentsService *self)
 {
-	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_DOCUMENTS_SERVICE, GDataDocumentsServicePrivate);
-	g_signal_connect (self, "notify::authenticated", G_CALLBACK (notify_authenticated_cb), NULL);
-	g_signal_connect (self, "notify::proxy-uri", G_CALLBACK (notify_proxy_uri_cb), NULL);
+	/* Nothing to see here */
 }
 
-static void
-gdata_documents_service_dispose (GObject *object)
+static GList *
+get_authorization_domains (void)
 {
-	GDataDocumentsServicePrivate *priv = GDATA_DOCUMENTS_SERVICE (object)->priv;
+	GList *authorization_domains = NULL;
 
-	if (priv->spreadsheet_service != NULL)
-		g_object_unref (priv->spreadsheet_service);
-	priv->spreadsheet_service = NULL;
+	authorization_domains = g_list_prepend (authorization_domains, get_documents_authorization_domain ());
+	authorization_domains = g_list_prepend (authorization_domains, get_spreadsheets_authorization_domain ());
 
-	G_OBJECT_CLASS (gdata_documents_service_parent_class)->dispose (object);
-}
-
-static void
-gdata_documents_service_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
-{
-	GDataDocumentsServicePrivate *priv = GDATA_DOCUMENTS_SERVICE (object)->priv;
-
-	switch (property_id) {
-		case PROP_SPREADSHEET_SERVICE:
-			g_value_set_object (value, priv->spreadsheet_service);
-			break;
-		default:
-			/* We don't have any other property... */
-			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-			break;
-	}
+	return authorization_domains;
 }
 
 /**
  * gdata_documents_service_new:
- * @client_id: your application's client ID
+ * @authorizer: (allow-none): a #GDataAuthorizer to authorize the service's requests, or %NULL
  *
- * Creates a new #GDataDocumentsService. The @client_id must be unique for your application, and as registered with Google.
+ * Creates a new #GDataDocumentsService using the given #GDataAuthorizer. If @authorizer is %NULL, all requests are made as an unauthenticated user.
  *
  * Return value: a new #GDataDocumentsService, or %NULL; unref with g_object_unref()
  *
- * Since: 0.4.0
- **/
+ * Since: 0.9.0
+ */
 GDataDocumentsService *
-gdata_documents_service_new (const gchar *client_id)
+gdata_documents_service_new (GDataAuthorizer *authorizer)
 {
-	g_return_val_if_fail (client_id != NULL, NULL);
+	g_return_val_if_fail (authorizer == NULL || GDATA_IS_AUTHORIZER (authorizer), NULL);
 
 	return g_object_new (GDATA_TYPE_DOCUMENTS_SERVICE,
-	                     "client-id", client_id,
+	                     "authorizer", authorizer,
 	                     NULL);
 }
 
 /**
+ * gdata_documents_service_get_primary_authorization_domain:
+ *
+ * The primary #GDataAuthorizationDomain for interacting with Google Documents. This will not normally need to be used, as it's used internally
+ * by the #GDataDocumentsService methods. However, if using the plain #GDataService methods to implement custom queries or requests which libgdata
+ * does not support natively, then this domain may be needed to authorize the requests.
+ *
+ * The domain never changes, and is interned so that pointer comparison can be used to differentiate it from other authorization domains.
+ *
+ * Return value: (transfer none): the service's authorization domain
+ *
+ * Since: 0.9.0
+ */
+GDataAuthorizationDomain *
+gdata_documents_service_get_primary_authorization_domain (void)
+{
+	return get_documents_authorization_domain ();
+}
+
+/**
+ * gdata_documents_service_get_spreadsheet_authorization_domain:
+ *
+ * The #GDataAuthorizationDomain for interacting with spreadsheet data. This will not normally need to be used, as it's automatically used internally
+ * by the #GDataDocumentsService methods. However, if using the plain #GDataService methods to implement custom queries or requests which libgdata
+ * does not support natively, then this domain may be needed to authorize the requests which pertain to the Google Spreadsheets Data API, such as
+ * requests to download or upload spreadsheet documents.
+ *
+ * The domain never changes, and is interned so that pointer comparison can be used to differentiate it from other authorization domains.
+ *
+ * Return value: (transfer none): the service's authorization domain
+ *
+ * Since: 0.9.0
+ */
+GDataAuthorizationDomain *
+gdata_documents_service_get_spreadsheet_authorization_domain (void)
+{
+	return get_spreadsheets_authorization_domain ();
+}
+
+/**
  * gdata_documents_service_query_documents:
  * @self: a #GDataDocumentsService
  * @query: (allow-none): a #GDataDocumentsQuery with the query parameters, or %NULL
@@ -364,7 +351,8 @@ gdata_documents_service_query_documents (GDataDocumentsService *self, GDataDocum
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_documents_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to query documents."));
 		return NULL;
@@ -376,8 +364,8 @@ gdata_documents_service_query_documents (GDataDocumentsService *self, GDataDocum
 	else
 		request_uri = g_strconcat (_gdata_service_get_scheme (), "://docs.google.com/feeds/documents/private/full", NULL);
 
-	feed = gdata_service_query (GDATA_SERVICE (self), request_uri, GDATA_QUERY (query), GDATA_TYPE_DOCUMENTS_ENTRY, cancellable,
-	                            progress_callback, progress_user_data, error);
+	feed = gdata_service_query (GDATA_SERVICE (self), get_documents_authorization_domain (), request_uri, GDATA_QUERY (query),
+	                            GDATA_TYPE_DOCUMENTS_ENTRY, cancellable, progress_callback, progress_user_data, error);
 	g_free (request_uri);
 
 	return GDATA_DOCUMENTS_FEED (feed);
@@ -414,7 +402,8 @@ gdata_documents_service_query_documents_async (GDataDocumentsService *self, GDat
 	g_return_if_fail (callback != NULL);
 
 	/* Ensure we're authenticated first */
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_documents_authorization_domain ()) == FALSE) {
 		g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
 		                                     GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                                     _("You must be authenticated to query documents."));
@@ -422,44 +411,11 @@ gdata_documents_service_query_documents_async (GDataDocumentsService *self, GDat
 	}
 
 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://docs.google.com/feeds/documents/private/full", NULL);
-	gdata_service_query_async (GDATA_SERVICE (self), request_uri, GDATA_QUERY (query), GDATA_TYPE_DOCUMENTS_ENTRY,
-	                           cancellable, progress_callback, progress_user_data, callback, user_data);
+	gdata_service_query_async (GDATA_SERVICE (self), get_documents_authorization_domain (), request_uri, GDATA_QUERY (query),
+	                           GDATA_TYPE_DOCUMENTS_ENTRY, cancellable, progress_callback, progress_user_data, callback, user_data);
 	g_free (request_uri);
 }
 
-/*
- * To upload spreadsheet documents, another token is needed since the service for it is "wise" as apposed to "writely" for other operations.
- * This callback aims to authenticate to this service as a private property (@priv->spreadsheet_service) of #GDataDocumentsService.
- */
-static void
-notify_authenticated_cb (GObject *service, GParamSpec *pspec, GObject *self)
-{
-	GDataService *spreadsheet_service;
-	GDataDocumentsServicePrivate *priv = GDATA_DOCUMENTS_SERVICE (service)->priv;
-
-	if (priv->spreadsheet_service != NULL)
-		g_object_unref (priv->spreadsheet_service);
-
-	spreadsheet_service = g_object_new (GDATA_TYPE_SERVICE, "client-id", gdata_service_get_client_id (GDATA_SERVICE (service)), NULL);
-	GDATA_SERVICE_GET_CLASS (spreadsheet_service)->service_name = "wise";
-	gdata_service_authenticate (spreadsheet_service, gdata_service_get_username (GDATA_SERVICE (service)),
-	                            gdata_service_get_password (GDATA_SERVICE (service)), NULL, NULL);
-	priv->spreadsheet_service = spreadsheet_service;
-}
-
-/* Sets the proxy on @spreadsheet_service when it is set on the service */
-static void
-notify_proxy_uri_cb (GObject *service, GParamSpec *pspec, GObject *self)
-{
-	SoupURI *proxy_uri;
-
-	if (GDATA_DOCUMENTS_SERVICE (self)->priv->spreadsheet_service == NULL)
-		return;
-
-	proxy_uri = gdata_service_get_proxy_uri (GDATA_SERVICE (service));
-	gdata_service_set_proxy_uri (GDATA_DOCUMENTS_SERVICE (self)->priv->spreadsheet_service, proxy_uri);
-}
-
 static GDataUploadStream *
 upload_update_document (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, const gchar *content_type,
                         const gchar *method, const gchar *upload_uri, GCancellable *cancellable)
@@ -473,8 +429,8 @@ upload_update_document (GDataDocumentsService *self, GDataDocumentsDocument *doc
 		content_type = "application/x-vnd.oasis.opendocument.spreadsheet";
 
 	/* We need streaming file I/O: GDataUploadStream */
-	return GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), method, upload_uri, GDATA_ENTRY (document), slug, content_type,
-	                                                     cancellable));
+	return GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), get_documents_authorization_domain (), method, upload_uri,
+	                                                     GDATA_ENTRY (document), slug, content_type, cancellable));
 }
 
 /**
@@ -522,7 +478,8 @@ gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocum
 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_documents_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to upload documents."));
 		return NULL;
@@ -582,7 +539,8 @@ gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocum
 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_documents_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to update documents."));
 		return NULL;
@@ -693,7 +651,8 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD
 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_documents_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to move documents and folders."));
 		return NULL;
@@ -703,7 +662,7 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD
 	folder_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (folder));
 	g_assert (folder_id != NULL);
 	uri = g_strconcat (_gdata_service_get_scheme (), "://docs.google.com/feeds/folders/private/full/folder%3A", folder_id, NULL);
-	message = _gdata_service_build_message (GDATA_SERVICE (self), SOUP_METHOD_POST, uri, NULL, TRUE);
+	message = _gdata_service_build_message (GDATA_SERVICE (self), get_documents_authorization_domain (), SOUP_METHOD_POST, uri, NULL, TRUE);
 	g_free (uri);
 
 	/* Append the data */
@@ -877,7 +836,8 @@ gdata_documents_service_remove_entry_from_folder (GDataDocumentsService *self, G
 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_documents_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to move documents and folders."));
 		return NULL;
@@ -905,7 +865,8 @@ gdata_documents_service_remove_entry_from_folder (GDataDocumentsService *self, G
 		g_assert_not_reached ();
 	}
 
-	message = _gdata_service_build_message (GDATA_SERVICE (self), SOUP_METHOD_DELETE, uri, gdata_entry_get_etag (GDATA_ENTRY (entry)), TRUE);
+	message = _gdata_service_build_message (GDATA_SERVICE (self), get_documents_authorization_domain (), SOUP_METHOD_DELETE, uri,
+	                                        gdata_entry_get_etag (GDATA_ENTRY (entry)), TRUE);
 	g_free (uri);
 
 	/* Send the message */
@@ -929,7 +890,8 @@ gdata_documents_service_remove_entry_from_folder (GDataDocumentsService *self, G
 
 	/* Google's servers don't return an updated copy of the entry, so we have to query for it again.
 	 * See: http://code.google.com/p/gdata-issues/issues/detail?id=1380 */
-	return GDATA_DOCUMENTS_ENTRY (gdata_service_query_single_entry (GDATA_SERVICE (self), gdata_entry_get_id (GDATA_ENTRY (entry)), NULL,
+	return GDATA_DOCUMENTS_ENTRY (gdata_service_query_single_entry (GDATA_SERVICE (self), get_documents_authorization_domain (),
+	                                                                gdata_entry_get_id (GDATA_ENTRY (entry)), NULL,
 	                                                                G_OBJECT_TYPE (entry), cancellable, error));
 }
 
@@ -1070,10 +1032,3 @@ gdata_documents_service_get_upload_uri (GDataDocumentsFolder *folder)
 	/* Otherwise return the default upload URI */
 	return g_strconcat (_gdata_service_get_scheme (), "://docs.google.com/feeds/documents/private/full", NULL);
 }
-
-GDataService *
-_gdata_documents_service_get_spreadsheet_service (GDataDocumentsService *self)
-{
-	g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL);
-	return self->priv->spreadsheet_service;
-}
diff --git a/gdata/services/documents/gdata-documents-service.h b/gdata/services/documents/gdata-documents-service.h
index 8a25e31..6c20f0b 100644
--- a/gdata/services/documents/gdata-documents-service.h
+++ b/gdata/services/documents/gdata-documents-service.h
@@ -80,7 +80,11 @@ typedef struct {
 GType gdata_documents_service_get_type (void) G_GNUC_CONST;
 GQuark gdata_documents_service_error_quark (void) G_GNUC_CONST;
 
-GDataDocumentsService *gdata_documents_service_new (const gchar *client_id) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+GDataDocumentsService *gdata_documents_service_new (GDataAuthorizer *authorizer) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
+GDataAuthorizationDomain *gdata_documents_service_get_primary_authorization_domain (void) G_GNUC_CONST;
+GDataAuthorizationDomain *gdata_documents_service_get_spreadsheet_authorization_domain (void) G_GNUC_CONST;
+
 GDataDocumentsFeed *gdata_documents_service_query_documents (GDataDocumentsService *self, GDataDocumentsQuery *query, GCancellable *cancellable,
                                                              GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
                                                              GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
diff --git a/gdata/services/documents/gdata-documents-spreadsheet.c b/gdata/services/documents/gdata-documents-spreadsheet.c
index 155651c..084d34a 100644
--- a/gdata/services/documents/gdata-documents-spreadsheet.c
+++ b/gdata/services/documents/gdata-documents-spreadsheet.c
@@ -49,7 +49,8 @@
  *
  *	/<!-- -->* Create the download stream *<!-- -->/
  *	download_uri = gdata_documents_spreadsheet_get_download_uri (spreadsheet, GDATA_DOCUMENTS_SPREADSHEET_CSV, gid);
- *	download_stream = GDATA_DOWNLOAD_STREAM (gdata_download_stream_new (service, download_uri, NULL));
+ *	download_stream = GDATA_DOWNLOAD_STREAM (gdata_download_stream_new (service, gdata_documents_service_get_spreadsheet_authorization_domain (),
+ *	                                                                    download_uri, NULL));
  *	g_free (download_uri);
  *
  *	g_object_unref (spreadsheet);
diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.c b/gdata/services/picasaweb/gdata-picasaweb-service.c
index 5a5b14b..fc555b4 100644
--- a/gdata/services/picasaweb/gdata-picasaweb-service.c
+++ b/gdata/services/picasaweb/gdata-picasaweb-service.c
@@ -33,12 +33,14 @@
  * <example>
  * 	<title>Authenticating and Creating a New Album</title>
  * 	<programlisting>
+ *	GDataClientLoginAuthorizer *authorizer;
  *	GDataPicasaWebService *service;
  *	GDataPicasaWebAlbum *album, *inserted_album;
  *
- *	/<!-- -->* Create a service object and authenticate with the PicasaWeb server *<!-- -->/
- *	service = gdata_picasaweb_service_new ("companyName-applicationName-versionID");
- *	gdata_service_authenticate (GDATA_SERVICE (service), username, password, NULL, NULL);
+ *	/<!-- -->* Create a service object and authorize against the PicasaWeb service *<!-- -->/
+ *	authorizer = gdata_client_login_authorizer_new ("companyName-applicationName-versionID", GDATA_TYPE_PICASAWEB_SERVICE);
+ *	gdata_client_login_authorizer_authenticate (authorizer, username, password, NULL, NULL);
+ *	service = gdata_picasaweb_service_new (GDATA_AUTHORIZER (authorizer));
  *
  *	/<!-- -->* Create a GDataPicasaWebAlbum entry for the new album, setting some information about it *<!-- -->/
  *	album = gdata_picasaweb_album_new (NULL);
@@ -52,6 +54,7 @@
  *	g_object_unref (album);
  *	g_object_unref (inserted_album);
  *	g_object_unref (service);
+ *	g_object_unref (authorizer);
  *	</programlisting>
  * </example>
  *
@@ -118,14 +121,17 @@
 #include "gdata-upload-stream.h"
 #include "gdata-picasaweb-feed.h"
 
+static GList *get_authorization_domains (void);
+
+_GDATA_DEFINE_AUTHORIZATION_DOMAIN (picasaweb, "lh2", "http://picasaweb.google.com/data/";)
 G_DEFINE_TYPE (GDataPicasaWebService, gdata_picasaweb_service, GDATA_TYPE_SERVICE)
 
 static void
 gdata_picasaweb_service_class_init (GDataPicasaWebServiceClass *klass)
 {
 	GDataServiceClass *service_class = GDATA_SERVICE_CLASS (klass);
-	service_class->service_name = "lh2";
 	service_class->feed_type = GDATA_TYPE_PICASAWEB_FEED;
+	service_class->get_authorization_domains = get_authorization_domains;
 }
 
 static void
@@ -134,23 +140,49 @@ gdata_picasaweb_service_init (GDataPicasaWebService *self)
 	/* Nothing to see here */
 }
 
+static GList *
+get_authorization_domains (void)
+{
+	return g_list_prepend (NULL, get_picasaweb_authorization_domain ());
+}
+
 /**
  * gdata_picasaweb_service_new:
- * @client_id: your application's client ID
+ * @authorizer: (allow-none): a #GDataAuthorizer to authorize the service's requests, or %NULL
  *
- * Creates a new #GDataPicasaWebService. The @client_id must be unique for your application, and as registered with Google.
- * The <ulink type="http" url="http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html#Request";>recommended
- * form</ulink> is "companyName-applicationName-versionID".
+ * Creates a new #GDataPicasaWebService using the given #GDataAuthorizer. If @authorizer is %NULL, all requests are made as an unauthenticated user.
  *
- * Return value: a new #GDataPicasaWebService, or %NULL
+ * Return value: a new #GDataPicasaWebService, or %NULL; unref with g_object_unref()
  *
- * Since: 0.4.0
- **/
+ * Since: 0.9.0
+ */
 GDataPicasaWebService *
-gdata_picasaweb_service_new (const gchar *client_id)
+gdata_picasaweb_service_new (GDataAuthorizer *authorizer)
+{
+	g_return_val_if_fail (authorizer == NULL || GDATA_IS_AUTHORIZER (authorizer), NULL);
+
+	return g_object_new (GDATA_TYPE_PICASAWEB_SERVICE,
+	                     "authorizer", authorizer,
+	                     NULL);
+}
+
+/**
+ * gdata_picasaweb_service_get_primary_authorization_domain:
+ *
+ * The primary #GDataAuthorizationDomain for interacting with PicasaWeb. This will not normally need to be used, as it's used internally
+ * by the #GDataPicasaWebService methods. However, if using the plain #GDataService methods to implement custom queries or requests which libgdata
+ * does not support natively, then this domain may be needed to authorize the requests.
+ *
+ * The domain never changes, and is interned so that pointer comparison can be used to differentiate it from other authorization domains.
+ *
+ * Return value: (transfer none): the service's authorization domain
+ *
+ * Since: 0.9.0
+ */
+GDataAuthorizationDomain *
+gdata_picasaweb_service_get_primary_authorization_domain (void)
 {
-	g_return_val_if_fail (client_id != NULL, NULL);
-	return g_object_new (GDATA_TYPE_PICASAWEB_SERVICE, "client-id", client_id, NULL);
+	return get_picasaweb_authorization_domain ();
 }
 
 /*
@@ -169,15 +201,17 @@ static gchar *
 create_uri (GDataPicasaWebService *self, const gchar *username, const gchar *type)
 {
 	if (username == NULL) {
-		/* Ensure we're authenticated first */
-		if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE)
+		/* Ensure we're authorized first */
+		if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+		                                               get_picasaweb_authorization_domain ()) == FALSE) {
 			return NULL;
+		}
 
 		/* Querying Picasa albums for the "default" user when logged in returns the albums for the authenticated user */
 		username = "default";
 	}
 
-	return _gdata_service_build_uri ("http://picasaweb.google.com/data/%s/api/user/%s";, type, username);
+	return _gdata_service_build_uri ("https://picasaweb.google.com/data/%s/api/user/%s";, type, username);
 }
 
 /**
@@ -211,7 +245,7 @@ gdata_picasaweb_service_get_user (GDataPicasaWebService *self, const gchar *user
 		return NULL;
 	}
 
-	message = _gdata_service_query (GDATA_SERVICE (self), uri, NULL, cancellable, error);
+	message = _gdata_service_query (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), uri, NULL, cancellable, error);
 	g_free (uri);
 
 	if (message == NULL)
@@ -273,7 +307,7 @@ gdata_picasaweb_service_query_all_albums (GDataPicasaWebService *self, GDataQuer
 	}
 
 	/* Execute the query */
-	album_feed = gdata_service_query (GDATA_SERVICE (self), uri, query, GDATA_TYPE_PICASAWEB_ALBUM,
+	album_feed = gdata_service_query (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), uri, query, GDATA_TYPE_PICASAWEB_ALBUM,
 	                                  cancellable, progress_callback, progress_user_data, error);
 	g_free (uri);
 
@@ -329,8 +363,8 @@ gdata_picasaweb_service_query_all_albums_async (GDataPicasaWebService *self, GDa
 	}
 
 	/* Schedule the async query */
-	gdata_service_query_async (GDATA_SERVICE (self), uri, query, GDATA_TYPE_PICASAWEB_ALBUM, cancellable, progress_callback, progress_user_data,
-	                           callback, user_data);
+	gdata_service_query_async (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), uri, query, GDATA_TYPE_PICASAWEB_ALBUM, cancellable,
+	                           progress_callback, progress_user_data, callback, user_data);
 	g_free (uri);
 }
 
@@ -349,7 +383,7 @@ get_query_files_uri (GDataPicasaWebAlbum *album, GError **error)
 		return gdata_link_get_uri (_link);
 	} else {
 		/* Default URI */
-		return "http://picasaweb.google.com/data/feed/api/user/default/albumid/default";;
+		return "https://picasaweb.google.com/data/feed/api/user/default/albumid/default";;
 	}
 }
 
@@ -389,8 +423,8 @@ gdata_picasaweb_service_query_files (GDataPicasaWebService *self, GDataPicasaWeb
 		return NULL;
 
 	/* Execute the query */
-	return gdata_service_query (GDATA_SERVICE (self), uri, GDATA_QUERY (query), GDATA_TYPE_PICASAWEB_FILE, cancellable,
-	                            progress_callback, progress_user_data, error);
+	return gdata_service_query (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), uri, GDATA_QUERY (query), GDATA_TYPE_PICASAWEB_FILE,
+	                            cancellable, progress_callback, progress_user_data, error);
 }
 
 /**
@@ -434,8 +468,8 @@ gdata_picasaweb_service_query_files_async (GDataPicasaWebService *self, GDataPic
 		return;
 	}
 
-	gdata_service_query_async (GDATA_SERVICE (self), request_uri, GDATA_QUERY (query), GDATA_TYPE_PICASAWEB_FILE, cancellable, progress_callback,
-	                           progress_user_data, callback, user_data);
+	gdata_service_query_async (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), request_uri, GDATA_QUERY (query),
+	                           GDATA_TYPE_PICASAWEB_FILE, cancellable, progress_callback, progress_user_data, callback, user_data);
 }
 
 /**
@@ -473,7 +507,7 @@ GDataUploadStream *
 gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataPicasaWebFile *file_entry, const gchar *slug,
                                      const gchar *content_type, GCancellable *cancellable, GError **error)
 {
-	const gchar *user_id = NULL, *album_id = NULL;
+	const gchar *album_id = NULL;
 	GDataUploadStream *upload_stream;
 	gchar *upload_uri;
 
@@ -491,7 +525,8 @@ gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWeb
 		return NULL;
 	}
 
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_picasaweb_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to upload a file."));
 		return NULL;
@@ -499,12 +534,11 @@ gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWeb
 
 	/* PicasaWeb allows you to post to a default Dropbox */
 	album_id = (album != NULL) ? gdata_entry_get_id (GDATA_ENTRY (album)) : "default";
-	user_id = gdata_service_get_username (GDATA_SERVICE (self));
 
 	/* Build the upload URI and upload stream */
-	upload_uri = _gdata_service_build_uri ("http://picasaweb.google.com/data/feed/api/user/%s/albumid/%s";, user_id, album_id);
-	upload_stream = GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), SOUP_METHOD_POST, upload_uri, GDATA_ENTRY (file_entry),
-	                                                              slug, content_type, cancellable));
+	upload_uri = _gdata_service_build_uri ("https://picasaweb.google.com/data/feed/api/user/default/albumid/%s";, album_id);
+	upload_stream = GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), SOUP_METHOD_POST,
+	                                                              upload_uri, GDATA_ENTRY (file_entry), slug, content_type, cancellable));
 	g_free (upload_uri);
 
 	return upload_stream;
@@ -574,13 +608,15 @@ gdata_picasaweb_service_insert_album (GDataPicasaWebService *self, GDataPicasaWe
 		return NULL;
 	}
 
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_picasaweb_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to insert an album."));
 		return NULL;
 	}
 
-	return GDATA_PICASAWEB_ALBUM (gdata_service_insert_entry (GDATA_SERVICE (self), "http://picasaweb.google.com/data/feed/api/user/default";,
+	return GDATA_PICASAWEB_ALBUM (gdata_service_insert_entry (GDATA_SERVICE (self), get_picasaweb_authorization_domain (),
+	                                                          "https://picasaweb.google.com/data/feed/api/user/default";,
 	                                                          GDATA_ENTRY (album), cancellable, error));
 }
 
@@ -611,6 +647,7 @@ gdata_picasaweb_service_insert_album_async (GDataPicasaWebService *self, GDataPi
 	g_return_if_fail (GDATA_IS_PICASAWEB_ALBUM (album));
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
-	gdata_service_insert_entry_async (GDATA_SERVICE (self), "http://picasaweb.google.com/data/feed/api/user/default";, GDATA_ENTRY (album),
-	                                  cancellable, callback, user_data);
+	gdata_service_insert_entry_async (GDATA_SERVICE (self), get_picasaweb_authorization_domain (),
+	                                  "https://picasaweb.google.com/data/feed/api/user/default";, GDATA_ENTRY (album), cancellable, callback,
+	                                  user_data);
 }
diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.h b/gdata/services/picasaweb/gdata-picasaweb-service.h
index ced89d8..72e1545 100644
--- a/gdata/services/picasaweb/gdata-picasaweb-service.h
+++ b/gdata/services/picasaweb/gdata-picasaweb-service.h
@@ -63,7 +63,9 @@ typedef struct {
 
 GType gdata_picasaweb_service_get_type (void) G_GNUC_CONST;
 
-GDataPicasaWebService *gdata_picasaweb_service_new (const gchar *client_id) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+GDataPicasaWebService *gdata_picasaweb_service_new (GDataAuthorizer *authorizer) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
+GDataAuthorizationDomain *gdata_picasaweb_service_get_primary_authorization_domain (void) G_GNUC_CONST;
 
 #include <gdata/services/picasaweb/gdata-picasaweb-query.h>
 
diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c
index 253f680..15f2acb 100644
--- a/gdata/services/youtube/gdata-youtube-service.c
+++ b/gdata/services/youtube/gdata-youtube-service.c
@@ -224,10 +224,12 @@ gdata_youtube_service_error_quark (void)
 static void gdata_youtube_service_finalize (GObject *object);
 static void gdata_youtube_service_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
 static void gdata_youtube_service_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
-static void append_query_headers (GDataService *self, SoupMessage *message);
+static void append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, SoupMessage *message);
 static void parse_error_response (GDataService *self, GDataOperationType operation_type, guint status, const gchar *reason_phrase,
                                   const gchar *response_body, gint length, GError **error);
 
+static GList *get_authorization_domains (void);
+
 struct _GDataYouTubeServicePrivate {
 	gchar *developer_key;
 };
@@ -236,6 +238,7 @@ enum {
 	PROP_DEVELOPER_KEY = 1
 };
 
+_GDATA_DEFINE_AUTHORIZATION_DOMAIN (youtube, "youtube", "http://gdata.youtube.com";)
 G_DEFINE_TYPE_WITH_CODE (GDataYouTubeService, gdata_youtube_service, GDATA_TYPE_SERVICE, G_IMPLEMENT_INTERFACE (GDATA_TYPE_BATCHABLE, NULL))
 
 static void
@@ -250,17 +253,15 @@ gdata_youtube_service_class_init (GDataYouTubeServiceClass *klass)
 	gobject_class->get_property = gdata_youtube_service_get_property;
 	gobject_class->finalize = gdata_youtube_service_finalize;
 
-	service_class->service_name = "youtube";
 	service_class->append_query_headers = append_query_headers;
 	service_class->parse_error_response = parse_error_response;
+	service_class->get_authorization_domains = get_authorization_domains;
 
 	/**
 	 * GDataYouTubeService:developer-key:
 	 *
 	 * The developer key your application has registered with the YouTube API. For more information, see the <ulink type="http"
 	 * url="http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html#Developer_Key";>online documentation</ulink>.
-	 *
-	 * The matching #GDataService:client-id property belongs to #GDataService.
 	 **/
 	g_object_class_install_property (gobject_class, PROP_DEVELOPER_KEY,
 	                                 g_param_spec_string ("developer-key",
@@ -319,7 +320,7 @@ gdata_youtube_service_set_property (GObject *object, guint property_id, const GV
 }
 
 static void
-append_query_headers (GDataService *self, SoupMessage *message)
+append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, SoupMessage *message)
 {
 	GDataYouTubeServicePrivate *priv = GDATA_YOUTUBE_SERVICE (self)->priv;
 	gchar *key_header;
@@ -332,7 +333,7 @@ append_query_headers (GDataService *self, SoupMessage *message)
 	g_free (key_header);
 
 	/* Chain up to the parent class */
-	GDATA_SERVICE_CLASS (gdata_youtube_service_parent_class)->append_query_headers (self, message);
+	GDATA_SERVICE_CLASS (gdata_youtube_service_parent_class)->append_query_headers (self, domain, message);
 }
 
 static void
@@ -415,8 +416,7 @@ parse_error_response (GDataService *self, GDataOperationType operation_type, gui
 				g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_UNAVAILABLE,
 				             _("This service is not available at the moment."));
 			} else if (xmlStrcmp (domain, (xmlChar*) "yt:authentication") == 0) {
-				/* Authentication problem; make sure to set our status as unauthenticated */
-				_gdata_service_set_authenticated (GDATA_SERVICE (self), FALSE);
+				/* Authentication problem */
 				g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 				             _("You must be authenticated to do this."));
 			} else if (xmlStrcmp (domain, (xmlChar*) "yt:quota") == 0) {
@@ -467,52 +467,80 @@ parent:
 	                                                                                response_body, length, error);
 }
 
+static GList *
+get_authorization_domains (void)
+{
+	return g_list_prepend (NULL, get_youtube_authorization_domain ());
+}
+
 /**
  * gdata_youtube_service_new:
  * @developer_key: your application's developer API key
- * @client_id: your application's client ID
+ * @authorizer: (allow-none): a #GDataAuthorizer to authorize the service's requests, or %NULL
  *
- * Creates a new #GDataYouTubeService. The @developer_key and @client_id must be unique for your application, and as
+ * Creates a new #GDataYouTubeService using the given #GDataAuthorizer. If @authorizer is %NULL, all requests are made as an unauthenticated user.
+ * The @developer_key must be unique for your application, and as
  * <ulink type="http" url="http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html#Developer_Key";>registered with Google</ulink>.
  *
- * Return value: a new #GDataYouTubeService, or %NULL
- **/
+ * Return value: a new #GDataYouTubeService, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.9.0
+ */
 GDataYouTubeService *
-gdata_youtube_service_new (const gchar *developer_key, const gchar *client_id)
+gdata_youtube_service_new (const gchar *developer_key, GDataAuthorizer *authorizer)
 {
 	g_return_val_if_fail (developer_key != NULL, NULL);
-	g_return_val_if_fail (client_id != NULL, NULL);
+	g_return_val_if_fail (authorizer == NULL || GDATA_IS_AUTHORIZER (authorizer), NULL);
 
 	return g_object_new (GDATA_TYPE_YOUTUBE_SERVICE,
 	                     "developer-key", developer_key,
-	                     "client-id", client_id,
+	                     "authorizer", authorizer,
 	                     NULL);
 }
 
+/**
+ * gdata_youtube_service_get_primary_authorization_domain:
+ *
+ * The primary #GDataAuthorizationDomain for interacting with YouTube. This will not normally need to be used, as it's used internally
+ * by the #GDataYouTubeService methods. However, if using the plain #GDataService methods to implement custom queries or requests which libgdata
+ * does not support natively, then this domain may be needed to authorize the requests.
+ *
+ * The domain never changes, and is interned so that pointer comparison can be used to differentiate it from other authorization domains.
+ *
+ * Return value: (transfer none): the service's authorization domain
+ *
+ * Since: 0.9.0
+ */
+GDataAuthorizationDomain *
+gdata_youtube_service_get_primary_authorization_domain (void)
+{
+	return get_youtube_authorization_domain ();
+}
+
 static const gchar *
 standard_feed_type_to_feed_uri (GDataYouTubeStandardFeedType feed_type)
 {
 	switch (feed_type) {
 	case GDATA_YOUTUBE_TOP_RATED_FEED:
-		return "http://gdata.youtube.com/feeds/api/standardfeeds/top_rated";;
+		return "https://gdata.youtube.com/feeds/api/standardfeeds/top_rated";;
 	case GDATA_YOUTUBE_TOP_FAVORITES_FEED:
-		return "http://gdata.youtube.com/feeds/api/standardfeeds/top_favorites";;
+		return "https://gdata.youtube.com/feeds/api/standardfeeds/top_favorites";;
 	case GDATA_YOUTUBE_MOST_VIEWED_FEED:
-		return "http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed";;
+		return "https://gdata.youtube.com/feeds/api/standardfeeds/most_viewed";;
 	case GDATA_YOUTUBE_MOST_POPULAR_FEED:
-		return "http://gdata.youtube.com/feeds/api/standardfeeds/most_popular";;
+		return "https://gdata.youtube.com/feeds/api/standardfeeds/most_popular";;
 	case GDATA_YOUTUBE_MOST_RECENT_FEED:
-		return "http://gdata.youtube.com/feeds/api/standardfeeds/most_recent";;
+		return "https://gdata.youtube.com/feeds/api/standardfeeds/most_recent";;
 	case GDATA_YOUTUBE_MOST_DISCUSSED_FEED:
-		return "http://gdata.youtube.com/feeds/api/standardfeeds/most_discussed";;
+		return "https://gdata.youtube.com/feeds/api/standardfeeds/most_discussed";;
 	case GDATA_YOUTUBE_MOST_LINKED_FEED:
-		return "http://gdata.youtube.com/feeds/api/standardfeeds/most_linked";;
+		return "https://gdata.youtube.com/feeds/api/standardfeeds/most_linked";;
 	case GDATA_YOUTUBE_MOST_RESPONDED_FEED:
-		return "http://gdata.youtube.com/feeds/api/standardfeeds/most_responded";;
+		return "https://gdata.youtube.com/feeds/api/standardfeeds/most_responded";;
 	case GDATA_YOUTUBE_RECENTLY_FEATURED_FEED:
-		return "http://gdata.youtube.com/feeds/api/standardfeeds/recently_featured";;
+		return "https://gdata.youtube.com/feeds/api/standardfeeds/recently_featured";;
 	case GDATA_YOUTUBE_WATCH_ON_MOBILE_FEED:
-		return "http://gdata.youtube.com/feeds/api/standardfeeds/watch_on_mobile";;
+		return "https://gdata.youtube.com/feeds/api/standardfeeds/watch_on_mobile";;
 	default:
 		g_assert_not_reached ();
 	}
@@ -545,7 +573,7 @@ gdata_youtube_service_query_standard_feed (GDataYouTubeService *self, GDataYouTu
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	/* TODO: Support the "time" parameter, as well as category- and region-specific feeds */
-	return gdata_service_query (GDATA_SERVICE (self), standard_feed_type_to_feed_uri (feed_type), query,
+	return gdata_service_query (GDATA_SERVICE (self), get_youtube_authorization_domain (), standard_feed_type_to_feed_uri (feed_type), query,
 	                            GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, error);
 }
 
@@ -578,7 +606,7 @@ gdata_youtube_service_query_standard_feed_async (GDataYouTubeService *self, GDat
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 	g_return_if_fail (callback != NULL);
 
-	gdata_service_query_async (GDATA_SERVICE (self), standard_feed_type_to_feed_uri (feed_type), query,
+	gdata_service_query_async (GDATA_SERVICE (self), get_youtube_authorization_domain (), standard_feed_type_to_feed_uri (feed_type), query,
 	                           GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, callback, user_data);
 }
 
@@ -608,7 +636,7 @@ gdata_youtube_service_query_videos (GDataYouTubeService *self, GDataQuery *query
 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-	return gdata_service_query (GDATA_SERVICE (self), "http://gdata.youtube.com/feeds/api/videos";, query,
+	return gdata_service_query (GDATA_SERVICE (self), get_youtube_authorization_domain (), "https://gdata.youtube.com/feeds/api/videos";, query,
 	                            GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, error);
 }
 
@@ -640,7 +668,7 @@ gdata_youtube_service_query_videos_async (GDataYouTubeService *self, GDataQuery
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 	g_return_if_fail (callback != NULL);
 
-	gdata_service_query_async (GDATA_SERVICE (self), "http://gdata.youtube.com/feeds/api/videos";, query,
+	gdata_service_query_async (GDATA_SERVICE (self), get_youtube_authorization_domain (), "https://gdata.youtube.com/feeds/api/videos";, query,
 	                           GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, callback, user_data);
 }
 
@@ -667,6 +695,8 @@ gdata_youtube_service_query_related (GDataYouTubeService *self, GDataYouTubeVide
                                      GError **error)
 {
 	GDataLink *related_link;
+	GDataFeed *feed;
+	gchar *uri;
 
 	g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL);
 	g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (video), NULL);
@@ -684,8 +714,12 @@ gdata_youtube_service_query_related (GDataYouTubeService *self, GDataYouTubeVide
 	}
 
 	/* Execute the query */
-	return gdata_service_query (GDATA_SERVICE (self), gdata_link_get_uri (related_link), query,
+	uri = _gdata_service_fix_uri_scheme (gdata_link_get_uri (related_link));
+	feed = gdata_service_query (GDATA_SERVICE (self), get_youtube_authorization_domain (), uri, query,
 	                            GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, error);
+	g_free (uri);
+
+	return feed;
 }
 
 /**
@@ -713,6 +747,7 @@ gdata_youtube_service_query_related_async (GDataYouTubeService *self, GDataYouTu
                                            GAsyncReadyCallback callback, gpointer user_data)
 {
 	GDataLink *related_link;
+	gchar *uri;
 
 	g_return_if_fail (GDATA_IS_YOUTUBE_SERVICE (self));
 	g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (video));
@@ -730,8 +765,10 @@ gdata_youtube_service_query_related_async (GDataYouTubeService *self, GDataYouTu
 		return;
 	}
 
-	gdata_service_query_async (GDATA_SERVICE (self), gdata_link_get_uri (related_link), query,
+	uri = _gdata_service_fix_uri_scheme (gdata_link_get_uri (related_link));
+	gdata_service_query_async (GDATA_SERVICE (self), get_youtube_authorization_domain (), uri, query,
 	                           GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, callback, user_data);
+	g_free (uri);
 }
 
 /**
@@ -779,15 +816,16 @@ gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo
 		return NULL;
 	}
 
-	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+	                                               get_youtube_authorization_domain ()) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
 		                     _("You must be authenticated to upload a video."));
 		return NULL;
 	}
 
 	/* Streaming upload support using GDataUploadStream; automatically handles the XML and multipart stuff for us */
-	return GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), SOUP_METHOD_POST,
-	                                                     "http://uploads.gdata.youtube.com/feeds/api/users/default/uploads";,
+	return GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), get_youtube_authorization_domain (), SOUP_METHOD_POST,
+	                                                     "https://uploads.gdata.youtube.com/feeds/api/users/default/uploads";,
 	                                                      GDATA_ENTRY (video), slug, content_type, cancellable));
 }
 
@@ -867,7 +905,8 @@ gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *c
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	/* Download the category list. Note that this is (service) locale-dependent. */
-	message = _gdata_service_query (GDATA_SERVICE (self), "http://gdata.youtube.com/schemas/2007/categories.cat";, NULL, cancellable, error);
+	message = _gdata_service_query (GDATA_SERVICE (self), get_youtube_authorization_domain (),
+	                                "https://gdata.youtube.com/schemas/2007/categories.cat";, NULL, cancellable, error);
 	if (message == NULL)
 		return NULL;
 
diff --git a/gdata/services/youtube/gdata-youtube-service.h b/gdata/services/youtube/gdata-youtube-service.h
index f132ec8..102dab5 100644
--- a/gdata/services/youtube/gdata-youtube-service.h
+++ b/gdata/services/youtube/gdata-youtube-service.h
@@ -107,7 +107,9 @@ typedef struct {
 GType gdata_youtube_service_get_type (void) G_GNUC_CONST;
 GQuark gdata_youtube_service_error_quark (void) G_GNUC_CONST;
 
-GDataYouTubeService *gdata_youtube_service_new (const gchar *developer_key, const gchar *client_id) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+GDataYouTubeService *gdata_youtube_service_new (const gchar *developer_key, GDataAuthorizer *authorizer) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
+GDataAuthorizationDomain *gdata_youtube_service_get_primary_authorization_domain (void) G_GNUC_CONST;
 
 GDataFeed *gdata_youtube_service_query_standard_feed (GDataYouTubeService *self, GDataYouTubeStandardFeedType feed_type, GDataQuery *query,
                                                       GCancellable *cancellable,
diff --git a/gdata/services/youtube/gdata-youtube-video.c b/gdata/services/youtube/gdata-youtube-video.c
index a381195..524bc4f 100644
--- a/gdata/services/youtube/gdata-youtube-video.c
+++ b/gdata/services/youtube/gdata-youtube-video.c
@@ -924,7 +924,7 @@ get_entry_uri (const gchar *id)
 	g_assert (video_id != NULL);
 
 	/* Build the URI using the video ID */
-	uri = g_strconcat ("http://gdata.youtube.com/feeds/api/videos/";, video_id, NULL);
+	uri = g_strconcat ("https://gdata.youtube.com/feeds/api/videos/";, video_id, NULL);
 	g_strfreev (parts);
 
 	return uri;
diff --git a/gdata/tests/Makefile.am b/gdata/tests/Makefile.am
index 8df357b..29d5635 100644
--- a/gdata/tests/Makefile.am
+++ b/gdata/tests/Makefile.am
@@ -48,6 +48,12 @@ perf_SOURCES			 = perf.c $(TEST_SRCS)
 TEST_PROGS			+= streams
 streams_SOURCES			 = streams.c $(TEST_SRCS)
 
+TEST_PROGS			+= authorization
+authorization_SOURCES		 = authorization.c $(TEST_SRCS)
+
+TEST_PROGS			+= client-login-authorizer
+client_login_authorizer_SOURCES	 = client-login-authorizer.c $(TEST_SRCS)
+
 EXTRA_DIST += \
 	photo.jpg		\
 	sample.ogg		\
diff --git a/gdata/tests/authorization.c b/gdata/tests/authorization.c
new file mode 100644
index 0000000..f44278a
--- /dev/null
+++ b/gdata/tests/authorization.c
@@ -0,0 +1,795 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011 <philip tecnocode co uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <glib.h>
+#include <gdata/gdata.h>
+
+#include "common.h"
+
+/* Used as common "testing domains" to simplify test code. */
+static GDataAuthorizationDomain *test_domain1 = NULL;
+static GDataAuthorizationDomain *test_domain2 = NULL;
+
+static void
+test_authorization_domain_properties (void)
+{
+	GDataAuthorizationDomain *domain;
+	gchar *service_name, *scope;
+
+	/* NOTE: It's not expected that client code will normally get hold of GDataAuthorizationDomain instances this way.
+	 * This is just for testing purposes. */
+	domain = GDATA_AUTHORIZATION_DOMAIN (g_object_new (GDATA_TYPE_AUTHORIZATION_DOMAIN,
+	                                                   "service-name", "service-name",
+	                                                   "scope", "scope",
+	                                                   NULL));
+
+	g_assert_cmpstr (gdata_authorization_domain_get_service_name (domain), ==, "service-name");
+	g_assert_cmpstr (gdata_authorization_domain_get_scope (domain), ==, "scope");
+
+	g_object_get (domain,
+	              "service-name", &service_name,
+	              "scope", &scope,
+	              NULL);
+
+	g_assert_cmpstr (service_name, ==, "service-name");
+	g_assert_cmpstr (scope, ==, "scope");
+
+	g_free (service_name);
+	g_free (scope);
+}
+
+/* Simple implementation of GDataAuthorizer for test purposes */
+#define TYPE_SIMPLE_AUTHORIZER		(simple_authorizer_get_type ())
+#define SIMPLE_AUTHORIZER(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), SIMPLE_TYPE_AUTHORIZER, SimpleAuthorizer))
+#define SIMPLE_AUTHORIZER_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), SIMPLE_TYPE_AUTHORIZER, SimpleAuthorizerClass))
+#define IS_SIMPLE_AUTHORIZER(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), SIMPLE_TYPE_AUTHORIZER))
+#define IS_SIMPLE_AUTHORIZER_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), SIMPLE_TYPE_AUTHORIZER))
+#define SIMPLE_AUTHORIZER_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), SIMPLE_TYPE_AUTHORIZER, SimpleAuthorizerClass))
+
+typedef struct {
+	GObject parent;
+} SimpleAuthorizer;
+
+typedef struct {
+	GObjectClass parent;
+} SimpleAuthorizerClass;
+
+static GType simple_authorizer_get_type (void) G_GNUC_CONST;
+static void simple_authorizer_authorizer_init (GDataAuthorizerInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (SimpleAuthorizer, simple_authorizer, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDATA_TYPE_AUTHORIZER, simple_authorizer_authorizer_init))
+
+static void
+simple_authorizer_class_init (SimpleAuthorizerClass *klass)
+{
+	/* Nothing to see here */
+}
+
+static void
+simple_authorizer_init (SimpleAuthorizer *self)
+{
+	/* Nothing to see here */
+}
+
+static void
+simple_authorizer_process_request (GDataAuthorizer *self, GDataAuthorizationDomain *domain, SoupMessage *message)
+{
+	SoupURI *test_uri;
+
+	/* Check that the domain and message are as expected */
+	g_assert (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain));
+	if (domain != NULL) {
+		g_assert_cmpstr (gdata_authorization_domain_get_scope (domain), ==, "scope1");
+	}
+
+	g_assert (message != NULL);
+	g_assert (SOUP_IS_MESSAGE (message));
+	test_uri = soup_uri_new ("http://example.com/";);
+	g_assert (soup_uri_equal (soup_message_get_uri (message), test_uri) == TRUE);
+	soup_uri_free (test_uri);
+
+	/* Check that this is the first time we've touched the message, and if so, flag the message as touched */
+	if (domain != NULL) {
+		g_assert (soup_message_headers_get_one (message->request_headers, "process_request") == NULL);
+		soup_message_headers_append (message->request_headers, "process_request", "1");
+	} else {
+		soup_message_headers_append (message->request_headers, "process_request_null", "1");
+	}
+}
+
+static gboolean
+simple_authorizer_is_authorized_for_domain (GDataAuthorizer *self, GDataAuthorizationDomain *domain)
+{
+	gboolean is_test_domain1, is_test_domain2;
+
+	/* Check that the domain is as expected */
+	g_assert (domain != NULL);
+	g_assert (GDATA_IS_AUTHORIZATION_DOMAIN (domain));
+
+	is_test_domain1 = (strcmp (gdata_authorization_domain_get_scope (domain), "scope1") == 0) ? TRUE : FALSE;
+	is_test_domain2 = (strcmp (gdata_authorization_domain_get_scope (domain), "scope2") == 0) ? TRUE : FALSE;
+
+	g_assert (is_test_domain1 == TRUE || is_test_domain2 == TRUE);
+
+	/* Increment the counter on the domain so we know if this function's been called more than once on each domain */
+	g_object_set_data (G_OBJECT (domain), "counter", GUINT_TO_POINTER (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (domain), "counter")) + 1));
+
+	/* Only authorise test_domain1 */
+	return is_test_domain1;
+}
+
+static void
+simple_authorizer_authorizer_init (GDataAuthorizerInterface *iface)
+{
+	iface->process_request = simple_authorizer_process_request;
+	iface->is_authorized_for_domain = simple_authorizer_is_authorized_for_domain;
+}
+
+/* Normal implementation of GDataAuthorizer for test purposes */
+#define TYPE_NORMAL_AUTHORIZER		(normal_authorizer_get_type ())
+#define NORMAL_AUTHORIZER(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), NORMAL_TYPE_AUTHORIZER, NormalAuthorizer))
+#define NORMAL_AUTHORIZER_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), NORMAL_TYPE_AUTHORIZER, NormalAuthorizerClass))
+#define IS_NORMAL_AUTHORIZER(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), NORMAL_TYPE_AUTHORIZER))
+#define IS_NORMAL_AUTHORIZER_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), NORMAL_TYPE_AUTHORIZER))
+#define NORMAL_AUTHORIZER_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), NORMAL_TYPE_AUTHORIZER, NormalAuthorizerClass))
+
+typedef struct {
+	GObject parent;
+} NormalAuthorizer;
+
+typedef struct {
+	GObjectClass parent;
+} NormalAuthorizerClass;
+
+static GType normal_authorizer_get_type (void) G_GNUC_CONST;
+static void normal_authorizer_authorizer_init (GDataAuthorizerInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (NormalAuthorizer, normal_authorizer, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDATA_TYPE_AUTHORIZER, normal_authorizer_authorizer_init))
+
+static void
+normal_authorizer_class_init (NormalAuthorizerClass *klass)
+{
+	/* Nothing to see here */
+}
+
+static void
+normal_authorizer_init (NormalAuthorizer *self)
+{
+	/* Nothing to see here */
+}
+
+static gboolean
+normal_authorizer_refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, GError **error)
+{
+	/* Check the inputs */
+	g_assert (GDATA_IS_AUTHORIZER (self));
+	g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+	g_assert (error == NULL || *error == NULL);
+
+	/* Increment the counter on the authorizer so we know if this function's been called more than once */
+	g_object_set_data (G_OBJECT (self), "counter", GUINT_TO_POINTER (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (self), "counter")) + 1));
+
+	/* If we're instructed to set an error, do so (with an arbitrary error code) */
+	if (g_object_get_data (G_OBJECT (self), "error") != NULL) {
+		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, "Error message");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void
+normal_authorizer_authorizer_init (GDataAuthorizerInterface *iface)
+{
+	/* Use the same implementation as SimpleAuthorizer for process_request() and is_authorized_for_domain(). */
+	iface->process_request = simple_authorizer_process_request;
+	iface->is_authorized_for_domain = simple_authorizer_is_authorized_for_domain;
+
+	/* Unlike SimpleAuthorizer, also implement refresh_authorization() (but not the async versions). */
+	iface->refresh_authorization = normal_authorizer_refresh_authorization;
+}
+
+/* Complex implementation of GDataAuthorizer for test purposes */
+#define TYPE_COMPLEX_AUTHORIZER		(complex_authorizer_get_type ())
+#define COMPLEX_AUTHORIZER(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), COMPLEX_TYPE_AUTHORIZER, ComplexAuthorizer))
+#define COMPLEX_AUTHORIZER_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), COMPLEX_TYPE_AUTHORIZER, ComplexAuthorizerClass))
+#define IS_COMPLEX_AUTHORIZER(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), COMPLEX_TYPE_AUTHORIZER))
+#define IS_COMPLEX_AUTHORIZER_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), COMPLEX_TYPE_AUTHORIZER))
+#define COMPLEX_AUTHORIZER_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), COMPLEX_TYPE_AUTHORIZER, ComplexAuthorizerClass))
+
+typedef struct {
+	GObject parent;
+} ComplexAuthorizer;
+
+typedef struct {
+	GObjectClass parent;
+} ComplexAuthorizerClass;
+
+static GType complex_authorizer_get_type (void) G_GNUC_CONST;
+static void complex_authorizer_authorizer_init (GDataAuthorizerInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (ComplexAuthorizer, complex_authorizer, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDATA_TYPE_AUTHORIZER, complex_authorizer_authorizer_init))
+
+static void
+complex_authorizer_class_init (ComplexAuthorizerClass *klass)
+{
+	/* Nothing to see here */
+}
+
+static void
+complex_authorizer_init (ComplexAuthorizer *self)
+{
+	/* Nothing to see here */
+}
+
+static void
+complex_authorizer_refresh_authorization_async (GDataAuthorizer *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+{
+	GSimpleAsyncResult *result;
+	GError *error = NULL;
+
+	/* Check the inputs */
+	g_assert (GDATA_IS_AUTHORIZER (self));
+	g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, complex_authorizer_refresh_authorization_async);
+
+	/* Increment the async counter on the authorizer so we know if this function's been called more than once */
+	g_object_set_data (G_OBJECT (self), "async-counter",
+	                   GUINT_TO_POINTER (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (self), "async-counter")) + 1));
+
+	if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
+		/* Handle cancellation */
+		g_simple_async_result_set_from_error (result, error);
+	} else if (g_object_get_data (G_OBJECT (self), "error") != NULL) {
+		/* If we're instructed to set an error, do so (with an arbitrary error code) */
+		g_simple_async_result_set_error (result, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NETWORK_ERROR, "%s", "Error message");
+	}
+
+	g_simple_async_result_complete_in_idle (result);
+
+	g_object_unref (result);
+	g_clear_error (&error);
+}
+
+static gboolean
+complex_authorizer_refresh_authorization_finish (GDataAuthorizer *self, GAsyncResult *async_result, GError **error)
+{
+	/* Check the inputs */
+	g_assert (GDATA_IS_AUTHORIZER (self));
+	g_assert (G_IS_ASYNC_RESULT (async_result));
+	g_assert (error == NULL || *error == NULL);
+
+	g_assert (g_simple_async_result_is_valid (async_result, G_OBJECT (self), complex_authorizer_refresh_authorization_async) == TRUE);
+
+	/* Assert that the async function's already been called (once) */
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (self), "async-counter")), ==, 1);
+
+	/* Increment the finish counter on the authorizer so we know if this function's been called more than once */
+	g_object_set_data (G_OBJECT (self), "finish-counter",
+	                   GUINT_TO_POINTER (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (self), "finish-counter")) + 1));
+
+	return (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (async_result), error) == FALSE) ? TRUE : FALSE;
+}
+
+static void
+complex_authorizer_authorizer_init (GDataAuthorizerInterface *iface)
+{
+	/* Use the same implementation as SimpleAuthorizer/NormalAuthorizer for process_request(), is_authorized_for_domain() and
+	 * refresh_authorization(). */
+	iface->process_request = simple_authorizer_process_request;
+	iface->is_authorized_for_domain = simple_authorizer_is_authorized_for_domain;
+	iface->refresh_authorization = normal_authorizer_refresh_authorization;
+
+	/* Unlike NormalAuthorizer, also implement the async versions of refresh_authorization(). */
+	iface->refresh_authorization_async = complex_authorizer_refresh_authorization_async;
+	iface->refresh_authorization_finish = complex_authorizer_refresh_authorization_finish;
+}
+
+/* Testing data for generic GDataAuthorizer interface tests */
+typedef struct {
+	GDataAuthorizer *authorizer;
+} AuthorizerData;
+
+static void
+set_up_simple_authorizer_data (AuthorizerData *data, gconstpointer user_data)
+{
+	data->authorizer = GDATA_AUTHORIZER (g_object_new (TYPE_SIMPLE_AUTHORIZER, NULL));
+}
+
+static void
+set_up_normal_authorizer_data (AuthorizerData *data, gconstpointer user_data)
+{
+	data->authorizer = GDATA_AUTHORIZER (g_object_new (TYPE_NORMAL_AUTHORIZER, NULL));
+}
+
+static void
+set_up_complex_authorizer_data (AuthorizerData *data, gconstpointer user_data)
+{
+	data->authorizer = GDATA_AUTHORIZER (g_object_new (TYPE_COMPLEX_AUTHORIZER, NULL));
+}
+
+static void
+tear_down_authorizer_data (AuthorizerData *data, gconstpointer user_data)
+{
+	g_object_unref (data->authorizer);
+}
+
+/* Test that calling gdata_authorizer_process_request() happens correctly */
+static void
+test_authorizer_process_request (AuthorizerData *data, gconstpointer user_data)
+{
+	SoupMessage *message;
+
+	message = soup_message_new (SOUP_METHOD_GET, "http://example.com/";);
+
+	gdata_authorizer_process_request (data->authorizer, test_domain1, message);
+	g_assert_cmpstr (soup_message_headers_get_one (message->request_headers, "process_request"), ==, "1");
+	g_assert (soup_message_headers_get_one (message->request_headers, "process_request_null") == NULL);
+
+	g_object_unref (message);
+}
+
+/* Test that calling gdata_authorizer_process_request() happens correctly for a NULL domain */
+static void
+test_authorizer_process_request_null (AuthorizerData *data, gconstpointer user_data)
+{
+	SoupMessage *message;
+
+	message = soup_message_new (SOUP_METHOD_GET, "http://example.com/";);
+
+	gdata_authorizer_process_request (data->authorizer, NULL, message);
+	g_assert (soup_message_headers_get_one (message->request_headers, "process_request") == NULL);
+	g_assert_cmpstr (soup_message_headers_get_one (message->request_headers, "process_request_null"), ==, "1");
+
+	g_object_unref (message);
+}
+
+/* Test that calling gdata_authorizer_is_authorized_for_domain() happens correctly */
+static void
+test_authorizer_is_authorized_for_domain (AuthorizerData *data, gconstpointer user_data)
+{
+	/* Set some counters on the test domains to check that the interface implementation is only called once per domain */
+	g_object_set_data (G_OBJECT (test_domain1), "counter", GUINT_TO_POINTER (0));
+	g_object_set_data (G_OBJECT (test_domain2), "counter", GUINT_TO_POINTER (0));
+
+	g_assert (gdata_authorizer_is_authorized_for_domain (data->authorizer, test_domain1) == TRUE);
+	g_assert (gdata_authorizer_is_authorized_for_domain (data->authorizer, test_domain2) == FALSE);
+
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (test_domain1), "counter")), ==, 1);
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (test_domain2), "counter")), ==, 1);
+}
+
+/* Test that calling gdata_authorizer_is_authorized_for_domain() with a NULL authorizer always returns FALSE */
+static void
+test_authorizer_is_authorized_for_domain_null (AuthorizerData *data, gconstpointer user_data)
+{
+	g_assert (gdata_authorizer_is_authorized_for_domain (NULL, test_domain1) == FALSE);
+	g_assert (gdata_authorizer_is_authorized_for_domain (NULL, test_domain2) == FALSE);
+}
+
+/* Test that calling refresh_authorization() on an authorizer which implements it returns TRUE without error, and only calls the implementation
+ * once */
+static void
+test_authorizer_refresh_authorization (AuthorizerData *data, gconstpointer user_data)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	/* Set a counter on the authoriser to check that the interface implementation is only called once */
+	g_object_set_data (G_OBJECT (data->authorizer), "counter", GUINT_TO_POINTER (0));
+
+	success = gdata_authorizer_refresh_authorization (data->authorizer, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success == TRUE);
+	g_clear_error (&error);
+
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "counter")), ==, 1);
+}
+
+/* Test that calling refresh_authorization() on an authorizer which implements it with errors returns FALSE with an error, and only calls the
+ * implementation once */
+static void
+test_authorizer_refresh_authorization_error (AuthorizerData *data, gconstpointer user_data)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	/* Set a counter on the authoriser to check that the interface implementation is only called once */
+	g_object_set_data (G_OBJECT (data->authorizer), "counter", GUINT_TO_POINTER (0));
+
+	/* Set a flag on the authoriser to make the NormalAuthorizer implementation return an error for refresh_authorization() */
+	g_object_set_data (G_OBJECT (data->authorizer), "error", GUINT_TO_POINTER (TRUE));
+
+	success = gdata_authorizer_refresh_authorization (data->authorizer, NULL, &error);
+	g_assert_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "counter")), ==, 1);
+}
+
+/* Test that calling refresh_authorization() on an authorizer which doesn't implement it returns FALSE without an error */
+static void
+test_authorizer_refresh_authorization_unimplemented (AuthorizerData *data, gconstpointer user_data)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_authorizer_refresh_authorization (data->authorizer, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+}
+
+/* Test that calling refresh_authorization() on an authorizer which doesn't implement it, then cancelling the call returns FALSE without an error
+ * (not even a cancellation error) */
+static void
+test_authorizer_refresh_authorization_cancellation_unimplemented (AuthorizerData *data, gconstpointer user_data)
+{
+	GCancellable *cancellable;
+	gboolean success;
+	GError *error = NULL;
+
+	cancellable = g_cancellable_new ();
+	g_cancellable_cancel (cancellable);
+
+	success = gdata_authorizer_refresh_authorization (data->authorizer, cancellable, &error);
+	g_assert_no_error (error);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+
+	g_object_unref (cancellable);
+}
+
+/* Set of standard async callback functions for refresh_authorization_async() which check various combinations of success and error value */
+static void
+test_authorizer_refresh_authorization_async_success_no_error_cb (GDataAuthorizer *authorizer, GAsyncResult *async_result, GMainLoop *main_loop)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_authorizer_refresh_authorization_finish (authorizer, async_result, &error);
+	g_assert_no_error (error);
+	g_assert (success == TRUE);
+	g_clear_error (&error);
+
+	g_main_loop_quit (main_loop);
+}
+
+static void
+test_authorizer_refresh_authorization_async_failure_no_error_cb (GDataAuthorizer *authorizer, GAsyncResult *async_result, GMainLoop *main_loop)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_authorizer_refresh_authorization_finish (authorizer, async_result, &error);
+	g_assert_no_error (error);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+
+	g_main_loop_quit (main_loop);
+}
+
+static void
+test_authorizer_refresh_authorization_async_network_error_cb (GDataAuthorizer *authorizer, GAsyncResult *async_result, GMainLoop *main_loop)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_authorizer_refresh_authorization_finish (authorizer, async_result, &error);
+	g_assert_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NETWORK_ERROR);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+
+	g_main_loop_quit (main_loop);
+}
+
+static void
+test_authorizer_refresh_authorization_async_protocol_error_cb (GDataAuthorizer *authorizer, GAsyncResult *async_result, GMainLoop *main_loop)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_authorizer_refresh_authorization_finish (authorizer, async_result, &error);
+	g_assert_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+
+	g_main_loop_quit (main_loop);
+}
+
+static void
+test_authorizer_refresh_authorization_async_cancelled_error_cb (GDataAuthorizer *authorizer, GAsyncResult *async_result, GMainLoop *main_loop)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_authorizer_refresh_authorization_finish (authorizer, async_result, &error);
+	g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+
+	g_main_loop_quit (main_loop);
+}
+
+/* Test that calling refresh_authorization_async() on an authorizer which implements it returns TRUE without an error */
+static void
+test_authorizer_refresh_authorization_async (AuthorizerData *data, gconstpointer user_data)
+{
+	GMainLoop *main_loop;
+
+	/* Set counters on the authoriser to check that the interface implementations are only called once */
+	g_object_set_data (G_OBJECT (data->authorizer), "counter", GUINT_TO_POINTER (0));
+	g_object_set_data (G_OBJECT (data->authorizer), "async-counter", GUINT_TO_POINTER (0));
+	g_object_set_data (G_OBJECT (data->authorizer), "finish-counter", GUINT_TO_POINTER (0));
+
+	main_loop = g_main_loop_new (NULL, FALSE);
+
+	gdata_authorizer_refresh_authorization_async (data->authorizer, NULL,
+	                                              (GAsyncReadyCallback) test_authorizer_refresh_authorization_async_success_no_error_cb,
+	                                              main_loop);
+
+	g_main_loop_run (main_loop);
+
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "counter")), ==, 0);
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "async-counter")), ==, 1);
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "finish-counter")), ==, 1);
+
+	g_main_loop_unref (main_loop);
+}
+
+/* Test that calling refresh_authorization_async() on an authorizer which implements it with an error returns FALSE with the appropriate error */
+static void
+test_authorizer_refresh_authorization_async_error (AuthorizerData *data, gconstpointer user_data)
+{
+	GMainLoop *main_loop;
+
+	/* Set counters on the authoriser to check that the interface implementations are only called once */
+	g_object_set_data (G_OBJECT (data->authorizer), "counter", GUINT_TO_POINTER (0));
+	g_object_set_data (G_OBJECT (data->authorizer), "async-counter", GUINT_TO_POINTER (0));
+	g_object_set_data (G_OBJECT (data->authorizer), "finish-counter", GUINT_TO_POINTER (0));
+
+	/* Set a flag on the authoriser to make the ComplexAuthorizer implementation return an error for refresh_authorization_async() */
+	g_object_set_data (G_OBJECT (data->authorizer), "error", GUINT_TO_POINTER (TRUE));
+
+	main_loop = g_main_loop_new (NULL, FALSE);
+
+	gdata_authorizer_refresh_authorization_async (data->authorizer, NULL,
+	                                              (GAsyncReadyCallback) test_authorizer_refresh_authorization_async_network_error_cb, main_loop);
+
+	g_main_loop_run (main_loop);
+
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "counter")), ==, 0);
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "async-counter")), ==, 1);
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "finish-counter")), ==, 1);
+
+	g_main_loop_unref (main_loop);
+}
+
+/* Test that calling refresh_authorization_async() on an authorizer which implements it, then cancelling the call returns FALSE with a cancellation
+ * error */
+static void
+test_authorizer_refresh_authorization_async_cancellation (AuthorizerData *data, gconstpointer user_data)
+{
+	GCancellable *cancellable;
+	GMainLoop *main_loop;
+
+	/* Set counters on the authoriser to check that the interface implementations are only called once */
+	g_object_set_data (G_OBJECT (data->authorizer), "counter", GUINT_TO_POINTER (0));
+	g_object_set_data (G_OBJECT (data->authorizer), "async-counter", GUINT_TO_POINTER (0));
+	g_object_set_data (G_OBJECT (data->authorizer), "finish-counter", GUINT_TO_POINTER (0));
+
+	main_loop = g_main_loop_new (NULL, FALSE);
+
+	cancellable = g_cancellable_new ();
+	g_cancellable_cancel (cancellable);
+
+	gdata_authorizer_refresh_authorization_async (data->authorizer, cancellable,
+	                                              (GAsyncReadyCallback) test_authorizer_refresh_authorization_async_cancelled_error_cb,
+	                                              main_loop);
+
+	g_main_loop_run (main_loop);
+
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "counter")), ==, 0);
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "async-counter")), ==, 1);
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "finish-counter")), ==, 1);
+
+	g_object_unref (cancellable);
+	g_main_loop_unref (main_loop);
+}
+
+/* Test that calling refresh_authorization_async() on an authorizer which doesn't implement it, but does implement refresh_authorization(), returns
+ * TRUE without an error */
+static void
+test_authorizer_refresh_authorization_async_simulated (AuthorizerData *data, gconstpointer user_data)
+{
+	GMainLoop *main_loop;
+
+	/* Set a counter on the authoriser to check that the interface implementation is only called once */
+	g_object_set_data (G_OBJECT (data->authorizer), "counter", GUINT_TO_POINTER (0));
+
+	main_loop = g_main_loop_new (NULL, FALSE);
+
+	gdata_authorizer_refresh_authorization_async (data->authorizer, NULL,
+	                                              (GAsyncReadyCallback) test_authorizer_refresh_authorization_async_success_no_error_cb,
+	                                              main_loop);
+
+	g_main_loop_run (main_loop);
+
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "counter")), ==, 1);
+
+	g_main_loop_unref (main_loop);
+}
+
+/* Test that calling refresh_authorization_async() on an authorizer which doesn't implement it, but does implement refresh_authorization() with an
+ * error, returns FALSE with the appropriate error */
+static void
+test_authorizer_refresh_authorization_async_error_simulated (AuthorizerData *data, gconstpointer user_data)
+{
+	GMainLoop *main_loop;
+
+	/* Set a counter on the authoriser to check that the interface implementation is only called once */
+	g_object_set_data (G_OBJECT (data->authorizer), "counter", GUINT_TO_POINTER (0));
+
+	/* Set a flag on the authoriser to make the NormalAuthorizer implementation return an error for refresh_authorization() */
+	g_object_set_data (G_OBJECT (data->authorizer), "error", GUINT_TO_POINTER (TRUE));
+
+	main_loop = g_main_loop_new (NULL, FALSE);
+
+	gdata_authorizer_refresh_authorization_async (data->authorizer, NULL,
+	                                              (GAsyncReadyCallback) test_authorizer_refresh_authorization_async_protocol_error_cb, main_loop);
+
+	g_main_loop_run (main_loop);
+
+	g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->authorizer), "counter")), ==, 1);
+
+	g_main_loop_unref (main_loop);
+}
+
+/* Test that calling refresh_authorization_async() on an authorizer which doesn't implement it, but does implement refresh_authorization(), then
+ * cancelling the call returns FALSE with a cancellation error */
+static void
+test_authorizer_refresh_authorization_async_cancellation_simulated (AuthorizerData *data, gconstpointer user_data)
+{
+	GCancellable *cancellable;
+	GMainLoop *main_loop;
+
+	main_loop = g_main_loop_new (NULL, FALSE);
+
+	cancellable = g_cancellable_new ();
+	g_cancellable_cancel (cancellable);
+
+	/* Note we don't count how many times the implementation of refresh_authorization() is called, since cancellation can legitimately be
+	 * handled by the gdata_authorizer_refresh_authorization_async() code before refresh_authorization() is ever called. */
+	gdata_authorizer_refresh_authorization_async (data->authorizer, cancellable,
+	                                              (GAsyncReadyCallback) test_authorizer_refresh_authorization_async_cancelled_error_cb,
+	                                              main_loop);
+
+	g_main_loop_run (main_loop);
+
+	g_object_unref (cancellable);
+	g_main_loop_unref (main_loop);
+}
+
+/* Test that calling refresh_authorization_async() on an authorizer which doesn't implement it returns FALSE without an error */
+static void
+test_authorizer_refresh_authorization_async_unimplemented (AuthorizerData *data, gconstpointer user_data)
+{
+	GMainLoop *main_loop;
+
+	main_loop = g_main_loop_new (NULL, FALSE);
+
+	gdata_authorizer_refresh_authorization_async (data->authorizer, NULL,
+	                                              (GAsyncReadyCallback) test_authorizer_refresh_authorization_async_failure_no_error_cb,
+	                                              main_loop);
+
+	g_main_loop_run (main_loop);
+
+	g_main_loop_unref (main_loop);
+}
+
+/* Test that calling refresh_authorization_async() on an authorizer which doesn't implement it, then cancelling the call returns FALSE without an
+ * error (not even a cancellation error) */
+static void
+test_authorizer_refresh_authorization_async_cancellation_unimplemented (AuthorizerData *data, gconstpointer user_data)
+{
+	GCancellable *cancellable;
+	GMainLoop *main_loop;
+
+	main_loop = g_main_loop_new (NULL, FALSE);
+
+	cancellable = g_cancellable_new ();
+	g_cancellable_cancel (cancellable);
+
+	gdata_authorizer_refresh_authorization_async (data->authorizer, cancellable,
+	                                              (GAsyncReadyCallback) test_authorizer_refresh_authorization_async_failure_no_error_cb,
+	                                              main_loop);
+
+	g_main_loop_run (main_loop);
+
+	g_object_unref (cancellable);
+	g_main_loop_unref (main_loop);
+}
+
+int
+main (int argc, char *argv[])
+{
+	int retval;
+
+	gdata_test_init (argc, argv);
+
+	/* Note: This is not how GDataAuthorizationDomains are meant to be constructed. Client code is not expected to do this. */
+	test_domain1 = g_object_new (GDATA_TYPE_AUTHORIZATION_DOMAIN,
+	                             "service-name", "service-name1",
+	                             "scope", "scope1",
+	                             NULL);
+	test_domain2 = g_object_new (GDATA_TYPE_AUTHORIZATION_DOMAIN,
+	                             "service-name", "service-name2",
+	                             "scope", "scope2",
+	                             NULL);
+
+	/* GDataAuthorizationDomain tests */
+	g_test_add_func ("/authorization-domain/properties", test_authorization_domain_properties);
+
+	/* GDataAuthorizer interface tests */
+	g_test_add ("/authorizer/process-request", AuthorizerData, NULL, set_up_simple_authorizer_data, test_authorizer_process_request,
+	            tear_down_authorizer_data);
+	g_test_add ("/authorizer/process-request/null", AuthorizerData, NULL, set_up_simple_authorizer_data, test_authorizer_process_request_null,
+	            tear_down_authorizer_data);
+	g_test_add ("/authorizer/is-authorized-for-domain", AuthorizerData, NULL, set_up_simple_authorizer_data,
+	            test_authorizer_is_authorized_for_domain, tear_down_authorizer_data);
+	g_test_add ("/authorizer/is-authorized-for-domain/null", AuthorizerData, NULL, set_up_simple_authorizer_data,
+	            test_authorizer_is_authorized_for_domain_null, tear_down_authorizer_data);
+	g_test_add ("/authorizer/refresh-authorization", AuthorizerData, NULL, set_up_normal_authorizer_data,
+	            test_authorizer_refresh_authorization, tear_down_authorizer_data);
+	g_test_add ("/authorizer/refresh-authorization/error", AuthorizerData, NULL, set_up_normal_authorizer_data,
+	            test_authorizer_refresh_authorization_error, tear_down_authorizer_data);
+	g_test_add ("/authorizer/refresh-authorization/unimplemented", AuthorizerData, NULL, set_up_simple_authorizer_data,
+	            test_authorizer_refresh_authorization_unimplemented, tear_down_authorizer_data);
+	g_test_add ("/authorizer/refresh-authorization/cancellation/unimplemented", AuthorizerData, NULL, set_up_simple_authorizer_data,
+	            test_authorizer_refresh_authorization_cancellation_unimplemented, tear_down_authorizer_data);
+	g_test_add ("/authorizer/refresh-authorization/async", AuthorizerData, NULL, set_up_complex_authorizer_data,
+	            test_authorizer_refresh_authorization_async, tear_down_authorizer_data);
+	g_test_add ("/authorizer/refresh-authorization/async/error", AuthorizerData, NULL, set_up_complex_authorizer_data,
+	            test_authorizer_refresh_authorization_async_error, tear_down_authorizer_data);
+	g_test_add ("/authorizer/refresh-authorization/async/cancellation", AuthorizerData, NULL, set_up_complex_authorizer_data,
+	            test_authorizer_refresh_authorization_async_cancellation, tear_down_authorizer_data);
+	g_test_add ("/authorizer/refresh-authorization/async/simulated", AuthorizerData, NULL, set_up_normal_authorizer_data,
+	            test_authorizer_refresh_authorization_async_simulated, tear_down_authorizer_data);
+	g_test_add ("/authorizer/refresh-authorization/async/error/simulated", AuthorizerData, NULL, set_up_normal_authorizer_data,
+	            test_authorizer_refresh_authorization_async_error_simulated, tear_down_authorizer_data);
+	g_test_add ("/authorizer/refresh-authorization/async/cancellation/simulated", AuthorizerData, NULL, set_up_normal_authorizer_data,
+	            test_authorizer_refresh_authorization_async_cancellation_simulated, tear_down_authorizer_data);
+	g_test_add ("/authorizer/refresh-authorization/async/unimplemented", AuthorizerData, NULL, set_up_simple_authorizer_data,
+	            test_authorizer_refresh_authorization_async_unimplemented, tear_down_authorizer_data);
+	g_test_add ("/authorizer/refresh-authorization/async/cancellation/unimplemented", AuthorizerData, NULL, set_up_simple_authorizer_data,
+	            test_authorizer_refresh_authorization_async_cancellation_unimplemented, tear_down_authorizer_data);
+
+	retval = g_test_run ();
+
+	g_object_unref (test_domain2);
+	g_object_unref (test_domain1);
+
+	return retval;
+}
diff --git a/gdata/tests/calendar.c b/gdata/tests/calendar.c
index 8b2c346..3bb7b85 100644
--- a/gdata/tests/calendar.c
+++ b/gdata/tests/calendar.c
@@ -52,37 +52,37 @@ static void
 test_authentication (void)
 {
 	gboolean retval;
-	GDataService *service;
+	GDataClientLoginAuthorizer *authorizer;
 	GError *error = NULL;
 
-	/* Create a service */
-	service = GDATA_SERVICE (gdata_calendar_service_new (CLIENT_ID));
+	/* Create an authorizer */
+	authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_CALENDAR_SERVICE);
 
-	g_assert (service != NULL);
-	g_assert (GDATA_IS_SERVICE (service));
-	g_assert_cmpstr (gdata_service_get_client_id (service), ==, CLIENT_ID);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (authorizer), ==, CLIENT_ID);
 
 	/* Log in */
-	retval = gdata_service_authenticate (service, USERNAME, PASSWORD, NULL, &error);
+	retval = gdata_client_login_authorizer_authenticate (authorizer, USERNAME, PASSWORD, NULL, &error);
 	g_assert_no_error (error);
 	g_assert (retval == TRUE);
 	g_clear_error (&error);
 
 	/* Check all is as it should be */
-	g_assert (gdata_service_is_authenticated (service) == TRUE);
-	g_assert_cmpstr (gdata_service_get_username (service), ==, USERNAME);
-	g_assert_cmpstr (gdata_service_get_password (service), ==, PASSWORD);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, USERNAME);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, PASSWORD);
 
-	g_object_unref (service);
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	                                                     gdata_calendar_service_get_primary_authorization_domain ()) == TRUE);
+
+	g_object_unref (authorizer);
 }
 
 static void
-test_authentication_async_cb (GDataService *service, GAsyncResult *async_result, GMainLoop *main_loop)
+test_authentication_async_cb (GDataClientLoginAuthorizer *authorizer, GAsyncResult *async_result, GMainLoop *main_loop)
 {
 	gboolean retval;
 	GError *error = NULL;
 
-	retval = gdata_service_authenticate_finish (service, async_result, &error);
+	retval = gdata_client_login_authorizer_authenticate_finish (authorizer, async_result, &error);
 	g_assert_no_error (error);
 	g_assert (retval == TRUE);
 	g_clear_error (&error);
@@ -90,29 +90,32 @@ test_authentication_async_cb (GDataService *service, GAsyncResult *async_result,
 	g_main_loop_quit (main_loop);
 
 	/* Check all is as it should be */
-	g_assert (gdata_service_is_authenticated (service) == TRUE);
-	g_assert_cmpstr (gdata_service_get_username (service), ==, USERNAME);
-	g_assert_cmpstr (gdata_service_get_password (service), ==, PASSWORD);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, USERNAME);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, PASSWORD);
+
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	                                                     gdata_calendar_service_get_primary_authorization_domain ()) == TRUE);
 }
 
 static void
 test_authentication_async (void)
 {
-	GDataService *service;
-	GMainLoop *main_loop = g_main_loop_new (NULL, TRUE);
+	GMainLoop *main_loop;
+	GDataClientLoginAuthorizer *authorizer;
 
-	/* Create a service */
-	service = GDATA_SERVICE (gdata_calendar_service_new (CLIENT_ID));
+	/* Create an authorizer */
+	authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_CALENDAR_SERVICE);
 
-	g_assert (service != NULL);
-	g_assert (GDATA_IS_SERVICE (service));
+	g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (authorizer), ==, CLIENT_ID);
 
-	gdata_service_authenticate_async (service, USERNAME, PASSWORD, NULL, (GAsyncReadyCallback) test_authentication_async_cb, main_loop);
+	main_loop = g_main_loop_new (NULL, TRUE);
+	gdata_client_login_authorizer_authenticate_async (authorizer, USERNAME, PASSWORD, NULL,
+	                                                  (GAsyncReadyCallback) test_authentication_async_cb, main_loop);
 
 	g_main_loop_run (main_loop);
-	g_main_loop_unref (main_loop);
 
-	g_object_unref (service);
+	g_main_loop_unref (main_loop);
+	g_object_unref (authorizer);
 }
 
 static void
@@ -741,7 +744,8 @@ test_acls_insert_rule (gconstpointer service)
 	_link = gdata_entry_look_up_link (GDATA_ENTRY (calendar), GDATA_LINK_ACCESS_CONTROL_LIST);
 	g_assert (_link != NULL);
 
-	new_rule = GDATA_ACCESS_RULE (gdata_service_insert_entry (GDATA_SERVICE (service), gdata_link_get_uri (_link), GDATA_ENTRY (rule),
+	new_rule = GDATA_ACCESS_RULE (gdata_service_insert_entry (GDATA_SERVICE (service), gdata_calendar_service_get_primary_authorization_domain (),
+	                                                          gdata_link_get_uri (_link), GDATA_ENTRY (rule),
 	                                                          NULL, &error));
 	g_assert_no_error (error);
 	g_assert (GDATA_IS_ACCESS_RULE (new_rule));
@@ -808,7 +812,8 @@ test_acls_update_rule (gconstpointer service)
 	g_assert_cmpstr (gdata_access_rule_get_role (rule), ==, GDATA_CALENDAR_ACCESS_ROLE_READ);
 
 	/* Send the update to the server */
-	new_rule = GDATA_ACCESS_RULE (gdata_service_update_entry (GDATA_SERVICE (service), GDATA_ENTRY (rule), NULL, &error));
+	new_rule = GDATA_ACCESS_RULE (gdata_service_update_entry (GDATA_SERVICE (service), gdata_calendar_service_get_primary_authorization_domain (),
+	                                                          GDATA_ENTRY (rule), NULL, &error));
 	g_assert_no_error (error);
 	g_assert (GDATA_IS_ACCESS_RULE (new_rule));
 	g_clear_error (&error);
@@ -860,7 +865,8 @@ test_acls_delete_rule (gconstpointer service)
 	g_object_unref (feed);
 
 	/* Delete the rule */
-	success = gdata_service_delete_entry (GDATA_SERVICE (service), GDATA_ENTRY (rule), NULL, &error);
+	success = gdata_service_delete_entry (GDATA_SERVICE (service), gdata_calendar_service_get_primary_authorization_domain (),
+	                                      GDATA_ENTRY (rule), NULL, &error);
 	g_assert_no_error (error);
 	g_assert (success == TRUE);
 	g_clear_error (&error);
@@ -884,7 +890,8 @@ test_batch (gconstpointer service)
 	calendar = get_calendar (service, &error);
 
 	/* Here we hardcode the feed URI, but it should really be extracted from an event feed, as the GDATA_LINK_BATCH link */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://www.google.com/calendar/feeds/default/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_calendar_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/calendar/feeds/default/private/full/batch";);
 
 	/* Check the properties of the operation */
 	g_assert (gdata_batch_operation_get_service (operation) == service);
@@ -917,7 +924,8 @@ test_batch (gconstpointer service)
 	event2 = gdata_calendar_event_new (NULL);
 	gdata_entry_set_title (GDATA_ENTRY (event2), "Cow Lunch");
 
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://www.google.com/calendar/feeds/default/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_calendar_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/calendar/feeds/default/private/full/batch";);
 	op_id = gdata_test_batch_operation_insertion (operation, GDATA_ENTRY (event2), &inserted_entry2, NULL);
 	op_id2 = gdata_test_batch_operation_query (operation, gdata_entry_get_id (inserted_entry), GDATA_TYPE_CALENDAR_EVENT, inserted_entry, NULL,
 	                                           NULL);
@@ -934,7 +942,8 @@ test_batch (gconstpointer service)
 	gdata_entry_set_title (inserted_entry2, "Toby");
 	event3 = gdata_calendar_event_new ("foobar");
 
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://www.google.com/calendar/feeds/default/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_calendar_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/calendar/feeds/default/private/full/batch";);
 	op_id = gdata_test_batch_operation_deletion (operation, inserted_entry, NULL);
 	op_id2 = gdata_test_batch_operation_deletion (operation, GDATA_ENTRY (event3), &entry_error);
 	op_id3 = gdata_test_batch_operation_update (operation, inserted_entry2, &inserted_entry3, NULL);
@@ -969,7 +978,8 @@ test_batch (gconstpointer service)
 	g_object_unref (inserted_entry2);
 
 	/* Run a final batch operation to delete the second entry */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://www.google.com/calendar/feeds/default/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_calendar_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/calendar/feeds/default/private/full/batch";);
 	gdata_test_batch_operation_deletion (operation, inserted_entry3, NULL);
 	g_assert (gdata_test_batch_operation_run (operation, NULL, &error) == TRUE);
 	g_assert_no_error (error);
@@ -1024,7 +1034,8 @@ test_batch_async (BatchAsyncData *data, gconstpointer service)
 	GMainLoop *main_loop;
 
 	/* Run an async query operation on the event */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://www.google.com/calendar/feeds/default/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_calendar_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/calendar/feeds/default/private/full/batch";);
 	gdata_test_batch_operation_query (operation, gdata_entry_get_id (GDATA_ENTRY (data->new_event)), GDATA_TYPE_CALENDAR_EVENT,
 	                                  GDATA_ENTRY (data->new_event), NULL, NULL);
 
@@ -1061,7 +1072,8 @@ test_batch_async_cancellation (BatchAsyncData *data, gconstpointer service)
 	GError *error = NULL;
 
 	/* Run an async query operation on the event */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://www.google.com/calendar/feeds/default/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_calendar_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/calendar/feeds/default/private/full/batch";);
 	gdata_test_batch_operation_query (operation, gdata_entry_get_id (GDATA_ENTRY (data->new_event)), GDATA_TYPE_CALENDAR_EVENT,
 	                                  GDATA_ENTRY (data->new_event), NULL, &error);
 
@@ -1087,7 +1099,8 @@ teardown_batch_async (BatchAsyncData *data, gconstpointer service)
 	GError *error = NULL;
 
 	/* Delete the event */
-	g_assert (gdata_service_delete_entry (GDATA_SERVICE (service), GDATA_ENTRY (data->new_event), NULL, &error) == TRUE);
+	g_assert (gdata_service_delete_entry (GDATA_SERVICE (service), gdata_calendar_service_get_primary_authorization_domain (),
+	                                      GDATA_ENTRY (data->new_event), NULL, &error) == TRUE);
 	g_assert_no_error (error);
 	g_clear_error (&error);
 
@@ -1098,13 +1111,16 @@ int
 main (int argc, char *argv[])
 {
 	gint retval;
+	GDataAuthorizer *authorizer = NULL;
 	GDataService *service = NULL;
 
 	gdata_test_init (argc, argv);
 
 	if (gdata_test_internet () == TRUE) {
-		service = GDATA_SERVICE (gdata_calendar_service_new (CLIENT_ID));
-		gdata_service_authenticate (service, USERNAME, PASSWORD, NULL, NULL);
+		authorizer = GDATA_AUTHORIZER (gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_CALENDAR_SERVICE));
+		gdata_client_login_authorizer_authenticate (GDATA_CLIENT_LOGIN_AUTHORIZER (authorizer), USERNAME, PASSWORD, NULL, NULL);
+
+		service = GDATA_SERVICE (gdata_calendar_service_new (authorizer));
 
 		g_test_add_func ("/calendar/authentication", test_authentication);
 		g_test_add_func ("/calendar/authentication_async", test_authentication_async);
diff --git a/gdata/tests/client-login-authorizer.c b/gdata/tests/client-login-authorizer.c
new file mode 100644
index 0000000..06b6001
--- /dev/null
+++ b/gdata/tests/client-login-authorizer.c
@@ -0,0 +1,745 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011 <philip tecnocode co uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <gdata/gdata.h>
+
+#include "common.h"
+
+static GThread *main_thread = NULL;
+
+static void
+test_client_login_authorizer_constructor (void)
+{
+	GDataClientLoginAuthorizer *authorizer;
+
+	authorizer = gdata_client_login_authorizer_new ("client-id", GDATA_TYPE_YOUTUBE_SERVICE);
+
+	g_assert (authorizer != NULL);
+	g_assert (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (authorizer));
+	g_assert (GDATA_IS_AUTHORIZER (authorizer));
+
+	g_object_unref (authorizer);
+}
+
+static void
+test_client_login_authorizer_constructor_for_domains (void)
+{
+	GDataClientLoginAuthorizer *authorizer;
+	GDataAuthorizationDomain *domain;
+	GList *domains;
+
+	/* Try with standard domains first */
+	domains = gdata_service_get_authorization_domains (GDATA_TYPE_YOUTUBE_SERVICE);
+	authorizer = gdata_client_login_authorizer_new_for_authorization_domains ("client-id", domains);
+	g_list_free (domains);
+
+	g_assert (authorizer != NULL);
+	g_assert (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (authorizer));
+	g_assert (GDATA_IS_AUTHORIZER (authorizer));
+
+	g_object_unref (authorizer);
+
+	/* Try again with a custom domain. Note that, as in test_authorization_domain_properties() this should not normally happen in client code. */
+	domain = GDATA_AUTHORIZATION_DOMAIN (g_object_new (GDATA_TYPE_AUTHORIZATION_DOMAIN,
+	                                                   "service-name", "test",
+	                                                   "scope", "test",
+	                                                   NULL));
+
+	domains = g_list_prepend (NULL, domain);
+	authorizer = gdata_client_login_authorizer_new_for_authorization_domains ("client-id", domains);
+	g_list_free (domains);
+
+	g_assert (authorizer != NULL);
+	g_assert (GDATA_IS_CLIENT_LOGIN_AUTHORIZER (authorizer));
+	g_assert (GDATA_IS_AUTHORIZER (authorizer));
+
+	g_object_unref (authorizer);
+	g_object_unref (domain);
+}
+
+typedef struct {
+	GDataClientLoginAuthorizer *authorizer;
+
+	guint proxy_uri_notification_count;
+	gulong proxy_uri_signal_handler;
+	guint timeout_notification_count;
+	gulong timeout_signal_handler;
+	guint username_notification_count;
+	gulong username_signal_handler;
+	guint password_notification_count;
+	gulong password_signal_handler;
+} ClientLoginAuthorizerData;
+
+/* Used to count that exactly the right number of notify signals are emitted when setting properties */
+static void
+notify_cb (GObject *object, GParamSpec *pspec, guint *notification_count)
+{
+	/* Check we're running in the main thread */
+	g_assert (g_thread_self () == main_thread);
+
+	/* Increment the notification count */
+	*notification_count = *notification_count + 1;
+}
+
+static void
+connect_to_client_login_authorizer (ClientLoginAuthorizerData *data)
+{
+	/* Connect to notifications from the object to verify they're only emitted the correct number of times */
+	data->proxy_uri_signal_handler = g_signal_connect (data->authorizer, "notify::proxy-uri", (GCallback) notify_cb,
+	                                                   &(data->proxy_uri_notification_count));
+	data->timeout_signal_handler = g_signal_connect (data->authorizer, "notify::timeout", (GCallback) notify_cb,
+	                                                 &(data->timeout_notification_count));
+	data->username_signal_handler = g_signal_connect (data->authorizer, "notify::username", (GCallback) notify_cb,
+	                                                  &(data->username_notification_count));
+	data->password_signal_handler = g_signal_connect (data->authorizer, "notify::password", (GCallback) notify_cb,
+	                                                  &(data->password_notification_count));
+}
+
+static void
+set_up_client_login_authorizer_data (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	data->authorizer = gdata_client_login_authorizer_new ("client-id", GDATA_TYPE_YOUTUBE_SERVICE);
+	connect_to_client_login_authorizer (data);
+}
+
+static void
+set_up_client_login_authorizer_data_multiple_domains (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	GList *authorization_domains = NULL;
+
+	authorization_domains = g_list_prepend (authorization_domains, gdata_youtube_service_get_primary_authorization_domain ());
+	authorization_domains = g_list_prepend (authorization_domains, gdata_picasaweb_service_get_primary_authorization_domain ());
+	data->authorizer = gdata_client_login_authorizer_new_for_authorization_domains ("client-id", authorization_domains);
+	g_list_free (authorization_domains);
+
+	connect_to_client_login_authorizer (data);
+}
+
+static void
+set_up_client_login_authorizer_data_authenticated (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	data->authorizer = gdata_client_login_authorizer_new ("client-id", GDATA_TYPE_YOUTUBE_SERVICE);
+	g_assert (gdata_client_login_authorizer_authenticate (data->authorizer, USERNAME, PASSWORD, NULL, NULL) == TRUE);
+	connect_to_client_login_authorizer (data);
+}
+
+static void
+tear_down_client_login_authorizer_data (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	/* Clean up signal handlers */
+	g_signal_handler_disconnect (data->authorizer, data->password_signal_handler);
+	g_signal_handler_disconnect (data->authorizer, data->username_signal_handler);
+	g_signal_handler_disconnect (data->authorizer, data->timeout_signal_handler);
+	g_signal_handler_disconnect (data->authorizer, data->proxy_uri_signal_handler);
+
+	g_object_unref (data->authorizer);
+}
+
+/* Test getting and setting the client-id property */
+static void
+test_client_login_authorizer_properties_client_id (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	gchar *client_id;
+
+	/* Verifying the normal state of the property in a newly-constructed instance of GDataClientLoginAuthorizer */
+	g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (data->authorizer), ==, "client-id");
+
+	g_object_get (data->authorizer, "client-id", &client_id, NULL);
+	g_assert_cmpstr (client_id, ==, "client-id");
+	g_free (client_id);
+}
+
+/* Test getting and setting the username property */
+static void
+test_client_login_authorizer_properties_username (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	gchar *username;
+
+	/* Verifying the normal state of the property in a newly-constructed instance of GDataClientLoginAuthorizer */
+	g_assert (gdata_client_login_authorizer_get_username (data->authorizer) == NULL);
+
+	g_object_get (data->authorizer, "username", &username, NULL);
+	g_assert (username == NULL);
+	g_free (username);
+}
+
+/* Test getting and setting the password property */
+static void
+test_client_login_authorizer_properties_password (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	gchar *password;
+
+	/* Verifying the normal state of the property in a newly-constructed instance of GDataClientLoginAuthorizer */
+	g_assert (gdata_client_login_authorizer_get_password (data->authorizer) == NULL);
+
+	g_object_get (data->authorizer, "password", &password, NULL);
+	g_assert (password == NULL);
+	g_free (password);
+}
+
+/* Test getting and setting the proxy-uri property */
+static void
+test_client_login_authorizer_properties_proxy_uri (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	SoupURI *proxy_uri, *new_proxy_uri;
+
+	/* Verifying the normal state of the property in a newly-constructed instance of GDataClientLoginAuthorizer */
+	g_assert (gdata_client_login_authorizer_get_proxy_uri (data->authorizer) == NULL);
+
+	g_object_get (data->authorizer, "proxy-uri", &proxy_uri, NULL);
+	g_assert (proxy_uri == NULL);
+
+	g_assert_cmpuint (data->proxy_uri_notification_count, ==, 0);
+
+	/* Check setting it works and emits a notification */
+	new_proxy_uri = soup_uri_new ("http://example.com/";);
+	gdata_client_login_authorizer_set_proxy_uri (data->authorizer, new_proxy_uri);
+
+	g_assert_cmpuint (data->proxy_uri_notification_count, ==, 1);
+
+	g_assert (gdata_client_login_authorizer_get_proxy_uri (data->authorizer) != NULL);
+	g_assert (soup_uri_equal (gdata_client_login_authorizer_get_proxy_uri (data->authorizer), new_proxy_uri) == TRUE);
+
+	g_object_get (data->authorizer, "proxy-uri", &proxy_uri, NULL);
+	g_assert (proxy_uri != NULL);
+	g_assert (soup_uri_equal (gdata_client_login_authorizer_get_proxy_uri (data->authorizer), new_proxy_uri) == TRUE);
+	soup_uri_free (proxy_uri);
+
+	soup_uri_free (new_proxy_uri);
+
+	/* Check setting it back to NULL works */
+	gdata_client_login_authorizer_set_proxy_uri (data->authorizer, NULL);
+
+	g_assert_cmpuint (data->proxy_uri_notification_count, ==, 2);
+
+	g_assert (gdata_client_login_authorizer_get_proxy_uri (data->authorizer) == NULL);
+
+	g_object_get (data->authorizer, "proxy-uri", &proxy_uri, NULL);
+	g_assert (proxy_uri == NULL);
+
+	/* Test that setting it using g_object_set() works */
+	new_proxy_uri = soup_uri_new ("http://example.com/";);
+	g_object_set (data->authorizer, "proxy-uri", new_proxy_uri, NULL);
+	soup_uri_free (new_proxy_uri);
+
+	g_assert (gdata_client_login_authorizer_get_proxy_uri (data->authorizer) != NULL);
+}
+
+/* Test getting and setting the timeout property */
+static void
+test_client_login_authorizer_properties_timeout (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	guint timeout;
+
+	/* Verifying the normal state of the property in a newly-constructed instance of GDataClientLoginAuthorizer */
+	g_assert_cmpuint (gdata_client_login_authorizer_get_timeout (data->authorizer), ==, 0);
+
+	g_object_get (data->authorizer, "timeout", &timeout, NULL);
+	g_assert_cmpuint (timeout, ==, 0);
+
+	g_assert_cmpuint (data->timeout_notification_count, ==, 0);
+
+	/* Check setting it works and emits a notification */
+	gdata_client_login_authorizer_set_timeout (data->authorizer, 30);
+
+	g_assert_cmpuint (data->timeout_notification_count, ==, 1);
+
+	g_assert_cmpuint (gdata_client_login_authorizer_get_timeout (data->authorizer), ==, 30);
+
+	g_object_get (data->authorizer, "timeout", &timeout, NULL);
+	g_assert_cmpuint (timeout, ==, 30);
+
+	/* Check setting it back to 0 works */
+	gdata_client_login_authorizer_set_timeout (data->authorizer, 0);
+
+	g_assert_cmpuint (data->timeout_notification_count, ==, 2);
+
+	g_assert_cmpuint (gdata_client_login_authorizer_get_timeout (data->authorizer), ==, 0);
+
+	g_object_get (data->authorizer, "timeout", &timeout, NULL);
+	g_assert_cmpuint (timeout, ==, 0);
+
+	/* Test that setting it using g_object_set() works */
+	g_object_set (data->authorizer, "timeout", 15, NULL);
+	g_assert_cmpuint (gdata_client_login_authorizer_get_timeout (data->authorizer), ==, 15);
+}
+
+/* Standard tests for pre-authentication in sync and async tests with single or multiple domains */
+static void
+pre_test_authentication (ClientLoginAuthorizerData *data)
+{
+	/* Check we're not already authorised any domains */
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (data->authorizer),
+	          gdata_youtube_service_get_primary_authorization_domain ()) == FALSE);
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (data->authorizer),
+	          gdata_picasaweb_service_get_primary_authorization_domain ()) == FALSE);
+
+	g_assert_cmpuint (data->username_notification_count, ==, 0);
+	g_assert_cmpuint (data->password_notification_count, ==, 0);
+}
+
+/* Standard tests for post-authentication (successful or not controlled by @authorized) in sync tests with single domains */
+static void
+post_test_authentication (ClientLoginAuthorizerData *data, gboolean authorized)
+{
+	/* Are we authorised now? */
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (data->authorizer),
+	          gdata_youtube_service_get_primary_authorization_domain ()) == authorized);
+
+	g_assert_cmpuint (data->username_notification_count, ==, 1);
+	g_assert_cmpuint (data->password_notification_count, ==, 1);
+
+	if (authorized == TRUE) {
+		/* Check the username and password were set correctly. Note that we always assert that the domain name is present in the username. */
+		g_assert_cmpstr (gdata_client_login_authorizer_get_username (data->authorizer), ==, USERNAME);
+		g_assert_cmpstr (gdata_client_login_authorizer_get_password (data->authorizer), ==, PASSWORD);
+	} else {
+		/* Check the username and password are *not* set. */
+		g_assert (gdata_client_login_authorizer_get_username (data->authorizer) == NULL);
+		g_assert (gdata_client_login_authorizer_get_password (data->authorizer) == NULL);
+	}
+}
+
+/* Test that synchronous authentication against a single authorization domains succeeds */
+static void
+test_client_login_authorizer_authenticate_sync (ClientLoginAuthorizerData *data, gconstpointer _username)
+{
+	const gchar *username = (const gchar*) _username;
+	gboolean success;
+	GError *error = NULL;
+
+	pre_test_authentication (data);
+
+	/* Authenticate! */
+	success = gdata_client_login_authorizer_authenticate (data->authorizer, username, PASSWORD, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success == TRUE);
+	g_clear_error (&error);
+
+	post_test_authentication (data, TRUE);
+}
+
+/* Test that authentication using an incorrect password fails */
+static void
+test_client_login_authorizer_authenticate_sync_bad_password (ClientLoginAuthorizerData *data, gconstpointer _username)
+{
+	const gchar *username = (const gchar*) _username;
+	gboolean success;
+	GError *error = NULL;
+
+	pre_test_authentication (data);
+
+	/* Authenticate! */
+	success = gdata_client_login_authorizer_authenticate (data->authorizer, username, INCORRECT_PASSWORD, NULL, &error);
+	g_assert_error (error, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR, GDATA_CLIENT_LOGIN_AUTHORIZER_ERROR_BAD_AUTHENTICATION);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+
+	post_test_authentication (data, FALSE);
+}
+
+/* Test that authentication against multiple authorization domains simultaneously and synchronously works */
+static void
+test_client_login_authorizer_authenticate_sync_multiple_domains (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	pre_test_authentication (data);
+
+	/* Authenticate! */
+	success = gdata_client_login_authorizer_authenticate (data->authorizer, USERNAME, PASSWORD, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success == TRUE);
+	g_clear_error (&error);
+
+	/* Are we authorised in the second domain now? */
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (data->authorizer),
+	          gdata_picasaweb_service_get_primary_authorization_domain ()) == TRUE);
+
+	post_test_authentication (data, TRUE);
+}
+
+/* Test that synchronous authentication can be cancelled */
+static void
+test_client_login_authorizer_authenticate_sync_cancellation (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	gboolean success;
+	GCancellable *cancellable;
+	GError *error = NULL;
+
+	pre_test_authentication (data);
+
+	/* Set up the cancellable */
+	cancellable = g_cancellable_new ();
+
+	/* Authenticate! This should return immediately as the cancellable was cancelled beforehand. */
+	g_cancellable_cancel (cancellable);
+	success = gdata_client_login_authorizer_authenticate (data->authorizer, USERNAME, PASSWORD, cancellable, &error);
+	g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+
+	post_test_authentication (data, FALSE);
+
+	g_object_unref (cancellable);
+}
+
+typedef struct {
+	ClientLoginAuthorizerData parent;
+	GMainLoop *main_loop;
+} ClientLoginAuthorizerAsyncData;
+
+static void
+set_up_client_login_authorizer_async_data (ClientLoginAuthorizerAsyncData *data, gconstpointer user_data)
+{
+	/* Chain up */
+	set_up_client_login_authorizer_data ((ClientLoginAuthorizerData*) data, user_data);
+
+	/* Set up the main loop */
+	data->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+set_up_client_login_authorizer_async_data_multiple_domains (ClientLoginAuthorizerAsyncData *data, gconstpointer user_data)
+{
+	/* Chain up */
+	set_up_client_login_authorizer_data_multiple_domains ((ClientLoginAuthorizerData*) data, user_data);
+
+	/* Set up the main loop */
+	data->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+tear_down_client_login_authorizer_async_data (ClientLoginAuthorizerAsyncData *data, gconstpointer user_data)
+{
+	g_main_loop_unref (data->main_loop);
+
+	/* Chain up */
+	tear_down_client_login_authorizer_data ((ClientLoginAuthorizerData*) data, user_data);
+}
+
+/* Standard tests for post-authentication (successful or not controlled by @authorized) in async tests with single domains */
+static void
+post_test_authentication_async (ClientLoginAuthorizerAsyncData *data, gboolean authorized)
+{
+	/* Spin on the notification counts being incremented */
+	while (data->parent.username_notification_count == 0 || data->parent.password_notification_count == 0) {
+		g_main_context_iteration (g_main_loop_get_context (data->main_loop), FALSE);
+	}
+
+	post_test_authentication ((ClientLoginAuthorizerData*) data, authorized);
+}
+
+static void
+test_client_login_authorizer_authenticate_async_cb (GDataClientLoginAuthorizer *authorizer, GAsyncResult *async_result,
+                                                    ClientLoginAuthorizerAsyncData *data)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_client_login_authorizer_authenticate_finish (authorizer, async_result, &error);
+	g_assert_no_error (error);
+	g_assert (success == TRUE);
+	g_clear_error (&error);
+
+	post_test_authentication_async (data, TRUE);
+
+	g_main_loop_quit (data->main_loop);
+}
+
+/* Test that asynchronous authentication against a single authorization domain works */
+static void
+test_client_login_authorizer_authenticate_async (ClientLoginAuthorizerAsyncData *data, gconstpointer user_data)
+{
+	pre_test_authentication ((ClientLoginAuthorizerData*) data);
+
+	/* Create a main loop and authenticate */
+	gdata_client_login_authorizer_authenticate_async (data->parent.authorizer, USERNAME, PASSWORD, NULL,
+	                                                  (GAsyncReadyCallback) test_client_login_authorizer_authenticate_async_cb, data);
+
+	g_main_loop_run (data->main_loop);
+}
+
+static void
+test_client_login_authorizer_authenticate_async_multiple_domains_cb (GDataClientLoginAuthorizer *authorizer, GAsyncResult *async_result,
+                                                                     ClientLoginAuthorizerAsyncData *data)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_client_login_authorizer_authenticate_finish (authorizer, async_result, &error);
+	g_assert_no_error (error);
+	g_assert (success == TRUE);
+	g_clear_error (&error);
+
+	/* Assert that we're now authorised in the second domain */
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	          gdata_picasaweb_service_get_primary_authorization_domain ()) == TRUE);
+
+	post_test_authentication_async (data, TRUE);
+
+	g_main_loop_quit (data->main_loop);
+}
+
+/* Test that authentication against multiple authorization domains simultaneously and asynchronously works */
+static void
+test_client_login_authorizer_authenticate_async_multiple_domains (ClientLoginAuthorizerAsyncData *data, gconstpointer user_data)
+{
+	pre_test_authentication ((ClientLoginAuthorizerData*) data);
+
+	/* Create a main loop and authenticate */
+	gdata_client_login_authorizer_authenticate_async (data->parent.authorizer, USERNAME, PASSWORD, NULL,
+	                                                  (GAsyncReadyCallback) test_client_login_authorizer_authenticate_async_multiple_domains_cb,
+	                                                  data);
+
+	g_main_loop_run (data->main_loop);
+}
+
+static void
+test_client_login_authorizer_authenticate_async_cancellation_cb (GDataClientLoginAuthorizer *authorizer, GAsyncResult *async_result,
+                                                                 ClientLoginAuthorizerAsyncData *data)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_client_login_authorizer_authenticate_finish (authorizer, async_result, &error);
+	g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+
+	post_test_authentication_async (data, FALSE);
+
+	g_main_loop_quit (data->main_loop);
+}
+
+/* Test that cancellation of asynchronous authentication works */
+static void
+test_client_login_authorizer_authenticate_async_cancellation (ClientLoginAuthorizerAsyncData *data, gconstpointer user_data)
+{
+	GCancellable *cancellable;
+
+	pre_test_authentication ((ClientLoginAuthorizerData*) data);
+
+	/* Set up the cancellable */
+	cancellable = g_cancellable_new ();
+
+	/* Create a main loop and authenticate */
+	gdata_client_login_authorizer_authenticate_async (data->parent.authorizer, USERNAME, PASSWORD, cancellable,
+	                                                  (GAsyncReadyCallback) test_client_login_authorizer_authenticate_async_cancellation_cb,
+	                                                  data);
+	g_cancellable_cancel (cancellable);
+
+	g_main_loop_run (data->main_loop);
+
+	g_object_unref (cancellable);
+}
+
+/* Test that gdata_authorizer_refresh_authorization() is a no-op (when authorised or not) */
+static void
+test_client_login_authorizer_refresh_authorization (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	GError *error = NULL;
+
+	g_assert (gdata_authorizer_refresh_authorization (GDATA_AUTHORIZER (data->authorizer), NULL, &error) == FALSE);
+	g_assert_no_error (error);
+	g_clear_error (&error);
+}
+
+/* Test that processing a request with a NULL domain will not change the request. */
+static void
+test_client_login_authorizer_process_request_null (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	SoupMessage *message;
+	SoupMessageHeadersIter iter;
+	guint header_count = 0;
+	const gchar *name, *value;
+
+	/* Create a new message with an empty set of request headers */
+	message = soup_message_new (SOUP_METHOD_GET, "https://example.com/";);
+
+	/* Process the message */
+	gdata_authorizer_process_request (GDATA_AUTHORIZER (data->authorizer), NULL, message);
+
+	/* Check that the set of request headers is still empty */
+	soup_message_headers_iter_init (&iter, message->request_headers);
+
+	while (soup_message_headers_iter_next (&iter, &name, &value) == TRUE) {
+		header_count++;
+	}
+
+	g_assert_cmpuint (header_count, ==, 0);
+
+	g_object_unref (message);
+}
+
+/* Test that processing a request with an authorizer which hasn't been authenticated yet will not change the request. */
+static void
+test_client_login_authorizer_process_request_unauthenticated (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	SoupMessage *message;
+	SoupMessageHeadersIter iter;
+	guint header_count = 0;
+	const gchar *name, *value;
+
+	/* Create a new message with an empty set of request headers */
+	message = soup_message_new (SOUP_METHOD_GET, "https://example.com/";);
+
+	/* Process the message */
+	gdata_authorizer_process_request (GDATA_AUTHORIZER (data->authorizer), gdata_youtube_service_get_primary_authorization_domain (), message);
+
+	/* Check that the set of request headers is still empty */
+	soup_message_headers_iter_init (&iter, message->request_headers);
+
+	while (soup_message_headers_iter_next (&iter, &name, &value) == TRUE) {
+		header_count++;
+	}
+
+	g_assert_cmpuint (header_count, ==, 0);
+
+	g_object_unref (message);
+}
+
+/* Test that processing a request with an authorizer which has been authenticated will change the request. */
+static void
+test_client_login_authorizer_process_request_authenticated (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	SoupMessage *message;
+	SoupMessageHeadersIter iter;
+	guint header_count = 0;
+	const gchar *name, *value;
+
+	/* Create a new message with an empty set of request headers */
+	message = soup_message_new (SOUP_METHOD_GET, "https://example.com/";);
+
+	/* Process the message */
+	gdata_authorizer_process_request (GDATA_AUTHORIZER (data->authorizer), gdata_youtube_service_get_primary_authorization_domain (), message);
+
+	/* Check that at least one new header has been set */
+	soup_message_headers_iter_init (&iter, message->request_headers);
+
+	while (soup_message_headers_iter_next (&iter, &name, &value) == TRUE) {
+		header_count++;
+	}
+
+	g_assert_cmpuint (header_count, >, 0);
+
+	g_object_unref (message);
+}
+
+/* Test that processing a HTTP request (as opposed to the more normal HTTPS request) with an authenticated authorizer will abort rather than
+ * transmitting the user's private auth token over an insecure HTTP connection. */
+static void
+test_client_login_authorizer_process_request_insecure (ClientLoginAuthorizerData *data, gconstpointer user_data)
+{
+	SoupMessage *message;
+
+	/* Create a new message which uses HTTP instead of HTTPS */
+	message = soup_message_new (SOUP_METHOD_GET, "http://example.com/";);
+
+	/* Process the message */
+	if (g_test_trap_fork (0, 0) == TRUE) {
+		gdata_authorizer_process_request (GDATA_AUTHORIZER (data->authorizer), gdata_youtube_service_get_primary_authorization_domain (),
+		                                message);
+		exit (0);
+	}
+
+	/* Assert that it aborted */
+	g_test_trap_assert_failed ();
+	g_test_trap_assert_stderr_unmatched ("Not authorizing a non-HTTPS message with the user's ClientLogin "
+	                                     "auth token as the connection isn't secure.");
+
+	g_object_unref (message);
+}
+
+int
+main (int argc, char *argv[])
+{
+	gdata_test_init (argc, argv);
+
+	main_thread = g_thread_self ();
+
+	g_test_add_func ("/client-login-authorizer/constructor", test_client_login_authorizer_constructor);
+	g_test_add_func ("/client-login-authorizer/constructor/for-domains", test_client_login_authorizer_constructor_for_domains);
+
+	g_test_add ("/client-login-authorizer/properties/client-id", ClientLoginAuthorizerData, NULL, set_up_client_login_authorizer_data,
+	            test_client_login_authorizer_properties_client_id, tear_down_client_login_authorizer_data);
+	g_test_add ("/client-login-authorizer/properties/username", ClientLoginAuthorizerData, NULL, set_up_client_login_authorizer_data,
+	            test_client_login_authorizer_properties_username, tear_down_client_login_authorizer_data);
+	g_test_add ("/client-login-authorizer/properties/password", ClientLoginAuthorizerData, NULL, set_up_client_login_authorizer_data,
+	            test_client_login_authorizer_properties_password, tear_down_client_login_authorizer_data);
+	g_test_add ("/client-login-authorizer/properties/proxy-uri", ClientLoginAuthorizerData, NULL, set_up_client_login_authorizer_data,
+	            test_client_login_authorizer_properties_proxy_uri, tear_down_client_login_authorizer_data);
+	g_test_add ("/client-login-authorizer/properties/timeout", ClientLoginAuthorizerData, NULL, set_up_client_login_authorizer_data,
+	            test_client_login_authorizer_properties_timeout, tear_down_client_login_authorizer_data);
+
+	g_test_add ("/client-login-authorizer/refresh-authorization/unauthenticated", ClientLoginAuthorizerData, NULL,
+	            set_up_client_login_authorizer_data, test_client_login_authorizer_refresh_authorization,
+	            tear_down_client_login_authorizer_data);
+
+	g_test_add ("/client-login-authorizer/process-request/null", ClientLoginAuthorizerData, NULL,
+	            set_up_client_login_authorizer_data, test_client_login_authorizer_process_request_null, tear_down_client_login_authorizer_data);
+	g_test_add ("/client-login-authorizer/process-request/unauthenticated", ClientLoginAuthorizerData, NULL,
+	            set_up_client_login_authorizer_data, test_client_login_authorizer_process_request_unauthenticated,
+	            tear_down_client_login_authorizer_data);
+
+	if (gdata_test_internet () == TRUE) {
+		/* Test once with the domain attached and once without */
+		g_test_add ("/client-login-authorizer/authenticate/sync", ClientLoginAuthorizerData, USERNAME, set_up_client_login_authorizer_data,
+		            test_client_login_authorizer_authenticate_sync, tear_down_client_login_authorizer_data);
+		g_test_add ("/client-login-authorizer/authenticate/sync/no-domain", ClientLoginAuthorizerData, USERNAME_NO_DOMAIN,
+		            set_up_client_login_authorizer_data, test_client_login_authorizer_authenticate_sync,
+		            tear_down_client_login_authorizer_data);
+		g_test_add ("/client-login-authorizer/authenticate/sync/bad-password", ClientLoginAuthorizerData, USERNAME,
+		            set_up_client_login_authorizer_data, test_client_login_authorizer_authenticate_sync_bad_password,
+		            tear_down_client_login_authorizer_data);
+		g_test_add ("/client-login-authorizer/authenticate/sync/multiple-domains", ClientLoginAuthorizerData, NULL,
+		            set_up_client_login_authorizer_data_multiple_domains, test_client_login_authorizer_authenticate_sync_multiple_domains,
+		            tear_down_client_login_authorizer_data);
+		g_test_add ("/client-login-authorizer/authenticate/sync/cancellation", ClientLoginAuthorizerData, NULL,
+		            set_up_client_login_authorizer_data, test_client_login_authorizer_authenticate_sync_cancellation,
+		            tear_down_client_login_authorizer_data);
+
+		/* Async tests */
+		g_test_add ("/client-login-authorizer/authenticate/async", ClientLoginAuthorizerAsyncData, NULL,
+		            set_up_client_login_authorizer_async_data, test_client_login_authorizer_authenticate_async,
+		            tear_down_client_login_authorizer_async_data);
+		g_test_add ("/client-login-authorizer/authenticate/async/multiple-domains", ClientLoginAuthorizerAsyncData, NULL,
+		            set_up_client_login_authorizer_async_data_multiple_domains,
+		            test_client_login_authorizer_authenticate_async_multiple_domains, tear_down_client_login_authorizer_async_data);
+		g_test_add ("/client-login-authorizer/authenticate/async/cancellation", ClientLoginAuthorizerAsyncData, NULL,
+		            set_up_client_login_authorizer_async_data, test_client_login_authorizer_authenticate_async_cancellation,
+		            tear_down_client_login_authorizer_async_data);
+
+		/* Miscellaneous other tests which require authentication */
+		g_test_add ("/client-login-authorizer/refresh-authorization/authenticated", ClientLoginAuthorizerData, NULL,
+		            set_up_client_login_authorizer_data_authenticated, test_client_login_authorizer_refresh_authorization,
+		            tear_down_client_login_authorizer_data);
+
+		g_test_add ("/client-login-authorizer/process-request/authenticated", ClientLoginAuthorizerData, NULL,
+		            set_up_client_login_authorizer_data_authenticated, test_client_login_authorizer_process_request_authenticated,
+		            tear_down_client_login_authorizer_data);
+		g_test_add ("/client-login-authorizer/process-request/insecure", ClientLoginAuthorizerData, NULL,
+		            set_up_client_login_authorizer_data_authenticated, test_client_login_authorizer_process_request_insecure,
+		            tear_down_client_login_authorizer_data);
+	}
+
+	return g_test_run ();
+}
diff --git a/gdata/tests/common.h b/gdata/tests/common.h
index ae780f0..488f9be 100644
--- a/gdata/tests/common.h
+++ b/gdata/tests/common.h
@@ -26,9 +26,18 @@
 G_BEGIN_DECLS
 
 #define CLIENT_ID "ytapi-GNOME-libgdata-444fubtt-0"
-#define USERNAME "libgdata test gmail com"
 #define DOCUMENTS_USERNAME "libgdata documents gmail com"
+
+/* These two must match */
+#define USERNAME_NO_DOMAIN "libgdata.test"
+#define USERNAME USERNAME_NO_DOMAIN "@gmail.com"
+
+/* This must not match the above two */
+#define INCORRECT_USERNAME "libgdata test invalid gmail com"
+
+/* These two must not match (obviously) */
 #define PASSWORD "gdata-libgdata"
+#define INCORRECT_PASSWORD "bad-password"
 
 void gdata_test_init (int argc, char **argv);
 
diff --git a/gdata/tests/contacts.c b/gdata/tests/contacts.c
index 2cf9e9e..2732d86 100644
--- a/gdata/tests/contacts.c
+++ b/gdata/tests/contacts.c
@@ -76,28 +76,70 @@ static void
 test_authentication (void)
 {
 	gboolean retval;
-	GDataService *service;
+	GDataClientLoginAuthorizer *authorizer;
 	GError *error = NULL;
 
-	/* Create a service */
-	service = GDATA_SERVICE (gdata_contacts_service_new (CLIENT_ID));
+	/* Create an authorizer */
+	authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_CONTACTS_SERVICE);
 
-	g_assert (service != NULL);
-	g_assert (GDATA_IS_SERVICE (service));
-	g_assert_cmpstr (gdata_service_get_client_id (service), ==, CLIENT_ID);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (authorizer), ==, CLIENT_ID);
 
 	/* Log in */
-	retval = gdata_service_authenticate (service, USERNAME, PASSWORD, NULL, &error);
+	retval = gdata_client_login_authorizer_authenticate (authorizer, USERNAME, PASSWORD, NULL, &error);
 	g_assert_no_error (error);
 	g_assert (retval == TRUE);
 	g_clear_error (&error);
 
 	/* Check all is as it should be */
-	g_assert (gdata_service_is_authenticated (service) == TRUE);
-	g_assert_cmpstr (gdata_service_get_username (service), ==, USERNAME);
-	g_assert_cmpstr (gdata_service_get_password (service), ==, PASSWORD);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, USERNAME);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, PASSWORD);
 
-	g_object_unref (service);
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	                                                     gdata_contacts_service_get_primary_authorization_domain ()) == TRUE);
+
+	g_object_unref (authorizer);
+}
+
+static void
+test_authentication_async_cb (GDataClientLoginAuthorizer *authorizer, GAsyncResult *async_result, GMainLoop *main_loop)
+{
+	gboolean retval;
+	GError *error = NULL;
+
+	retval = gdata_client_login_authorizer_authenticate_finish (authorizer, async_result, &error);
+	g_assert_no_error (error);
+	g_assert (retval == TRUE);
+	g_clear_error (&error);
+
+	g_main_loop_quit (main_loop);
+
+	/* Check all is as it should be */
+	g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, USERNAME);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, PASSWORD);
+
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	                                                     gdata_contacts_service_get_primary_authorization_domain ()) == TRUE);
+}
+
+static void
+test_authentication_async (void)
+{
+	GMainLoop *main_loop;
+	GDataClientLoginAuthorizer *authorizer;
+
+	/* Create an authorizer */
+	authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_CONTACTS_SERVICE);
+
+	g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (authorizer), ==, CLIENT_ID);
+
+	main_loop = g_main_loop_new (NULL, TRUE);
+	gdata_client_login_authorizer_authenticate_async (authorizer, USERNAME, PASSWORD, NULL,
+	                                                  (GAsyncReadyCallback) test_authentication_async_cb, main_loop);
+
+	g_main_loop_run (main_loop);
+
+	g_main_loop_unref (main_loop);
+	g_object_unref (authorizer);
 }
 
 static void
@@ -596,7 +638,9 @@ test_update_simple (gconstpointer service)
 	g_assert (gdata_contacts_contact_set_extended_property (contact, "contact-test", "value"));
 
 	/* Update the contact */
-	new_contact = GDATA_CONTACTS_CONTACT (gdata_service_update_entry (GDATA_SERVICE (service), GDATA_ENTRY (contact), NULL, &error));
+	new_contact = GDATA_CONTACTS_CONTACT (gdata_service_update_entry (GDATA_SERVICE (service),
+	                                                                  gdata_contacts_service_get_primary_authorization_domain (),
+	                                                                  GDATA_ENTRY (contact), NULL, &error));
 	g_assert_no_error (error);
 	g_assert (GDATA_IS_CONTACTS_CONTACT (new_contact));
 	check_kind (GDATA_ENTRY (new_contact), "http://schemas.google.com/contact/2008#contact";);
@@ -733,7 +777,8 @@ test_insert_group (gconstpointer service)
 	g_assert_cmpstr (gdata_contacts_group_get_extended_property (new_group, "foobar"), ==, "barfoo");
 
 	/* Delete the group, just to be tidy */
-	g_assert (gdata_service_delete_entry (GDATA_SERVICE (service), GDATA_ENTRY (new_group), NULL, &error) == TRUE);
+	g_assert (gdata_service_delete_entry (GDATA_SERVICE (service), gdata_contacts_service_get_primary_authorization_domain (),
+	                                      GDATA_ENTRY (new_group), NULL, &error) == TRUE);
 	g_assert_no_error (error);
 	g_clear_error (&error);
 
@@ -755,7 +800,8 @@ test_insert_group_async_cb (GDataService *service, GAsyncResult *async_result, G
 	/* TODO: Tests? */
 
 	/* Delete the group, just to be tidy */
-	g_assert (gdata_service_delete_entry (GDATA_SERVICE (service), entry, NULL, &error) == TRUE);
+	g_assert (gdata_service_delete_entry (GDATA_SERVICE (service), gdata_contacts_service_get_primary_authorization_domain (),
+	                                      entry, NULL, &error) == TRUE);
 	g_assert_no_error (error);
 	g_clear_error (&error);
 
@@ -1756,11 +1802,12 @@ test_batch (gconstpointer service)
 	GError *error = NULL, *entry_error = NULL;
 
 	/* Here we hardcode the feed URI, but it should really be extracted from a contacts feed, as the GDATA_LINK_BATCH link */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "http://www.google.com/m8/feeds/contacts/default/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_contacts_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/m8/feeds/contacts/default/full/batch";);
 
 	/* Check the properties of the operation */
 	g_assert (gdata_batch_operation_get_service (operation) == service);
-	g_assert_cmpstr (gdata_batch_operation_get_feed_uri (operation), ==, "http://www.google.com/m8/feeds/contacts/default/full/batch";);
+	g_assert_cmpstr (gdata_batch_operation_get_feed_uri (operation), ==, "https://www.google.com/m8/feeds/contacts/default/full/batch";);
 
 	g_object_get (operation,
 	              "service", &service2,
@@ -1768,7 +1815,7 @@ test_batch (gconstpointer service)
 	              NULL);
 
 	g_assert (service2 == service);
-	g_assert_cmpstr (feed_uri, ==, "http://www.google.com/m8/feeds/contacts/default/full/batch";);
+	g_assert_cmpstr (feed_uri, ==, "https://www.google.com/m8/feeds/contacts/default/full/batch";);
 
 	g_object_unref (service2);
 	g_free (feed_uri);
@@ -1789,7 +1836,8 @@ test_batch (gconstpointer service)
 	contact2 = gdata_contacts_contact_new (NULL);
 	gdata_entry_set_title (GDATA_ENTRY (contact2), "Brian");
 
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "http://www.google.com/m8/feeds/contacts/default/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_contacts_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/m8/feeds/contacts/default/full/batch";);
 	op_id = gdata_test_batch_operation_insertion (operation, GDATA_ENTRY (contact2), &inserted_entry2, NULL);
 	op_id2 = gdata_test_batch_operation_query (operation, gdata_entry_get_id (inserted_entry), GDATA_TYPE_CONTACTS_CONTACT, inserted_entry, NULL,
 	                                           NULL);
@@ -1806,7 +1854,8 @@ test_batch (gconstpointer service)
 	gdata_entry_set_title (inserted_entry2, "Toby");
 	contact3 = gdata_contacts_contact_new ("foobar");
 
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "http://www.google.com/m8/feeds/contacts/default/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_contacts_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/m8/feeds/contacts/default/full/batch";);
 	op_id = gdata_test_batch_operation_deletion (operation, inserted_entry, NULL);
 	op_id2 = gdata_test_batch_operation_deletion (operation, GDATA_ENTRY (contact3), &entry_error);
 	op_id3 = gdata_test_batch_operation_update (operation, inserted_entry2, &inserted_entry3, NULL);
@@ -1827,7 +1876,8 @@ test_batch (gconstpointer service)
 
 	/* Run another batch operation to update the second entry with the wrong ETag (i.e. pass the old version of the entry to the batch operation
 	 * to test error handling */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "http://www.google.com/m8/feeds/contacts/default/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_contacts_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/m8/feeds/contacts/default/full/batch";);
 	gdata_test_batch_operation_update (operation, inserted_entry2, NULL, &entry_error);
 	g_assert (gdata_test_batch_operation_run (operation, NULL, &error) == TRUE);
 	g_assert_no_error (error);
@@ -1840,7 +1890,8 @@ test_batch (gconstpointer service)
 	g_object_unref (inserted_entry2);
 
 	/* Run a final batch operation to delete the second entry */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "http://www.google.com/m8/feeds/contacts/default/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_contacts_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/m8/feeds/contacts/default/full/batch";);
 	gdata_test_batch_operation_deletion (operation, inserted_entry3, NULL);
 	g_assert (gdata_test_batch_operation_run (operation, NULL, &error) == TRUE);
 	g_assert_no_error (error);
@@ -1894,7 +1945,8 @@ test_batch_async (BatchAsyncData *data, gconstpointer service)
 	GMainLoop *main_loop;
 
 	/* Run an async query operation on the contact */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "http://www.google.com/m8/feeds/contacts/default/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_contacts_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/m8/feeds/contacts/default/full/batch";);
 	gdata_test_batch_operation_query (operation, gdata_entry_get_id (GDATA_ENTRY (data->new_contact)), GDATA_TYPE_CONTACTS_CONTACT,
 	                                  GDATA_ENTRY (data->new_contact), NULL, NULL);
 
@@ -1931,7 +1983,8 @@ test_batch_async_cancellation (BatchAsyncData *data, gconstpointer service)
 	GError *error = NULL;
 
 	/* Run an async query operation on the contact */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "http://www.google.com/m8/feeds/contacts/default/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_contacts_service_get_primary_authorization_domain (),
+	                                              "https://www.google.com/m8/feeds/contacts/default/full/batch";);
 	gdata_test_batch_operation_query (operation, gdata_entry_get_id (GDATA_ENTRY (data->new_contact)), GDATA_TYPE_CONTACTS_CONTACT,
 	                                  GDATA_ENTRY (data->new_contact), NULL, &error);
 
@@ -1957,7 +2010,8 @@ teardown_batch_async (BatchAsyncData *data, gconstpointer service)
 	GError *error = NULL;
 
 	/* Delete the contact */
-	g_assert (gdata_service_delete_entry (GDATA_SERVICE (service), GDATA_ENTRY (data->new_contact), NULL, &error) == TRUE);
+	g_assert (gdata_service_delete_entry (GDATA_SERVICE (service), gdata_contacts_service_get_primary_authorization_domain (),
+	                                      GDATA_ENTRY (data->new_contact), NULL, &error) == TRUE);
 	g_assert_no_error (error);
 	g_clear_error (&error);
 
@@ -2044,15 +2098,19 @@ int
 main (int argc, char *argv[])
 {
 	gint retval;
+	GDataAuthorizer *authorizer = NULL;
 	GDataService *service = NULL;
 
 	gdata_test_init (argc, argv);
 
 	if (gdata_test_internet () == TRUE) {
-		service = GDATA_SERVICE (gdata_contacts_service_new (CLIENT_ID));
-		gdata_service_authenticate (service, USERNAME, PASSWORD, NULL, NULL);
+		authorizer = GDATA_AUTHORIZER (gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_CONTACTS_SERVICE));
+		gdata_client_login_authorizer_authenticate (GDATA_CLIENT_LOGIN_AUTHORIZER (authorizer), USERNAME, PASSWORD, NULL, NULL);
+
+		service = GDATA_SERVICE (gdata_contacts_service_new (authorizer));
 
 		g_test_add_func ("/contacts/authentication", test_authentication);
+		g_test_add_func ("/contacts/authentication_async", test_authentication_async);
 
 		g_test_add_data_func ("/contacts/insert/simple", service, test_insert_simple);
 		g_test_add_data_func ("/contacts/update/simple", service, test_update_simple);
diff --git a/gdata/tests/documents.c b/gdata/tests/documents.c
index ae878eb..310b022 100644
--- a/gdata/tests/documents.c
+++ b/gdata/tests/documents.c
@@ -48,28 +48,74 @@ static void
 test_authentication (void)
 {
 	gboolean retval;
-	GDataDocumentsService *service;
+	GDataClientLoginAuthorizer *authorizer;
 	GError *error = NULL;
 
-	/* Create a service */
-	service = gdata_documents_service_new (CLIENT_ID);
+	/* Create an authorizer */
+	authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_DOCUMENTS_SERVICE);
 
-	g_assert (service != NULL);
-	g_assert (GDATA_IS_SERVICE (service));
-	g_assert_cmpstr (gdata_service_get_client_id (GDATA_SERVICE (service)), ==, CLIENT_ID);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (authorizer), ==, CLIENT_ID);
 
 	/* Log in */
-	retval = gdata_service_authenticate (GDATA_SERVICE (service), DOCUMENTS_USERNAME, PASSWORD, NULL, &error);
+	retval = gdata_client_login_authorizer_authenticate (authorizer, USERNAME, PASSWORD, NULL, &error);
 	g_assert_no_error (error);
 	g_assert (retval == TRUE);
 	g_clear_error (&error);
 
 	/* Check all is as it should be */
-	g_assert (gdata_service_is_authenticated (GDATA_SERVICE (service)) == TRUE);
-	g_assert_cmpstr (gdata_service_get_username (GDATA_SERVICE (service)), ==, DOCUMENTS_USERNAME);
-	g_assert_cmpstr (gdata_service_get_password (GDATA_SERVICE (service)), ==, PASSWORD);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, USERNAME);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, PASSWORD);
+
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	                                                     gdata_documents_service_get_primary_authorization_domain ()) == TRUE);
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	                                                     gdata_documents_service_get_spreadsheet_authorization_domain ()) == TRUE);
 
-	g_object_unref (service);
+	g_object_unref (authorizer);
+}
+
+static void
+test_authentication_async_cb (GDataClientLoginAuthorizer *authorizer, GAsyncResult *async_result, GMainLoop *main_loop)
+{
+	gboolean retval;
+	GError *error = NULL;
+
+	retval = gdata_client_login_authorizer_authenticate_finish (authorizer, async_result, &error);
+	g_assert_no_error (error);
+	g_assert (retval == TRUE);
+	g_clear_error (&error);
+
+	g_main_loop_quit (main_loop);
+
+	/* Check all is as it should be */
+	g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, USERNAME);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, PASSWORD);
+
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	                                                     gdata_documents_service_get_primary_authorization_domain ()) == TRUE);
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	                                                     gdata_documents_service_get_spreadsheet_authorization_domain ()) == TRUE);
+}
+
+static void
+test_authentication_async (void)
+{
+	GMainLoop *main_loop;
+	GDataClientLoginAuthorizer *authorizer;
+
+	/* Create an authorizer */
+	authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_DOCUMENTS_SERVICE);
+
+	g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (authorizer), ==, CLIENT_ID);
+
+	main_loop = g_main_loop_new (NULL, TRUE);
+	gdata_client_login_authorizer_authenticate_async (authorizer, USERNAME, PASSWORD, NULL,
+	                                                  (GAsyncReadyCallback) test_authentication_async_cb, main_loop);
+
+	g_main_loop_run (main_loop);
+
+	g_main_loop_unref (main_loop);
+	g_object_unref (authorizer);
 }
 
 static void
@@ -92,7 +138,8 @@ test_remove_all_documents_and_folders (gconstpointer service)
 	/* We delete the folders after all the files so we don't get ETag mismatches; deleting a folder changes the version
 	 * of all the documents inside it. Conversely, deleting an entry inside a folder changes the version of the folder. */
 	for (i = gdata_feed_get_entries (GDATA_FEED (feed)); i != NULL; i = i->next) {
-		gdata_service_delete_entry (GDATA_SERVICE (service), GDATA_ENTRY (i->data), NULL, &error);
+		gdata_service_delete_entry (GDATA_SERVICE (service), gdata_documents_service_get_primary_authorization_domain (),
+		                            GDATA_ENTRY (i->data), NULL, &error);
 		g_assert_no_error (error);
 		g_clear_error (&error);
 	}
@@ -107,7 +154,8 @@ test_remove_all_documents_and_folders (gconstpointer service)
 	g_assert (GDATA_IS_DOCUMENTS_FEED (feed));
 
 	for (i = gdata_feed_get_entries (GDATA_FEED (feed)); i != NULL; i = i->next) {
-		gdata_service_delete_entry (GDATA_SERVICE (service), GDATA_ENTRY (i->data), NULL, &error);
+		gdata_service_delete_entry (GDATA_SERVICE (service), gdata_documents_service_get_primary_authorization_domain (),
+		                            GDATA_ENTRY (i->data), NULL, &error);
 		g_assert_no_error (error);
 		g_clear_error (&error);
 	}
@@ -199,7 +247,9 @@ test_upload_metadata (gconstpointer service)
 
 	/* Insert the document */
 	upload_uri = gdata_documents_service_get_upload_uri (NULL);
-	new_document = GDATA_DOCUMENTS_ENTRY (gdata_service_insert_entry (GDATA_SERVICE (service), upload_uri, GDATA_ENTRY (document), NULL, &error));
+	new_document = GDATA_DOCUMENTS_ENTRY (gdata_service_insert_entry (GDATA_SERVICE (service),
+	                                                                  gdata_documents_service_get_primary_authorization_domain (),
+	                                                                  upload_uri, GDATA_ENTRY (document), NULL, &error));
 	g_free (upload_uri);
 	g_assert_no_error (error);
 	g_assert (GDATA_IS_DOCUMENTS_SPREADSHEET (new_document));
@@ -308,7 +358,8 @@ test_upload_file_get_entry (gconstpointer service)
 	g_object_unref (upload_stream);
 
 	/* Get the entry on the server */
-	new_presentation = gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_entry_get_id (GDATA_ENTRY (new_document)), NULL,
+	new_presentation = gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_documents_service_get_primary_authorization_domain (),
+	                                                     gdata_entry_get_id (GDATA_ENTRY (new_document)), NULL,
 	                                                     GDATA_TYPE_DOCUMENTS_PRESENTATION, NULL, &error);
 	g_assert_no_error (error);
 	g_assert (GDATA_IS_DOCUMENTS_PRESENTATION (new_presentation));
@@ -344,7 +395,9 @@ setup_folders (FoldersData *data, GDataDocumentsService *service, gboolean initi
 
 	/* Insert the folder */
 	upload_uri = gdata_documents_service_get_upload_uri (NULL);
-	data->folder = GDATA_DOCUMENTS_FOLDER (gdata_service_insert_entry (GDATA_SERVICE (service), upload_uri, GDATA_ENTRY (folder), NULL, &error));
+	data->folder = GDATA_DOCUMENTS_FOLDER (gdata_service_insert_entry (GDATA_SERVICE (service),
+	                                                                   gdata_documents_service_get_primary_authorization_domain (),
+	                                                                   upload_uri, GDATA_ENTRY (folder), NULL, &error));
 	g_free (upload_uri);
 
 	g_assert_no_error (error);
@@ -403,19 +456,21 @@ teardown_folders_add_to_folder (FoldersData *data, gconstpointer service)
 	GDataEntry *entry;
 
 	/* Re-query (to get an updated ETag) and delete the document (we don't care if this fails) */
-	entry = gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_entry_get_id (GDATA_ENTRY (data->document)), NULL,
+	entry = gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_documents_service_get_primary_authorization_domain (),
+	                                          gdata_entry_get_id (GDATA_ENTRY (data->document)), NULL,
 	                                          GDATA_TYPE_DOCUMENTS_TEXT, NULL, NULL);
 	if (entry != NULL) {
-		gdata_service_delete_entry (GDATA_SERVICE (service), entry, NULL, NULL);
+		gdata_service_delete_entry (GDATA_SERVICE (service), gdata_documents_service_get_primary_authorization_domain (), entry, NULL, NULL);
 		g_object_unref (entry);
 	}
 	g_object_unref (data->document);
 
 	/* Re-query (to get an updated ETag) and delete the folder (we don't care if this fails) */
-	entry = gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_entry_get_id (GDATA_ENTRY (data->folder)), NULL,
+	entry = gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_documents_service_get_primary_authorization_domain (),
+	                                          gdata_entry_get_id (GDATA_ENTRY (data->folder)), NULL,
 	                                          GDATA_TYPE_DOCUMENTS_FOLDER, NULL, NULL);
 	if (entry != NULL) {
-		gdata_service_delete_entry (GDATA_SERVICE (service), entry, NULL, NULL);
+		gdata_service_delete_entry (GDATA_SERVICE (service), gdata_documents_service_get_primary_authorization_domain (), entry, NULL, NULL);
 		g_object_unref (entry);
 	}
 	g_object_unref (data->folder);
@@ -660,7 +715,9 @@ test_upload_file_metadata_in_new_folder (gconstpointer service)
 
 	/* Insert the folder */
 	upload_uri = gdata_documents_service_get_upload_uri (NULL);
-	new_folder = GDATA_DOCUMENTS_FOLDER (gdata_service_insert_entry (GDATA_SERVICE (service), upload_uri, GDATA_ENTRY (folder), NULL, &error));
+	new_folder = GDATA_DOCUMENTS_FOLDER (gdata_service_insert_entry (GDATA_SERVICE (service),
+	                                                                 gdata_documents_service_get_primary_authorization_domain (),
+	                                                                 upload_uri, GDATA_ENTRY (folder), NULL, &error));
 	g_free (upload_uri);
 
 	g_assert_no_error (error);
@@ -728,7 +785,9 @@ test_update_metadata (gconstpointer service)
 
 	/* Insert the document */
 	upload_uri = gdata_documents_service_get_upload_uri (NULL);
-	new_document = GDATA_DOCUMENTS_ENTRY (gdata_service_insert_entry (GDATA_SERVICE (service), upload_uri, GDATA_ENTRY (document), NULL, &error));
+	new_document = GDATA_DOCUMENTS_ENTRY (gdata_service_insert_entry (GDATA_SERVICE (service),
+	                                                                  gdata_documents_service_get_primary_authorization_domain (),
+	                                                                  upload_uri, GDATA_ENTRY (document), NULL, &error));
 	g_free (upload_uri);
 
 	g_assert_no_error (error);
@@ -739,6 +798,7 @@ test_update_metadata (gconstpointer service)
 	 * trying this to allow the various Google servers to catch up with each other. */
 	g_usleep (5 * G_USEC_PER_SEC);
 	new_document2 = GDATA_DOCUMENTS_ENTRY (gdata_service_query_single_entry (GDATA_SERVICE (service),
+	                                                                         gdata_documents_service_get_primary_authorization_domain (),
 	                                                                         gdata_entry_get_id (GDATA_ENTRY (new_document)), NULL,
 	                                                                         GDATA_TYPE_DOCUMENTS_TEXT, NULL, &error));
 
@@ -748,7 +808,9 @@ test_update_metadata (gconstpointer service)
 	gdata_entry_set_title (GDATA_ENTRY (new_document2), "update_metadata_updated_title");
 
 	/* Update the document */
-	updated_document = GDATA_DOCUMENTS_ENTRY (gdata_service_update_entry (GDATA_SERVICE (service), GDATA_ENTRY (new_document2), NULL, &error));
+	updated_document = GDATA_DOCUMENTS_ENTRY (gdata_service_update_entry (GDATA_SERVICE (service),
+	                                                                      gdata_documents_service_get_primary_authorization_domain (),
+	                                                                      GDATA_ENTRY (new_document2), NULL, &error));
 	g_assert_no_error (error);
 	g_assert (GDATA_IS_DOCUMENTS_TEXT (updated_document));
 
@@ -780,7 +842,9 @@ test_update_metadata_file (gconstpointer service)
 
 	/* Insert the document's metadata */
 	upload_uri = gdata_documents_service_get_upload_uri (NULL);
-	new_document = GDATA_DOCUMENTS_DOCUMENT (gdata_service_insert_entry (GDATA_SERVICE (service), upload_uri, GDATA_ENTRY (document), NULL,
+	new_document = GDATA_DOCUMENTS_DOCUMENT (gdata_service_insert_entry (GDATA_SERVICE (service),
+	                                                                     gdata_documents_service_get_primary_authorization_domain (),
+	                                                                     upload_uri, GDATA_ENTRY (document), NULL,
 	                                                                     &error));
 	g_free (upload_uri);
 	g_assert_no_error (error);
@@ -793,6 +857,7 @@ test_update_metadata_file (gconstpointer service)
 	 * trying this to allow the various Google servers to catch up with each other. */
 	g_usleep (5 * G_USEC_PER_SEC);
 	new_document2 = GDATA_DOCUMENTS_DOCUMENT (gdata_service_query_single_entry (GDATA_SERVICE (service),
+	                                                                            gdata_documents_service_get_primary_authorization_domain (),
 	                                                                            gdata_entry_get_id (GDATA_ENTRY (new_document)), NULL,
 	                                                                            GDATA_TYPE_DOCUMENTS_TEXT, NULL, &error));
 
@@ -897,6 +962,7 @@ test_update_file (gconstpointer service)
 	 * trying this to allow the various Google servers to catch up with each other. */
 	g_usleep (5 * G_USEC_PER_SEC);
 	new_document2 = GDATA_DOCUMENTS_DOCUMENT (gdata_service_query_single_entry (GDATA_SERVICE (service),
+	                                                                            gdata_documents_service_get_primary_authorization_domain (),
 	                                                                            gdata_entry_get_id (GDATA_ENTRY (new_document)), NULL,
 	                                                                            GDATA_TYPE_DOCUMENTS_PRESENTATION, NULL, &error));
 
@@ -1038,7 +1104,9 @@ test_new_document_with_collaborator (gconstpointer service)
 
 	/* Insert the document */
 	upload_uri = gdata_documents_service_get_upload_uri (NULL);
-	new_document = GDATA_DOCUMENTS_ENTRY (gdata_service_insert_entry (GDATA_SERVICE (service), upload_uri, GDATA_ENTRY (document), NULL, &error));
+	new_document = GDATA_DOCUMENTS_ENTRY (gdata_service_insert_entry (GDATA_SERVICE (service),
+	                                                                  gdata_documents_service_get_primary_authorization_domain (),
+	                                                                  upload_uri, GDATA_ENTRY (document), NULL, &error));
 	g_free (upload_uri);
 
 	g_assert_no_error (error);
@@ -1053,7 +1121,9 @@ test_new_document_with_collaborator (gconstpointer service)
 	_link = gdata_entry_look_up_link (GDATA_ENTRY (new_document), GDATA_LINK_ACCESS_CONTROL_LIST);
 	g_assert (_link != NULL);
 
-	new_access_rule = GDATA_ACCESS_RULE (gdata_service_insert_entry (GDATA_SERVICE (service), gdata_link_get_uri (_link),
+	new_access_rule = GDATA_ACCESS_RULE (gdata_service_insert_entry (GDATA_SERVICE (service),
+	                                                                 gdata_documents_service_get_primary_authorization_domain (),
+	                                                                 gdata_link_get_uri (_link),
 	                                                                 GDATA_ENTRY (access_rule), NULL, &error));
 	g_assert_no_error (error);
 	g_assert (GDATA_IS_ACCESS_RULE (new_access_rule));
@@ -1103,7 +1173,8 @@ test_batch (gconstpointer service)
 	GError *error = NULL, *entry_error = NULL;
 
 	/* Here we hardcode the feed URI, but it should really be extracted from a document feed, as the GDATA_LINK_BATCH link */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://docs.google.com/feeds/documents/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_documents_service_get_primary_authorization_domain (),
+	                                              "https://docs.google.com/feeds/documents/private/full/batch";);
 
 	/* Check the properties of the operation */
 	g_assert (gdata_batch_operation_get_service (operation) == service);
@@ -1136,7 +1207,8 @@ test_batch (gconstpointer service)
 	doc2 = gdata_documents_text_new (NULL);
 	gdata_entry_set_title (GDATA_ENTRY (doc2), "I'm a poet and I didn't know it");
 
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://docs.google.com/feeds/documents/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_documents_service_get_primary_authorization_domain (),
+	                                              "https://docs.google.com/feeds/documents/private/full/batch";);
 	op_id = gdata_test_batch_operation_insertion (operation, GDATA_ENTRY (doc2), &inserted_entry2, NULL);
 	op_id2 = gdata_test_batch_operation_query (operation, gdata_entry_get_id (inserted_entry), GDATA_TYPE_DOCUMENTS_TEXT, inserted_entry, NULL,
 	                                           NULL);
@@ -1151,7 +1223,8 @@ test_batch (gconstpointer service)
 
 	/* Run another batch operation to query one of the entries we just created, since it seems that the ETags for documents change for no
 	 * apparent reason when you're not looking. */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://docs.google.com/feeds/documents/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_documents_service_get_primary_authorization_domain (),
+	                                              "https://docs.google.com/feeds/documents/private/full/batch";);
 	gdata_test_batch_operation_query (operation, gdata_entry_get_id (inserted_entry), GDATA_TYPE_DOCUMENTS_TEXT, inserted_entry,
 	                                  &inserted_entry_updated, NULL);
 
@@ -1165,7 +1238,8 @@ test_batch (gconstpointer service)
 	/* Run another batch operation to query the other entry we just created. It would be sensible to batch this query together with the previous
 	 * one, seeing as we're testing _batch_ functionality. Funnily enough, the combination of two idempotent operations changes the ETags and
 	 * makes the whole effort worthless. */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://docs.google.com/feeds/documents/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_documents_service_get_primary_authorization_domain (),
+	                                              "https://docs.google.com/feeds/documents/private/full/batch";);
 	gdata_test_batch_operation_query (operation, gdata_entry_get_id (inserted_entry2), GDATA_TYPE_DOCUMENTS_TEXT, inserted_entry2,
 	                                  &inserted_entry2_updated, NULL);
 
@@ -1179,7 +1253,8 @@ test_batch (gconstpointer service)
 	gdata_entry_set_title (inserted_entry2_updated, "War & Peace");
 	doc3 = gdata_documents_text_new ("foobar");
 
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://docs.google.com/feeds/documents/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_documents_service_get_primary_authorization_domain (),
+	                                              "https://docs.google.com/feeds/documents/private/full/batch";);
 	op_id = gdata_test_batch_operation_deletion (operation, inserted_entry_updated, NULL);
 	op_id2 = gdata_test_batch_operation_deletion (operation, GDATA_ENTRY (doc3), &entry_error);
 	op_id3 = gdata_test_batch_operation_update (operation, inserted_entry2_updated, &inserted_entry3, NULL);
@@ -1201,7 +1276,8 @@ test_batch (gconstpointer service)
 
 	/* Run another batch operation to update the second entry with the wrong ETag (i.e. pass the old version of the entry to the batch operation
 	 * to test error handling */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://docs.google.com/feeds/documents/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_documents_service_get_primary_authorization_domain (),
+	                                              "https://docs.google.com/feeds/documents/private/full/batch";);
 	gdata_test_batch_operation_update (operation, inserted_entry2, NULL, &entry_error);
 	g_assert (gdata_test_batch_operation_run (operation, NULL, &error) == TRUE);
 	g_assert_no_error (error);
@@ -1214,7 +1290,8 @@ test_batch (gconstpointer service)
 	g_object_unref (inserted_entry2);
 
 	/* Run a final batch operation to delete the second entry */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://docs.google.com/feeds/documents/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_documents_service_get_primary_authorization_domain (),
+	                                              "https://docs.google.com/feeds/documents/private/full/batch";);
 	gdata_test_batch_operation_deletion (operation, inserted_entry3, NULL);
 	g_assert (gdata_test_batch_operation_run (operation, NULL, &error) == TRUE);
 	g_assert_no_error (error);
@@ -1240,7 +1317,9 @@ setup_batch_async (BatchAsyncData *data, gconstpointer service)
 	gdata_entry_set_title (GDATA_ENTRY (doc), "A View from the Bridge");
 
 	upload_uri = gdata_documents_service_get_upload_uri (NULL);
-	data->new_doc = GDATA_DOCUMENTS_ENTRY (gdata_service_insert_entry (GDATA_SERVICE (service), upload_uri, GDATA_ENTRY (doc), NULL, &error));
+	data->new_doc = GDATA_DOCUMENTS_ENTRY (gdata_service_insert_entry (GDATA_SERVICE (service),
+	                                                                   gdata_documents_service_get_primary_authorization_domain (),
+	                                                                   upload_uri, GDATA_ENTRY (doc), NULL, &error));
 	g_free (upload_uri);
 
 	g_assert_no_error (error);
@@ -1272,7 +1351,8 @@ test_batch_async (BatchAsyncData *data, gconstpointer service)
 	GMainLoop *main_loop;
 
 	/* Run an async query operation on the document */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://docs.google.com/feeds/documents/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_documents_service_get_primary_authorization_domain (),
+	                                              "https://docs.google.com/feeds/documents/private/full/batch";);
 	gdata_test_batch_operation_query (operation, gdata_entry_get_id (GDATA_ENTRY (data->new_doc)), GDATA_TYPE_DOCUMENTS_TEXT,
 	                                  GDATA_ENTRY (data->new_doc), NULL, NULL);
 
@@ -1309,7 +1389,8 @@ test_batch_async_cancellation (BatchAsyncData *data, gconstpointer service)
 	GError *error = NULL;
 
 	/* Run an async query operation on the document */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://docs.google.com/feeds/documents/private/full/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_documents_service_get_primary_authorization_domain (),
+	                                              "https://docs.google.com/feeds/documents/private/full/batch";);
 	gdata_test_batch_operation_query (operation, gdata_entry_get_id (GDATA_ENTRY (data->new_doc)), GDATA_TYPE_DOCUMENTS_TEXT,
 	                                  GDATA_ENTRY (data->new_doc), NULL, &error);
 
@@ -1336,13 +1417,15 @@ teardown_batch_async (BatchAsyncData *data, gconstpointer service)
 	GError *error = NULL;
 
 	/* Re-query the document in case its ETag has changed */
-	document = gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_entry_get_id (GDATA_ENTRY (data->new_doc)), NULL,
+	document = gdata_service_query_single_entry (GDATA_SERVICE (service),
+	                                             gdata_documents_service_get_primary_authorization_domain (),
+	                                             gdata_entry_get_id (GDATA_ENTRY (data->new_doc)), NULL,
 	                                             GDATA_TYPE_DOCUMENTS_TEXT, NULL, &error);
 	g_assert_no_error (error);
 	g_clear_error (&error);
 
 	/* Delete the document (we don't care if this fails) */
-	gdata_service_delete_entry (GDATA_SERVICE (service), document, NULL, NULL);
+	gdata_service_delete_entry (GDATA_SERVICE (service), gdata_documents_service_get_primary_authorization_domain (), document, NULL, NULL);
 
 	g_object_unref (data->new_doc);
 	g_object_unref (document);
@@ -1352,15 +1435,19 @@ int
 main (int argc, char *argv[])
 {
 	gint retval;
+	GDataAuthorizer *authorizer = NULL;
 	GDataService *service = NULL;
 
 	gdata_test_init (argc, argv);
 
 	if (gdata_test_internet () == TRUE) {
-		service = GDATA_SERVICE (gdata_documents_service_new (CLIENT_ID));
-		gdata_service_authenticate (service, DOCUMENTS_USERNAME, PASSWORD, NULL, NULL);
+		authorizer = GDATA_AUTHORIZER (gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_DOCUMENTS_SERVICE));
+		gdata_client_login_authorizer_authenticate (GDATA_CLIENT_LOGIN_AUTHORIZER (authorizer), DOCUMENTS_USERNAME, PASSWORD, NULL, NULL);
+
+		service = GDATA_SERVICE (gdata_documents_service_new (authorizer));
 
 		g_test_add_func ("/documents/authentication", test_authentication);
+		g_test_add_func ("/documents/authentication_async", test_authentication_async);
 
 		g_test_add_data_func ("/documents/remove/all", service, test_remove_all_documents_and_folders);
 
diff --git a/gdata/tests/general.c b/gdata/tests/general.c
index 16248a1..377c0fe 100644
--- a/gdata/tests/general.c
+++ b/gdata/tests/general.c
@@ -964,10 +964,11 @@ test_service_network_error (void)
 	GError *error = NULL;
 
 	/* This is a little hacky, but it should work */
-	service = g_object_new (GDATA_TYPE_SERVICE, "client-id", CLIENT_ID, NULL);
+	service = g_object_new (GDATA_TYPE_SERVICE, NULL);
 
 	/* Try a query which should always fail due to errors resolving the hostname */
-	g_assert (gdata_service_query (service, "http://thisshouldnotexist.localhost";, NULL, GDATA_TYPE_ENTRY, NULL, NULL, NULL, &error) == NULL);
+	g_assert (gdata_service_query (service, NULL, "http://thisshouldnotexist.localhost";, NULL, GDATA_TYPE_ENTRY,
+	                               NULL, NULL, NULL, &error) == NULL);
 	g_assert_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NETWORK_ERROR);
 	g_clear_error (&error);
 
@@ -994,7 +995,7 @@ test_service_locale (void)
 	gchar *locale;
 
 	/* This is a little hacky, but it should work */
-	service = g_object_new (GDATA_TYPE_SERVICE, "client-id", CLIENT_ID, NULL);
+	service = g_object_new (GDATA_TYPE_SERVICE, NULL);
 
 	/* Just test setting and getting the locale */
 	g_assert (gdata_service_get_locale (service) == NULL);
diff --git a/gdata/tests/memory.c b/gdata/tests/memory.c
index 23498f8..67bbb4a 100644
--- a/gdata/tests/memory.c
+++ b/gdata/tests/memory.c
@@ -26,15 +26,17 @@ static void
 test_query_events (void)
 {
 	GDataFeed *feed, *calendar_feed;
+	GDataClientLoginAuthorizer *authorizer;
 	GDataCalendarCalendar *calendar;
 	GDataCalendarService *service;
 	GList *calendars;
 	GError *error = NULL;
 
-	service = gdata_calendar_service_new (CLIENT_ID);
+	authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_CALENDAR_SERVICE);
+	service = gdata_calendar_service_new (GDATA_AUTHORIZER (authorizer));
 
 	/* Log in */
-	gdata_service_authenticate (GDATA_SERVICE (service), USERNAME, PASSWORD, NULL, &error);
+	gdata_client_login_authorizer_authenticate (authorizer, USERNAME, PASSWORD, NULL, &error);
 	g_assert_no_error (error);
 
 	/* Get a calendar */
@@ -54,6 +56,7 @@ test_query_events (void)
 	g_object_unref (feed);
 	g_object_unref (calendar);
 	g_object_unref (service);
+	g_object_unref (authorizer);
 }
 
 int
diff --git a/gdata/tests/picasaweb.c b/gdata/tests/picasaweb.c
index 6871fce..2014a10 100644
--- a/gdata/tests/picasaweb.c
+++ b/gdata/tests/picasaweb.c
@@ -40,37 +40,37 @@ static void
 test_authentication (void)
 {
 	gboolean retval;
-	GDataService *service;
+	GDataClientLoginAuthorizer *authorizer;
 	GError *error = NULL;
 
-	/* Create a service */
-	service = GDATA_SERVICE (gdata_picasaweb_service_new (CLIENT_ID));
+	/* Create an authorizer */
+	authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_PICASAWEB_SERVICE);
 
-	g_assert (service != NULL);
-	g_assert (GDATA_IS_SERVICE (service));
-	g_assert_cmpstr (gdata_service_get_client_id (service), ==, CLIENT_ID);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (authorizer), ==, CLIENT_ID);
 
 	/* Log in */
-	retval = gdata_service_authenticate (service, PW_USERNAME, PASSWORD, NULL, &error);
+	retval = gdata_client_login_authorizer_authenticate (authorizer, PW_USERNAME, PASSWORD, NULL, &error);
 	g_assert_no_error (error);
 	g_assert (retval == TRUE);
 	g_clear_error (&error);
 
 	/* Check all is as it should be */
-	g_assert (gdata_service_is_authenticated (service) == TRUE);
-	g_assert_cmpstr (gdata_service_get_username (service), ==, PW_USERNAME);
-	g_assert_cmpstr (gdata_service_get_password (service), ==, PASSWORD);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, PW_USERNAME);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, PASSWORD);
 
-	g_object_unref (service);
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	                                                     gdata_picasaweb_service_get_primary_authorization_domain ()) == TRUE);
+
+	g_object_unref (authorizer);
 }
 
 static void
-test_authentication_async_cb (GDataService *service, GAsyncResult *async_result, GMainLoop *main_loop)
+test_authentication_async_cb (GDataClientLoginAuthorizer *authorizer, GAsyncResult *async_result, GMainLoop *main_loop)
 {
 	gboolean retval;
 	GError *error = NULL;
 
-	retval = gdata_service_authenticate_finish (service, async_result, &error);
+	retval = gdata_client_login_authorizer_authenticate_finish (authorizer, async_result, &error);
 	g_assert_no_error (error);
 	g_assert (retval == TRUE);
 	g_clear_error (&error);
@@ -78,29 +78,32 @@ test_authentication_async_cb (GDataService *service, GAsyncResult *async_result,
 	g_main_loop_quit (main_loop);
 
 	/* Check all is as it should be */
-	g_assert (gdata_service_is_authenticated (service) == TRUE);
-	g_assert_cmpstr (gdata_service_get_username (service), ==, PW_USERNAME);
-	g_assert_cmpstr (gdata_service_get_password (service), ==, PASSWORD);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, PW_USERNAME);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, PASSWORD);
+
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	                                                     gdata_picasaweb_service_get_primary_authorization_domain ()) == TRUE);
 }
 
 static void
 test_authentication_async (void)
 {
-	GDataService *service;
-	GMainLoop *main_loop = g_main_loop_new (NULL, TRUE);
+	GMainLoop *main_loop;
+	GDataClientLoginAuthorizer *authorizer;
 
-	/* Create a service */
-	service = GDATA_SERVICE (gdata_picasaweb_service_new (CLIENT_ID));
+	/* Create an authorizer */
+	authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_PICASAWEB_SERVICE);
 
-	g_assert (service != NULL);
-	g_assert (GDATA_IS_SERVICE (service));
+	g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (authorizer), ==, CLIENT_ID);
 
-	gdata_service_authenticate_async (service, PW_USERNAME, PASSWORD, NULL, (GAsyncReadyCallback) test_authentication_async_cb, main_loop);
+	main_loop = g_main_loop_new (NULL, TRUE);
+	gdata_client_login_authorizer_authenticate_async (authorizer, PW_USERNAME, PASSWORD, NULL,
+	                                                  (GAsyncReadyCallback) test_authentication_async_cb, main_loop);
 
 	g_main_loop_run (main_loop);
-	g_main_loop_unref (main_loop);
 
-	g_object_unref (service);
+	g_main_loop_unref (main_loop);
+	g_object_unref (authorizer);
 }
 
 static void
@@ -384,7 +387,7 @@ test_photo (gconstpointer service)
 
 	content = GDATA_MEDIA_CONTENT (list->data);
 	g_assert_cmpstr (gdata_media_content_get_uri (content), ==,
-	                 "https://lh3.googleusercontent.com/_1kdcGyvOb8c/SfQFWPnuovI/AAAAAAAAAB0/MI0L4Sd11Eg/100_0269.jpg";);
+	                 "https://lh3.googleusercontent.com/--1R6jzZZ1oI/SfQFWPnuovI/AAAAAAAAAB0/WdINsvmFPf8/100_0269.jpg";);
 	g_assert_cmpstr (gdata_media_content_get_content_type (content), ==, "image/jpeg");
 	g_assert_cmpuint (gdata_media_content_get_width (content), ==, 1600);
 	g_assert_cmpuint (gdata_media_content_get_height (content), ==, 1200);
@@ -401,7 +404,7 @@ test_photo (gconstpointer service)
 
 	thumbnail = GDATA_MEDIA_THUMBNAIL (list->data);
 	g_assert_cmpstr (gdata_media_thumbnail_get_uri (thumbnail), ==,
-	                 "https://lh3.googleusercontent.com/_1kdcGyvOb8c/SfQFWPnuovI/AAAAAAAAAB0/MI0L4Sd11Eg/s288/100_0269.jpg";);
+	                 "https://lh3.googleusercontent.com/--1R6jzZZ1oI/SfQFWPnuovI/AAAAAAAAAB0/WdINsvmFPf8/s288/100_0269.jpg";);
 	g_assert_cmpuint (gdata_media_thumbnail_get_width (thumbnail), ==, 288);
 	g_assert_cmpuint (gdata_media_thumbnail_get_height (thumbnail), ==, 216);
 	g_assert_cmpint (gdata_media_thumbnail_get_time (thumbnail), ==, -1); /* PicasaWeb doesn't set anything better */
@@ -454,7 +457,7 @@ test_photo_feed_entry (gconstpointer service)
 	g_assert_cmpint (gdata_entry_get_published (photo_entry), ==, 1240728920);
 	g_assert (gdata_entry_get_content (photo_entry) == NULL);
 	g_assert_cmpstr (gdata_entry_get_content_uri (photo_entry), ==,
-	                 "https://lh3.googleusercontent.com/_1kdcGyvOb8c/SfQFWPnuovI/AAAAAAAAAB0/MI0L4Sd11Eg/100_0269.jpg";);
+	                 "https://lh3.googleusercontent.com/--1R6jzZZ1oI/SfQFWPnuovI/AAAAAAAAAB0/WdINsvmFPf8/100_0269.jpg";);
 
 	xml = gdata_parsable_get_xml (GDATA_PARSABLE (photo_entry));
 	g_assert_cmpstr (xml, !=, NULL);
@@ -512,7 +515,8 @@ test_photo_single (gconstpointer service)
 
 	const gchar *entry_id =
 		"https://picasaweb.google.com/data/entry/user/libgdata.picasaweb/albumid/5328889949261497249/photoid/5328890138794566386";;
-	photo = gdata_service_query_single_entry (GDATA_SERVICE (service), entry_id, NULL, GDATA_TYPE_PICASAWEB_FILE, NULL, &error);
+	photo = gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_picasaweb_service_get_primary_authorization_domain (),
+	                                          entry_id, NULL, GDATA_TYPE_PICASAWEB_FILE, NULL, &error);
 
 	g_assert_no_error (error);
 	g_assert (photo != NULL);
@@ -668,7 +672,7 @@ test_album (gconstpointer service)
 	content = GDATA_MEDIA_CONTENT (contents->data);
 
 	g_assert_cmpstr (gdata_media_content_get_uri (content), ==,
-	                 "https://lh5.googleusercontent.com/_1kdcGyvOb8c/SfQFLNjhg6E/AAAAAAAAAB8/2WtMjZCa71k/TestAlbum1VenicePublic.jpg";);
+	                 "https://lh5.googleusercontent.com/-Cdx1RdQou5E/SfQFLNjhg6E/AAAAAAAAAB8/DZlVjtcAqjg/TestAlbum1VenicePublic.jpg";);
 	g_assert_cmpstr (gdata_media_content_get_content_type (content), ==, "image/jpeg");
 	g_assert_cmpuint (gdata_media_content_get_medium (content), ==, GDATA_MEDIA_IMAGE);
 
@@ -684,7 +688,7 @@ test_album (gconstpointer service)
 	thumbnail = GDATA_MEDIA_THUMBNAIL (thumbnails->data);
 
 	g_assert_cmpstr (gdata_media_thumbnail_get_uri (thumbnail), ==,
-	                 "https://lh5.googleusercontent.com/_1kdcGyvOb8c/SfQFLNjhg6E/AAAAAAAAAB8/2WtMjZCa71k/s160-c/TestAlbum1VenicePublic.jpg";);
+	                 "https://lh5.googleusercontent.com/-Cdx1RdQou5E/SfQFLNjhg6E/AAAAAAAAAB8/DZlVjtcAqjg/s160-c/TestAlbum1VenicePublic.jpg";);
 	g_assert_cmpint (gdata_media_thumbnail_get_time (thumbnail), ==, -1); /* PicasaWeb doesn't set anything better */
 	g_assert_cmpint (gdata_media_thumbnail_get_width (thumbnail), ==, 160);
 	g_assert_cmpint (gdata_media_thumbnail_get_height (thumbnail), ==, 160);
@@ -800,14 +804,15 @@ test_insert_album (gconstpointer service)
 
 	album_found = FALSE;
 	for (node = albums; node != NULL; node = node->next) {
-		if (g_strcmp0 (gdata_entry_get_title (GDATA_ENTRY (node->data)), "Thanksgiving photos")) {
+		if (g_strcmp0 (gdata_entry_get_title (GDATA_ENTRY (node->data)), "Thanksgiving photos") == 0) {
 			album_found = TRUE;
 		}
 	}
 	g_assert (album_found);
 
 	/* Clean up the evidence */
-	gdata_service_delete_entry (GDATA_SERVICE (service), GDATA_ENTRY (inserted_album), NULL, &error);
+	gdata_service_delete_entry (GDATA_SERVICE (service), gdata_picasaweb_service_get_primary_authorization_domain (),
+	                            GDATA_ENTRY (inserted_album), NULL, &error);
 	g_assert_no_error (error);
 
 	g_object_unref (album_feed);
@@ -830,7 +835,8 @@ test_insert_album_async_cb (GDataService *service, GAsyncResult *async_result, G
 	g_assert_cmpstr (gdata_entry_get_title (entry), ==, "Asynchronous album!");
 
 	/* Delete the album, just to be tidy */
-	g_assert (gdata_service_delete_entry (GDATA_SERVICE (service), entry, NULL, &error) == TRUE);
+	g_assert (gdata_service_delete_entry (GDATA_SERVICE (service), gdata_picasaweb_service_get_primary_authorization_domain (),
+	                                      entry, NULL, &error) == TRUE);
 	g_assert_no_error (error);
 	g_clear_error (&error);
 
@@ -1054,7 +1060,8 @@ teardown_upload (UploadData *data, gconstpointer service)
 {
 	/* Delete the uploaded photo (don't worry if this fails) */
 	if (data->updated_photo != NULL) {
-		gdata_service_delete_entry (GDATA_SERVICE (service), GDATA_ENTRY (data->updated_photo), NULL, NULL);
+		gdata_service_delete_entry (GDATA_SERVICE (service), gdata_picasaweb_service_get_primary_authorization_domain (),
+		                            GDATA_ENTRY (data->updated_photo), NULL, NULL);
 		g_object_unref (data->updated_photo);
 	}
 
@@ -1433,13 +1440,16 @@ int
 main (int argc, char *argv[])
 {
 	gint retval;
+	GDataAuthorizer *authorizer = NULL;
 	GDataService *service = NULL;
 
 	gdata_test_init (argc, argv);
 
 	if (gdata_test_internet () == TRUE) {
-		service = GDATA_SERVICE (gdata_picasaweb_service_new (CLIENT_ID));
-		gdata_service_authenticate (service, PW_USERNAME, PASSWORD, NULL, NULL);
+		authorizer = GDATA_AUTHORIZER (gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_PICASAWEB_SERVICE));
+		gdata_client_login_authorizer_authenticate (GDATA_CLIENT_LOGIN_AUTHORIZER (authorizer), PW_USERNAME, PASSWORD, NULL, NULL);
+
+		service = GDATA_SERVICE (gdata_picasaweb_service_new (authorizer));
 
 		g_test_add_func ("/picasaweb/authentication", test_authentication);
 		g_test_add_func ("/picasaweb/authentication_async", test_authentication_async);
diff --git a/gdata/tests/streams.c b/gdata/tests/streams.c
index 1eaeff9..330791c 100644
--- a/gdata/tests/streams.c
+++ b/gdata/tests/streams.c
@@ -110,8 +110,8 @@ test_download_stream_download_content_length (void)
 
 	/* Create a new download stream connected to the server */
 	download_uri = g_strdup_printf ("http://127.0.0.1:%u/";, soup_server_get_port (server));
-	service = GDATA_SERVICE (gdata_youtube_service_new ("developer-key", "client-id"));
-	download_stream = gdata_download_stream_new (service, download_uri, NULL);
+	service = GDATA_SERVICE (gdata_youtube_service_new ("developer-key", NULL));
+	download_stream = gdata_download_stream_new (service, NULL, download_uri, NULL);
 	g_object_unref (service);
 	g_free (download_uri);
 
@@ -216,8 +216,8 @@ test_upload_stream_upload_no_entry_content_length (void)
 
 	/* Create a new upload stream uploading to the server */
 	upload_uri = g_strdup_printf ("http://127.0.0.1:%u/";, soup_server_get_port (server));
-	service = GDATA_SERVICE (gdata_youtube_service_new ("developer-key", "client-id"));
-	upload_stream = gdata_upload_stream_new (service, SOUP_METHOD_POST, upload_uri, NULL, "slug", "text/plain", NULL);
+	service = GDATA_SERVICE (gdata_youtube_service_new ("developer-key", NULL));
+	upload_stream = gdata_upload_stream_new (service, NULL, SOUP_METHOD_POST, upload_uri, NULL, "slug", "text/plain", NULL);
 	g_object_unref (service);
 	g_free (upload_uri);
 
diff --git a/gdata/tests/youtube.c b/gdata/tests/youtube.c
index 7852dd3..95adc3d 100644
--- a/gdata/tests/youtube.c
+++ b/gdata/tests/youtube.c
@@ -29,38 +29,37 @@ static void
 test_authentication (void)
 {
 	gboolean retval;
-	GDataService *service;
+	GDataClientLoginAuthorizer *authorizer;
 	GError *error = NULL;
 
-	/* Create a service */
-	service = GDATA_SERVICE (gdata_youtube_service_new (DEVELOPER_KEY, CLIENT_ID));
+	/* Create an authorizer */
+	authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_YOUTUBE_SERVICE);
 
-	g_assert (service != NULL);
-	g_assert (GDATA_IS_SERVICE (service));
-	g_assert_cmpstr (gdata_service_get_client_id (service), ==, CLIENT_ID);
-	g_assert_cmpstr (gdata_youtube_service_get_developer_key (GDATA_YOUTUBE_SERVICE (service)), ==, DEVELOPER_KEY);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (authorizer), ==, CLIENT_ID);
 
 	/* Log in */
-	retval = gdata_service_authenticate (service, USERNAME, PASSWORD, NULL, &error);
+	retval = gdata_client_login_authorizer_authenticate (authorizer, USERNAME, PASSWORD, NULL, &error);
 	g_assert_no_error (error);
 	g_assert (retval == TRUE);
 	g_clear_error (&error);
 
 	/* Check all is as it should be */
-	g_assert (gdata_service_is_authenticated (service) == TRUE);
-	g_assert_cmpstr (gdata_service_get_username (service), ==, USERNAME);
-	g_assert_cmpstr (gdata_service_get_password (service), ==, PASSWORD);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, USERNAME);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, PASSWORD);
 
-	g_object_unref (service);
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	                                                     gdata_youtube_service_get_primary_authorization_domain ()) == TRUE);
+
+	g_object_unref (authorizer);
 }
 
 static void
-test_authentication_async_cb (GDataService *service, GAsyncResult *async_result, GMainLoop *main_loop)
+test_authentication_async_cb (GDataClientLoginAuthorizer *authorizer, GAsyncResult *async_result, GMainLoop *main_loop)
 {
 	gboolean retval;
 	GError *error = NULL;
 
-	retval = gdata_service_authenticate_finish (service, async_result, &error);
+	retval = gdata_client_login_authorizer_authenticate_finish (authorizer, async_result, &error);
 	g_assert_no_error (error);
 	g_assert (retval == TRUE);
 	g_clear_error (&error);
@@ -68,28 +67,46 @@ test_authentication_async_cb (GDataService *service, GAsyncResult *async_result,
 	g_main_loop_quit (main_loop);
 
 	/* Check all is as it should be */
-	g_assert (gdata_service_is_authenticated (service) == TRUE);
-	g_assert_cmpstr (gdata_service_get_username (service), ==, USERNAME);
-	g_assert_cmpstr (gdata_service_get_password (service), ==, PASSWORD);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, USERNAME);
+	g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, PASSWORD);
+
+	g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+	                                                     gdata_youtube_service_get_primary_authorization_domain ()) == TRUE);
 }
 
 static void
 test_authentication_async (void)
 {
 	GMainLoop *main_loop;
-	GDataService *service;
+	GDataClientLoginAuthorizer *authorizer;
 
-	/* Create a service */
-	service = GDATA_SERVICE (gdata_youtube_service_new (DEVELOPER_KEY, CLIENT_ID));
+	/* Create an authorizer */
+	authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_YOUTUBE_SERVICE);
 
-	g_assert (service != NULL);
-	g_assert (GDATA_IS_SERVICE (service));
+	g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (authorizer), ==, CLIENT_ID);
 
 	main_loop = g_main_loop_new (NULL, TRUE);
-	gdata_service_authenticate_async (service, USERNAME, PASSWORD, NULL, (GAsyncReadyCallback) test_authentication_async_cb, main_loop);
+	gdata_client_login_authorizer_authenticate_async (authorizer, USERNAME, PASSWORD, NULL,
+	                                                  (GAsyncReadyCallback) test_authentication_async_cb, main_loop);
 
 	g_main_loop_run (main_loop);
+
 	g_main_loop_unref (main_loop);
+	g_object_unref (authorizer);
+}
+
+static void
+test_service_properties (void)
+{
+	GDataService *service;
+
+	/* Create a service */
+	service = GDATA_SERVICE (gdata_youtube_service_new (DEVELOPER_KEY, NULL));
+
+	g_assert (service != NULL);
+	g_assert (GDATA_IS_SERVICE (service));
+	g_assert_cmpstr (gdata_youtube_service_get_developer_key (GDATA_YOUTUBE_SERVICE (service)), ==, DEVELOPER_KEY);
+
 	g_object_unref (service);
 }
 
@@ -314,7 +331,8 @@ teardown_upload (UploadData *data, gconstpointer service)
 {
 	/* Delete the uploaded video, if possible */
 	if (data->updated_video != NULL) {
-		gdata_service_delete_entry (GDATA_SERVICE (service), GDATA_ENTRY (data->updated_video), NULL, NULL);
+		gdata_service_delete_entry (GDATA_SERVICE (service), gdata_youtube_service_get_primary_authorization_domain (),
+		                            GDATA_ENTRY (data->updated_video), NULL, NULL);
 		g_object_unref (data->updated_video);
 	}
 
@@ -1101,7 +1119,9 @@ test_query_single (gconstpointer service)
 	GDataYouTubeVideo *video;
 	GError *error = NULL;
 
-	video = GDATA_YOUTUBE_VIDEO (gdata_service_query_single_entry (GDATA_SERVICE (service), "tag:youtube.com,2008:video:_LeQuMpwbW4", NULL,
+	video = GDATA_YOUTUBE_VIDEO (gdata_service_query_single_entry (GDATA_SERVICE (service),
+	                                                               gdata_youtube_service_get_primary_authorization_domain (),
+	                                                               "tag:youtube.com,2008:video:_LeQuMpwbW4", NULL,
 	                                                               GDATA_TYPE_YOUTUBE_VIDEO, NULL, &error));
 
 	g_assert_no_error (error);
@@ -1137,7 +1157,8 @@ test_query_single_async (gconstpointer service)
 {
 	GMainLoop *main_loop = g_main_loop_new (NULL, TRUE);
 
-	gdata_service_query_single_entry_async (GDATA_SERVICE (service), "tag:youtube.com,2008:video:_LeQuMpwbW4", NULL, GDATA_TYPE_YOUTUBE_VIDEO,
+	gdata_service_query_single_entry_async (GDATA_SERVICE (service), gdata_youtube_service_get_primary_authorization_domain (),
+	                                        "tag:youtube.com,2008:video:_LeQuMpwbW4", NULL, GDATA_TYPE_YOUTUBE_VIDEO,
 	                                        NULL, (GAsyncReadyCallback) test_query_single_async_cb, main_loop);
 
 	g_main_loop_run (main_loop);
@@ -1268,14 +1289,16 @@ setup_batch (BatchData *data, gconstpointer service)
 
 	/* We can't insert new videos as they'd just hit the moderation queue and cause tests to fail. Instead, we rely on two videos already existing
 	 * on the server with the given IDs. */
-	video = gdata_service_query_single_entry (GDATA_SERVICE (service), "tag:youtube.com,2008:video:RzR2k8yo4NY", NULL, GDATA_TYPE_YOUTUBE_VIDEO,
+	video = gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_youtube_service_get_primary_authorization_domain (),
+	                                          "tag:youtube.com,2008:video:RzR2k8yo4NY", NULL, GDATA_TYPE_YOUTUBE_VIDEO,
 	                                          NULL, &error);
 	g_assert_no_error (error);
 	g_assert (GDATA_IS_YOUTUBE_VIDEO (video));
 
 	data->new_video = video;
 
-	video = gdata_service_query_single_entry (GDATA_SERVICE (service), "tag:youtube.com,2008:video:VppEcVz8qaI", NULL, GDATA_TYPE_YOUTUBE_VIDEO,
+	video = gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_youtube_service_get_primary_authorization_domain (),
+	                                          "tag:youtube.com,2008:video:VppEcVz8qaI", NULL, GDATA_TYPE_YOUTUBE_VIDEO,
 	                                          NULL, &error);
 	g_assert_no_error (error);
 	g_assert (GDATA_IS_YOUTUBE_VIDEO (video));
@@ -1294,11 +1317,12 @@ test_batch (BatchData *data, gconstpointer service)
 
 	/* Here we hardcode the feed URI, but it should really be extracted from a video feed, as the GDATA_LINK_BATCH link.
 	 * It looks like this feed is read-only, so we can only test querying. */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "http://gdata.youtube.com/feeds/api/videos/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_youtube_service_get_primary_authorization_domain (),
+	                                              "https://gdata.youtube.com/feeds/api/videos/batch";);
 
 	/* Check the properties of the operation */
 	g_assert (gdata_batch_operation_get_service (operation) == service);
-	g_assert_cmpstr (gdata_batch_operation_get_feed_uri (operation), ==, "http://gdata.youtube.com/feeds/api/videos/batch";);
+	g_assert_cmpstr (gdata_batch_operation_get_feed_uri (operation), ==, "https://gdata.youtube.com/feeds/api/videos/batch";);
 
 	g_object_get (operation,
 	              "service", &service2,
@@ -1306,7 +1330,7 @@ test_batch (BatchData *data, gconstpointer service)
 	              NULL);
 
 	g_assert (service2 == service);
-	g_assert_cmpstr (feed_uri, ==, "http://gdata.youtube.com/feeds/api/videos/batch";);
+	g_assert_cmpstr (feed_uri, ==, "https://gdata.youtube.com/feeds/api/videos/batch";);
 
 	g_object_unref (service2);
 	g_free (feed_uri);
@@ -1321,7 +1345,8 @@ test_batch (BatchData *data, gconstpointer service)
 	g_object_unref (operation);
 
 	/* Run another batch operation to query the two entries */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "http://gdata.youtube.com/feeds/api/videos/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_youtube_service_get_primary_authorization_domain (),
+	                                              "https://gdata.youtube.com/feeds/api/videos/batch";);
 	op_id = gdata_test_batch_operation_query (operation, gdata_entry_get_id (data->new_video), GDATA_TYPE_YOUTUBE_VIDEO, data->new_video, NULL,
 	                                          NULL);
 	op_id2 = gdata_test_batch_operation_query (operation, gdata_entry_get_id (data->new_video2), GDATA_TYPE_YOUTUBE_VIDEO, data->new_video2, NULL,
@@ -1354,7 +1379,8 @@ test_batch_async (BatchData *data, gconstpointer service)
 	GMainLoop *main_loop;
 
 	/* Run an async query operation on the video */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "http://gdata.youtube.com/feeds/api/videos/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_youtube_service_get_primary_authorization_domain (),
+	                                              "https://gdata.youtube.com/feeds/api/videos/batch";);
 	gdata_test_batch_operation_query (operation, gdata_entry_get_id (data->new_video), GDATA_TYPE_YOUTUBE_VIDEO, data->new_video, NULL, NULL);
 
 	main_loop = g_main_loop_new (NULL, TRUE);
@@ -1386,7 +1412,8 @@ test_batch_async_cancellation (BatchData *data, gconstpointer service)
 	GError *error = NULL;
 
 	/* Run an async query operation on the video */
-	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "http://gdata.youtube.com/feeds/api/videos/batch";);
+	operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_youtube_service_get_primary_authorization_domain (),
+	                                              "https://gdata.youtube.com/feeds/api/videos/batch";);
 	gdata_test_batch_operation_query (operation, gdata_entry_get_id (data->new_video), GDATA_TYPE_YOUTUBE_VIDEO, data->new_video, NULL, &error);
 
 	main_loop = g_main_loop_new (NULL, TRUE);
@@ -1416,13 +1443,16 @@ int
 main (int argc, char *argv[])
 {
 	gint retval;
+	GDataAuthorizer *authorizer = NULL;
 	GDataService *service = NULL;
 
 	gdata_test_init (argc, argv);
 
 	if (gdata_test_internet () == TRUE) {
-		service = GDATA_SERVICE (gdata_youtube_service_new (DEVELOPER_KEY, CLIENT_ID));
-		gdata_service_authenticate (service, USERNAME, PASSWORD, NULL, NULL);
+		authorizer = GDATA_AUTHORIZER (gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_YOUTUBE_SERVICE));
+		gdata_client_login_authorizer_authenticate (GDATA_CLIENT_LOGIN_AUTHORIZER (authorizer), USERNAME, PASSWORD, NULL, NULL);
+
+		service = GDATA_SERVICE (gdata_youtube_service_new (DEVELOPER_KEY, authorizer));
 
 		g_test_add_func ("/youtube/authentication", test_authentication);
 		g_test_add_func ("/youtube/authentication_async", test_authentication_async);
@@ -1446,6 +1476,8 @@ main (int argc, char *argv[])
 		g_test_add ("/youtube/batch/async/cancellation", BatchData, service, setup_batch, test_batch_async_cancellation, teardown_batch);
 	}
 
+	g_test_add_func ("/youtube/service/properties", test_service_properties);
+
 	g_test_add_func ("/youtube/parsing/app:control", test_parsing_app_control);
 	/*g_test_add_func ("/youtube/parsing/comments/feedLink", test_parsing_comments_feed_link);*/
 	g_test_add_func ("/youtube/parsing/yt:recorded", test_parsing_yt_recorded);
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6b0ce68..5c7efd8 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -2,6 +2,7 @@
 # Please keep this file sorted alphabetically.
 [encoding: UTF-8]
 gdata/gdata-access-handler.c
+gdata/gdata-client-login-authorizer.c
 gdata/gdata-download-stream.c
 gdata/gdata-entry.c
 gdata/gdata-feed.c



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]