From 2de2d6068646becec4b3b042e0d1d7f7777be041 Mon Sep 17 00:00:00 2001 From: Sagnik Pal Date: Sat, 16 May 2026 19:19:29 +0530 Subject: [PATCH 1/2] feat(auth): expose OAuth credential tokens in user credential result --- packages/auth/__tests__/auth.test.ts | 44 +++++++++++++++++++ .../auth/ReactNativeFirebaseAuthModule.java | 40 +++++++++++++++++ packages/auth/ios/RNFBAuth/RNFBAuthModule.m | 44 +++++++++++++++++-- packages/auth/lib/index.d.ts | 28 +++++++++++- packages/auth/lib/index.js | 1 + packages/auth/lib/web/RNFBAuthModule.js | 10 +++++ packages/auth/type-test.ts | 2 + 7 files changed, 165 insertions(+), 4 deletions(-) diff --git a/packages/auth/__tests__/auth.test.ts b/packages/auth/__tests__/auth.test.ts index 87f042960c..3980fcde6d 100644 --- a/packages/auth/__tests__/auth.test.ts +++ b/packages/auth/__tests__/auth.test.ts @@ -171,6 +171,50 @@ describe('Auth', function () { }); }); + describe('UserCredential', function () { + it('should include OAuth credential data returned by native auth result', function () { + const returnedCredential = { + providerId: 'oidc.test-provider', + accessToken: 'access-token', + idToken: 'id-token', + secret: null, + }; + const nativeUserCredential = { + additionalUserInfo: { + isNewUser: false, + profile: null, + providerId: 'oidc.test-provider', + username: null, + }, + credential: returnedCredential, + user: { + uid: 'test-user-id', + displayName: null, + email: 'test@example.com', + emailVerified: true, + isAnonymous: false, + metadata: { + lastSignInTime: '2023-01-01T00:00:00.000Z', + creationTime: '2023-01-01T00:00:00.000Z', + }, + multiFactor: { + enrolledFactors: [], + }, + phoneNumber: null, + tenantId: null, + photoURL: null, + providerData: [], + providerId: 'firebase', + }, + }; + // @ts-ignore test + const userCredential = auth()._setUserCredential(nativeUserCredential); + + expect(userCredential.user.uid).toBe('test-user-id'); + expect(userCredential.credential).toEqual(returnedCredential); + }); + }); + describe('getMultiFactorResolver', function () { it('should return null if no resolver object is found', function () { const unknownError = NativeFirebaseError.fromEvent( diff --git a/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java b/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java index e22c8ea421..60e469f9db 100644 --- a/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java +++ b/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java @@ -59,6 +59,7 @@ import com.google.firebase.auth.MultiFactorInfo; import com.google.firebase.auth.MultiFactorResolver; import com.google.firebase.auth.MultiFactorSession; +import com.google.firebase.auth.OAuthCredential; import com.google.firebase.auth.OAuthProvider; import com.google.firebase.auth.PhoneAuthCredential; import com.google.firebase.auth.PhoneAuthOptions; @@ -2373,6 +2374,14 @@ private void promiseWithAuthResult(AuthResult authResult, Promise promise) { authResultMap.putMap("additionalUserInfo", additionalUserInfoMap); } + + WritableMap credentialMap = authCredentialToMap(authResult.getCredential()); + if (credentialMap != null) { + authResultMap.putMap("credential", credentialMap); + } else { + authResultMap.putNull("credential"); + } + authResultMap.putMap("user", userMap); promise.resolve(authResultMap); @@ -2382,6 +2391,37 @@ private void promiseWithAuthResult(AuthResult authResult, Promise promise) { } } + @Nullable + private WritableMap authCredentialToMap(@Nullable AuthCredential authCredential) { + if (!(authCredential instanceof OAuthCredential)) { + return null; + } + + OAuthCredential oauthCredential = (OAuthCredential) authCredential; + WritableMap credentialMap = Arguments.createMap(); + credentialMap.putString("providerId", oauthCredential.getProvider()); + + if (oauthCredential.getAccessToken() != null) { + credentialMap.putString("accessToken", oauthCredential.getAccessToken()); + } else { + credentialMap.putNull("accessToken"); + } + + if (oauthCredential.getIdToken() != null) { + credentialMap.putString("idToken", oauthCredential.getIdToken()); + } else { + credentialMap.putNull("idToken"); + } + + if (oauthCredential.getSecret() != null) { + credentialMap.putString("secret", oauthCredential.getSecret()); + } else { + credentialMap.putNull("secret"); + } + + return credentialMap; + } + /** * promiseRejectAuthException * diff --git a/packages/auth/ios/RNFBAuth/RNFBAuthModule.m b/packages/auth/ios/RNFBAuth/RNFBAuthModule.m index 40ee9aa2e4..305c0fc6f0 100644 --- a/packages/auth/ios/RNFBAuth/RNFBAuthModule.m +++ b/packages/auth/ios/RNFBAuth/RNFBAuthModule.m @@ -29,13 +29,17 @@ static NSString *const keyUser = @"user"; static NSString *const keyEmail = @"email"; static NSString *const keyAndroid = @"android"; +static NSString *const keyIdToken = @"idToken"; +static NSString *const keySecret = @"secret"; static NSString *const keyProfile = @"profile"; static NSString *const keyNewUser = @"isNewUser"; static NSString *const keyUsername = @"username"; +static NSString *const keyCredential = @"credential"; static NSString *const keyMultiFactor = @"multiFactor"; static NSString *const keyPhotoUrl = @"photoURL"; static NSString *const keyBundleId = @"bundleId"; static NSString *const keyInstallApp = @"installApp"; +static NSString *const keyAccessToken = @"accessToken"; static NSString *const keyProviderId = @"providerId"; static NSString *const keyPhoneNumber = @"phoneNumber"; static NSString *const keyDisplayName = @"displayName"; @@ -664,7 +668,8 @@ - (void)invalidate { [self promiseWithAuthResult:resolve rejecter:reject - authResult:authResult]; + authResult:authResult + credential:credential]; }]; } }]; @@ -1304,7 +1309,8 @@ - (void)invalidate { [self promiseWithAuthResult:resolve rejecter:reject - authResult:authResult]; + authResult:authResult + credential:credential]; }]; } }]; @@ -1433,7 +1439,8 @@ - (void)invalidate { [self promiseWithAuthResult:resolve rejecter:reject - authResult:authResult]; + authResult:authResult + credential:credential]; }]; } }]; @@ -1730,6 +1737,13 @@ - (void)promiseWithUser:(RCTPromiseResolveBlock)resolve - (void)promiseWithAuthResult:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject authResult:(FIRAuthDataResult *)authResult { + [self promiseWithAuthResult:resolve rejecter:reject authResult:authResult credential:nil]; +} + +- (void)promiseWithAuthResult:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject + authResult:(FIRAuthDataResult *)authResult + credential:(FIRAuthCredential *)credential { if (authResult && authResult.user) { NSMutableDictionary *authResultDict = [NSMutableDictionary dictionary]; @@ -1761,6 +1775,14 @@ - (void)promiseWithAuthResult:(RCTPromiseResolveBlock)resolve [authResultDict setValue:[NSNull null] forKey:keyAdditionalUserInfo]; } + FIRAuthCredential *resultCredential = authResult.credential ?: credential; + NSDictionary *credentialDict = [self oauthResultCredentialToDict:resultCredential]; + if (credentialDict) { + [authResultDict setValue:credentialDict forKey:keyCredential]; + } else { + [authResultDict setValue:[NSNull null] forKey:keyCredential]; + } + [authResultDict setValue:[self firebaseUserToDict:authResult.user] forKey:keyUser]; resolve(authResultDict); } else { @@ -1768,6 +1790,22 @@ - (void)promiseWithAuthResult:(RCTPromiseResolveBlock)resolve } } +- (NSDictionary *)oauthResultCredentialToDict:(FIRAuthCredential *)authCredential { + if (![authCredential isKindOfClass:[FIROAuthCredential class]]) { + return nil; + } + + FIROAuthCredential *oauthCredential = (FIROAuthCredential *)authCredential; + NSMutableDictionary *credentialDict = [NSMutableDictionary dictionary]; + + [credentialDict setValue:oauthCredential.provider forKey:keyProviderId]; + [credentialDict setValue:oauthCredential.accessToken ?: [NSNull null] forKey:keyAccessToken]; + [credentialDict setValue:oauthCredential.IDToken ?: [NSNull null] forKey:keyIdToken]; + [credentialDict setValue:oauthCredential.secret ?: [NSNull null] forKey:keySecret]; + + return credentialDict; +} + - (NSArray *)convertProviderData:(NSArray> *)providerData { NSMutableArray *output = [NSMutableArray array]; diff --git a/packages/auth/lib/index.d.ts b/packages/auth/lib/index.d.ts index 7e87730bb7..041bc89025 100644 --- a/packages/auth/lib/index.d.ts +++ b/packages/auth/lib/index.d.ts @@ -91,6 +91,28 @@ export namespace FirebaseAuthTypes { secret: string; } + /** + * Represents the OAuth credential returned by a sign-in, link, or reauthentication operation. + */ + export interface OAuthCredential { + /** + * The authentication provider ID for the credential. For example, 'facebook.com', or 'google.com'. + */ + providerId: string; + /** + * The OAuth access token associated with the credential, if one was returned by the provider. + */ + accessToken?: string | null; + /** + * The OAuth ID token associated with the credential, if one was returned by the provider. + */ + idToken?: string | null; + /** + * The OAuth access token secret associated with the credential, if one was returned by the provider. + */ + secret?: string | null; + } + /** * Interface that represents an auth provider. Implemented by other providers. */ @@ -507,13 +529,17 @@ export namespace FirebaseAuthTypes { * information that was returned from the identity provider. operationType could be 'signIn' for * a sign-in operation, 'link' for a linking operation and 'reauthenticate' for a re-authentication operation. * - * TODO @salakar; missing credential, operationType + * TODO @salakar; missing operationType */ export interface UserCredential { /** * Any additional user information assigned to the user. */ additionalUserInfo?: AdditionalUserInfo; + /** + * The OAuth credential returned by the identity provider, if one was returned. + */ + credential?: OAuthCredential | null; /** * Returns the {@link User} interface of this credential. */ diff --git a/packages/auth/lib/index.js b/packages/auth/lib/index.js index 3ddb6dc9f9..07504c9f41 100644 --- a/packages/auth/lib/index.js +++ b/packages/auth/lib/index.js @@ -202,6 +202,7 @@ class FirebaseAuthModule extends FirebaseModule { this.emitter.emit(this.eventNameForApp('onUserChanged'), this._user); return { additionalUserInfo: userCredential.additionalUserInfo, + credential: userCredential.credential ?? null, user, }; } diff --git a/packages/auth/lib/web/RNFBAuthModule.js b/packages/auth/lib/web/RNFBAuthModule.js index f5b02d9c48..c79330e21e 100644 --- a/packages/auth/lib/web/RNFBAuthModule.js +++ b/packages/auth/lib/web/RNFBAuthModule.js @@ -225,6 +225,8 @@ function multiFactorInfoToObject(multiFactorInfo) { */ function authResultToObject(userCredential) { const additional = getAdditionalUserInfo(userCredential); + const credential = OAuthProvider.credentialFromResult(userCredential); + return { user: userToObject(userCredential.user), additionalUserInfo: { @@ -233,6 +235,14 @@ function authResultToObject(userCredential) { providerId: additional.providerId, username: additional.username, }, + credential: credential + ? { + providerId: credential.providerId, + accessToken: credential.accessToken ?? null, + idToken: credential.idToken ?? null, + secret: credential.secret ?? null, + } + : null, }; } diff --git a/packages/auth/type-test.ts b/packages/auth/type-test.ts index ab298449fa..8096bc85ad 100644 --- a/packages/auth/type-test.ts +++ b/packages/auth/type-test.ts @@ -202,6 +202,8 @@ const emailCredential = EmailAuthProvider.credential('test@example.com', 'passwo signInWithCredential(authInstance, emailCredential).then( (credential: FirebaseAuthTypes.UserCredential) => { console.log(credential.user.email); + console.log(credential.credential?.accessToken); + console.log(credential.credential?.idToken); }, ); From 3716581cbd02487f0d4140e36cc0109b89debde4 Mon Sep 17 00:00:00 2001 From: Sagnik Pal Date: Sat, 16 May 2026 19:55:08 +0530 Subject: [PATCH 2/2] Fix review changes --- .../auth/ReactNativeFirebaseAuthModule.java | 23 ++++--------------- packages/auth/ios/RNFBAuth/RNFBAuthModule.m | 2 +- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java b/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java index 60e469f9db..4860d32ab8 100644 --- a/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java +++ b/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java @@ -2399,25 +2399,10 @@ private WritableMap authCredentialToMap(@Nullable AuthCredential authCredential) OAuthCredential oauthCredential = (OAuthCredential) authCredential; WritableMap credentialMap = Arguments.createMap(); - credentialMap.putString("providerId", oauthCredential.getProvider()); - - if (oauthCredential.getAccessToken() != null) { - credentialMap.putString("accessToken", oauthCredential.getAccessToken()); - } else { - credentialMap.putNull("accessToken"); - } - - if (oauthCredential.getIdToken() != null) { - credentialMap.putString("idToken", oauthCredential.getIdToken()); - } else { - credentialMap.putNull("idToken"); - } - - if (oauthCredential.getSecret() != null) { - credentialMap.putString("secret", oauthCredential.getSecret()); - } else { - credentialMap.putNull("secret"); - } + SharedUtils.mapPutValue("providerId", oauthCredential.getProvider(), credentialMap); + SharedUtils.mapPutValue("accessToken", oauthCredential.getAccessToken(), credentialMap); + SharedUtils.mapPutValue("idToken", oauthCredential.getIdToken(), credentialMap); + SharedUtils.mapPutValue("secret", oauthCredential.getSecret(), credentialMap); return credentialMap; } diff --git a/packages/auth/ios/RNFBAuth/RNFBAuthModule.m b/packages/auth/ios/RNFBAuth/RNFBAuthModule.m index 305c0fc6f0..70e9c16337 100644 --- a/packages/auth/ios/RNFBAuth/RNFBAuthModule.m +++ b/packages/auth/ios/RNFBAuth/RNFBAuthModule.m @@ -1798,7 +1798,7 @@ - (NSDictionary *)oauthResultCredentialToDict:(FIRAuthCredential *)authCredentia FIROAuthCredential *oauthCredential = (FIROAuthCredential *)authCredential; NSMutableDictionary *credentialDict = [NSMutableDictionary dictionary]; - [credentialDict setValue:oauthCredential.provider forKey:keyProviderId]; + [credentialDict setValue:oauthCredential.provider ?: [NSNull null] forKey:keyProviderId]; [credentialDict setValue:oauthCredential.accessToken ?: [NSNull null] forKey:keyAccessToken]; [credentialDict setValue:oauthCredential.IDToken ?: [NSNull null] forKey:keyIdToken]; [credentialDict setValue:oauthCredential.secret ?: [NSNull null] forKey:keySecret];