Skip to content

Commit 0c213b9

Browse files
authored
feat(authentication): add useCredentialManager option for Google Sign-In (#859)
* feat(authentication): add useCredentialManager option for Google Sign-In Add definitions for SignInWithGoogleOptions. * feat(authentication): add useCredentialManager option for Google Sign-In Enable legacy google sign in on Android. * feat(authentication): add useCredentialManager option for Google Sign-In Generate changeset. * feat(authentication): add useCredentialManager option for Google Sign-In Refactor userCredential logic.
1 parent 33743e2 commit 0c213b9

File tree

7 files changed

+187
-31
lines changed

7 files changed

+187
-31
lines changed

.changeset/mighty-windows-guess.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@capacitor-firebase/authentication': minor
3+
---
4+
5+
feat(android): add option to disable the new Android Credential Manager

packages/authentication/README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,14 +1292,14 @@ Starts the GitHub sign-in flow.
12921292
### signInWithGoogle(...)
12931293

12941294
```typescript
1295-
signInWithGoogle(options?: SignInWithOAuthOptions | undefined) => Promise<SignInResult>
1295+
signInWithGoogle(options?: SignInWithGoogleOptions | undefined) => Promise<SignInResult>
12961296
```
12971297

12981298
Starts the Google sign-in flow.
12991299

1300-
| Param | Type |
1301-
| ------------- | ------------------------------------------------------------------------- |
1302-
| **`options`** | <code><a href="#signinwithoauthoptions">SignInWithOAuthOptions</a></code> |
1300+
| Param | Type |
1301+
| ------------- | --------------------------------------------------------------------------- |
1302+
| **`options`** | <code><a href="#signinwithgoogleoptions">SignInWithGoogleOptions</a></code> |
13031303

13041304
**Returns:** <code>Promise&lt;<a href="#signinresult">SignInResult</a>&gt;</code>
13051305

@@ -2040,6 +2040,13 @@ An interface covering the possible persistence mechanism types.
20402040
| **`skipNativeAuth`** | <code>boolean</code> | Whether the plugin should skip the native authentication or not. Only needed if you want to use the Firebase JavaScript SDK. This value overwrites the configrations value of the `skipNativeAuth` option. If no value is set, the configuration value is used. **Note that the plugin may behave differently across the platforms.** `skipNativeAuth` cannot be used in combination with `signInWithCustomToken`, `createUserWithEmailAndPassword` or `signInWithEmailAndPassword`. Only available for Android and iOS. | 1.1.0 |
20412041

20422042

2043+
#### SignInWithGoogleOptions
2044+
2045+
| Prop | Type | Description | Default | Since |
2046+
| -------------------------- | -------------------- | --------------------------------------------------------------------------------- | ----------------- | ----- |
2047+
| **`useCredentialManager`** | <code>boolean</code> | Whether to use the Credential Manager API to sign in. Only available for Android. | <code>true</code> | 7.2.0 |
2048+
2049+
20432050
#### UnlinkResult
20442051

20452052
| Prop | Type | Description | Since |

packages/authentication/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/authentication/FirebaseAuthentication.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,14 @@ public void startActivityForResult(final PluginCall call, Intent intent, String
609609
plugin.startActivityForResult(call, intent, callbackName);
610610
}
611611

612+
public void handleGoogleAuthProviderSignInActivityResult(@NonNull final PluginCall call, @NonNull ActivityResult result) {
613+
googleAuthProviderHandler.handleOnActivityResult(call, result, false);
614+
}
615+
616+
public void handleGoogleAuthProviderLinkActivityResult(@NonNull final PluginCall call, @NonNull ActivityResult result) {
617+
googleAuthProviderHandler.handleOnActivityResult(call, result, true);
618+
}
619+
612620
public void handlePlayGamesAuthProviderSignInActivityResult(@NonNull final PluginCall call, @NonNull ActivityResult result) {
613621
playGamesAuthProviderHandler.handleOnActivityResult(call, result, false);
614622
}

packages/authentication/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/authentication/FirebaseAuthenticationPlugin.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,22 @@ protected void handleOnActivityResult(int requestCode, int resultCode, @Nullable
10191019
implementation.handleOnActivityResult(requestCode, resultCode, data);
10201020
}
10211021

1022+
@ActivityCallback
1023+
private void handleGoogleAuthProviderSignInActivityResult(@Nullable PluginCall call, @Nullable ActivityResult result) {
1024+
if (call == null || result == null) {
1025+
return;
1026+
}
1027+
implementation.handleGoogleAuthProviderSignInActivityResult(call, result);
1028+
}
1029+
1030+
@ActivityCallback
1031+
private void handleGoogleAuthProviderLinkActivityResult(@Nullable PluginCall call, @Nullable ActivityResult result) {
1032+
if (call == null || result == null) {
1033+
return;
1034+
}
1035+
implementation.handleGoogleAuthProviderLinkActivityResult(call, result);
1036+
}
1037+
10221038
@ActivityCallback
10231039
private void handlePlayGamesAuthProviderSignInActivityResult(@Nullable PluginCall call, @Nullable ActivityResult result) {
10241040
if (call == null || result == null) {

packages/authentication/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/authentication/handlers/GoogleAuthProviderHandler.java

Lines changed: 129 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,18 @@
2121
import com.getcapacitor.JSArray;
2222
import com.getcapacitor.Logger;
2323
import com.getcapacitor.PluginCall;
24+
import com.google.android.gms.auth.GoogleAuthException;
25+
import com.google.android.gms.auth.GoogleAuthUtil;
2426
import com.google.android.gms.auth.api.identity.AuthorizationRequest;
2527
import com.google.android.gms.auth.api.identity.AuthorizationResult;
2628
import com.google.android.gms.auth.api.identity.Identity;
29+
import com.google.android.gms.auth.api.signin.GoogleSignIn;
30+
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
31+
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
32+
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
2733
import com.google.android.gms.common.api.ApiException;
2834
import com.google.android.gms.common.api.Scope;
35+
import com.google.android.gms.tasks.Task;
2936
import com.google.android.libraries.identity.googleid.GetGoogleIdOption;
3037
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential;
3138
import com.google.firebase.auth.AuthCredential;
@@ -34,6 +41,7 @@
3441
import io.capawesome.capacitorjs.plugins.firebase.authentication.FirebaseAuthenticationPlugin;
3542
import io.capawesome.capacitorjs.plugins.firebase.authentication.R;
3643
import io.capawesome.capacitorjs.plugins.firebase.authentication.interfaces.NonEmptyCallback;
44+
import java.io.IOException;
3745
import java.util.ArrayList;
3846
import java.util.List;
3947
import java.util.concurrent.Executor;
@@ -43,6 +51,7 @@
4351
public class GoogleAuthProviderHandler {
4452

4553
private FirebaseAuthentication pluginImplementation;
54+
private GoogleSignInClient mGoogleSignInClient;
4655

4756
@Nullable
4857
private AuthCredential lastAuthCredential;
@@ -57,6 +66,7 @@ public class GoogleAuthProviderHandler {
5766

5867
public GoogleAuthProviderHandler(FirebaseAuthentication pluginImplementation) {
5968
this.pluginImplementation = pluginImplementation;
69+
this.mGoogleSignInClient = buildGoogleSignInClient();
6070
}
6171

6272
public void handleActivityResult(@NonNull ActivityResult result) {
@@ -75,6 +85,53 @@ public void handleActivityResult(@NonNull ActivityResult result) {
7585
}
7686
}
7787

88+
public void handleOnActivityResult(@NonNull final PluginCall call, @NonNull ActivityResult result, boolean isLink) {
89+
Intent data = result.getData();
90+
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
91+
try {
92+
GoogleSignInAccount account = task.getResult(ApiException.class);
93+
String idToken = account.getIdToken();
94+
String serverAuthCode = account.getServerAuthCode();
95+
AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);
96+
97+
new Thread(() -> {
98+
String accessToken = null;
99+
List<String> scopes = new ArrayList<>();
100+
scopes.add("oauth2:email");
101+
scopes.addAll(getScopesAsList(call));
102+
103+
try {
104+
accessToken = GoogleAuthUtil.getToken(
105+
mGoogleSignInClient.getApplicationContext(),
106+
account.getAccount(),
107+
String.join(" ", scopes)
108+
);
109+
// Clears local cache after every login attempt
110+
// to ensure permissions changes elsewhere are reflected in future tokens
111+
GoogleAuthUtil.clearToken(mGoogleSignInClient.getApplicationContext(), accessToken);
112+
} catch (IOException | GoogleAuthException exception) {
113+
if (isLink) {
114+
pluginImplementation.handleFailedLink(call, null, exception);
115+
} else {
116+
pluginImplementation.handleFailedSignIn(call, null, exception);
117+
}
118+
return;
119+
}
120+
if (isLink) {
121+
pluginImplementation.handleSuccessfulLink(call, credential, idToken, null, accessToken, serverAuthCode);
122+
} else {
123+
pluginImplementation.handleSuccessfulSignIn(call, credential, idToken, null, accessToken, serverAuthCode, null);
124+
}
125+
}).start();
126+
} catch (ApiException exception) {
127+
if (isLink) {
128+
pluginImplementation.handleFailedLink(call, null, exception);
129+
} else {
130+
pluginImplementation.handleFailedSignIn(call, null, exception);
131+
}
132+
}
133+
}
134+
78135
public void handleAuthorizationResult(@NonNull AuthorizationResult authorizationResult) {
79136
if (lastCall == null) {
80137
return;
@@ -105,12 +162,32 @@ public void handleAuthorizationResultError(@NonNull Exception exception) {
105162
lastIdToken = null;
106163
}
107164

165+
public void signIn(final PluginCall call) {
166+
signInOrLink(call, false);
167+
}
168+
108169
public void link(final PluginCall call) {
109170
signInOrLink(call, true);
110171
}
111172

112-
public void signIn(final PluginCall call) {
113-
signInOrLink(call, false);
173+
private GoogleSignInClient buildGoogleSignInClient() {
174+
return buildGoogleSignInClient(null);
175+
}
176+
177+
private GoogleSignInClient buildGoogleSignInClient(@Nullable PluginCall call) {
178+
GoogleSignInOptions.Builder googleSignInOptionsBuilder = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
179+
.requestIdToken(pluginImplementation.getPlugin().getContext().getString(R.string.default_web_client_id))
180+
.requestServerAuthCode(pluginImplementation.getPlugin().getContext().getString(R.string.default_web_client_id))
181+
.requestEmail();
182+
183+
if (call != null) {
184+
List<String> scopeList = getScopesAsList(call);
185+
for (String scope : scopeList) {
186+
googleSignInOptionsBuilder = googleSignInOptionsBuilder.requestScopes(new Scope(scope));
187+
}
188+
}
189+
190+
return GoogleSignIn.getClient(pluginImplementation.getPlugin().getActivity(), googleSignInOptionsBuilder.build());
114191
}
115192

116193
public void signOut() {
@@ -151,6 +228,19 @@ private List<Scope> buildScopeList(@NonNull PluginCall call) {
151228
return scopeList;
152229
}
153230

231+
private List<String> getScopesAsList(@NonNull PluginCall call) {
232+
List<String> scopeList = new ArrayList<>();
233+
JSArray scopes = call.getArray("scopes");
234+
if (scopes != null) {
235+
try {
236+
scopeList = scopes.toList();
237+
} catch (JSONException exception) {
238+
Log.e(FirebaseAuthenticationPlugin.TAG, "getScopesAsList failed.", exception);
239+
}
240+
}
241+
return scopeList;
242+
}
243+
154244
private void handleGetCredentialError(final PluginCall call, final boolean isLink, final GetCredentialException exception) {
155245
if (isLink) {
156246
pluginImplementation.handleFailedLink(call, null, exception);
@@ -205,32 +295,46 @@ public void error(Exception exception) {
205295
}
206296

207297
private void signInOrLink(final PluginCall call, final boolean isLink) {
208-
Executor executor = Executors.newSingleThreadExecutor();
209-
GetGoogleIdOption googleIdOption = new GetGoogleIdOption.Builder()
210-
// Your server's client ID, not your Android client ID
211-
.setServerClientId(pluginImplementation.getPlugin().getContext().getString(R.string.default_web_client_id))
212-
// Show all accounts on the device (not just the accounts that have been used previously)
213-
.setFilterByAuthorizedAccounts(false)
214-
.build();
215-
GetCredentialRequest request = new GetCredentialRequest.Builder().addCredentialOption(googleIdOption).build();
216-
CredentialManager credentialManager = CredentialManager.create(pluginImplementation.getPlugin().getActivity());
217-
credentialManager.getCredentialAsync(
218-
pluginImplementation.getPlugin().getContext(),
219-
request,
220-
null,
221-
executor,
222-
new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
223-
@Override
224-
public void onResult(GetCredentialResponse response) {
225-
handleGetCredentialResult(call, isLink, response);
226-
}
298+
Boolean useCredentialManagerRaw = call.getBoolean("useCredentialManager");
299+
boolean useCredentialManager = (useCredentialManagerRaw != null) ? useCredentialManagerRaw : true;
227300

228-
@Override
229-
public void onError(@NonNull GetCredentialException exception) {
230-
handleGetCredentialError(call, isLink, exception);
301+
if (useCredentialManager) {
302+
Executor executor = Executors.newSingleThreadExecutor();
303+
GetGoogleIdOption googleIdOption = new GetGoogleIdOption.Builder()
304+
// Your server's client ID, not your Android client ID
305+
.setServerClientId(pluginImplementation.getPlugin().getContext().getString(R.string.default_web_client_id))
306+
// Show all accounts on the device (not just the accounts that have been used previously)
307+
.setFilterByAuthorizedAccounts(false)
308+
.build();
309+
GetCredentialRequest request = new GetCredentialRequest.Builder().addCredentialOption(googleIdOption).build();
310+
CredentialManager credentialManager = CredentialManager.create(pluginImplementation.getPlugin().getActivity());
311+
credentialManager.getCredentialAsync(
312+
pluginImplementation.getPlugin().getContext(),
313+
request,
314+
null,
315+
executor,
316+
new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
317+
@Override
318+
public void onResult(GetCredentialResponse response) {
319+
handleGetCredentialResult(call, isLink, response);
320+
}
321+
322+
@Override
323+
public void onError(@NonNull GetCredentialException exception) {
324+
handleGetCredentialError(call, isLink, exception);
325+
}
231326
}
327+
);
328+
} else {
329+
mGoogleSignInClient = buildGoogleSignInClient(call);
330+
Intent signInIntent = mGoogleSignInClient.getSignInIntent();
331+
332+
if (isLink) {
333+
pluginImplementation.startActivityForResult(call, signInIntent, "handleGoogleAuthProviderLinkActivityResult");
334+
} else {
335+
pluginImplementation.startActivityForResult(call, signInIntent, "handleGoogleAuthProviderSignInActivityResult");
232336
}
233-
);
337+
}
234338
}
235339

236340
/**

packages/authentication/src/definitions.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ export interface FirebaseAuthenticationPlugin {
377377
*
378378
* @since 0.1.0
379379
*/
380-
signInWithGoogle(options?: SignInWithOAuthOptions): Promise<SignInResult>;
380+
signInWithGoogle(options?: SignInWithGoogleOptions): Promise<SignInResult>;
381381
/**
382382
* Starts the Microsoft sign-in flow.
383383
*
@@ -1041,6 +1041,21 @@ export interface SignInWithFacebookOptions extends SignInWithOAuthOptions {
10411041
useLimitedLogin?: boolean;
10421042
}
10431043

1044+
/**
1045+
* @since 7.2.0
1046+
*/
1047+
export interface SignInWithGoogleOptions extends SignInWithOAuthOptions {
1048+
/**
1049+
* Whether to use the Credential Manager API to sign in.
1050+
*
1051+
* Only available for Android.
1052+
*
1053+
* @since 7.2.0
1054+
* @default true
1055+
*/
1056+
useCredentialManager?: boolean;
1057+
}
1058+
10441059
/**
10451060
* @since 7.2.0
10461061
*/

packages/authentication/src/web.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import type {
9393
SignInWithCustomTokenOptions,
9494
SignInWithEmailAndPasswordOptions,
9595
SignInWithEmailLinkOptions,
96+
SignInWithGoogleOptions,
9697
SignInWithOAuthOptions,
9798
SignInWithOpenIdConnectOptions,
9899
SignInWithPhoneNumberOptions,
@@ -586,7 +587,7 @@ export class FirebaseAuthenticationWeb
586587
}
587588

588589
public async signInWithGoogle(
589-
options?: SignInWithOAuthOptions,
590+
options?: SignInWithGoogleOptions,
590591
): Promise<SignInResult> {
591592
const provider = new GoogleAuthProvider();
592593
this.applySignInOptions(options || {}, provider);

0 commit comments

Comments
 (0)