Skip to content

feat(auth): add reauthenticateWithProvider #9570

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

Merged
merged 5 commits into from
Sep 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 18 additions & 1 deletion docs/auth/federated-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,4 +363,21 @@ if (kIsWeb) {
}

// You're anonymous user is now upgraded to be able to connect with Sign In With Apple
```
```

# Reauthenticate with provider

The same pattern can be used with `reauthenticateWithProvider` which can be used to retrieve fresh
credentials for sensitive operations that require recent login.

```dart
final appleProvider = AppleAuthProvider();

if (kIsWeb) {
await FirebaseAuth.instance.currentUser?.reauthenticateWithPopup(appleProvider);
} else {
await FirebaseAuth.instance.currentUser?.reauthenticateWithProvider(appleProvider);
}

// You can now perform sensitive operations
```
Original file line number Diff line number Diff line change
Expand Up @@ -1497,6 +1497,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
case "User#linkWithProvider":
methodCallTask = startActivityForLinkWithProvider(call.arguments());
break;
case "User#reauthenticateWithProvider":
methodCallTask = reauthenticateWithProvider(call.arguments());
break;
case "User#delete":
methodCallTask = deleteUser(call.arguments());
break;
Expand Down Expand Up @@ -1594,6 +1597,47 @@ private Task<Map<String, Object>> startActivityForLinkWithProvider(
return taskCompletionSource.getTask();
}

private Task<Map<String, Object>> reauthenticateWithProvider(Map<String, Object> arguments) {
TaskCompletionSource<Map<String, Object>> taskCompletionSource = new TaskCompletionSource<>();

cachedThreadPool.execute(
() -> {
try {
FirebaseUser firebaseUser = getCurrentUser(arguments);

String providerId =
(String) Objects.requireNonNull(arguments.get(Constants.SIGN_IN_PROVIDER));
@SuppressWarnings("unchecked")
List<String> scopes = (List<String>) arguments.get(Constants.SIGN_IN_PROVIDER_SCOPE);
@SuppressWarnings("unchecked")
Map<String, String> customParameters =
(Map<String, String>) arguments.get(Constants.SIGN_IN_PROVIDER_CUSTOM_PARAMETERS);

OAuthProvider.Builder provider = OAuthProvider.newBuilder(providerId);
if (scopes != null) {
provider.setScopes(scopes);
}
if (customParameters != null) {
provider.addCustomParameters(customParameters);
}

AuthResult authResult =
Tasks.await(
firebaseUser.startActivityForReauthenticateWithProvider(
/* activity= */ activity, provider.build()));
taskCompletionSource.setResult(parseAuthResult(authResult));
} catch (Exception e) {
if (e.getCause() instanceof FirebaseAuthMultiFactorException) {
handleMultiFactorException(arguments, taskCompletionSource, e);
} else {
taskCompletionSource.setException(e);
}
}
});

return taskCompletionSource.getTask();
}

private Task<Map<String, Object>> signInWithProvider(Map<String, Object> arguments) {
TaskCompletionSource<Map<String, Object>> taskCompletionSource = new TaskCompletionSource<>();

Expand Down
4 changes: 4 additions & 0 deletions packages/firebase_auth/firebase_auth/example/lib/auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,10 @@ class _AuthGateState extends State<AuthGate> {
} else {
await _auth.signInWithProvider(microsoftProvider);
}

await FirebaseAuth.instance.currentUser?.reauthenticateWithProvider(
microsoftProvider,
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)flutter
[self userLinkWithCredential:call.arguments withMethodCallResult:methodCallResult];
} else if ([@"User#linkWithProvider" isEqualToString:call.method]) {
[self userLinkWithProvider:call.arguments withMethodCallResult:methodCallResult];
} else if ([@"User#reauthenticateWithProvider" isEqualToString:call.method]) {
[self reauthenticateWithProvider:call.arguments withMethodCallResult:methodCallResult];
} else if ([@"User#reauthenticateUserWithCredential" isEqualToString:call.method]) {
[self userReauthenticateUserWithCredential:call.arguments
withMethodCallResult:methodCallResult];
Expand Down Expand Up @@ -999,6 +1001,50 @@ - (void)userLinkWithProvider:(id)arguments
#endif
}

- (void)reauthenticateWithProvider:(id)arguments
withMethodCallResult:(FLTFirebaseMethodCallResult *)result {
FIRAuth *auth = [self getFIRAuthFromArguments:arguments];

FIRUser *currentUser = auth.currentUser;
if (currentUser == nil) {
result.error(kErrCodeNoCurrentUser, kErrMsgNoCurrentUser, nil, nil);
return;
}

if ([arguments[@"signInProvider"] isEqualToString:kSignInMethodApple]) {
if (@available(iOS 13.0, macOS 10.15, *)) {
self.linkWithAppleUser = currentUser;
launchAppleSignInRequest(self, arguments, result);
} else {
NSLog(@"Sign in with Apple was introduced in iOS 13, update your Podfile with platform :ios, "
@"'13.0'");
}
return;
}
#if TARGET_OS_OSX
NSLog(@"reauthenticateWithProvider is not supported on the "
@"MacOS platform.");
result.success(nil);
#else
self.authProvider = [FIROAuthProvider providerWithProviderID:arguments[@"signInProvider"]];
NSArray *scopes = arguments[kArgumentProviderScope];
if (scopes != nil) {
[self.authProvider setScopes:scopes];
}
NSDictionary *customParameters = arguments[kArgumentProviderCustomParameters];
if (customParameters != nil) {
[self.authProvider setCustomParameters:customParameters];
}

[currentUser reauthenticateWithProvider:self.authProvider
UIDelegate:nil
completion:^(FIRAuthDataResult *authResult, NSError *error) {
handleAppleAuthResult(self, arguments, auth, authResult.credential,
error, result);
}];
#endif
}

- (void)userLinkWithCredential:(id)arguments
withMethodCallResult:(FLTFirebaseMethodCallResult *)result {
FIRAuth *auth = [self getFIRAuthFromArguments:arguments];
Expand Down
38 changes: 38 additions & 0 deletions packages/firebase_auth/firebase_auth/lib/src/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,44 @@ class User {
}
}

/// Re-authenticates a user using a Provider.
///
/// Use before operations such as [User.updatePassword] that require tokens
/// from recent sign-in attempts.
///
/// A [FirebaseAuthException] maybe thrown with the following error code:
/// - **user-mismatch**:
/// - Thrown if the credential given does not correspond to the user.
/// - **user-not-found**:
/// - Thrown if the credential given does not correspond to any existing
/// user.
/// - **invalid-credential**:
/// - Thrown if the provider's credential is not valid. This can happen if it
/// has already expired when calling link, or if it used invalid token(s).
/// See the Firebase documentation for your provider, and make sure you
/// pass in the correct parameters to the credential method.
/// - **invalid-email**:
/// - Thrown if the email used in a [EmailAuthProvider.credential] is
/// invalid.
/// - **wrong-password**:
/// - Thrown if the password used in a [EmailAuthProvider.credential] is not
/// correct or when the user associated with the email does not have a
/// password.
/// - **invalid-verification-code**:
/// - Thrown if the credential is a [PhoneAuthProvider.credential] and the
/// verification code of the credential is not valid.
/// - **invalid-verification-id**:
/// - Thrown if the credential is a [PhoneAuthProvider.credential] and the
/// verification ID of the credential is not valid.
Future<UserCredential> reauthenticateWithProvider(
AuthProvider provider,
) async {
return UserCredential._(
_auth,
await _delegate.reauthenticateWithProvider(provider),
);
}

/// Links the user account with the given provider.
///
/// A [FirebaseAuthException] maybe thrown with the following error code:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,35 @@ class MethodChannelUser extends UserPlatform {
}
}

@override
Future<UserCredentialPlatform> reauthenticateWithProvider(
AuthProvider provider,
) async {
try {
// To extract scopes and custom parameters from the provider
final convertedProvider = convertToOAuthProvider(provider);

Map<String, dynamic> data = (await MethodChannelFirebaseAuth.channel
.invokeMapMethod<String, dynamic>(
'User#reauthenticateWithProvider',
_withChannelDefaults({
'signInProvider': convertedProvider.providerId,
if (convertedProvider is OAuthProvider) ...{
'scopes': convertedProvider.scopes,
'customParameters': convertedProvider.parameters
},
})))!;

MethodChannelUserCredential userCredential =
MethodChannelUserCredential(auth, data);

auth.currentUser = userCredential.user;
return userCredential;
} catch (e, stack) {
convertPlatformException(e, stack);
}
}

@override
Future<UserCredentialPlatform> reauthenticateWithCredential(
AuthCredential credential,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,44 @@ abstract class UserPlatform extends PlatformInterface {
throw UnimplementedError('linkWithProvider() is not implemented');
}

/// Renews the user’s authentication using the provided auth provider instance.
/// On Web you should use [linkWithPopup] instead.
///
/// A [FirebaseAuthException] maybe thrown with the following error code:
/// - **invalid-credential**:
/// - Thrown if the provider's credential is not valid. This can happen if it
/// has already expired when calling link, or if it used invalid token(s).
/// See the Firebase documentation for your provider, and make sure you
/// pass in the correct parameters to the credential method.
/// - **operation-not-allowed**:
/// - Thrown if you have not enabled the provider in the Firebase Console. Go
/// to the Firebase Console for your project, in the Auth section and the
/// Sign in Method tab and configure the provider.
Future<UserCredentialPlatform> reauthenticateWithProvider(
AuthProvider provider,
) {
throw UnimplementedError('reauthenticateWithProvider() is not implemented');
}

/// Renews the user’s authentication using the provided auth provider instance.
/// On mobile you should use [reauthenticateWithProvider] instead.
///
/// A [FirebaseAuthException] maybe thrown with the following error code:
/// - **invalid-credential**:
/// - Thrown if the provider's credential is not valid. This can happen if it
/// has already expired when calling link, or if it used invalid token(s).
/// See the Firebase documentation for your provider, and make sure you
/// pass in the correct parameters to the credential method.
/// - **operation-not-allowed**:
/// - Thrown if you have not enabled the provider in the Firebase Console. Go
/// to the Firebase Console for your project, in the Auth section and the
/// Sign in Method tab and configure the provider.
Future<UserCredentialPlatform> reauthenticateWithPopup(
AuthProvider provider,
) {
throw UnimplementedError('reauthenticateWithPopup() is not implemented');
}

/// Links the user account with the given provider.
///
/// A [FirebaseAuthException] maybe thrown with the following error code:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,19 @@ class UserWeb extends UserPlatform {
}
}

@override
Future<UserCredentialPlatform> reauthenticateWithPopup(
AuthProvider provider) async {
_assertIsSignedOut(auth);
try {
auth_interop.UserCredential userCredential = await _webUser
.reauthenticateWithPopup(convertPlatformAuthProvider(provider));
return UserCredentialWeb(auth, userCredential);
} catch (e) {
throw getFirebaseAuthException(e);
}
}

@override
Future<void> reload() async {
_assertIsSignedOut(auth);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,6 @@ class User extends UserInfo<auth_interop.UserJsImpl> {
Future<void> linkWithRedirect(AuthProvider provider) => handleThenable(
auth_interop.linkWithRedirect(jsObject, provider.jsObject));

// FYI: as of 2017-07-03 – the return type of this guy is documented as
// Promise (Future)<nothing> - Filed a bug internally.
/// Re-authenticates a user using a fresh credential, and returns any
/// available additional user information, such as user name.
Future<UserCredential> reauthenticateWithCredential(
Expand Down