Skip to content

Commit d9d3191

Browse files
[google_sign_in] Redesign API for current identity SDKs (#9267)
This is a full overhaul of the `google_sign_in` API, with breaking changes for all component packages—including the platform interface. The usual model of adding the new approach while keeping the old one is not viable here, as the underlying SDKs have changed significantly since the original API was designed. Web already had some only-partially-compatible shims for this reason, and Android would have had to do something similar; see flutter/flutter#119300 and flutter/flutter#154205, and [the design doc](https://flutter.dev/go/google-sign-in-authn-authz-updates) for more background. - Fixes flutter/flutter#119300 - Fixes flutter/flutter#154205 - Fixes flutter/flutter#139406 - Fixes flutter/flutter#150365 - Fixes flutter/flutter#137727 - Fixes flutter/flutter#124206 - Fixes flutter/flutter#117794 - Fixes flutter/flutter#107532 - Fixes flutter/flutter#85439 - Fixes flutter/flutter#74308 - Fixes flutter/flutter#71607 - Fixes flutter/flutter#70427 - Fixes flutter/flutter#161890 - Fixes flutter/flutter#157639 - Fixes flutter/flutter#67308 - Fixes flutter/flutter#36673 - Fixes flutter/flutter#32441 (there may be more to do here over time, since we may find exceptions that are not caught, but we now have a structured system to convert errors to as we find specific unhandled cases) ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent d848b16 commit d9d3191

21 files changed

+1504
-1173
lines changed

packages/google_sign_in/google_sign_in/CHANGELOG.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
## NEXT
2-
3-
* Updates README to indicate that Andoid SDK <21 is no longer supported.
1+
## 7.0.0
2+
3+
* **BREAKING CHANGE**: Many APIs have changed or been replaced to reflect the
4+
current APIs and best practices of the underlying platform SDKs. For full
5+
details, see the README and migration guide, but notable highlights include:
6+
* The `GoogleSignIn` instance is now a singleton.
7+
* Clients must call and await the new `initialize` method before calling any
8+
other methods on the instance.
9+
* Authentication and authorization are now separate steps.
10+
* Access tokens and server auth codes are obtained via separate calls.
411

512
## 6.3.0
613

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Migrating from `google_sign_in` 6.x to 7.x
2+
3+
The API of `google_sign_in` 6.x and earlier was designed for the Google Sign-In
4+
SDK, which has been deprecated on both Android and Web, and replaced with new
5+
SDKs that have significantly different structures. As a result, the
6+
`google_sign_in` API surface has changed significantly. Notable differences
7+
include:
8+
* `GoogleSignIn` is now a singleton, which is obtained via
9+
`GoogleSignIn.instance`. In practice, creating multiple `GoogleSignIn`
10+
instances in 6.x would not work correctly, so this just enforces an existing
11+
restriction.
12+
* There is now an explicit `initialize` step that must be called exactly once,
13+
before any other methods. On some platforms the future will complete almost
14+
immediately, but on others (for example, web) it may take some time.
15+
* The plugin no longer tracks a single "current" signed in user. Instead,
16+
applications that assume a single signed in user should track this at the
17+
application level using the `authenticationEvents` stream.
18+
* Authentication (signing in) and authorization (allowing access to user data
19+
in the form of scopes) are now separate steps. Recommended practice is to
20+
authenticate as soon as it makes sense for a user to potentially be signed in,
21+
but to delay authorization until the point where the data will actually be
22+
used.
23+
* In applications where these steps should happen at the same time, you can
24+
pass a `scopeHint` during the authentication step. On platforms that support
25+
it this allows for a combined authentication and authorization UI flow.
26+
Not all platforms allow combining these flows, so your application should be
27+
prepared to trigger a separate authorization prompt if necessary.
28+
* Authorization is further separated into client and server authorization.
29+
Applications that need a `serverAuthCode` must now call a separate method,
30+
`authorizeServer`, to obtain that code.
31+
* Client authorization is handled via two new methods:
32+
* `authorizationForScopes`, which returns an access token if the requested
33+
scopes are already authorized, or null if not, and
34+
* `authorizeScopes`, which requests that the user authorize the scopes, and
35+
is expected to show UI.
36+
37+
Clients should generally attempt to get tokens via `authorizationForScopes`,
38+
and if they are unable to do so, show some UI to request authoriaztion that
39+
calls `authorizeScopes`. This is similar to the previously web-only flow
40+
of calling `canAccessScopes` and then calling `addScopes` if necessary.
41+
* `signInSilently` has been replaced with `attemptLightweightAuthentication`.
42+
The intended usage is essentially the same, but the change reflects that it
43+
is no longer guaranteed to be silent. For example, as of the publishing of
44+
7.0, on web this may show a floating sign-in card, and on Android it may show
45+
an account selection sheet.
46+
* This new method is no longer guaranteed to return a future. This allows
47+
clients to distinguish, at runtime:
48+
* platforms where a definitive "signed in" or "not signed in" response
49+
can be returned quickly, and thus `await`-ing completion is reasonable,
50+
in which case a `Future` is returned, and
51+
* platforms (such as web) where it could take an arbitrary amount of time,
52+
in which case no `Future` is returned, and clients should assume a
53+
non-signed-in state until/unless a sign-in event is eventually posted to
54+
the `authenticationEvents` stream.
55+
* `authenticate` replaces the authentication portion of `signIn` on platforms
56+
that support it (see below).
57+
* The new `supportsAuthenticate` method allows clients to determine at runtime
58+
whether the `authenticate` method is supported, as some platforms do not allow
59+
custom UI to trigger explicit authentication. These platforms instead provide
60+
some other platform-specific way of triggering authentication. As of
61+
publishing, the only platform that does not support `authenticate` is web,
62+
where `google_sign_in_web`'s `renderButton` is used to create a sign-in
63+
button.

packages/google_sign_in/google_sign_in/README.md

Lines changed: 94 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -6,182 +6,145 @@ A Flutter plugin for [Google Sign In](https://developers.google.com/identity/).
66
|-------------|---------|-------|--------|-----|
77
| **Support** | SDK 21+ | 12.0+ | 10.15+ | Any |
88

9-
## Platform integration
9+
## Setup
1010

11-
### Android integration
12-
13-
To access Google Sign-In, you'll need to make sure to
14-
[register your application](https://firebase.google.com/docs/android/setup).
15-
16-
You don't need to include the google-services.json file in your app unless you
17-
are using Google services that require it. You do need to enable the OAuth APIs
18-
that you want, using the
19-
[Google Cloud Platform API manager](https://console.developers.google.com/). For
20-
example, if you want to mimic the behavior of the Google Sign-In sample app,
21-
you'll need to enable the
22-
[Google People API](https://developers.google.com/people/).
23-
24-
Make sure you've filled out all required fields in the console for
25-
[OAuth consent screen](https://console.developers.google.com/apis/credentials/consent).
26-
Otherwise, you may encounter `APIException` errors.
27-
28-
### iOS integration
29-
30-
Please see [instructions on integrating Google Sign-In for iOS](https://pub.dev/packages/google_sign_in_ios#ios-integration).
31-
32-
#### iOS additional requirement
33-
34-
Note that according to
35-
https://developer.apple.com/sign-in-with-apple/get-started, starting June 30,
36-
2020, apps that use login services must also offer a "Sign in with Apple" option
37-
when submitting to the Apple App Store.
38-
39-
Consider also using an Apple sign in plugin from pub.dev.
40-
41-
The Flutter Favorite
42-
[sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) plugin could
43-
be an option.
44-
45-
### macOS integration
46-
47-
Please see [instructions on integrating Google Sign-In for macOS](https://pub.dev/packages/google_sign_in_ios#macos-setup).
48-
49-
### Web integration
11+
### Import the package
5012

51-
The new SDK used by the web has fully separated Authentication from Authorization,
52-
so `signIn` and `signInSilently` no longer authorize OAuth `scopes`.
13+
To use this plugin, follow the
14+
[plugin installation instructions](https://pub.dev/packages/google_sign_in/install),
15+
then follow the platform integration steps below for all platforms you support.
5316

54-
Flutter apps must be able to detect what scopes have been granted by their users,
55-
and if the grants are still valid.
17+
### Platform integration
5618

57-
Read below about **Working with scopes, and incremental authorization** for
58-
general information about changes that may be needed on an app, and for more
59-
specific web integration details, see the
60-
[`google_sign_in_web` package](https://pub.dev/packages/google_sign_in_web).
19+
* **Android**: Please see [the `google_sign_in_android` README](https://pub.dev/packages/google_sign_in_android#integration).
20+
* **iOS**: Please see [the `google_sign_in_ios` README](https://pub.dev/packages/google_sign_in_ios#ios-integration).
21+
* **macOS**: Please see [the `google_sign_in_ios` README](https://pub.dev/packages/google_sign_in_ios#macos-integration) (which also supports macOS).
22+
* **Web**: Please see [the `google_sign_in_web` README](https://pub.dev/packages/google_sign_in_web#integration).
6123

6224
## Usage
6325

64-
### Import the package
26+
### Initialization and authentication
6527

66-
To use this plugin, follow the
67-
[plugin installation instructions](https://pub.dev/packages/google_sign_in/install).
28+
Initialize the `GoogleSignIn` instance, and (optionally) start the lightweight
29+
authentication process:
6830

69-
### Use the plugin
70-
71-
Initialize `GoogleSignIn` with the scopes you want:
72-
73-
<?code-excerpt "example/lib/main.dart (Initialize)"?>
31+
<?code-excerpt "example/lib/main.dart (Setup)"?>
7432
```dart
75-
const List<String> scopes = <String>[
76-
'email',
77-
'https://www.googleapis.com/auth/contacts.readonly',
78-
];
79-
80-
GoogleSignIn _googleSignIn = GoogleSignIn(
81-
// Optional clientId
82-
// clientId: 'your-client_id.apps.googleusercontent.com',
83-
scopes: scopes,
84-
);
33+
final GoogleSignIn signIn = GoogleSignIn.instance;
34+
unawaited(signIn
35+
.initialize(clientId: clientId, serverClientId: serverClientId)
36+
.then((_) {
37+
signIn.authenticationEvents
38+
.listen(_handleAuthenticationEvent)
39+
.onError(_handleAuthenticationError);
40+
41+
/// This example always uses the stream-based approach to determining
42+
/// which UI state to show, rather than using the future returned here,
43+
/// if any, to conditionally skip directly to the signed-in state.
44+
signIn.attemptLightweightAuthentication();
45+
}));
8546
```
8647

87-
[Full list of available scopes](https://developers.google.com/identity/protocols/googlescopes).
88-
89-
You can now use the `GoogleSignIn` class to authenticate in your Dart code, e.g.
48+
If the user isn't signed in by the lightweight method, you can show UI to
49+
start a sign-in flow. This uses `authenticate` on platforms that return true
50+
for `supportsAuthenticate`, otherwise applications should fall back to a
51+
platform-specific approach. For instance, user-initiated sign in on web must
52+
use a button rendered by the sign in SDK, rather than application-provided
53+
UI:
9054

91-
<?code-excerpt "example/lib/main.dart (SignIn)"?>
55+
<?code-excerpt "example/lib/main.dart (ExplicitSignIn)"?>
9256
```dart
93-
Future<void> _handleSignIn() async {
94-
try {
95-
await _googleSignIn.signIn();
96-
} catch (error) {
97-
print(error);
98-
}
99-
}
57+
if (GoogleSignIn.instance.supportsAuthenticate())
58+
ElevatedButton(
59+
onPressed: () async {
60+
try {
61+
await GoogleSignIn.instance.authenticate();
62+
} catch (e) {
63+
// ···
64+
}
65+
},
66+
child: const Text('SIGN IN'),
67+
)
68+
else ...<Widget>[
69+
if (kIsWeb)
70+
web.renderButton()
71+
// ···
72+
]
10073
```
10174

102-
In the web, you should use the **Google Sign In button** (and not the `signIn` method)
103-
to guarantee that your user authentication contains a valid `idToken`.
104-
105-
For more details, take a look at the
106-
[`google_sign_in_web` package](https://pub.dev/packages/google_sign_in_web).
107-
108-
## Working with scopes, and incremental authorization.
109-
110-
If your app supports both mobile and web, read this section!
75+
## Authorization
11176

11277
### Checking if scopes have been granted
11378

114-
Users may (or may *not*) grant all the scopes that an application requests at
115-
Sign In. In fact, in the web, no scopes are granted by `signIn`, `silentSignIn`
116-
or the `renderButton` widget anymore.
117-
118-
Applications must be able to:
119-
120-
* Detect if the authenticated user has authorized the scopes they need.
121-
* Determine if the scopes that were granted a few minutes ago are still valid.
122-
123-
There's a new method that enables the checks above, `canAccessScopes`:
79+
If the user has previously authorized the scopes required by your application,
80+
you can silently request an access token for those scopes:
12481

125-
<?code-excerpt "example/lib/main.dart (CanAccessScopes)"?>
82+
<?code-excerpt "example/lib/main.dart (CheckAuthorization)" plaster="none"?>
12683
```dart
127-
// In mobile, being authenticated means being authorized...
128-
bool isAuthorized = account != null;
129-
// However, on web...
130-
if (kIsWeb && account != null) {
131-
isAuthorized = await _googleSignIn.canAccessScopes(scopes);
132-
}
84+
const List<String> scopes = <String>[
85+
'https://www.googleapis.com/auth/contacts.readonly',
86+
];
87+
final GoogleSignInAccount? user = // ...
88+
final GoogleSignInClientAuthorization? authorization =
89+
await user?.authorizationClient.authorizationForScopes(scopes);
13390
```
13491

135-
_(Only implemented in the web platform, from version 6.1.0 of this package)_
92+
[Full list of available scopes](https://developers.google.com/identity/protocols/googlescopes).
13693

13794
### Requesting more scopes when needed
13895

13996
If an app determines that the user hasn't granted the scopes it requires, it
140-
should initiate an Authorization request. (Remember that in the web platform,
141-
this request **must be initiated from an user interaction**, like a button press).
97+
should initiate an Authorization request. On platforms where
98+
`authorizationRequiresUserInteraction()` returns true,
99+
this request **must be initiated from a user interaction** like a button press.
142100

143-
<?code-excerpt "example/lib/main.dart (RequestScopes)" plaster="none"?>
101+
<?code-excerpt "example/lib/main.dart (RequestScopes)"?>
144102
```dart
145-
Future<void> _handleAuthorizeScopes() async {
146-
final bool isAuthorized = await _googleSignIn.requestScopes(scopes);
147-
if (isAuthorized) {
148-
unawaited(_handleGetContact(_currentUser!));
149-
}
103+
final GoogleSignInClientAuthorization authorization =
104+
await user.authorizationClient.authorizeScopes(scopes);
150105
```
151106

152-
The `requestScopes` returns a `boolean` value that is `true` if the user has
153-
granted all the requested scopes or `false` otherwise.
154-
155-
Once your app determines that the current user `isAuthorized` to access the
156-
services for which you need `scopes`, it can proceed normally.
157-
158107
### Authorization expiration
159108

160109
In the web, **the `accessToken` is no longer refreshed**. It expires after 3600
161110
seconds (one hour), so your app needs to be able to handle failed REST requests,
162111
and update its UI to prompt the user for a new Authorization round.
163112

164113
This can be done by combining the error responses from your REST requests with
165-
the `canAccessScopes` and `requestScopes` methods described above.
114+
the authorization methods described above.
166115

167116
For more details, take a look at the
168117
[`google_sign_in_web` package](https://pub.dev/packages/google_sign_in_web).
169118

170-
### Does an app always need to check `canAccessScopes`?
119+
### Requesting a server auth code
171120

172-
The new web SDK implicitly grant access to the `email`, `profile` and `openid`
173-
scopes when users complete the sign-in process (either via the One Tap UX or the
174-
Google Sign In button).
121+
If your application needs to access user data from a backend server, you can
122+
request a server auth code to send to the server:
175123

176-
If an app only needs an `idToken`, or only requests permissions to any/all of
177-
the three scopes mentioned above
178-
([OpenID Connect scopes](https://developers.google.com/identity/protocols/oauth2/scopes#openid-connect)),
179-
it won't need to implement any additional scope handling.
124+
<?code-excerpt "example/lib/main.dart (RequestServerAuth)"?>
125+
```dart
126+
final GoogleSignInServerAuthorization? serverAuth =
127+
await user.authorizationClient.authorizeServer(scopes);
128+
```
129+
130+
Server auth codes are not always available on all platforms. For instance, on
131+
some platforms they may only be returned when a user initially signs in, and
132+
not for subsequent authentications via the lightweight process. If you
133+
need a server auth code you should request it as soon as possible after initial
134+
sign-in, and manage server tokens for that user entirely on the server side
135+
unless the signed in user changes.
180136

181-
If an app needs any scope other than `email`, `profile` and `openid`, it **must**
182-
implement a more complete scope handling, as described above.
137+
On platforms where `authorizationRequiresUserInteraction()` returns true,
138+
this request **must be initiated from a user interaction** like a button press.
183139

184140
## Example
185141

186-
Find the example wiring in the
187-
[Google sign-in example application](https://github.com/flutter/packages/blob/main/packages/google_sign_in/google_sign_in/example/lib/main.dart).
142+
The
143+
[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
144+
package to sign a user in and authorize access to specific user data.
145+
146+
## Migration from pre-7.0 versions
147+
148+
If you used version 6.x or earlier of `google_sign_in`, see
149+
[the migration guide](https://github.com/flutter/packages/blob/main/packages/google_sign_in/google_sign_in/MIGRATION.md)
150+
for more information about the changes.

packages/google_sign_in/google_sign_in/example/integration_test/google_sign_in_test.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ void main() {
1010
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
1111

1212
testWidgets('Can initialize the plugin', (WidgetTester tester) async {
13-
final GoogleSignIn signIn = GoogleSignIn();
13+
final GoogleSignIn signIn = GoogleSignIn.instance;
1414
expect(signIn, isNotNull);
15+
16+
await signIn.initialize();
1517
});
1618
}

0 commit comments

Comments
 (0)