Skip to content

Commit d21ac64

Browse files
Add Sign in with Apple Display Name API and unit test (#10068)
* Add api addtion for SIWA and add Function for Unit test * Adding Unit test for apple signIn flow in FIROAuthProviderTests and FIRAuthTest * remove AuthSample.xcodeproj fiels and application.plist file under Sample * Revert "remove AuthSample.xcodeproj fiels and application.plist file under Sample" This reverts commit 8578f96. * remove changes in AuthSample 1/5 * remove changes in AuthSample 2/5 * remove changes in AuthSample 3/5 * remove changes in AuthSample 4/5 * remove changes in AuthSample 5/5 * overwrite with master firles to remove white space changes in the two files * Delete Application.plist file * Clean extra methods in FIROAuthCredentialInternal and related files to add displayname * Add displayName in FIRVerifyAssertionRequest * Fix issues in comments, merge GoogleAuthprovider, fix files in tests * fix issues * Clang-format all files * Adding comment for testSignInWithCredentialSuccess * Adding comments for testSignInWithCredentialSuccess * Updates to reflect approved API. * Address review comments. * Update unit tests. * Update changelog. * Remove JSON pretty-print and add one more unit test. --------- Co-authored-by: Rosalyn Tan <[email protected]>
1 parent 8c9d546 commit d21ac64

File tree

11 files changed

+235
-5
lines changed

11 files changed

+235
-5
lines changed

FirebaseAuth/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Unreleased
2+
- [added] Added an API for developers to pass the fullName from the Sign in with Apple credential to Firebase. (#10068)
3+
14
# 10.6.0
25
- [fixed] Fixed a bug where user is created in a specific tenant although tenantID was not specified. (#10748)
36
- [fixed] Fixed a bug where the resolver exposed in MFA is not associated to the correct app. (#10690)

FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthCredential.m

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ - (instancetype)initWithProviderID:(NSString *)providerID
4545
rawNonce:(nullable NSString *)rawNonce
4646
accessToken:(nullable NSString *)accessToken
4747
secret:(nullable NSString *)secret
48+
fullName:(nullable NSPersonNameComponents *)fullName
4849
pendingToken:(nullable NSString *)pendingToken {
4950
self = [super initWithProvider:providerID];
5051
if (self) {
@@ -53,6 +54,7 @@ - (instancetype)initWithProviderID:(NSString *)providerID
5354
_accessToken = accessToken;
5455
_pendingToken = pendingToken;
5556
_secret = secret;
57+
_fullName = fullName;
5658
}
5759
return self;
5860
}
@@ -65,6 +67,7 @@ - (instancetype)initWithProviderID:(NSString *)providerID
6567
rawNonce:nil
6668
accessToken:nil
6769
secret:nil
70+
fullName:nil
6871
pendingToken:nil];
6972
if (self) {
7073
_OAuthResponseURLString = OAuthResponseURLString;
@@ -81,6 +84,7 @@ - (nullable instancetype)initWithVerifyAssertionResponse:(FIRVerifyAssertionResp
8184
rawNonce:nil
8285
accessToken:response.oauthAccessToken
8386
secret:response.oauthSecretToken
87+
fullName:nil
8488
pendingToken:response.pendingToken];
8589
}
8690
return nil;
@@ -94,6 +98,7 @@ - (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request {
9498
request.sessionID = _sessionID;
9599
request.providerOAuthTokenSecret = _secret;
96100
request.pendingToken = _pendingToken;
101+
request.fullName = _fullName;
97102
}
98103

99104
#pragma mark - NSSecureCoding
@@ -108,11 +113,14 @@ - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
108113
NSString *accessToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"accessToken"];
109114
NSString *pendingToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"pendingToken"];
110115
NSString *secret = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"secret"];
116+
NSPersonNameComponents *fullName = [aDecoder decodeObjectOfClass:[NSPersonNameComponents class]
117+
forKey:@"fullName"];
111118
self = [self initWithProviderID:self.provider
112119
IDToken:IDToken
113120
rawNonce:rawNonce
114121
accessToken:accessToken
115122
secret:secret
123+
fullName:fullName
116124
pendingToken:pendingToken];
117125
return self;
118126
}
@@ -123,6 +131,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder {
123131
[aCoder encodeObject:self.accessToken forKey:@"accessToken"];
124132
[aCoder encodeObject:self.pendingToken forKey:@"pendingToken"];
125133
[aCoder encodeObject:self.secret forKey:@"secret"];
134+
[aCoder encodeObject:self.fullName forKey:@"fullName"];
126135
}
127136

128137
@end

FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthCredential_Internal.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,27 @@ NS_ASSUME_NONNULL_BEGIN
4141
*/
4242
@property(nonatomic, readonly, nullable) NSString *pendingToken;
4343

44-
/** @fn initWithProviderId:IDToken:accessToken:secret:pendingToken
44+
/** @property fullName
45+
@brief The full name of the user associated with this OAuthCredential.
46+
*/
47+
@property(nonatomic, readonly, nullable) NSPersonNameComponents *fullName;
48+
49+
/** @fn initWithProviderId:IDToken:rawNonce:accessToken:secret:fullName:pendingToken
4550
@brief Designated initializer.
4651
@param providerID The provider ID associated with the credential being created.
4752
@param IDToken The ID Token associated with the credential being created.
4853
@param rawNonce The raw nonce associated with the Auth credential being created.
4954
@param accessToken The access token associated with the credential being created.
5055
@param secret The secret associated with the credential being created.
56+
@param fullName The full name associated with the credential being created.
5157
@param pendingToken The pending token associated with the credential being created.
5258
*/
5359
- (instancetype)initWithProviderID:(NSString *)providerID
5460
IDToken:(nullable NSString *)IDToken
5561
rawNonce:(nullable NSString *)rawNonce
5662
accessToken:(nullable NSString *)accessToken
5763
secret:(nullable NSString *)secret
64+
fullName:(nullable NSPersonNameComponents *)fullName
5865
pendingToken:(nullable NSString *)pendingToken NS_DESIGNATED_INITIALIZER;
5966

6067
/** @fn initWithProviderId:sessionID:OAuthResponseURLString:

FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthProvider.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
8888
rawNonce:nil
8989
accessToken:accessToken
9090
secret:nil
91+
fullName:nil
9192
pendingToken:nil];
9293
}
9394

@@ -98,6 +99,7 @@ + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
9899
rawNonce:nil
99100
accessToken:accessToken
100101
secret:nil
102+
fullName:nil
101103
pendingToken:nil];
102104
}
103105

@@ -110,6 +112,7 @@ + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
110112
rawNonce:rawNonce
111113
accessToken:accessToken
112114
secret:nil
115+
fullName:nil
113116
pendingToken:nil];
114117
}
115118

@@ -121,6 +124,19 @@ + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
121124
rawNonce:rawNonce
122125
accessToken:nil
123126
secret:nil
127+
fullName:nil
128+
pendingToken:nil];
129+
}
130+
131+
+ (FIROAuthCredential *)appleCredentialWithIDToken:(NSString *)IDToken
132+
rawNonce:(nullable NSString *)rawNonce
133+
fullName:(nullable NSPersonNameComponents *)fullName {
134+
return [[FIROAuthCredential alloc] initWithProviderID:@"apple.com"
135+
IDToken:IDToken
136+
rawNonce:rawNonce
137+
accessToken:nil
138+
secret:nil
139+
fullName:fullName
124140
pendingToken:nil];
125141
}
126142

FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ NS_ASSUME_NONNULL_BEGIN
9696
*/
9797
@property(nonatomic, assign) BOOL autoCreate;
9898

99+
/** @property fullName
100+
@brief A full name from the IdP.
101+
*/
102+
@property(nonatomic, copy, nullable) NSPersonNameComponents *fullName;
103+
99104
/** @fn initWithEndpoint:requestConfiguration:
100105
@brief Please use initWithProviderID:requestConfifuration instead.
101106
*/

FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.m

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,28 @@
8484
*/
8585
static NSString *const kReturnSecureTokenKey = @"returnSecureToken";
8686

87+
/** @var kUserKey
88+
@brief The key for the "user" value in the request. The value is a JSON object that contains the
89+
name of the user.
90+
*/
91+
static NSString *const kUserKey = @"user";
92+
93+
/** @var kNameKey
94+
@brief The key for the "name" value in the request. The value is a JSON object that contains the
95+
first and/or last name of the user.
96+
*/
97+
static NSString *const kNameKey = @"name";
98+
99+
/** @var kFirstNameKey
100+
@brief The key for the "firstName" value in the request.
101+
*/
102+
static NSString *const kFirstNameKey = @"firstName";
103+
104+
/** @var kLastNameKey
105+
@brief The key for the "lastName" value in the request.
106+
*/
107+
static NSString *const kLastNameKey = @"lastName";
108+
87109
/** @var kReturnIDPCredentialKey
88110
@brief The key for the "returnIdpCredential" value in the request.
89111
*/
@@ -148,6 +170,24 @@ - (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)
148170
if (_inputEmail) {
149171
[queryItems addObject:[NSURLQueryItem queryItemWithName:kIdentifierKey value:_inputEmail]];
150172
}
173+
174+
if (_fullName.givenName || _fullName.familyName) {
175+
NSMutableDictionary *nameDict = [[NSMutableDictionary alloc] init];
176+
if (_fullName.givenName) {
177+
nameDict[kFirstNameKey] = _fullName.givenName;
178+
}
179+
if (_fullName.familyName) {
180+
nameDict[kLastNameKey] = _fullName.familyName;
181+
}
182+
NSDictionary *userDict = [NSDictionary dictionaryWithObject:nameDict forKey:kNameKey];
183+
NSData *userJson = [NSJSONSerialization dataWithJSONObject:userDict options:0 error:error];
184+
[queryItems
185+
addObject:[NSURLQueryItem
186+
queryItemWithName:kUserKey
187+
value:[[NSString alloc] initWithData:userJson
188+
encoding:NSUTF8StringEncoding]]];
189+
}
190+
151191
[components setQueryItems:queryItems];
152192
NSMutableDictionary *body = [@{
153193
kRequestURIKey : _requestURI ?: @"http://localhost", // Unused by server, but required
@@ -171,6 +211,7 @@ - (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)
171211
if (_sessionID) {
172212
body[kSessionIDKey] = _sessionID;
173213
}
214+
174215
if (self.tenantID) {
175216
body[kTenantIDKey] = self.tenantID;
176217
}

FirebaseAuth/Sources/Public/FirebaseAuth/FIROAuthProvider.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,22 @@ NS_SWIFT_NAME(OAuthProvider)
114114
IDToken:(NSString *)IDToken
115115
rawNonce:(nullable NSString *)rawNonce;
116116

117+
/** @fn appleCredentialWithIDToken:rawNonce:fullName:
118+
* @brief Creates an `AuthCredential` for the Sign in with Apple OAuth 2 provider identified by ID
119+
* token, raw nonce, and full name. This method is specific to the Sign in with Apple OAuth 2
120+
* provider as this provider requires the full name to be passed explicitly.
121+
*
122+
* @param IDToken The IDToken associated with the Sign in with Apple Auth credential being created.
123+
* @param rawNonce The raw nonce associated with the Sign in with Apple Auth credential being
124+
* created.
125+
* @param fullName The full name associated with the Sign in with Apple Auth credential being
126+
* created.
127+
* @return An `AuthCredential`.
128+
*/
129+
+ (FIROAuthCredential *)appleCredentialWithIDToken:(NSString *)IDToken
130+
rawNonce:(nullable NSString *)rawNonce
131+
fullName:(nullable NSPersonNameComponents *)fullName;
132+
117133
/** @fn init
118134
@brief This class is not meant to be initialized.
119135
*/

FirebaseAuth/Tests/Sample/Sample/MainViewController+OAuth.m

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -410,10 +410,10 @@ - (void)reauthenticateWithApple {
410410
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) {
411411
ASAuthorizationAppleIDCredential* appleIDCredential = authorization.credential;
412412
NSString *IDToken = [NSString stringWithUTF8String:[appleIDCredential.identityToken bytes]];
413-
FIROAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com"
414-
IDToken:IDToken
415-
rawNonce:self.appleRawNonce
416-
accessToken:nil];
413+
FIROAuthCredential *credential =
414+
[FIROAuthProvider appleCredentialWithIDToken:IDToken
415+
rawNonce:self.appleRawNonce
416+
fullName:appleIDCredential.fullName];
417417

418418
if ([appleIDCredential.state isEqualToString:@"signIn"]) {
419419
[FIRAuth.auth signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {

FirebaseAuth/Tests/Unit/FIRAuthTests.m

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,16 @@
135135
*/
136136
static NSString *const kDisplayName = @"User Doe";
137137

138+
/** @var kFakeGivenName
139+
@brief The fake user given name.
140+
*/
141+
static NSString *const kFakeGivenName = @"Firstname";
142+
143+
/** @var kFakeFamilyName
144+
@brief The fake user family name.
145+
*/
146+
static NSString *const kFakeFamilyName = @"Lastname";
147+
138148
/** @var kGoogleUD
139149
@brief The fake user ID under Google Sign-In.
140150
*/
@@ -160,6 +170,16 @@
160170
*/
161171
static NSString *const kGoogleIDToken = @"GOOGLE_ID_TOKEN";
162172

173+
/** @var kAppleAuthProviderID
174+
@brief The provider ID for Apple Sign-In.
175+
*/
176+
static NSString *const kAppleAuthProviderID = @"apple.com";
177+
178+
/** @var kAppleIDToken
179+
@brief The fake ID token from Apple Sign-In.
180+
*/
181+
static NSString *const kAppleIDToken = @"APPLE_ID_TOKEN";
182+
163183
/** @var kCustomToken
164184
@brief The fake custom token to sign in.
165185
*/
@@ -1374,6 +1394,66 @@ - (void)testSignInWithGoogleCredentialFailure {
13741394
OCMVerifyAll(_mockBackend);
13751395
}
13761396

1397+
/** @fn testSignInWithAppleCredentialFullNameInRequest
1398+
@brief Tests the flow of a successful @c signInWithCredential:completion: call
1399+
with an Apple Sign-In credential with a full name. This test differentiates from
1400+
@c testSignInWithCredentialSuccess only in verifying the full name.
1401+
*/
1402+
- (void)testSignInWithAppleCredentialFullNameInRequestSuccess {
1403+
NSPersonNameComponents *fullName = [[NSPersonNameComponents alloc] init];
1404+
fullName.givenName = kFakeGivenName;
1405+
fullName.familyName = kFakeFamilyName;
1406+
OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
1407+
.andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
1408+
FIRVerifyAssertionResponseCallback callback) {
1409+
XCTAssertEqualObjects(request.APIKey, kAPIKey);
1410+
XCTAssertEqualObjects(request.providerID, kAppleAuthProviderID);
1411+
XCTAssertEqualObjects(request.providerIDToken, kAppleIDToken);
1412+
// Verify that the full name is passed to the backend request.
1413+
XCTAssertEqualObjects(request.fullName, fullName);
1414+
XCTAssertTrue(request.returnSecureToken);
1415+
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
1416+
id mockVerifyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
1417+
OCMStub([mockVerifyAssertionResponse providerID]).andReturn(kAppleAuthProviderID);
1418+
[self stubTokensWithMockResponse:mockVerifyAssertionResponse];
1419+
callback(mockVerifyAssertionResponse, nil);
1420+
});
1421+
});
1422+
OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
1423+
.andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
1424+
FIRGetAccountInfoResponseCallback callback) {
1425+
XCTAssertEqualObjects(request.APIKey, kAPIKey);
1426+
XCTAssertEqualObjects(request.accessToken, kAccessToken);
1427+
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
1428+
id mockAppleUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
1429+
OCMStub([mockAppleUserInfo providerID]).andReturn(kAppleAuthProviderID);
1430+
id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
1431+
OCMStub([mockGetAccountInfoResponseUser providerUserInfo])
1432+
.andReturn((@[ mockAppleUserInfo ]));
1433+
id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
1434+
OCMStub([mockGetAccountInfoResponse users]).andReturn(@[
1435+
mockGetAccountInfoResponseUser
1436+
]);
1437+
callback(mockGetAccountInfoResponse, nil);
1438+
});
1439+
});
1440+
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
1441+
[[FIRAuth auth] signOut:NULL];
1442+
FIRAuthCredential *appleCredential = [FIROAuthProvider appleCredentialWithIDToken:kAppleIDToken
1443+
rawNonce:nil
1444+
fullName:fullName];
1445+
[[FIRAuth auth]
1446+
signInWithCredential:appleCredential
1447+
completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) {
1448+
XCTAssertTrue([NSThread isMainThread]);
1449+
XCTAssertNil(error);
1450+
[expectation fulfill];
1451+
}];
1452+
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
1453+
XCTAssertNotNil([FIRAuth auth].currentUser);
1454+
OCMVerifyAll(_mockBackend);
1455+
}
1456+
13771457
/** @fn testSignInAnonymouslySuccess
13781458
@brief Tests the flow of a successful @c signInAnonymouslyWithCompletion: call.
13791459
*/

FirebaseAuth/Tests/Unit/FIROAuthProviderTests.m

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@
6767
*/
6868
static NSString *const kFakeProviderID = @"fakeProviderID";
6969

70+
/** @var kFakeGivenName
71+
@brief A fake given name for testing.
72+
*/
73+
static NSString *const kFakeGivenName = @"fakeGivenName";
74+
75+
/** @var kFakeFamilyName
76+
@brief A fake family name for testing.
77+
*/
78+
static NSString *const kFakeFamilyName = @"fakeFamilyName";
79+
7080
/** @var kFakeAPIKey
7181
@brief A fake API key.
7282
*/
@@ -238,6 +248,25 @@ - (void)testObtainingOAuthCredentialNoIDToken {
238248
XCTAssertNil(OAuthCredential.IDToken);
239249
}
240250

251+
/** @fn testObtainingOAuthCredentialWithFullName
252+
@brief Tests the correct creation of an OAuthCredential with a fullName.
253+
*/
254+
- (void)testObtainingOAuthCredentialWithFullName {
255+
NSPersonNameComponents *fullName = [[NSPersonNameComponents alloc] init];
256+
fullName.givenName = kFakeGivenName;
257+
fullName.familyName = kFakeFamilyName;
258+
FIRAuthCredential *credential = [FIROAuthProvider appleCredentialWithIDToken:kFakeIDToken
259+
rawNonce:nil
260+
fullName:fullName];
261+
262+
XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
263+
FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
264+
XCTAssertEqualObjects(OAuthCredential.provider, @"apple.com");
265+
XCTAssertEqualObjects(OAuthCredential.IDToken, kFakeIDToken);
266+
XCTAssertEqualObjects(OAuthCredential.fullName, fullName);
267+
XCTAssertNil(OAuthCredential.accessToken);
268+
}
269+
241270
/** @fn testObtainingOAuthCredentialWithIDToken
242271
@brief Tests the correct creation of an OAuthCredential with an IDToken
243272
*/

0 commit comments

Comments
 (0)