GoogleOAuth2 class
An OAuth2 authentication context.
class GoogleOAuth2 extends OAuth2 { final String _clientId; final List<String> _scopes; final List<String> _request_visible_actions; final String _provider; final Function _tokenLoaded; Future<_ProxyChannel> _channel; /// Future of the token we're waiting for. Future<Token> _tokenFuture; /// Destination for not-yet-validated tokens we're waiting to receive over /// the proxy channel. Completer<Token> _tokenCompleter; /// The last fetched token. Token __token; // Double-underscore because it has a private setter _token. /// Creates an OAuth2 context for the application identified by [clientId] /// and the permissions described by [scopes]. /// If [tokenLoaded] is provided, it will be called with a [Token] when one /// is available. This can be used e.g. to set up a 'logged in' view. GoogleOAuth2( String this._clientId, List<String> this._scopes, { List<String> request_visible_actions: null, String provider: "https://accounts.google.com/o/oauth2/", tokenLoaded(Token token), bool autoLogin: false } ) : _provider = provider, _tokenLoaded = tokenLoaded, _request_visible_actions = request_visible_actions, super() { _channel = _createFutureChannel(); // Attempt an immediate login, we may already be authorized. if (autoLogin) { login(immediate:true) .then((t) => print("Automatic login successful")) .catchError((e) => print("$e")); } } /// Set up the proxy iframe in the provider's origin that will receive /// postMessages and relay them to us. /// This completes asynchronously as the proxy iframe is not ready to use /// until we've received an 'oauth2relayReady' message from it. Future<_ProxyChannel> _createFutureChannel() { final completer = new Completer<_ProxyChannel>(); var channel; channel = new _ProxyChannel(_provider, (subject, args) { switch (subject) { case "oauth2relayReady": completer.complete(channel); break; case "oauth2callback": try { Token token = Token._parse(args[0]); _tokenCompleter.complete(token); } catch (exception) { _tokenCompleter.completeError(exception); } break; } }); return completer.future; } /// Get the URI that prompts the user for pemission (if required). String _getAuthorizeUri(bool immediate) { Map<String, String> queryParams = { "response_type": "token", "client_id": _clientId, "origin": window.location.origin, "redirect_uri": "postmessage", // Response will post to the proxy iframe "scope": _scopes.join(" "), "immediate": immediate, }; if (_request_visible_actions != null && _request_visible_actions.length > 0) { queryParams["request_visible_actions"] = _request_visible_actions.join(" "); } return UrlPattern.generatePattern("${_provider}auth", {}, queryParams); } /// Deletes the stored token void logout() { _token = null; } /// Attempt to authenticate. /// If you have an existing valid token, it will be immediately returned. /// If you have an expired token, it will be silently renewed (override /// with immediate:false) /// If you have no token, a popup prompt will be displayed. /// If the user declines, closes the popup, or the service returns a token /// that cannot be validated, an exception will be delivered. Future<Token> login({bool immediate: null}) { if (token != null) { if (token.expired) { if (immediate == null) { immediate = true; // We should be able to simply renew } } else { // We already have a good token return new Future<Token>.value(token); } } if (immediate == null) { immediate = false; } // Login may already be in progress if (_tokenFuture != null) { // An in-progress request will satisfy an immediate request // (even if it's not immediate). if (!immediate) { Completer result = new Completer<Token>(); _tokenFuture .then((value) => result.complete(value)) .catchError((e) { login(immediate:immediate) .then((value) => result.complete(value)) .catchError((e) => result.completeError(e)); }); return result.future; } } else { Completer<Token> tokenCompleter = new Completer(); tokenCompleter.future .then((token) { _tokenFuture = null; _token = token; }) .catchError((e) { _tokenFuture = null; _token = null; }); _tokenFuture = tokenCompleter.future; completeByPromptingUser() { _tokenCompleter = _wrapValidation(tokenCompleter); // Synchronous if the channel is already open -> avoids popup blocker _channel .then((value) { String uri = _getAuthorizeUri(immediate); if (immediate) { IFrameElement iframe = _iframe(uri); _tokenCompleter.future.whenComplete(() => iframe.remove()); } else { WindowBase popup = _popup(uri); new _WindowPoller(_tokenCompleter, popup).poll(); } }) .catchError((e) { return _tokenCompleter.completeError(e); }); } final stored = _storedToken; if ((stored != null) && !stored.expired) { stored.validate(_clientId) .then((v) => tokenCompleter.complete(stored)) .catchError((e) => completeByPromptingUser()); } else { completeByPromptingUser(); } } return _tokenFuture; } Future<HttpRequest> authenticate(HttpRequest request) => login().then((token) { request.setRequestHeader("Authorization", "${token.type} ${token.data}"); return request; }); /// Returns the OAuth2 token, if one is currently available. Token get token => __token; set _token(Token value) { final invokeCallbacks = (__token == null) && (value != null); try { _storedToken = value; } catch (e) { print("Failed to cache OAuth2 token: $e"); } __token = value; if (invokeCallbacks && (_tokenLoaded != null)) { var timer = new Timer(const Duration(milliseconds: 0), () { try { _tokenLoaded(value); } catch (e) { print("Failed to invoke tokenLoaded callback: $e"); } }); } } Token get _storedToken => window.localStorage.containsKey(_storageKey) ? new Token.fromJson(window.localStorage[_storageKey]) : null; set _storedToken(Token value) => (value == null) ? window.localStorage.remove(_storageKey) : window.localStorage[_storageKey] = value.toJson(); /// Returns a unique identifier for this context for use in localStorage. String get _storageKey => JSON.stringify({ "clientId": _clientId, "scopes": _scopes, "provider": _provider, }); /// Takes a completer that accepts validated tokens, and returns a completer /// that accepts unvalidated tokens. Completer<Token> _wrapValidation(Completer<Token> validTokenCompleter) { Completer<Token> result = new Completer(); result.future .then((value) { value.validate(_clientId) .then((validation) { if (validation) { validTokenCompleter.complete(value); } else { validTokenCompleter.completeError(new Exception("Server returned token is invalid")); } }) .catchError((e) => validTokenCompleter.completeError(e)); }) .catchError((e) => validTokenCompleter.completeError(e)); return result; } }
Extends
OAuth2 > GoogleOAuth2
Constructors
new GoogleOAuth2(String _clientId, List<String> _scopes, {List<String> request_visible_actions: null, String provider: "https://accounts.google.com/o/oauth2/", tokenLoaded(Token token), bool autoLogin: false}) #
Creates an OAuth2 context for the application identified by clientId
and the permissions described by scopes
.
If
tokenLoaded is provided, it will be called with a Token when one
is available. This can be used e.g. to set up a 'logged in' view.
GoogleOAuth2( String this._clientId, List<String> this._scopes, { List<String> request_visible_actions: null, String provider: "https://accounts.google.com/o/oauth2/", tokenLoaded(Token token), bool autoLogin: false } ) : _provider = provider, _tokenLoaded = tokenLoaded, _request_visible_actions = request_visible_actions, super() { _channel = _createFutureChannel(); // Attempt an immediate login, we may already be authorized. if (autoLogin) { login(immediate:true) .then((t) => print("Automatic login successful")) .catchError((e) => print("$e")); } }
Properties
Methods
Future<HttpRequest> authenticate(HttpRequest request) #
Take a request and return the request with the authorization headers set correctly
Future<HttpRequest> authenticate(HttpRequest request) => login().then((token) { request.setRequestHeader("Authorization", "${token.type} ${token.data}"); return request; });
Future<Token> login({bool immediate: null}) #
Attempt to authenticate. If you have an existing valid token, it will be immediately returned. If you have an expired token, it will be silently renewed (override with immediate:false) If you have no token, a popup prompt will be displayed. If the user declines, closes the popup, or the service returns a token that cannot be validated, an exception will be delivered.
Future<Token> login({bool immediate: null}) { if (token != null) { if (token.expired) { if (immediate == null) { immediate = true; // We should be able to simply renew } } else { // We already have a good token return new Future<Token>.value(token); } } if (immediate == null) { immediate = false; } // Login may already be in progress if (_tokenFuture != null) { // An in-progress request will satisfy an immediate request // (even if it's not immediate). if (!immediate) { Completer result = new Completer<Token>(); _tokenFuture .then((value) => result.complete(value)) .catchError((e) { login(immediate:immediate) .then((value) => result.complete(value)) .catchError((e) => result.completeError(e)); }); return result.future; } } else { Completer<Token> tokenCompleter = new Completer(); tokenCompleter.future .then((token) { _tokenFuture = null; _token = token; }) .catchError((e) { _tokenFuture = null; _token = null; }); _tokenFuture = tokenCompleter.future; completeByPromptingUser() { _tokenCompleter = _wrapValidation(tokenCompleter); // Synchronous if the channel is already open -> avoids popup blocker _channel .then((value) { String uri = _getAuthorizeUri(immediate); if (immediate) { IFrameElement iframe = _iframe(uri); _tokenCompleter.future.whenComplete(() => iframe.remove()); } else { WindowBase popup = _popup(uri); new _WindowPoller(_tokenCompleter, popup).poll(); } }) .catchError((e) { return _tokenCompleter.completeError(e); }); } final stored = _storedToken; if ((stored != null) && !stored.expired) { stored.validate(_clientId) .then((v) => tokenCompleter.complete(stored)) .catchError((e) => completeByPromptingUser()); } else { completeByPromptingUser(); } } return _tokenFuture; }
void logout() #
Deletes the stored token
void logout() { _token = null; }