-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[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
base: main
Are you sure you want to change the base?
Changes from 26 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
9fe4f32
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 |
---|---|---|
|
@@ -6,182 +6,145 @@ A Flutter plugin for [Google Sign In](https://developers.google.com/identity/). | |
|-------------|---------|-------|--------|-----| | ||
| **Support** | SDK 21+ | 12.0+ | 10.15+ | Any | | ||
|
||
## Platform integration | ||
## Setup | ||
|
||
### Android integration | ||
|
||
To access Google Sign-In, you'll need to make sure to | ||
[register your application](https://firebase.google.com/docs/android/setup). | ||
|
||
You don't need to include the google-services.json file in your app unless you | ||
are using Google services that require it. You do need to enable the OAuth APIs | ||
that you want, using the | ||
[Google Cloud Platform API manager](https://console.developers.google.com/). For | ||
example, if you want to mimic the behavior of the Google Sign-In sample app, | ||
you'll need to enable the | ||
[Google People API](https://developers.google.com/people/). | ||
|
||
Make sure you've filled out all required fields in the console for | ||
[OAuth consent screen](https://console.developers.google.com/apis/credentials/consent). | ||
Otherwise, you may encounter `APIException` errors. | ||
|
||
### iOS integration | ||
|
||
Please see [instructions on integrating Google Sign-In for iOS](https://pub.dev/packages/google_sign_in_ios#ios-integration). | ||
|
||
#### iOS additional requirement | ||
|
||
Note that according to | ||
https://developer.apple.com/sign-in-with-apple/get-started, starting June 30, | ||
2020, apps that use login services must also offer a "Sign in with Apple" option | ||
when submitting to the Apple App Store. | ||
|
||
Consider also using an Apple sign in plugin from pub.dev. | ||
|
||
The Flutter Favorite | ||
[sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) plugin could | ||
be an option. | ||
|
||
### macOS integration | ||
|
||
Please see [instructions on integrating Google Sign-In for macOS](https://pub.dev/packages/google_sign_in_ios#macos-setup). | ||
|
||
### Web integration | ||
### Import the package | ||
|
||
The new SDK used by the web has fully separated Authentication from Authorization, | ||
so `signIn` and `signInSilently` no longer authorize OAuth `scopes`. | ||
To use this plugin, follow the | ||
[plugin installation instructions](https://pub.dev/packages/google_sign_in/install), | ||
then follow the platform integration steps below for all platforms you support. | ||
|
||
Flutter apps must be able to detect what scopes have been granted by their users, | ||
and if the grants are still valid. | ||
### Platform integration | ||
|
||
Read below about **Working with scopes, and incremental authorization** for | ||
general information about changes that may be needed on an app, and for more | ||
specific web integration details, see the | ||
[`google_sign_in_web` package](https://pub.dev/packages/google_sign_in_web). | ||
* **Android**: Please see [the `google_sign_in_android` README](https://pub.dev/packages/google_sign_in_android#integration). | ||
* **iOS**: Please see [the `google_sign_in_ios` README](https://pub.dev/packages/google_sign_in_ios#ios-integration). | ||
* **macOS**: Please see [the `google_sign_in_ios` README](https://pub.dev/packages/google_sign_in_ios#macos-integration) (which also supports macOS). | ||
* **Web**: Please see [the `google_sign_in_web` README](https://pub.dev/packages/google_sign_in_web#integration). | ||
|
||
## Usage | ||
|
||
### Import the package | ||
|
||
To use this plugin, follow the | ||
[plugin installation instructions](https://pub.dev/packages/google_sign_in/install). | ||
### Initialization and authentication | ||
|
||
### Use the plugin | ||
Initialize the `GoogleSignIn` instance, and (optionally) start the lightweight | ||
authentication process: | ||
|
||
Initialize `GoogleSignIn` with the scopes you want: | ||
|
||
<?code-excerpt "example/lib/main.dart (Initialize)"?> | ||
<?code-excerpt "example/lib/main.dart (Setup)"?> | ||
```dart | ||
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, | ||
); | ||
final GoogleSignIn signIn = GoogleSignIn.instance; | ||
unawaited(signIn | ||
.initialize(clientId: clientId, serverClientId: serverClientId) | ||
.then((_) { | ||
signIn.authenticationEvents.listen(_handleAuthenticationEvent); | ||
|
||
/// 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(); | ||
})); | ||
``` | ||
|
||
[Full list of available scopes](https://developers.google.com/identity/protocols/googlescopes). | ||
|
||
You can now use the `GoogleSignIn` class to authenticate in your Dart code, e.g. | ||
If the user isn't signed in by the lightweight method, you can show UI to | ||
start a sign-in flow. This uses `authenticate` on platforms that return true | ||
for `supportsAuthenticate`, otherwise applications should fall back to a | ||
platform-specific approach. For instance, user-initiated sign in on web must | ||
use a button rendered by the sign in SDK, rather than application-provided | ||
UI: | ||
|
||
<?code-excerpt "example/lib/main.dart (SignIn)"?> | ||
<?code-excerpt "example/lib/main.dart (ExplicitSignIn)"?> | ||
```dart | ||
Future<void> _handleSignIn() async { | ||
try { | ||
await _googleSignIn.signIn(); | ||
} catch (error) { | ||
print(error); | ||
} | ||
} | ||
if (GoogleSignIn.instance.supportsAuthenticate()) | ||
ElevatedButton( | ||
onPressed: () async { | ||
try { | ||
await GoogleSignIn.instance.authenticate(); | ||
} catch (e) { | ||
// ··· | ||
} | ||
}, | ||
child: const Text('SIGN IN'), | ||
) | ||
else ...<Widget>[ | ||
if (kIsWeb) | ||
web.renderButton() | ||
// ··· | ||
] | ||
``` | ||
|
||
In the web, you should use the **Google Sign In button** (and not the `signIn` method) | ||
to guarantee that your user authentication contains a valid `idToken`. | ||
|
||
For more details, take a look at the | ||
[`google_sign_in_web` package](https://pub.dev/packages/google_sign_in_web). | ||
|
||
## Working with scopes, and incremental authorization. | ||
|
||
If your app supports both mobile and web, read this section! | ||
## Authorization | ||
|
||
### Checking if scopes have been granted | ||
|
||
Users may (or may *not*) grant all the scopes that an application requests at | ||
Sign In. In fact, in the web, no scopes are granted by `signIn`, `silentSignIn` | ||
or the `renderButton` widget anymore. | ||
|
||
Applications must be able to: | ||
|
||
* Detect if the authenticated user has authorized the scopes they need. | ||
* Determine if the scopes that were granted a few minutes ago are still valid. | ||
|
||
There's a new method that enables the checks above, `canAccessScopes`: | ||
If the user has previously authorized the scopes required by your application, | ||
you can silently request an access token for those scopes: | ||
|
||
<?code-excerpt "example/lib/main.dart (CanAccessScopes)"?> | ||
<?code-excerpt "example/lib/main.dart (CheckAuthorization)"?> | ||
```dart | ||
// In mobile, being authenticated means being authorized... | ||
bool isAuthorized = account != null; | ||
// However, on web... | ||
if (kIsWeb && account != null) { | ||
isAuthorized = await _googleSignIn.canAccessScopes(scopes); | ||
} | ||
const List<String> scopes = <String>[ | ||
'email', | ||
'https://www.googleapis.com/auth/contacts.readonly', | ||
]; | ||
// ··· | ||
GoogleSignInAccount? user; | ||
// ··· | ||
GoogleSignInClientAuthorization? authorization; | ||
if (user != null) { | ||
authorization = | ||
await user.authorizationClient.authorizationForScopes(scopes); | ||
} | ||
``` | ||
|
||
_(Only implemented in the web platform, from version 6.1.0 of this package)_ | ||
[Full list of available scopes](https://developers.google.com/identity/protocols/googlescopes). | ||
|
||
### Requesting more scopes when needed | ||
|
||
If an app determines that the user hasn't granted the scopes it requires, it | ||
should initiate an Authorization request. (Remember that in the web platform, | ||
this request **must be initiated from an user interaction**, like a button press). | ||
should initiate an Authorization request. On some platforms, such as web, | ||
this request **must be initiated from an user interaction** like a button press. | ||
|
||
<?code-excerpt "example/lib/main.dart (RequestScopes)" plaster="none"?> | ||
<?code-excerpt "example/lib/main.dart (RequestScopes)"?> | ||
```dart | ||
Future<void> _handleAuthorizeScopes() async { | ||
final bool isAuthorized = await _googleSignIn.requestScopes(scopes); | ||
if (isAuthorized) { | ||
unawaited(_handleGetContact(_currentUser!)); | ||
} | ||
final GoogleSignInClientAuthorization authorization = | ||
await user.authorizationClient.authorizeScopes(scopes); | ||
``` | ||
|
||
The `requestScopes` returns a `boolean` value that is `true` if the user has | ||
granted all the requested scopes or `false` otherwise. | ||
|
||
Once your app determines that the current user `isAuthorized` to access the | ||
services for which you need `scopes`, it can proceed normally. | ||
|
||
### Authorization expiration | ||
|
||
In the web, **the `accessToken` is no longer refreshed**. It expires after 3600 | ||
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. 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 commentThe 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 commentThe 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 |
||
seconds (one hour), so your app needs to be able to handle failed REST requests, | ||
and update its UI to prompt the user for a new Authorization round. | ||
|
||
This can be done by combining the error responses from your REST requests with | ||
the `canAccessScopes` and `requestScopes` methods described above. | ||
the authorization methods described above. | ||
|
||
For more details, take a look at the | ||
[`google_sign_in_web` package](https://pub.dev/packages/google_sign_in_web). | ||
|
||
### Does an app always need to check `canAccessScopes`? | ||
### Requesting a server auth code | ||
|
||
The new web SDK implicitly grant access to the `email`, `profile` and `openid` | ||
scopes when users complete the sign-in process (either via the One Tap UX or the | ||
Google Sign In button). | ||
If your application needs to access user data from a backend server, you can | ||
request a server auth code to send to the server: | ||
|
||
If an app only needs an `idToken`, or only requests permissions to any/all of | ||
the three scopes mentioned above | ||
([OpenID Connect scopes](https://developers.google.com/identity/protocols/oauth2/scopes#openid-connect)), | ||
it won't need to implement any additional scope handling. | ||
<?code-excerpt "example/lib/main.dart (RequestServerAuth)"?> | ||
```dart | ||
final GoogleSignInServerAuthorization? serverAuth = | ||
await user.authorizationClient.authorizeServer(scopes); | ||
``` | ||
|
||
If an app needs any scope other than `email`, `profile` and `openid`, it **must** | ||
implement a more complete scope handling, as described above. | ||
Server auth codes are not always available on all platforms. For instance, on | ||
some platforms they may only be returned when a user initially signs in, and | ||
not for subsequent authentications via the lightweight process. If you | ||
need a server auth code you should request it as soon as possible after initial | ||
sign-in, and manage server tokens for that user entirely on the server side | ||
unless the signed in user changes. | ||
|
||
## Example | ||
|
||
Find the example wiring in the | ||
[Google sign-in example application](https://github.com/flutter/packages/blob/main/packages/google_sign_in/google_sign_in/example/lib/main.dart). | ||
The | ||
[Google Sign-In example application](https://github.com/flutter/packages/blob/main/packages/google_sign_in/google_sign_in/example/lib/main.dart) demonstrates one approach to using this | ||
package to sign a user in and authorize access to specific user data. | ||
|
||
## Migration from pre-7.0 versions | ||
|
||
If you used version 6.x or earlier of `google_sign_in`, see | ||
[the migration guide](https://github.com/flutter/packages/blob/main/packages/google_sign_in/google_sign_in/MIGRATION.md) | ||
for more information about the changes. |
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.
'email'
seems to me like an "old school" scope docs, maybe don't show it in this example, or replace by'https://www.googleapis.com/auth/userinfo.email'
?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.
Ah, I missed that this was an old/new API distinction. That helps explain https://github.com/flutter/packages/pull/9267/files#diff-30357d9348f31b88b886dbd8d3d1e036c8eb13adcbe360b5c997983520eeed9cR175-R176
Removed since we don't seem to need it. The auth UI still says it'll share name, email, and profile photo without it, so it was probably just redundant.