From 6216fb8f4def3103fce82a3b2aa08505600982c7 Mon Sep 17 00:00:00 2001 From: Andrew Dollard Date: Tue, 6 May 2025 19:52:52 -0700 Subject: [PATCH 1/5] add signInWithAppleCredential method --- packages/auth/ios/RNFBAuth/RNFBAuthModule.m | 74 ++++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/packages/auth/ios/RNFBAuth/RNFBAuthModule.m b/packages/auth/ios/RNFBAuth/RNFBAuthModule.m index 2c9232e423..776ae21687 100644 --- a/packages/auth/ios/RNFBAuth/RNFBAuthModule.m +++ b/packages/auth/ios/RNFBAuth/RNFBAuthModule.m @@ -16,6 +16,7 @@ */ #import +#import #import #import "RNFBApp/RCTConvert+FIRApp.h" @@ -588,25 +589,35 @@ - (void)invalidate { token:authToken secret:authSecret firebaseApp:firebaseApp]; - if (credential == nil) { - [RNFBSharedUtils rejectPromiseWithUserInfo:reject - userInfo:(NSMutableDictionary *)@{ - @"code" : @"invalid-credential", - @"message" : @"The supplied auth credential is malformed, " - @"has expired or is not currently supported.", - }]; - } - DLog(@"using app SignInWithCredential: %@", firebaseApp.name); - [[FIRAuth authWithApp:firebaseApp] - signInWithCredential:credential - completion:^(FIRAuthDataResult *authResult, NSError *error) { - if (error) { - [self promiseRejectAuthException:reject error:error]; - } else { - [self promiseWithAuthResult:resolve rejecter:reject authResult:authResult]; - } - }]; + [self _signInWithCredential:credential firebaseApp:firebaseApp resolve:resolve reject:reject]; +} + +RCT_EXPORT_METHOD(signInWithAppleCredential + : (FIRApp *)firebaseApp + : (NSString *)provider + : (NSString *)authToken + : (NSString *)authSecret + : (NSDictionary *)fullNameDict + : (RCTPromiseResolveBlock)resolve + : (RCTPromiseRejectBlock)reject) { + NSPersonNameComponents *fullName = [[NSPersonNameComponents alloc] init]; + id (^safeString)(id) = ^id(id value) { + return (value && value != [NSNull null]) ? value : @""; + }; + + fullName.givenName = safeString(fullNameDict[@"givenName"]); + fullName.middleName = safeString(fullNameDict[@"middleName"]); + fullName.familyName = safeString(fullNameDict[@"familyName"]); + fullName.namePrefix = safeString(fullNameDict[@"namePrefix"]); + fullName.nameSuffix = safeString(fullNameDict[@"nameSuffix"]); + fullName.nickname = safeString(fullNameDict[@"nickname"]); + + FIRAuthCredential *credential = [FIROAuthProvider appleCredentialWithIDToken:authToken + rawNonce:authSecret + fullName:fullName]; + + [self _signInWithCredential:credential firebaseApp:firebaseApp resolve:resolve reject:reject]; } RCT_EXPORT_METHOD(signInWithProvider @@ -1421,6 +1432,33 @@ - (FIRAuthCredential *)getCredentialForProvider:(NSString *)provider return credential; } +- (void)_signInWithCredential:(FIRAuthCredential *)credential + firebaseApp:(FIRApp *)firebaseApp + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + if (credential == nil) { + [RNFBSharedUtils rejectPromiseWithUserInfo:reject + userInfo:(NSMutableDictionary *)@{ + @"code" : @"invalid-credential", + @"message" : @"The supplied auth credential is malformed, " + @"has expired or is not currently supported.", + }]; + return; + } + + DLog(@"using app SignInWithCredential: %@", firebaseApp.name); + + [[FIRAuth authWithApp:firebaseApp] + signInWithCredential:credential + completion:^(FIRAuthDataResult *authResult, NSError *error) { + if (error) { + [self promiseRejectAuthException:reject error:error]; + } else { + [self promiseWithAuthResult:resolve rejecter:reject authResult:authResult]; + } + }]; +} + // This is here to protect against bugs in the iOS SDK which don't // correctly refresh the user object when performing certain operations - (void)reloadAndReturnUser:(FIRUser *)user From d45033f0edce5f9689b124b2a7c1770b92a8f375 Mon Sep 17 00:00:00 2001 From: Andrew Dollard Date: Tue, 6 May 2025 19:55:30 -0700 Subject: [PATCH 2/5] update AppleAuthProvider --- packages/auth/lib/index.d.ts | 103 +++++++++++++++++- .../auth/lib/providers/AppleAuthProvider.js | 3 +- 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/packages/auth/lib/index.d.ts b/packages/auth/lib/index.d.ts index 9eece3fb4e..573dde581a 100644 --- a/packages/auth/lib/index.d.ts +++ b/packages/auth/lib/index.d.ts @@ -91,6 +91,52 @@ export namespace FirebaseAuthTypes { secret: string; } + /** + * An optional full name shared by the user when using Sign In With Apple. + * + * These fields are populated with values that the user authorized. + * + * @platform ios iOS + */ + interface AppleRequestResponseFullName { + /** + * Pre-nominal letters denoting title, salutation, or honorific, e.g. Dr., Mr. + */ + namePrefix: string | null; + + /** + * Name bestowed upon an individual by one's parents, e.g. Johnathan + */ + givenName: string | null; + + /** + * Secondary given name chosen to differentiate those with the same first name, e.g. Maple + */ + middleName: string | null; + + /** + * Name passed from one generation to another to indicate lineage, e.g. Appleseed + */ + familyName: string | null; + + /** + * Post-nominal letters denoting degree, accreditation, or other honor, e.g. Esq., Jr., Ph.D. + */ + nameSuffix: string | null; + + /** + * Name substituted for the purposes of familiarity, e.g. "Johnny" + */ + nickname: string | null; + } + + export interface AppleAuthCredential { + providerId: string; + token: string; + secret: string; + fullName: AppleRequestResponseFullName; + } + /** * Interface that represents an auth provider. Implemented by other providers. */ @@ -236,6 +282,31 @@ export namespace FirebaseAuthTypes { credentialWithLink: (email: string, emailLink: string) => AuthCredential; } + /** + * Interface that represents an Apple auth provider. + */ + export interface AppleAuthProvider extends AuthProvider { + /** + * The provider ID of the provider. + * + * @platform ios iOS + */ + PROVIDER_ID: string; + /** + * Creates a new `AuthCredential`. + * + * @returns {@link auth.AuthCredential}. + * @param token A provider token. + * @param secret A provider secret. + * @param fullName An `AppleRequestResponseFullName` object + */ + credential: ( + token: string | null, + secret: string, + fullName?: AppleRequestResponseFullName | null, + ) => AuthCredential; + } + /** * */ @@ -321,7 +392,7 @@ export namespace FirebaseAuthTypes { * firebase.auth.AppleAuthProvider; * ``` */ - AppleAuthProvider: AuthProvider; + AppleAuthProvider: AppleAuthProvider; /** * Github auth provider implementation. * @@ -1873,6 +1944,36 @@ export namespace FirebaseAuthTypes { */ signInWithCredential(credential: AuthCredential): Promise; + /** + * Signs the user in with a generated Apple-specific credential. + * + * @platform ios iOS + * + * #### Example + * + * ```js + * // Generate an Apple credential + * const appleCredential = ( + * auth.AppleAuthProvider.credential(identityToken, nonce, fullName) + * ); + * // Sign the user in with the credential + * const userCredential = await firebase.auth().signInWithAppleCredential(appleCredential); + * ``` + * + * @error auth/account-exists-with-different-credential Thrown if there already exists an account with the email address asserted by the credential. + * @error auth/invalid-credential Thrown if the credential is malformed or has expired. + * @error auth/operation-not-allowed Thrown if the type of account corresponding to the credential is not enabled. Enable the account type in the Firebase Console, under the Auth tab. + * @error auth/user-disabled Thrown if the user corresponding to the given credential has been disabled. + * @error auth/user-not-found Thrown if signing in with a credential from firebase.auth.EmailAuthProvider.credential and there is no user corresponding to the given email. + * @error auth/wrong-password Thrown if signing in with a credential from firebase.auth.EmailAuthProvider.credential and the password is invalid for the given email, or if the account corresponding to the email does not have a password set. + * @error auth/invalid-verification-code Thrown if the credential is a firebase.auth.PhoneAuthProvider.credential and the verification code of the credential is not valid. + * @error auth/invalid-verification-id Thrown if the credential is a firebase.auth.PhoneAuthProvider.credential and the verification ID of the credential is not valid. + * @param credential A generated `AuthCredential`, for example from social auth. + */ + signInWithAppleCredential( + credential: AuthCredential | AppleAuthCredential, + ): Promise; + /** * Signs the user in with a specified provider. This is a web-compatible API along with signInWithRedirect. * They both share the same call to the underlying native SDK signInWithProvider method. diff --git a/packages/auth/lib/providers/AppleAuthProvider.js b/packages/auth/lib/providers/AppleAuthProvider.js index 172cc1d12e..354726b423 100644 --- a/packages/auth/lib/providers/AppleAuthProvider.js +++ b/packages/auth/lib/providers/AppleAuthProvider.js @@ -26,11 +26,12 @@ export default class AppleAuthProvider { return providerId; } - static credential(token, secret) { + static credential(token, secret, fullName) { return { token, secret, providerId, + fullName, }; } } From 4983e4487b218990b9183aa684aec9ccc58dc9f4 Mon Sep 17 00:00:00 2001 From: Andrew Dollard Date: Tue, 6 May 2025 19:55:44 -0700 Subject: [PATCH 3/5] add signInWithAppleCredential in JS --- packages/auth/lib/index.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/auth/lib/index.js b/packages/auth/lib/index.js index 1d7751ca96..352f0216aa 100644 --- a/packages/auth/lib/index.js +++ b/packages/auth/lib/index.js @@ -346,6 +346,25 @@ class FirebaseAuthModule extends FirebaseModule { .then(userCredential => this._setUserCredential(userCredential)); } + signInWithAppleCredential(credential) { + if (credential.hasOwnProperty('fullName')) { + return this.native + .signInWithAppleCredential(credential.providerId, credential.token, credential.secret, { + namePrefix: credential.fullName.namePrefix, + givenName: credential.fullName.givenName, + middleName: credential.fullName.middleName, + familyName: credential.fullName.familyName, + nameSuffix: credential.fullName.nameSuffix, + nickname: credential.fullName.nickname, + }) + .then(userCredential => this._setUserCredential(userCredential)); + } else { + return this.native + .signInWithCredential(credential.providerId, credential.token, credential.secret) + .then(userCredential => this._setUserCredential(userCredential)); + } + } + revokeToken(authorizationCode) { return this.native.revokeToken(authorizationCode); } From 570d2109ae105fbf3fc8afc1ce76d35323b67e40 Mon Sep 17 00:00:00 2001 From: Andrew Dollard Date: Tue, 6 May 2025 19:55:51 -0700 Subject: [PATCH 4/5] update docs --- docs/auth/social-auth.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/auth/social-auth.md b/docs/auth/social-auth.md index 2cdd8ce50b..ada87b7780 100644 --- a/docs/auth/social-auth.md +++ b/docs/auth/social-auth.md @@ -70,11 +70,11 @@ async function onAppleButtonPress() { } // Create a Firebase credential from the response - const { identityToken, nonce } = appleAuthRequestResponse; - const appleCredential = AppleAuthProvider.credential(identityToken, nonce); + const { identityToken, nonce, fullName } = appleAuthRequestResponse; + const appleCredential = auth.AppleAuthProvider.credential(identityToken, nonce, fullName); // Sign the user in with the credential - return signInWithCredential(getAuth(), appleCredential); + return auth().signInWithAppleCredential(appleCredential); } ``` From 170c5625eec0579ee562f6990fa62d733ab0764b Mon Sep 17 00:00:00 2001 From: Andrew Dollard Date: Fri, 13 Jun 2025 16:00:04 -0700 Subject: [PATCH 5/5] Add signInWithAppleCredential to modular API --- packages/auth/__tests__/auth.test.ts | 5 +++++ packages/auth/lib/modular/index.d.ts | 12 ++++++++++++ packages/auth/lib/modular/index.js | 10 ++++++++++ 3 files changed, 27 insertions(+) diff --git a/packages/auth/__tests__/auth.test.ts b/packages/auth/__tests__/auth.test.ts index 5d5f6ab683..39cb7c721f 100644 --- a/packages/auth/__tests__/auth.test.ts +++ b/packages/auth/__tests__/auth.test.ts @@ -26,6 +26,7 @@ import auth, { setPersistence, signInAnonymously, signInWithCredential, + signInWithAppleCredential, signInWithCustomToken, signInWithEmailAndPassword, signInWithEmailLink, @@ -362,6 +363,10 @@ describe('Auth', function () { expect(signInWithCredential).toBeDefined(); }); + it('`signInWithAppleCredential` function is properly exposed to end user', function () { + expect(signInWithAppleCredential).toBeDefined(); + }); + it('`signInWithCustomToken` function is properly exposed to end user', function () { expect(signInWithCustomToken).toBeDefined(); }); diff --git a/packages/auth/lib/modular/index.d.ts b/packages/auth/lib/modular/index.d.ts index cc0bea66d7..bc18e91e78 100644 --- a/packages/auth/lib/modular/index.d.ts +++ b/packages/auth/lib/modular/index.d.ts @@ -272,6 +272,18 @@ export function signInWithCredential( credential: FirebaseAuthTypes.AuthCredential, ): Promise; +/** + * Asynchronously signs in with the given Apple credentials. + * + * @param auth - The Auth instance. + * @param credential - The auth credentials. + * @returns A promise that resolves with the user credentials. + */ +export function signInWithAppleCredential( + auth: Auth, + credential: FirebaseAuthTypes.AppleAuthCredential | FirebaseAuthTypes.AuthCredential, +): Promise; + /** * Asynchronously signs in using a custom token. * diff --git a/packages/auth/lib/modular/index.js b/packages/auth/lib/modular/index.js index 346f735940..b93cc500f7 100644 --- a/packages/auth/lib/modular/index.js +++ b/packages/auth/lib/modular/index.js @@ -252,6 +252,16 @@ export async function signInWithCredential(auth, credential) { return auth.signInWithCredential(credential); } +/** + * Asynchronously signs in with the given Apple credentials. + * @param {Auth} auth - The Auth instance. + * @param {AppleAuthCredential | AuthCredential} credential - The auth credentials. + * @returns {Promise} + */ +export async function signInWithAppleCredential(auth, credential) { + return auth.signInWithAppleCredential(credential); +} + /** * Asynchronously signs in using a custom token. * @param {Auth} auth - The Auth instance.