Skip to content

feat(auth): add support for fullName when using Sign in with Apple #8570

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 5 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 3 additions & 3 deletions docs/auth/social-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
```

Expand Down
5 changes: 5 additions & 0 deletions packages/auth/__tests__/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import auth, {
setPersistence,
signInAnonymously,
signInWithCredential,
signInWithAppleCredential,
signInWithCustomToken,
signInWithEmailAndPassword,
signInWithEmailLink,
Expand Down Expand Up @@ -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();
});
Expand Down
74 changes: 56 additions & 18 deletions packages/auth/ios/RNFBAuth/RNFBAuthModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

#import <Firebase/Firebase.h>
#import <Foundation/Foundation.h>
#import <React/RCTUtils.h>

#import "RNFBApp/RCTConvert+FIRApp.h"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
103 changes: 102 additions & 1 deletion packages/auth/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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;
}

/**
*
*/
Expand Down Expand Up @@ -321,7 +392,7 @@ export namespace FirebaseAuthTypes {
* firebase.auth.AppleAuthProvider;
* ```
*/
AppleAuthProvider: AuthProvider;
AppleAuthProvider: AppleAuthProvider;
/**
* Github auth provider implementation.
*
Expand Down Expand Up @@ -1873,6 +1944,36 @@ export namespace FirebaseAuthTypes {
*/
signInWithCredential(credential: AuthCredential): Promise<UserCredential>;

/**
* 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<UserCredential>;

/**
* 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.
Expand Down
19 changes: 19 additions & 0 deletions packages/auth/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
12 changes: 12 additions & 0 deletions packages/auth/lib/modular/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,18 @@ export function signInWithCredential(
credential: FirebaseAuthTypes.AuthCredential,
): Promise<FirebaseAuthTypes.UserCredential>;

/**
* 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<FirebaseAuthTypes.UserCredential>;

/**
* Asynchronously signs in using a custom token.
*
Expand Down
10 changes: 10 additions & 0 deletions packages/auth/lib/modular/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<UserCredential>}
*/
export async function signInWithAppleCredential(auth, credential) {
return auth.signInWithAppleCredential(credential);
}

/**
* Asynchronously signs in using a custom token.
* @param {Auth} auth - The Auth instance.
Expand Down
3 changes: 2 additions & 1 deletion packages/auth/lib/providers/AppleAuthProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ export default class AppleAuthProvider {
return providerId;
}

static credential(token, secret) {
static credential(token, secret, fullName) {
return {
token,
secret,
providerId,
fullName,
};
}
}