docs: function() {
  
  		// Create an object which inherits its parent as the prototype and constructs a new event chain.
  		var _this = this;
  		var utils = _this.utils;
  		var error = utils.error;
  		var promise = utils.Promise();
  
  		// Get parameters
  		var p = utils.args({network: 's', options: 'o', callback: 'f'}, arguments);
  
  		// Local vars
  		var url;
  
  		// Get all the custom options and store to be appended to the querystring
  		var qs = utils.diffKey(p.options, _this.settings);
  
  		// Merge/override options with app defaults
  		var opts = p.options = utils.merge(_this.settings, p.options || {});
  
  		// Merge/override options with app defaults
  		opts.popup = utils.merge(_this.settings.popup, p.options.popup || {});
  
  		// Network
  		p.network = p.network || _this.settings.default_service;
  
  		// Bind callback to both reject and fulfill states
  		promise.proxy.then(p.callback, p.callback);
  
  		// Trigger an event on the global listener
  		function emit(s, value) {
  			hello.emit(s, value);
  		}
  
  		promise.proxy.then(emit.bind(this, 'auth.login auth'), emit.bind(this, 'auth.failed auth'));
  
  		// Is our service valid?
  		if (typeof (p.network) !== 'string' || !(p.network in _this.services)) {
  			// Trigger the default login.
  			// Ahh we dont have one.
  			return promise.reject(error('invalid_network', 'The provided network was not recognized'));
  		}
  
  		var provider = _this.services[p.network];
  
  		// Create a global listener to capture events triggered out of scope
  		var callbackId = utils.globalEvent(function(obj) {
  
  			// The responseHandler returns a string, lets save this locally
  			if (obj) {
  				if (typeof (obj) == 'string') {
  					obj = JSON.parse(obj);
  				}
  			}
  			else {
  				obj = error('cancelled', 'The authentication was not completed');
  			}
  
  			// Handle these response using the local
  			// Trigger on the parent
  			if (!obj.error) {
  
  				// Save on the parent window the new credentials
  				// This fixes an IE10 bug i think... atleast it does for me.
  				utils.store(obj.network, obj);
  
  				// Fulfill a successful login
  				promise.fulfill({
  					network: obj.network,
  					authResponse: obj
  				});
  			}
  			else {
  				// Reject a successful login
  				promise.reject(obj);
  			}
  		});
  
  		var redirectUri = utils.url(opts.redirect_uri).href;
  
  		// May be a space-delimited list of multiple, complementary types
  		var responseType = provider.oauth.response_type || opts.response_type;
  
  		// Fallback to token if the module hasn't defined a grant url
  		if (/\bcode\b/.test(responseType) && !provider.oauth.grant) {
  			responseType = responseType.replace(/\bcode\b/, 'token');
  		}
  
  		// Query string parameters, we may pass our own arguments to form the querystring
  		p.qs = utils.merge(qs, {
  			client_id: encodeURIComponent(provider.id),
  			response_type: encodeURIComponent(responseType),
  			redirect_uri: encodeURIComponent(redirectUri),
  			state: {
  				client_id: provider.id,
  				network: p.network,
  				display: opts.display,
  				callback: callbackId,
  				state: opts.state,
  				redirect_uri: redirectUri
  			}
  		});
  
  		// Get current session for merging scopes, and for quick auth response
  		var session = utils.store(p.network);
  
  		// Scopes (authentication permisions)
  		// Ensure this is a string - IE has a problem moving Arrays between windows
  		// Append the setup scope
  		var SCOPE_SPLIT = /[,\s]+/;
  
  		// Include default scope settings (cloned).
  		var scope = _this.settings.scope ? [_this.settings.scope.toString()] : [];
  
  		// Extend the providers scope list with the default
  		var scopeMap = utils.merge(_this.settings.scope_map, provider.scope || {});
  
  		// Add user defined scopes...
  		if (opts.scope) {
  			scope.push(opts.scope.toString());
  		}
  
  		// Append scopes from a previous session.
  		// This helps keep app credentials constant,
  		// Avoiding having to keep tabs on what scopes are authorized
  		if (session && 'scope' in session && session.scope instanceof String) {
  			scope.push(session.scope);
  		}
  
  		// Join and Split again
  		scope = scope.join(',').split(SCOPE_SPLIT);
  
  		// Format remove duplicates and empty values
  		scope = utils.unique(scope).filter(filterEmpty);
  
  		// Save the the scopes to the state with the names that they were requested with.
  		p.qs.state.scope = scope.join(',');
  
  		// Map scopes to the providers naming convention
  		scope = scope.map(function(item) {
  			// Does this have a mapping?
  			return (item in scopeMap) ? scopeMap[item] : item;
  		});
  
  		// Stringify and Arrayify so that double mapped scopes are given the chance to be formatted
  		scope = scope.join(',').split(SCOPE_SPLIT);
  
  		// Again...
  		// Format remove duplicates and empty values
  		scope = utils.unique(scope).filter(filterEmpty);
  
  		// Join with the expected scope delimiter into a string
  		p.qs.scope = scope.join(provider.scope_delim || ',');
  
  		// Is the user already signed in with the appropriate scopes, valid access_token?
  		if (opts.force === false) {
  
  			if (session && 'access_token' in session && session.access_token && 'expires' in session && session.expires > ((new Date()).getTime() / 1e3)) {
  				// What is different about the scopes in the session vs the scopes in the new login?
  				var diff = utils.diff((session.scope || '').split(SCOPE_SPLIT), (p.qs.state.scope || '').split(SCOPE_SPLIT));
  				if (diff.length === 0) {
  
  					// OK trigger the callback
  					promise.fulfill({
  						unchanged: true,
  						network: p.network,
  						authResponse: session
  					});
  
  					// Nothing has changed
  					return promise;
  				}
  			}
  		}
  
  		// Page URL
  		if (opts.display === 'page' && opts.page_uri) {
  			// Add a page location, place to endup after session has authenticated
  			p.qs.state.page_uri = utils.url(opts.page_uri).href;
  		}
  
  		// Bespoke
  		// Override login querystrings from auth_options
  		if ('login' in provider && typeof (provider.login) === 'function') {
  			// Format the paramaters according to the providers formatting function
  			provider.login(p);
  		}
  
  		// Add OAuth to state
  		// Where the service is going to take advantage of the oauth_proxy
  		if (!/\btoken\b/.test(responseType) ||
  		parseInt(provider.oauth.version, 10) < 2 ||
  		(opts.display === 'none' && provider.oauth.grant && session && session.refresh_token)) {
  
  			// Add the oauth endpoints
  			p.qs.state.oauth = provider.oauth;
  
  			// Add the proxy url
  			p.qs.state.oauth_proxy = opts.oauth_proxy;
  
  		}
  
  		// Convert state to a string
  		p.qs.state = encodeURIComponent(JSON.stringify(p.qs.state));
  
  		// URL
  		if (parseInt(provider.oauth.version, 10) === 1) {
  
  			// Turn the request to the OAuth Proxy for 3-legged auth
  			url = utils.qs(opts.oauth_proxy, p.qs, encodeFunction);
  		}
  
  		// Refresh token
  		else if (opts.display === 'none' && provider.oauth.grant && session && session.refresh_token) {
  
  			// Add the refresh_token to the request
  			p.qs.refresh_token = session.refresh_token;
  
  			// Define the request path
  			url = utils.qs(opts.oauth_proxy, p.qs, encodeFunction);
  		}
  		else {
  			url = utils.qs(provider.oauth.auth, p.qs, encodeFunction);
  		}
  
  		// Broadcast this event as an auth:init
  		emit('auth.init', p);
  
  		// Execute
  		// Trigger how we want self displayed
  		if (opts.display === 'none') {
  			// Sign-in in the background, iframe
  			utils.iframe(url, redirectUri);
  		}
  
  		// Triggering popup?
  		else if (opts.display === 'popup') {
  
  			var popup = utils.popup(url, redirectUri, opts.popup);
  
  			var timer = setInterval(function() {
  				if (!popup || popup.closed) {
  					clearInterval(timer);
  					if (!promise.state) {
  
  						var response = error('cancelled', 'Login has been cancelled');
  
  						if (!popup) {
  							response = error('blocked', 'Popup was blocked');
  						}
  
  						response.network = p.network;
  
  						promise.reject(response);
  					}
  				}
  			}, 100);
  		}
  
  		else {
  			window.location = url;
  		}
  
  		return promise.proxy;
  
  		function encodeFunction(s) {return s;}
  
  		function filterEmpty(s) {return !!s;}
  	}