Skip to content

Commit 076d2e4

Browse files
[Passkey #5] Auth startPasskeySignInWithCompletion: and finalizePasskeySignInWithPlatformCredential:completion (#11952)
1 parent 9ab4d5f commit 076d2e4

File tree

3 files changed

+274
-0
lines changed

3 files changed

+274
-0
lines changed

FirebaseAuth/Sources/Auth/FIRAuth.m

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
#import "FirebaseAuth/Sources/Backend/RPC/FIRCreateAuthURIResponse.h"
4444
#import "FirebaseAuth/Sources/Backend/RPC/FIREmailLinkSignInRequest.h"
4545
#import "FirebaseAuth/Sources/Backend/RPC/FIREmailLinkSignInResponse.h"
46+
#import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInRequest.h"
47+
#import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.h"
4648
#import "FirebaseAuth/Sources/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h"
4749
#import "FirebaseAuth/Sources/Backend/RPC/FIRGetOOBConfirmationCodeResponse.h"
4850
#import "FirebaseAuth/Sources/Backend/RPC/FIRResetPasswordRequest.h"
@@ -57,6 +59,8 @@
5759
#import "FirebaseAuth/Sources/Backend/RPC/FIRSignInWithGameCenterResponse.h"
5860
#import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.h"
5961
#import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserResponse.h"
62+
#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.h"
63+
#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.h"
6064
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.h"
6165
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionResponse.h"
6266
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyCustomTokenRequest.h"
@@ -72,6 +76,7 @@
7276
#import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h"
7377
#import "FirebaseAuth/Sources/Utilities/FIRAuthExceptionUtils.h"
7478
#import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
79+
#import "FirebaseAuth/Sources/Utilities/NSData+FIRBase64.h"
7580

7681
#if TARGET_OS_IOS
7782
#import "FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h"
@@ -83,6 +88,10 @@
8388
#import "FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h"
8489
#endif
8590

91+
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
92+
#import <AuthenticationServices/AuthenticationServices.h>
93+
#endif
94+
8695
NS_ASSUME_NONNULL_BEGIN
8796

8897
#pragma mark-- Logger Service String.
@@ -1222,6 +1231,87 @@ - (void)signInWithCustomToken:(NSString *)token
12221231
});
12231232
}
12241233

1234+
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
1235+
- (void)startPasskeySignInWithCompletion:
1236+
(nullable void (^)(
1237+
ASAuthorizationPlatformPublicKeyCredentialAssertionRequest *_Nullable request,
1238+
NSError *_Nullable error))completion {
1239+
FIRStartPasskeySignInRequest *request =
1240+
[[FIRStartPasskeySignInRequest alloc] initWithRequestConfiguration:self.requestConfiguration];
1241+
[FIRAuthBackend
1242+
startPasskeySignIn:request
1243+
callback:^(FIRStartPasskeySignInResponse *_Nullable response,
1244+
NSError *_Nullable error) {
1245+
if (error) {
1246+
completion(nil, error);
1247+
return;
1248+
}
1249+
if (response) {
1250+
NSData *challengeInData =
1251+
[[NSData alloc] initWithBase64EncodedString:response.challenge options:0];
1252+
ASAuthorizationPlatformPublicKeyCredentialProvider *provider =
1253+
[[ASAuthorizationPlatformPublicKeyCredentialProvider alloc]
1254+
initWithRelyingPartyIdentifier:response.rpID];
1255+
ASAuthorizationPlatformPublicKeyCredentialAssertionRequest *request =
1256+
[provider createCredentialAssertionRequestWithChallenge:challengeInData];
1257+
1258+
completion(request, nil);
1259+
}
1260+
}];
1261+
}
1262+
1263+
- (void)finalizePasskeySignInWithPlatformCredential:
1264+
(ASAuthorizationPlatformPublicKeyCredentialAssertion *)platformCredential
1265+
completion:(nullable void (^)(
1266+
FIRAuthDataResult *_Nullable authResult,
1267+
NSError *_Nullable error))completion {
1268+
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
1269+
FIRAuthDataResultCallback decoratedCallback =
1270+
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
1271+
NSString *credentialID = [platformCredential.credentialID base64EncodedStringWithOptions:0];
1272+
NSString *clientDataJson =
1273+
[platformCredential.rawClientDataJSON base64EncodedStringWithOptions:0];
1274+
NSString *authenticatorData =
1275+
[platformCredential.rawAuthenticatorData base64EncodedStringWithOptions:0];
1276+
NSString *signature = [platformCredential.signature base64EncodedStringWithOptions:0];
1277+
NSString *userID = [platformCredential.userID base64EncodedStringWithOptions:0];
1278+
FIRFinalizePasskeySignInRequest *request =
1279+
[[FIRFinalizePasskeySignInRequest alloc] initWithCredentialID:credentialID
1280+
clientDataJson:clientDataJson
1281+
authenticatorData:authenticatorData
1282+
signature:signature
1283+
userID:userID
1284+
requestConfiguration:self.requestConfiguration];
1285+
[FIRAuthBackend
1286+
finalizePasskeySignIn:request
1287+
callback:^(FIRFinalizePasskeySignInResponse *_Nullable response,
1288+
NSError *_Nullable error) {
1289+
if (error) {
1290+
decoratedCallback(nil, error);
1291+
}
1292+
[self completeSignInWithAccessToken:response.idToken
1293+
accessTokenExpirationDate:nil
1294+
refreshToken:response.refreshToken
1295+
anonymous:NO
1296+
callback:^(FIRUser *_Nullable user,
1297+
NSError *_Nullable error) {
1298+
if (error) {
1299+
completion(nil, error);
1300+
return;
1301+
}
1302+
1303+
FIRAuthDataResult *authDataResult =
1304+
user ? [[FIRAuthDataResult alloc]
1305+
initWithUser:user
1306+
additionalUserInfo:nil]
1307+
: nil;
1308+
decoratedCallback(authDataResult, error);
1309+
}];
1310+
}];
1311+
});
1312+
}
1313+
#endif // #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
1314+
12251315
- (void)createUserWithEmail:(NSString *)email
12261316
password:(NSString *)password
12271317
completion:(nullable FIRAuthDataResultCallback)completion {

FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuth.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
@class FIRAuthDataResult;
2828
@class FIRAuthSettings;
2929
@class FIRUser;
30+
31+
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
32+
@class ASAuthorizationPlatformPublicKeyCredentialAssertion;
33+
@class ASAuthorizationPlatformPublicKeyCredentialAssertionRequest;
34+
#endif
35+
3036
@protocol FIRAuthUIDelegate;
3137
@protocol FIRFederatedAuthProvider;
3238

@@ -578,6 +584,40 @@ NS_SWIFT_NAME(Auth)
578584
completion:(nullable void (^)(FIRAuthDataResult *_Nullable authResult,
579585
NSError *_Nullable error))completion;
580586

587+
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
588+
/**
589+
@fn startPasskeySignInWithCompletion:
590+
@brief start sign in with passkey retrieving challenge from GCIP and create an assertion request.
591+
@param completion Optionally; a block which creates a assertation request.
592+
593+
@remarks // TODO @liubinj add possible error codes
594+
595+
*/
596+
- (void)startPasskeySignInWithCompletion:
597+
(nullable void (^)(
598+
ASAuthorizationPlatformPublicKeyCredentialAssertionRequest *_Nullable request,
599+
NSError *_Nullable error))completion NS_SWIFT_NAME(startPasskeySignIn(completion:))
600+
API_AVAILABLE(macos(12.0), ios(15.0), tvos(16.0));
601+
602+
/**
603+
@fn finalizePasskeySignInWithPlatformCredential:completion:
604+
@brief finalize sign in with passkey with existing credential assertion.
605+
@param platformCredential The existing credential assertion created by device.
606+
@param completion Optionally; a block which is invoked when the sign in with passkey flow finishes,
607+
or is canceled. Invoked asynchronously on the main thread in the future.
608+
609+
@remarks // TODO @liubinj add possible error codes
610+
611+
*/
612+
- (void)finalizePasskeySignInWithPlatformCredential:
613+
(ASAuthorizationPlatformPublicKeyCredentialAssertion *)platformCredential
614+
completion:(nullable void (^)(
615+
FIRAuthDataResult *_Nullable authResult,
616+
NSError *_Nullable error))completion
617+
NS_SWIFT_NAME(finalizePasskeySignIn(with:completion:))
618+
API_AVAILABLE(macos(12.0), ios(15.0), tvos(16.0));
619+
#endif
620+
581621
/** @fn createUserWithEmail:password:completion:
582622
@brief Creates and, on success, signs in a user with the given email address and password.
583623

FirebaseAuth/Tests/Unit/FIRAuthTests.m

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
#import "FirebaseAuth/Sources/Backend/RPC/FIRCreateAuthURIResponse.h"
4040
#import "FirebaseAuth/Sources/Backend/RPC/FIREmailLinkSignInRequest.h"
4141
#import "FirebaseAuth/Sources/Backend/RPC/FIREmailLinkSignInResponse.h"
42+
#import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInRequest.h"
43+
#import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.h"
4244
#import "FirebaseAuth/Sources/Backend/RPC/FIRGetAccountInfoRequest.h"
4345
#import "FirebaseAuth/Sources/Backend/RPC/FIRGetAccountInfoResponse.h"
4446
#import "FirebaseAuth/Sources/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h"
@@ -53,6 +55,8 @@
5355
#import "FirebaseAuth/Sources/Backend/RPC/FIRSetAccountInfoResponse.h"
5456
#import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.h"
5557
#import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserResponse.h"
58+
#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.h"
59+
#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.h"
5660
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.h"
5761
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionResponse.h"
5862
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyCustomTokenRequest.h"
@@ -78,6 +82,10 @@
7882
#import "FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h"
7983
#endif // TARGET_OS_IOS
8084

85+
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
86+
#import <AuthenticationServices/AuthenticationServices.h>
87+
#endif
88+
8189
/** @var kAPIKey
8290
@brief The fake API key.
8391
*/
@@ -278,6 +286,42 @@
278286
*/
279287
static NSString *const kFakeRecaptchaVersion = @"RecaptchaVersion";
280288

289+
/** @var kRpId
290+
@brief The fake passkey relying party identifier.
291+
*/
292+
static NSString *const kRpId = @"fake.rp.id";
293+
294+
/** @var kChallenge
295+
@brief The fake passkey challenge.
296+
*/
297+
static NSString *const kChallenge = @"Y2hhbGxlbmdl"; // decode to "challenge"
298+
299+
/** @var kCredentialID
300+
@brief The fake passkey credentialID.
301+
*/
302+
static NSString *const kCredentialID = @"Y3JlZGVudGlhbGlk"; // decode to "credentialid"
303+
304+
/** @var kClientDataJson
305+
@brief The fake clientDataJson object
306+
*/
307+
static NSString *const kClientDataJson = @"Y2xpZW50ZGF0YWpzb24="; // decode to "clientdatajson"
308+
309+
/** @var kAuthenticatorData
310+
@brief The fake authenticatorData object
311+
*/
312+
static NSString *const kAuthenticatorData =
313+
@"YXV0aGVudGljYXRvcmRhdGE="; // decode to "authenticatordata"
314+
315+
/** @var kSignature
316+
@brief The fake signature
317+
*/
318+
static NSString *const kSignature = @"c2lnbmF0dXJl"; // decode to "signature"
319+
320+
/** @var kUserID
321+
@brief The fake user ID / user handle
322+
*/
323+
static NSString *const kUserID = @"dXNlcmlk"; // decode to "userid"
324+
281325
#if TARGET_OS_IOS
282326
/** @class FIRAuthAppDelegate
283327
@brief Application delegate implementation to test the app delegate proxying
@@ -1805,6 +1849,106 @@ - (void)testSignInAndRetrieveDataWithCustomTokenFailure {
18051849
OCMVerifyAll(_mockBackend);
18061850
}
18071851

1852+
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
1853+
/** @fn testStartPasskeySignInSuccess
1854+
@brief Tests the flow of a successful @c startPasskeySignInWithCompletion: call.
1855+
*/
1856+
- (void)testStartPasskeySignInSuccess {
1857+
if (@available(iOS 15.0, tvOS 16.0, macOS 12.0, *)) {
1858+
OCMExpect([_mockBackend startPasskeySignIn:[OCMArg any] callback:[OCMArg any]])
1859+
.andCallBlock2(^(FIRStartPasskeySignInRequest *_Nullable request,
1860+
FIRStartPasskeySignInResponseCallback callback) {
1861+
XCTAssertEqualObjects(request.APIKey, kAPIKey);
1862+
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
1863+
id mockStartPasskeySignInResponse = OCMClassMock([FIRStartPasskeySignInResponse class]);
1864+
OCMStub([mockStartPasskeySignInResponse rpID]).andReturn(kRpId);
1865+
OCMStub([mockStartPasskeySignInResponse challenge]).andReturn(kChallenge);
1866+
callback(mockStartPasskeySignInResponse, nil);
1867+
});
1868+
});
1869+
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
1870+
[[FIRAuth auth] signOut:NULL];
1871+
[[FIRAuth auth]
1872+
startPasskeySignInWithCompletion:^(
1873+
ASAuthorizationPlatformPublicKeyCredentialAssertionRequest *_Nullable request,
1874+
NSError *_Nullable error) {
1875+
XCTAssertNil(error);
1876+
XCTAssertEqualObjects([[request challenge] base64EncodedStringWithOptions:0], kChallenge);
1877+
ASAuthorizationPlatformPublicKeyCredentialProvider *provider =
1878+
(ASAuthorizationPlatformPublicKeyCredentialProvider *)[request provider];
1879+
XCTAssertEqualObjects([provider relyingPartyIdentifier], kRpId);
1880+
[expectation fulfill];
1881+
}];
1882+
1883+
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
1884+
OCMVerifyAll(_mockBackend);
1885+
}
1886+
}
1887+
1888+
/** @fn testStartPasskeySignInFailure
1889+
@brief Tests the flow of a failed @c startPasskeySignInWithCompletion: call.
1890+
*/
1891+
- (void)testStartPasskeySignInFailure {
1892+
if (@available(iOS 15.0, tvOS 16.0, macOS 12.0, *)) {
1893+
OCMExpect([_mockBackend startPasskeySignIn:[OCMArg any] callback:[OCMArg any]])
1894+
.andDispatchError2([FIRAuthErrorUtils operationNotAllowedErrorWithMessage:nil]);
1895+
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
1896+
[[FIRAuth auth] signOut:NULL];
1897+
[[FIRAuth auth]
1898+
startPasskeySignInWithCompletion:^(
1899+
ASAuthorizationPlatformPublicKeyCredentialAssertionRequest *_Nullable request,
1900+
NSError *_Nullable error) {
1901+
XCTAssertNil(request);
1902+
XCTAssertEqual(error.code, FIRAuthErrorCodeOperationNotAllowed);
1903+
XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
1904+
[expectation fulfill];
1905+
}];
1906+
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
1907+
OCMVerifyAll(_mockBackend);
1908+
}
1909+
}
1910+
1911+
/** @fn testFinalizePasskeySignInFailure
1912+
@brief Tests the flow of a failed @c finalizePasskeySignInWithCompletion: call.
1913+
*/
1914+
1915+
- (void)testFinalizePasskeySignInFailure {
1916+
if (@available(iOS 15.0, tvOS 16.0, macOS 12.0, *)) {
1917+
OCMExpect([_mockBackend finalizePasskeySignIn:[OCMArg any] callback:[OCMArg any]])
1918+
.andDispatchError2([FIRAuthErrorUtils operationNotAllowedErrorWithMessage:nil]);
1919+
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
1920+
id mockPlatfromCredential =
1921+
OCMClassMock([ASAuthorizationPlatformPublicKeyCredentialAssertion class]);
1922+
OCMStub([mockPlatfromCredential credentialID])
1923+
.andReturn([[NSData alloc] initWithBase64EncodedString:kCredentialID options:0]);
1924+
OCMStub([mockPlatfromCredential rawClientDataJSON])
1925+
.andReturn([[NSData alloc] initWithBase64EncodedString:kClientDataJson options:0]);
1926+
OCMStub([mockPlatfromCredential signature])
1927+
.andReturn([[NSData alloc] initWithBase64EncodedString:kSignature options:0]);
1928+
OCMStub([mockPlatfromCredential userID])
1929+
.andReturn([[NSData alloc] initWithBase64EncodedString:kUserID options:0]);
1930+
OCMStub([mockPlatfromCredential rawAuthenticatorData])
1931+
.andReturn([[NSData alloc] initWithBase64EncodedString:kAuthenticatorData options:0]);
1932+
[[FIRAuth auth] signOut:NULL];
1933+
[[FIRAuth auth]
1934+
finalizePasskeySignInWithPlatformCredential:mockPlatfromCredential
1935+
completion:^(FIRAuthDataResult *_Nullable authResult,
1936+
NSError *_Nullable error) {
1937+
XCTAssertTrue([NSThread isMainThread]);
1938+
XCTAssertNil(authResult.user);
1939+
XCTAssertEqual(error.code,
1940+
FIRAuthErrorCodeOperationNotAllowed);
1941+
XCTAssertNotNil(
1942+
error.userInfo[NSLocalizedDescriptionKey]);
1943+
[expectation fulfill];
1944+
}];
1945+
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
1946+
OCMVerifyAll(_mockBackend);
1947+
}
1948+
}
1949+
1950+
#endif // #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
1951+
18081952
#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
18091953
/** @fn testCreateUserWithEmailPasswordWithRecaptchaVerificationSuccess
18101954
@brief Tests the flow of a successful @c createUserWithEmail:password:completion: call.

0 commit comments

Comments
 (0)