-
Notifications
You must be signed in to change notification settings - Fork 3.4k
[google_sign_in] Redesign API for current identity SDKs #9267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
432a627
df1cf87
6a1ec7c
49342c5
eba7a8b
f3d472f
728a4f9
c165834
dba0f3e
83c7b1d
82c0530
4123b18
165fb78
eda66bb
21635a0
39b7505
07fe543
e6c9f62
90a63aa
816f614
0d46beb
a0cf078
0a134ec
1b0d4d1
8f57604
e786998
78602e8
8783b72
7140fd0
5a587b4
5f78e10
a6036b8
9c9923c
b44ff39
8640443
86a7a1d
4fb7fea
563680c
dab117f
81f59f7
6b76be0
9817325
20a8fc5
d6bd8bc
6766860
8ad0051
98e55b2
f36a6c8
2823a54
012a6d7
86e9d00
fd6a5f8
9c96d9d
3c86d9f
82e3503
47b86c8
276e1c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# Migrating from `google_sign_in` 6.x to 7.x | ||
|
||
The API of `google_sign_in` 6.x and earlier was designed for the Google Sign-In | ||
SDK, which has been deprecated on both Android and Web, and replaced with new | ||
SDKs that have significantly different structures. As a result, the | ||
`google_sign_in` API surface has changed significantly. Notable differences | ||
include: | ||
* `GoogleSignIn` is now a singleton, which is obtained via | ||
`GoogleSignIn.instance`. In practice, creating multiple `GoogleSignIn` | ||
instances in 6.x would not work correctly, so this just enforces an existing | ||
restriction. | ||
* There is now an explicit `initialize` step that must be called exactly once, | ||
before any other methods. On some platforms the future will complete almost | ||
immediately, but on others (for example, web) it may take some time. | ||
* The plugin no longer tracks a single "current" signed in user. Instead, | ||
applications that assume a single signed in user should track this at the | ||
application level using the `authenticationEvents` stream. | ||
* Authentication (signing in) and authorization (allowing access to user data | ||
in the form of scopes) are now separate steps. Recommended practice is to | ||
authenticate as soon as it makes sense for a user to potentially be signed in, | ||
but to delay authorization until the point where the data will actually be | ||
used. | ||
* In applications where these steps should happen at the same time, you can | ||
pass a `scopeHint` during the authentication step. On platforms that support | ||
it this allows for a combined authentication and authorization UI flow. | ||
Not all platforms allow combining these flows, so your application should be | ||
prepared to trigger a separate authorization prompt if necessary. | ||
* Authorization is further separated into client and server authorization. | ||
Applications that need a `serverAuthCode` must now call a separate method, | ||
`authorizeServer`, to obtain that code. | ||
* Client authorization is handled via two new methods: | ||
* `authorizationForScopes`, which returns an access token if the requested | ||
scopes are already authorized, or null if not, and | ||
* `authorizeScopes`, which requests that the user authorize the scopes, and | ||
is expected to show UI. | ||
|
||
Clients should generally attempt to get tokens via `authorizationForScopes`, | ||
and if they are unable to do so, show some UI to request authoriaztion that | ||
calls `authorizeScopes`. This is similar to the previously web-only flow | ||
of calling `canAccessScopes` and then calling `addScopes` if necessary. | ||
* `signInSilently` has been replaced with `attemptLightweightAuthentication`. | ||
The intended usage is essentially the same, but the change reflects that it | ||
is no longer guaranteed to be silent. For example, as of the publishing of | ||
7.0, on web this may show a floating sign-in card, and on Android it may show | ||
an account selection sheet. | ||
* This new method is no longer guaranteed to return a future. This allows | ||
clients to distinguish, at runtime: | ||
* platforms where a definitive "signed in" or "not signed in" response | ||
can be returned quickly, and thus `await`-ing completion is reasonable, | ||
in which case a `Future` is returned, and | ||
* platforms (such as web) where it could take an arbitrary amount of time, | ||
in which case no `Future` is returned, and clients should assume a | ||
non-signed-in state until/unless a sign-in event is eventually posted to | ||
the `authenticationEvents` stream. | ||
* `authenticate` replaces the authentication portion of `signIn` on platforms | ||
that support it (see below). | ||
* The new `supportsAuthenticate` method allows clients to determine at runtime | ||
whether the `authenticate` method is supported, as some platforms do not allow | ||
custom UI to trigger explicit authentication. These platforms instead provide | ||
some other platform-specific way of triggering authentication. As of | ||
publishing, the only platform that does not support `authenticate` is web, | ||
where `google_sign_in_web`'s `renderButton` is used to create a sign-in | ||
button. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,21 +12,22 @@ import 'package:flutter/material.dart'; | |
import 'package:google_sign_in/google_sign_in.dart'; | ||
import 'package:http/http.dart' as http; | ||
|
||
import 'src/sign_in_button.dart'; | ||
import 'src/web_wrapper.dart' as web; | ||
|
||
/// To run this example, replace this value with your client ID, and/or | ||
/// update the relevant configuration files, as described in the README. | ||
String? clientId; | ||
|
||
/// To run this example, replace this value with your server client ID, and/or | ||
/// update the relevant configuration files, as described in the README. | ||
String? serverClientId; | ||
|
||
/// The scopes required by this application. | ||
// #docregion Initialize | ||
// #docregion CheckAuthorization | ||
const List<String> scopes = <String>[ | ||
'email', | ||
'https://www.googleapis.com/auth/contacts.readonly', | ||
]; | ||
|
||
GoogleSignIn _googleSignIn = GoogleSignIn( | ||
// Optional clientId | ||
// clientId: 'your-client_id.apps.googleusercontent.com', | ||
scopes: scopes, | ||
); | ||
// #enddocregion Initialize | ||
// #enddocregion CheckAuthorization | ||
|
||
void main() { | ||
runApp( | ||
|
@@ -50,58 +51,102 @@ class _SignInDemoState extends State<SignInDemo> { | |
GoogleSignInAccount? _currentUser; | ||
bool _isAuthorized = false; // has granted permissions? | ||
String _contactText = ''; | ||
String _errorMessage = ''; | ||
String _serverAuthCode = ''; | ||
|
||
@override | ||
void initState() { | ||
super.initState(); | ||
|
||
_googleSignIn.onCurrentUserChanged | ||
.listen((GoogleSignInAccount? account) async { | ||
// #docregion CanAccessScopes | ||
// In mobile, being authenticated means being authorized... | ||
bool isAuthorized = account != null; | ||
// However, on web... | ||
if (kIsWeb && account != null) { | ||
isAuthorized = await _googleSignIn.canAccessScopes(scopes); | ||
} | ||
// #enddocregion CanAccessScopes | ||
// #docregion Setup | ||
final GoogleSignIn signIn = GoogleSignIn.instance; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm this reminds me of flutter/flutter#168100, if the user connects / disconnects a mouse / keyboard on Android the activity will restart and that would result in the element tree being re-inflated anew. I guess that does not break the new flow (from reading the docs There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or when the activity restarts, the lightweight authentication path will be taken and the flow will typically be invisible to user / require no additional user interactions? EDIT: Just read the design doc and it mentioned "Fully silent sign in is no longer available" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's up to whether the app developer chooses to include auth state in their state restoration.
That's up to the app developer too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Oh I thought the developer would have to restore GoogleSignIn, and state restoration only supports value types. If the dart part of GoogleSignIn doesn't keep any secret state itself then that makes sense. |
||
unawaited(signIn | ||
.initialize(clientId: clientId, serverClientId: serverClientId) | ||
.then((_) { | ||
signIn.authenticationEvents | ||
.listen(_handleAuthenticationEvent) | ||
.onError(_handleAuthenticationError); | ||
|
||
setState(() { | ||
_currentUser = account; | ||
_isAuthorized = isAuthorized; | ||
}); | ||
/// This example always uses the stream-based approach to determining | ||
/// which UI state to show, rather than using the future returned here, | ||
/// if any, to conditionally skip directly to the signed-in state. | ||
signIn.attemptLightweightAuthentication(); | ||
})); | ||
// #enddocregion Setup | ||
} | ||
|
||
// Now that we know that the user can access the required scopes, the app | ||
// can call the REST API. | ||
if (isAuthorized) { | ||
unawaited(_handleGetContact(account!)); | ||
} | ||
Future<void> _handleAuthenticationEvent( | ||
GoogleSignInAuthenticationEvent event) async { | ||
// #docregion CheckAuthorization | ||
final GoogleSignInAccount? user = // ... | ||
// #enddocregion CheckAuthorization | ||
switch (event) { | ||
GoogleSignInAuthenticationEventSignIn() => event.user, | ||
GoogleSignInAuthenticationEventSignOut() => null, | ||
}; | ||
|
||
// Check for existing authorization. | ||
// #docregion CheckAuthorization | ||
final GoogleSignInClientAuthorization? authorization = | ||
await user?.authorizationClient.authorizationForScopes(scopes); | ||
// #enddocregion CheckAuthorization | ||
|
||
setState(() { | ||
_currentUser = user; | ||
_isAuthorized = authorization != null; | ||
_errorMessage = ''; | ||
}); | ||
|
||
// In the web, _googleSignIn.signInSilently() triggers the One Tap UX. | ||
// | ||
// It is recommended by Google Identity Services to render both the One Tap UX | ||
// and the Google Sign In button together to "reduce friction and improve | ||
// sign-in rates" ([docs](https://developers.google.com/identity/gsi/web/guides/display-button#html)). | ||
_googleSignIn.signInSilently(); | ||
// If the user has already granted access to the required scopes, call the | ||
// REST API. | ||
if (user != null && authorization != null) { | ||
unawaited(_handleGetContact(user)); | ||
} | ||
} | ||
|
||
Future<void> _handleAuthenticationError(Object e) async { | ||
setState(() { | ||
_currentUser = null; | ||
_isAuthorized = false; | ||
_errorMessage = e is GoogleSignInException | ||
? 'GoogleSignInException ${e.code}: ${e.description}' | ||
: 'Unknown error: $e'; | ||
}); | ||
} | ||
|
||
// Calls the People API REST endpoint for the signed-in user to retrieve information. | ||
Future<void> _handleGetContact(GoogleSignInAccount user) async { | ||
setState(() { | ||
_contactText = 'Loading contact info...'; | ||
}); | ||
final Map<String, String>? headers = | ||
await user.authorizationClient.authorizationHeaders(scopes); | ||
if (headers == null) { | ||
setState(() { | ||
_contactText = ''; | ||
_errorMessage = 'Failed to construct authorization headers.'; | ||
}); | ||
return; | ||
} | ||
final http.Response response = await http.get( | ||
Uri.parse('https://people.googleapis.com/v1/people/me/connections' | ||
'?requestMask.includeField=person.names'), | ||
headers: await user.authHeaders, | ||
headers: headers, | ||
); | ||
if (response.statusCode != 200) { | ||
setState(() { | ||
_contactText = 'People API gave a ${response.statusCode} ' | ||
'response. Check logs for details.'; | ||
}); | ||
print('People API ${response.statusCode} response: ${response.body}'); | ||
if (response.statusCode == 401 || response.statusCode == 403) { | ||
setState(() { | ||
_isAuthorized = false; | ||
_errorMessage = 'People API gave a ${response.statusCode} response. ' | ||
'Please re-authorize access.'; | ||
}); | ||
} else { | ||
print('People API ${response.statusCode} response: ${response.body}'); | ||
setState(() { | ||
_contactText = 'People API gave a ${response.statusCode} ' | ||
'response. Check logs for details.'; | ||
}); | ||
} | ||
return; | ||
} | ||
final Map<String, dynamic> data = | ||
|
@@ -136,94 +181,147 @@ class _SignInDemoState extends State<SignInDemo> { | |
return null; | ||
} | ||
|
||
// This is the on-click handler for the Sign In button that is rendered by Flutter. | ||
// Prompts the user to authorize `scopes`. | ||
// | ||
// On the web, the on-click handler of the Sign In button is owned by the JS | ||
// SDK, so this method can be considered mobile only. | ||
// #docregion SignIn | ||
Future<void> _handleSignIn() async { | ||
// If authorizationRequiresUserInteraction() is true, this must be called from | ||
// a user interaction (button click). In this example app, a button is used | ||
// regardless, so authorizationRequiresUserInteraction() is not checked. | ||
Future<void> _handleAuthorizeScopes(GoogleSignInAccount user) async { | ||
try { | ||
await _googleSignIn.signIn(); | ||
} catch (error) { | ||
print(error); | ||
// #docregion RequestScopes | ||
final GoogleSignInClientAuthorization authorization = | ||
await user.authorizationClient.authorizeScopes(scopes); | ||
// #enddocregion RequestScopes | ||
|
||
// The returned tokens are ignored since _handleGetContact uses the | ||
// authorizationHeaders method to re-read the token cached by | ||
// authorizeScopes. The code above is used as a README excerpt, so shows | ||
// the simpler pattern of getting the authorization for immediate use. | ||
// That results in an unused variable, which this statement suppresses | ||
// (without adding an ignore: directive to the README excerpt). | ||
// ignore: unnecessary_statements | ||
authorization; | ||
|
||
setState(() { | ||
_isAuthorized = true; | ||
_errorMessage = ''; | ||
}); | ||
unawaited(_handleGetContact(_currentUser!)); | ||
} on GoogleSignInException catch (e) { | ||
_errorMessage = 'GoogleSignInException ${e.code}: ${e.description}'; | ||
} | ||
} | ||
// #enddocregion SignIn | ||
|
||
// Prompts the user to authorize `scopes`. | ||
// Requests a server auth code for the authorized scopes. | ||
// | ||
// This action is **required** in platforms that don't perform Authentication | ||
// and Authorization at the same time (like the web). | ||
// | ||
// On the web, this must be called from an user interaction (button click). | ||
// #docregion RequestScopes | ||
Future<void> _handleAuthorizeScopes() async { | ||
final bool isAuthorized = await _googleSignIn.requestScopes(scopes); | ||
// #enddocregion RequestScopes | ||
setState(() { | ||
_isAuthorized = isAuthorized; | ||
}); | ||
// #docregion RequestScopes | ||
if (isAuthorized) { | ||
unawaited(_handleGetContact(_currentUser!)); | ||
// If authorizationRequiresUserInteraction() is true, this must be called from | ||
// a user interaction (button click). In this example app, a button is used | ||
// regardless, so authorizationRequiresUserInteraction() is not checked. | ||
Future<void> _handleGetAuthCode(GoogleSignInAccount user) async { | ||
try { | ||
// #docregion RequestServerAuth | ||
final GoogleSignInServerAuthorization? serverAuth = | ||
await user.authorizationClient.authorizeServer(scopes); | ||
// #enddocregion RequestServerAuth | ||
|
||
setState(() { | ||
_serverAuthCode = serverAuth == null ? '' : serverAuth.serverAuthCode; | ||
}); | ||
} on GoogleSignInException catch (e) { | ||
_errorMessage = 'GoogleSignInException ${e.code}: ${e.description}'; | ||
} | ||
// #enddocregion RequestScopes | ||
} | ||
|
||
Future<void> _handleSignOut() => _googleSignIn.disconnect(); | ||
Future<void> _handleSignOut() async { | ||
// Disconnect instead of just signing out, to reset the example state as | ||
// much as possible. | ||
await GoogleSignIn.instance.disconnect(); | ||
} | ||
|
||
Widget _buildBody() { | ||
final GoogleSignInAccount? user = _currentUser; | ||
if (user != null) { | ||
// The user is Authenticated | ||
return Column( | ||
mainAxisAlignment: MainAxisAlignment.spaceAround, | ||
children: <Widget>[ | ||
ListTile( | ||
leading: GoogleUserCircleAvatar( | ||
identity: user, | ||
), | ||
title: Text(user.displayName ?? ''), | ||
subtitle: Text(user.email), | ||
), | ||
const Text('Signed in successfully.'), | ||
if (_isAuthorized) ...<Widget>[ | ||
// The user has Authorized all required scopes | ||
Text(_contactText), | ||
ElevatedButton( | ||
child: const Text('REFRESH'), | ||
onPressed: () => _handleGetContact(user), | ||
), | ||
], | ||
if (!_isAuthorized) ...<Widget>[ | ||
// The user has NOT Authorized all required scopes. | ||
// (Mobile users may never see this button!) | ||
const Text('Additional permissions needed to read your contacts.'), | ||
ElevatedButton( | ||
onPressed: _handleAuthorizeScopes, | ||
child: const Text('REQUEST PERMISSIONS'), | ||
), | ||
], | ||
return Column( | ||
mainAxisAlignment: MainAxisAlignment.spaceAround, | ||
children: <Widget>[ | ||
if (user != null) | ||
..._buildAuthenticatedWidgets(user) | ||
else | ||
..._buildUnauthenticatedWidgets(), | ||
if (_errorMessage.isNotEmpty) Text(_errorMessage), | ||
], | ||
); | ||
} | ||
|
||
/// Returns the list of widgets to include if the user is authenticated. | ||
List<Widget> _buildAuthenticatedWidgets(GoogleSignInAccount user) { | ||
return <Widget>[ | ||
// The user is Authenticated. | ||
ListTile( | ||
leading: GoogleUserCircleAvatar( | ||
identity: user, | ||
), | ||
title: Text(user.displayName ?? ''), | ||
subtitle: Text(user.email), | ||
), | ||
const Text('Signed in successfully.'), | ||
if (_isAuthorized) ...<Widget>[ | ||
// The user has Authorized all required scopes. | ||
if (_contactText.isNotEmpty) Text(_contactText), | ||
ElevatedButton( | ||
child: const Text('REFRESH'), | ||
onPressed: () => _handleGetContact(user), | ||
), | ||
if (_serverAuthCode.isEmpty) | ||
ElevatedButton( | ||
onPressed: _handleSignOut, | ||
child: const Text('SIGN OUT'), | ||
), | ||
], | ||
); | ||
} else { | ||
// The user is NOT Authenticated | ||
return Column( | ||
mainAxisAlignment: MainAxisAlignment.spaceAround, | ||
children: <Widget>[ | ||
const Text('You are not currently signed in.'), | ||
// This method is used to separate mobile from web code with conditional exports. | ||
// See: src/sign_in_button.dart | ||
buildSignInButton( | ||
onPressed: _handleSignIn, | ||
), | ||
], | ||
); | ||
} | ||
child: const Text('REQUEST SERVER CODE'), | ||
onPressed: () => _handleGetAuthCode(user), | ||
) | ||
else | ||
Text('Server auth code:\n$_serverAuthCode'), | ||
] else ...<Widget>[ | ||
// The user has NOT Authorized all required scopes. | ||
const Text('Authorization needed to read your contacts.'), | ||
ElevatedButton( | ||
onPressed: () => _handleAuthorizeScopes(user), | ||
child: const Text('REQUEST PERMISSIONS'), | ||
), | ||
], | ||
ElevatedButton( | ||
onPressed: _handleSignOut, | ||
child: const Text('SIGN OUT'), | ||
), | ||
]; | ||
} | ||
|
||
/// Returns the list of widgets to include if the user is not authenticated. | ||
List<Widget> _buildUnauthenticatedWidgets() { | ||
return <Widget>[ | ||
const Text('You are not currently signed in.'), | ||
// #docregion ExplicitSignIn | ||
if (GoogleSignIn.instance.supportsAuthenticate()) | ||
ElevatedButton( | ||
onPressed: () async { | ||
try { | ||
await GoogleSignIn.instance.authenticate(); | ||
} catch (e) { | ||
// #enddocregion ExplicitSignIn | ||
_errorMessage = e.toString(); | ||
// #docregion ExplicitSignIn | ||
} | ||
}, | ||
child: const Text('SIGN IN'), | ||
) | ||
else ...<Widget>[ | ||
if (kIsWeb) | ||
web.renderButton() | ||
// #enddocregion ExplicitSignIn | ||
else | ||
const Text( | ||
'This platform does not have a known authentication method') | ||
Comment on lines
+319
to
+320
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fancy! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This exposes the biggest flaw in the |
||
// #docregion ExplicitSignIn | ||
] | ||
// #enddocregion ExplicitSignIn | ||
]; | ||
} | ||
|
||
@override | ||
|
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
export 'package:google_sign_in_web/web_only.dart'; |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
import 'package:flutter/foundation.dart'; | ||
|
||
import '../google_sign_in.dart'; | ||
|
||
export 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart' | ||
show GoogleSignInException; | ||
|
||
/// A base class for authentication event streams. | ||
@immutable | ||
sealed class GoogleSignInAuthenticationEvent { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these classes needed? It seems the sealed type is a glorified There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Originally there were three subclasses, but exceptions moved into It would make things less self-documenting though. It's non-obvious that getting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking maybe we can avoid introducing new classes by using typedef or extension types. I played with that idea but found the resulting code harder to read (the user code would still be the same though). So never mind. |
||
const GoogleSignInAuthenticationEvent(); | ||
} | ||
|
||
/// A sign-in event, corresponding to an authentication flow completing | ||
/// successfully. | ||
@immutable | ||
class GoogleSignInAuthenticationEventSignIn | ||
extends GoogleSignInAuthenticationEvent { | ||
/// Creates an event for a successful sign in. | ||
const GoogleSignInAuthenticationEventSignIn({required this.user}); | ||
|
||
/// The user that was authenticated. | ||
final GoogleSignInAccount user; | ||
} | ||
|
||
/// A sign-out event, corresponding to a user having been signed out. | ||
/// | ||
/// Implicit sign-outs (for example, due to server-side authentication | ||
/// revocation, or timeouts) are not guaranteed to send events. | ||
@immutable | ||
class GoogleSignInAuthenticationEventSignOut | ||
extends GoogleSignInAuthenticationEvent {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
import 'package:flutter/foundation.dart'; | ||
|
||
/// Holds authentication tokens. | ||
/// | ||
/// Currently there is only an idToken, but this wrapper class allows for the | ||
/// posibility of adding additional information in the future without breaking | ||
/// changes. | ||
@immutable | ||
class GoogleSignInAuthentication { | ||
/// Creates a new token container with the given tokens. | ||
const GoogleSignInAuthentication({required this.idToken}); | ||
|
||
/// An OpenID Connect ID token that identifies the user. | ||
final String? idToken; | ||
|
||
@override | ||
String toString() => 'GoogleSignInAuthentication: $idToken'; | ||
} | ||
|
||
/// Holds client authorization tokens. | ||
/// | ||
/// Currently there is only an accessToken, but this wrapper class allows for | ||
/// the posibility of adding additional information in the future without | ||
/// breaking changes. | ||
@immutable | ||
class GoogleSignInClientAuthorization { | ||
/// Creates a new token container with the given tokens. | ||
const GoogleSignInClientAuthorization({required this.accessToken}); | ||
|
||
/// The OAuth2 access token to access Google services. | ||
final String accessToken; | ||
|
||
@override | ||
String toString() => 'GoogleSignInClientAuthorization: $accessToken'; | ||
} | ||
|
||
/// Holds server authorization tokens. | ||
/// | ||
/// Currently there is only a serverAuthCode, but this wrapper class allows for | ||
/// the posibility of adding additional information in the future without | ||
/// breaking changes. | ||
@immutable | ||
class GoogleSignInServerAuthorization { | ||
/// Creates a new token container with the given tokens. | ||
const GoogleSignInServerAuthorization({required this.serverAuthCode}); | ||
|
||
/// Auth code to provide to a backend server to exchange for access or | ||
/// refresh tokens. | ||
final String serverAuthCode; | ||
|
||
@override | ||
String toString() => 'GoogleSignInServerAuthorization: $serverAuthCode'; | ||
} |
Large diffs are not rendered by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upon re-reading this... is this paragraph is a little bit incomplete? IIRC The authentication token (idToken) is also not refreshed, so both authorization and authentication end up expiring, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks that way. I can adjust the README accordingly, but maybe you just didn't mention it because normally (IIUC) clients don't need to keep using ID tokens they way they do access tokens.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a similar note in the authentication section, but pointing out that for authentication generally you should only need to use
idToken
when it's first received.