Skip to content

Commit

Permalink
[Passkey #5] Auth startPasskeySignInWithCompletion: and finalizePassk…
Browse files Browse the repository at this point in the history
…eySignInWithPlatformCredential:completion (#11952)
  • Loading branch information
Xiaoshouzi-gh authored Oct 23, 2023
1 parent 9ab4d5f commit 076d2e4
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 0 deletions.
90 changes: 90 additions & 0 deletions FirebaseAuth/Sources/Auth/FIRAuth.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
#import "FirebaseAuth/Sources/Backend/RPC/FIRCreateAuthURIResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIREmailLinkSignInRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIREmailLinkSignInResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRGetOOBConfirmationCodeResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRResetPasswordRequest.h"
Expand All @@ -57,6 +59,8 @@
#import "FirebaseAuth/Sources/Backend/RPC/FIRSignInWithGameCenterResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyCustomTokenRequest.h"
Expand All @@ -72,6 +76,7 @@
#import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h"
#import "FirebaseAuth/Sources/Utilities/FIRAuthExceptionUtils.h"
#import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
#import "FirebaseAuth/Sources/Utilities/NSData+FIRBase64.h"

#if TARGET_OS_IOS
#import "FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h"
Expand All @@ -83,6 +88,10 @@
#import "FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h"
#endif

#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
#import <AuthenticationServices/AuthenticationServices.h>
#endif

NS_ASSUME_NONNULL_BEGIN

#pragma mark-- Logger Service String.
Expand Down Expand Up @@ -1222,6 +1231,87 @@ - (void)signInWithCustomToken:(NSString *)token
});
}

#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
- (void)startPasskeySignInWithCompletion:
(nullable void (^)(
ASAuthorizationPlatformPublicKeyCredentialAssertionRequest *_Nullable request,
NSError *_Nullable error))completion {
FIRStartPasskeySignInRequest *request =
[[FIRStartPasskeySignInRequest alloc] initWithRequestConfiguration:self.requestConfiguration];
[FIRAuthBackend
startPasskeySignIn:request
callback:^(FIRStartPasskeySignInResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
completion(nil, error);
return;
}
if (response) {
NSData *challengeInData =
[[NSData alloc] initWithBase64EncodedString:response.challenge options:0];
ASAuthorizationPlatformPublicKeyCredentialProvider *provider =
[[ASAuthorizationPlatformPublicKeyCredentialProvider alloc]
initWithRelyingPartyIdentifier:response.rpID];
ASAuthorizationPlatformPublicKeyCredentialAssertionRequest *request =
[provider createCredentialAssertionRequestWithChallenge:challengeInData];

completion(request, nil);
}
}];
}

- (void)finalizePasskeySignInWithPlatformCredential:
(ASAuthorizationPlatformPublicKeyCredentialAssertion *)platformCredential
completion:(nullable void (^)(
FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error))completion {
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
FIRAuthDataResultCallback decoratedCallback =
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
NSString *credentialID = [platformCredential.credentialID base64EncodedStringWithOptions:0];
NSString *clientDataJson =
[platformCredential.rawClientDataJSON base64EncodedStringWithOptions:0];
NSString *authenticatorData =
[platformCredential.rawAuthenticatorData base64EncodedStringWithOptions:0];
NSString *signature = [platformCredential.signature base64EncodedStringWithOptions:0];
NSString *userID = [platformCredential.userID base64EncodedStringWithOptions:0];
FIRFinalizePasskeySignInRequest *request =
[[FIRFinalizePasskeySignInRequest alloc] initWithCredentialID:credentialID
clientDataJson:clientDataJson
authenticatorData:authenticatorData
signature:signature
userID:userID
requestConfiguration:self.requestConfiguration];
[FIRAuthBackend
finalizePasskeySignIn:request
callback:^(FIRFinalizePasskeySignInResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
decoratedCallback(nil, error);
}
[self completeSignInWithAccessToken:response.idToken
accessTokenExpirationDate:nil
refreshToken:response.refreshToken
anonymous:NO
callback:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
completion(nil, error);
return;
}

FIRAuthDataResult *authDataResult =
user ? [[FIRAuthDataResult alloc]
initWithUser:user
additionalUserInfo:nil]
: nil;
decoratedCallback(authDataResult, error);
}];
}];
});
}
#endif // #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST

- (void)createUserWithEmail:(NSString *)email
password:(NSString *)password
completion:(nullable FIRAuthDataResultCallback)completion {
Expand Down
40 changes: 40 additions & 0 deletions FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuth.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
@class FIRAuthDataResult;
@class FIRAuthSettings;
@class FIRUser;

#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
@class ASAuthorizationPlatformPublicKeyCredentialAssertion;
@class ASAuthorizationPlatformPublicKeyCredentialAssertionRequest;
#endif

@protocol FIRAuthUIDelegate;
@protocol FIRFederatedAuthProvider;

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

#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
/**
@fn startPasskeySignInWithCompletion:
@brief start sign in with passkey retrieving challenge from GCIP and create an assertion request.
@param completion Optionally; a block which creates a assertation request.
@remarks // TODO @liubinj add possible error codes
*/
- (void)startPasskeySignInWithCompletion:
(nullable void (^)(
ASAuthorizationPlatformPublicKeyCredentialAssertionRequest *_Nullable request,
NSError *_Nullable error))completion NS_SWIFT_NAME(startPasskeySignIn(completion:))
API_AVAILABLE(macos(12.0), ios(15.0), tvos(16.0));

/**
@fn finalizePasskeySignInWithPlatformCredential:completion:
@brief finalize sign in with passkey with existing credential assertion.
@param platformCredential The existing credential assertion created by device.
@param completion Optionally; a block which is invoked when the sign in with passkey flow finishes,
or is canceled. Invoked asynchronously on the main thread in the future.
@remarks // TODO @liubinj add possible error codes
*/
- (void)finalizePasskeySignInWithPlatformCredential:
(ASAuthorizationPlatformPublicKeyCredentialAssertion *)platformCredential
completion:(nullable void (^)(
FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error))completion
NS_SWIFT_NAME(finalizePasskeySignIn(with:completion:))
API_AVAILABLE(macos(12.0), ios(15.0), tvos(16.0));
#endif

/** @fn createUserWithEmail:password:completion:
@brief Creates and, on success, signs in a user with the given email address and password.
Expand Down
144 changes: 144 additions & 0 deletions FirebaseAuth/Tests/Unit/FIRAuthTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
#import "FirebaseAuth/Sources/Backend/RPC/FIRCreateAuthURIResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIREmailLinkSignInRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIREmailLinkSignInResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRFinalizePasskeySignInResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRGetAccountInfoRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRGetAccountInfoResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h"
Expand All @@ -53,6 +55,8 @@
#import "FirebaseAuth/Sources/Backend/RPC/FIRSetAccountInfoResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRSignUpNewUserResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRStartPasskeySignInResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionResponse.h"
#import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyCustomTokenRequest.h"
Expand All @@ -78,6 +82,10 @@
#import "FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h"
#endif // TARGET_OS_IOS

#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
#import <AuthenticationServices/AuthenticationServices.h>
#endif

/** @var kAPIKey
@brief The fake API key.
*/
Expand Down Expand Up @@ -278,6 +286,42 @@
*/
static NSString *const kFakeRecaptchaVersion = @"RecaptchaVersion";

/** @var kRpId
@brief The fake passkey relying party identifier.
*/
static NSString *const kRpId = @"fake.rp.id";

/** @var kChallenge
@brief The fake passkey challenge.
*/
static NSString *const kChallenge = @"Y2hhbGxlbmdl"; // decode to "challenge"

/** @var kCredentialID
@brief The fake passkey credentialID.
*/
static NSString *const kCredentialID = @"Y3JlZGVudGlhbGlk"; // decode to "credentialid"

/** @var kClientDataJson
@brief The fake clientDataJson object
*/
static NSString *const kClientDataJson = @"Y2xpZW50ZGF0YWpzb24="; // decode to "clientdatajson"

/** @var kAuthenticatorData
@brief The fake authenticatorData object
*/
static NSString *const kAuthenticatorData =
@"YXV0aGVudGljYXRvcmRhdGE="; // decode to "authenticatordata"

/** @var kSignature
@brief The fake signature
*/
static NSString *const kSignature = @"c2lnbmF0dXJl"; // decode to "signature"

/** @var kUserID
@brief The fake user ID / user handle
*/
static NSString *const kUserID = @"dXNlcmlk"; // decode to "userid"

#if TARGET_OS_IOS
/** @class FIRAuthAppDelegate
@brief Application delegate implementation to test the app delegate proxying
Expand Down Expand Up @@ -1805,6 +1849,106 @@ - (void)testSignInAndRetrieveDataWithCustomTokenFailure {
OCMVerifyAll(_mockBackend);
}

#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST
/** @fn testStartPasskeySignInSuccess
@brief Tests the flow of a successful @c startPasskeySignInWithCompletion: call.
*/
- (void)testStartPasskeySignInSuccess {
if (@available(iOS 15.0, tvOS 16.0, macOS 12.0, *)) {
OCMExpect([_mockBackend startPasskeySignIn:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(^(FIRStartPasskeySignInRequest *_Nullable request,
FIRStartPasskeySignInResponseCallback callback) {
XCTAssertEqualObjects(request.APIKey, kAPIKey);
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
id mockStartPasskeySignInResponse = OCMClassMock([FIRStartPasskeySignInResponse class]);
OCMStub([mockStartPasskeySignInResponse rpID]).andReturn(kRpId);
OCMStub([mockStartPasskeySignInResponse challenge]).andReturn(kChallenge);
callback(mockStartPasskeySignInResponse, nil);
});
});
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
[[FIRAuth auth] signOut:NULL];
[[FIRAuth auth]
startPasskeySignInWithCompletion:^(
ASAuthorizationPlatformPublicKeyCredentialAssertionRequest *_Nullable request,
NSError *_Nullable error) {
XCTAssertNil(error);
XCTAssertEqualObjects([[request challenge] base64EncodedStringWithOptions:0], kChallenge);
ASAuthorizationPlatformPublicKeyCredentialProvider *provider =
(ASAuthorizationPlatformPublicKeyCredentialProvider *)[request provider];
XCTAssertEqualObjects([provider relyingPartyIdentifier], kRpId);
[expectation fulfill];
}];

[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
OCMVerifyAll(_mockBackend);
}
}

/** @fn testStartPasskeySignInFailure
@brief Tests the flow of a failed @c startPasskeySignInWithCompletion: call.
*/
- (void)testStartPasskeySignInFailure {
if (@available(iOS 15.0, tvOS 16.0, macOS 12.0, *)) {
OCMExpect([_mockBackend startPasskeySignIn:[OCMArg any] callback:[OCMArg any]])
.andDispatchError2([FIRAuthErrorUtils operationNotAllowedErrorWithMessage:nil]);
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
[[FIRAuth auth] signOut:NULL];
[[FIRAuth auth]
startPasskeySignInWithCompletion:^(
ASAuthorizationPlatformPublicKeyCredentialAssertionRequest *_Nullable request,
NSError *_Nullable error) {
XCTAssertNil(request);
XCTAssertEqual(error.code, FIRAuthErrorCodeOperationNotAllowed);
XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
OCMVerifyAll(_mockBackend);
}
}

/** @fn testFinalizePasskeySignInFailure
@brief Tests the flow of a failed @c finalizePasskeySignInWithCompletion: call.
*/

- (void)testFinalizePasskeySignInFailure {
if (@available(iOS 15.0, tvOS 16.0, macOS 12.0, *)) {
OCMExpect([_mockBackend finalizePasskeySignIn:[OCMArg any] callback:[OCMArg any]])
.andDispatchError2([FIRAuthErrorUtils operationNotAllowedErrorWithMessage:nil]);
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
id mockPlatfromCredential =
OCMClassMock([ASAuthorizationPlatformPublicKeyCredentialAssertion class]);
OCMStub([mockPlatfromCredential credentialID])
.andReturn([[NSData alloc] initWithBase64EncodedString:kCredentialID options:0]);
OCMStub([mockPlatfromCredential rawClientDataJSON])
.andReturn([[NSData alloc] initWithBase64EncodedString:kClientDataJson options:0]);
OCMStub([mockPlatfromCredential signature])
.andReturn([[NSData alloc] initWithBase64EncodedString:kSignature options:0]);
OCMStub([mockPlatfromCredential userID])
.andReturn([[NSData alloc] initWithBase64EncodedString:kUserID options:0]);
OCMStub([mockPlatfromCredential rawAuthenticatorData])
.andReturn([[NSData alloc] initWithBase64EncodedString:kAuthenticatorData options:0]);
[[FIRAuth auth] signOut:NULL];
[[FIRAuth auth]
finalizePasskeySignInWithPlatformCredential:mockPlatfromCredential
completion:^(FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error) {
XCTAssertTrue([NSThread isMainThread]);
XCTAssertNil(authResult.user);
XCTAssertEqual(error.code,
FIRAuthErrorCodeOperationNotAllowed);
XCTAssertNotNil(
error.userInfo[NSLocalizedDescriptionKey]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
OCMVerifyAll(_mockBackend);
}
}

#endif // #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST

#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
/** @fn testCreateUserWithEmailPasswordWithRecaptchaVerificationSuccess
@brief Tests the flow of a successful @c createUserWithEmail:password:completion: call.
Expand Down

0 comments on commit 076d2e4

Please sign in to comment.