Skip to content

[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

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
432a627
Initial PoC using CredentialManager
stuartmorgan-g Feb 12, 2025
df1cf87
Pathify everything for local development
stuartmorgan-g Mar 20, 2025
6a1ec7c
Remove method channel implementation
stuartmorgan-g Mar 20, 2025
49342c5
First pass rework of interface, impls, and app-facing API
stuartmorgan-g Mar 20, 2025
eba7a8b
Add nonce support
stuartmorgan-g May 2, 2025
f3d472f
Initial README updates and migration guide
stuartmorgan-g May 2, 2025
728a4f9
Rework Android calls to match recommended practice
stuartmorgan-g May 5, 2025
c165834
Synthesize sign-out event for disconnect
stuartmorgan-g May 5, 2025
dba0f3e
platform interface tests
stuartmorgan-g May 5, 2025
83c7b1d
Simplify Android Pigeon API
stuartmorgan-g May 6, 2025
82c0530
Android Dart unit tests
stuartmorgan-g May 8, 2025
4123b18
iOS Dart unit tests
stuartmorgan-g May 9, 2025
165fb78
Web Dart 'unit' tests, and fix lightweight auth return
stuartmorgan-g May 9, 2025
eda66bb
Update example apps
stuartmorgan-g May 9, 2025
21635a0
Update native unit tests, fix an Obj-C selector name
stuartmorgan-g May 13, 2025
39b7505
Update to recent 1.5 release
stuartmorgan-g May 13, 2025
07fe543
Android native unit tests
stuartmorgan-g May 14, 2025
e6c9f62
README, CHANGELOG, and version updates
stuartmorgan-g May 16, 2025
90a63aa
App-facing Dart tests
stuartmorgan-g May 16, 2025
816f614
Revert unrelated Xcode project changes
stuartmorgan-g May 16, 2025
0d46beb
Analysis fix
stuartmorgan-g May 16, 2025
a0cf078
Revert extension package changes
stuartmorgan-g May 16, 2025
0a134ec
Fix Obj-C warnings
stuartmorgan-g May 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions packages/google_sign_in/google_sign_in/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
## NEXT

* Updates README to indicate that Andoid SDK <21 is no longer supported.
## 7.0.0

* **BREAKING CHANGE**: Many APIs have changed or been replaced to reflect the
current APIs and best practices of the underlying platform SDKs. For full
details, see the README and migration guide, but notable highlights include:
* The `GoogleSignIn` instance is now a singleton.
* Clients must call and await the new `initialize` method before calling any
other methods on the instance.
* Authentication and authorization are now separate steps.
* Access tokens and server auth codes are obtained via separate calls.

## 6.3.0

Expand Down
63 changes: 63 additions & 0 deletions packages/google_sign_in/google_sign_in/MIGRATION.md
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.
225 changes: 94 additions & 131 deletions packages/google_sign_in/google_sign_in/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
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.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

testWidgets('Can initialize the plugin', (WidgetTester tester) async {
final GoogleSignIn signIn = GoogleSignIn();
final GoogleSignIn signIn = GoogleSignIn.instance;
expect(signIn, isNotNull);

await signIn.initialize();
});
}
Loading