From 8f277174e95b1d473bfabc36abe846f14e30ce13 Mon Sep 17 00:00:00 2001 From: Liubin Jiang Date: Tue, 21 Nov 2023 15:41:50 -0800 Subject: [PATCH 1/3] Add sample code for passkey sign in and enrollment --- .../Sample/Sample/MainViewController+OAuth.m | 107 +++++++++++------- .../Sample/MainViewController+Passkey.h | 31 +++++ .../Sample/MainViewController+Passkey.m | 105 +++++++++++++++++ .../Tests/Sample/Sample/MainViewController.m | 3 + .../Sample/Sample/UserInfoViewController.m | 15 +++ 5 files changed, 219 insertions(+), 42 deletions(-) create mode 100644 FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.h create mode 100644 FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m diff --git a/FirebaseAuth/Tests/Sample/Sample/MainViewController+OAuth.m b/FirebaseAuth/Tests/Sample/Sample/MainViewController+OAuth.m index 194a54064c4..9f56ca8124e 100644 --- a/FirebaseAuth/Tests/Sample/Sample/MainViewController+OAuth.m +++ b/FirebaseAuth/Tests/Sample/Sample/MainViewController+OAuth.m @@ -434,53 +434,76 @@ - (void)revokeAppleTokenAndDeleteUser { } - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) { - ASAuthorizationAppleIDCredential* appleIDCredential = authorization.credential; - NSString *IDToken = [NSString stringWithUTF8String:[appleIDCredential.identityToken bytes]]; - FIROAuthCredential *credential = - [FIROAuthProvider appleCredentialWithIDToken:IDToken - rawNonce:self.appleRawNonce - fullName:appleIDCredential.fullName]; - - if ([appleIDCredential.state isEqualToString:@"signIn"]) { - [FIRAuth.auth signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { - if (!error) { - NSLog(@"%@", authResult.description); - } else { - NSLog(@"%@", error.description); - } - }]; - } else if ([appleIDCredential.state isEqualToString:@"link"]) { - [FIRAuth.auth.currentUser linkWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { - if (!error) { - NSLog(@"%@", authResult.description); - } else { - NSLog(@"%@", error.description); - } - }]; - } else if ([appleIDCredential.state isEqualToString:@"reauth"]) { - [FIRAuth.auth.currentUser reauthenticateWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { - if (!error) { - NSLog(@"%@", authResult.description); - } else { - NSLog(@"%@", error.description); - } - }]; - } else if ([appleIDCredential.state isEqualToString:@"revokeAppleTokenAndDeleteUser"]) { - NSString *code = [[NSString alloc] initWithData:appleIDCredential.authorizationCode encoding:NSUTF8StringEncoding]; + if (@available(iOS 16.0, *)) { + if ([authorization.credential isKindOfClass: [ASAuthorizationPlatformPublicKeyCredentialRegistration class]]) { + ASAuthorizationPlatformPublicKeyCredentialRegistration *platformCredential = (ASAuthorizationPlatformPublicKeyCredentialRegistration*) authorization.credential; + FIRUser *user = FIRAuth.auth.currentUser; - [FIRAuth.auth revokeTokenWithAuthorizationCode:code completion:^(NSError * _Nullable error) { + [user finalizePasskeyEnrollmentWithPlatformCredential:platformCredential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { + [self log:[NSString stringWithFormat:@"Passkey Enrollment succeed with uid: %@", authResult.user.uid] ]; + [self showTypicalUIForUserUpdateResultsWithTitle:@"Enrollment with passkey" error:error]; + }]; + + } else if ([authorization.credential isKindOfClass: [ASAuthorizationPlatformPublicKeyCredentialAssertion class]]) { + ASAuthorizationPlatformPublicKeyCredentialAssertion *platformCredential = (ASAuthorizationPlatformPublicKeyCredentialAssertion*) authorization.credential; + [[AppManager auth] finalizePasskeySignInWithPlatformCredential:platformCredential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { + [self log:[NSString stringWithFormat:@"Passkey sign-in succeed with uid: %@", authResult.user.uid]]; + [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign in with passkey" error:error]; + + }]; + } + } else if ([authorization.credential isKindOfClass: [ASAuthorizationAppleIDCredential class]]) { + ASAuthorizationAppleIDCredential* appleIDCredential = authorization.credential; + NSString *IDToken = [NSString stringWithUTF8String:[appleIDCredential.identityToken bytes]]; + FIROAuthCredential *credential = + [FIROAuthProvider appleCredentialWithIDToken:IDToken + rawNonce:self.appleRawNonce + fullName:appleIDCredential.fullName]; + + if ([appleIDCredential.state isEqualToString:@"signIn"]) { + [FIRAuth.auth signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (!error) { - // Token revocation succeeded then delete user again. - [user deleteWithCompletion:^(NSError *_Nullable error) { - if (error) { - [self logFailure:@"delete account failed" error:error]; - } - [self showTypicalUIForUserUpdateResultsWithTitle:@"Delete User" error:error]; - }]; + NSLog(@"%@", authResult.description); } else { NSLog(@"%@", error.description); } - }]; + }]; + } else if ([appleIDCredential.state isEqualToString:@"link"]) { + [FIRAuth.auth.currentUser linkWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { + if (!error) { + NSLog(@"%@", authResult.description); + } else { + NSLog(@"%@", error.description); + } + }]; + } else if ([appleIDCredential.state isEqualToString:@"reauth"]) { + [FIRAuth.auth.currentUser reauthenticateWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { + if (!error) { + NSLog(@"%@", authResult.description); + } else { + NSLog(@"%@", error.description); + } + }]; + } else if ([appleIDCredential.state isEqualToString:@"revokeAppleTokenAndDeleteUser"]) { + NSString *code = [[NSString alloc] initWithData:appleIDCredential.authorizationCode encoding:NSUTF8StringEncoding]; + FIRUser *user = FIRAuth.auth.currentUser; + [FIRAuth.auth revokeTokenWithAuthorizationCode:code completion:^(NSError * _Nullable error) { + if (!error) { + // Token revocation succeeded then delete user again. + [user deleteWithCompletion:^(NSError *_Nullable error) { + if (error) { + [self logFailure:@"delete account failed" error:error]; + } + [self showTypicalUIForUserUpdateResultsWithTitle:@"Delete User" error:error]; + }]; + } else { + NSLog(@"%@", error.description); + } + }]; + } + } else { + // TODO signInCredential + [self log:@"credential type not found or OS version too low."]; } } diff --git a/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.h b/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.h new file mode 100644 index 00000000000..d253e562138 --- /dev/null +++ b/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.h @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "MainViewController.h" + +#import "StaticContentTableViewManager.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MainViewController (Passkey) + +- (StaticContentTableViewSection *)passkeySection; + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m b/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m new file mode 100644 index 00000000000..eb7c3bb085e --- /dev/null +++ b/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m @@ -0,0 +1,105 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#import "MainViewController+Passkey.h" +#import "AppManager.h" +#import "MainViewController+Internal.h" +#import + + +NS_ASSUME_NONNULL_BEGIN +@interface MainViewController () +@end + +@implementation MainViewController (Passkey) + +- (StaticContentTableViewSection *)passkeySection { + __weak typeof(self) weakSelf = self; + return [StaticContentTableViewSection sectionWithTitle:@"Passkey" cells:@[ + [StaticContentTableViewCell cellWithTitle:@"Sign Up With Passkey" + action:^{ [weakSelf passkeySignUp]; }], + [StaticContentTableViewCell cellWithTitle:@"Sign In With Passkey" + action:^{ [weakSelf passkeySignIn]; }], + [StaticContentTableViewCell cellWithTitle:@"Enroll with Passkey" + action:^{ [weakSelf passkeyEnroll]; }], + ]]; +} + +- (void)passkeySignUp { + // sign in anoymously + [[AppManager auth] signInAnonymouslyWithCompletion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { + if (error) { + [self logFailure:@"sign-in anonymously failed" error:error]; + } else { + [self logSuccess:@"sign-in anonymously succeeded."]; + [self log:[NSString stringWithFormat:@"User ID : %@", result.user.uid]]; + [self passkeyEnroll]; + } + }]; + +} + +- (void)passkeyEnroll { + FIRUser *user = FIRAuth.auth.currentUser; + if (!user) { + [self logFailure:@"Please sign in first." error:nil]; + return; + } + [self showTextInputPromptWithMessage:@"passkey name" + keyboardType:UIKeyboardTypeEmailAddress + completionBlock:^(BOOL userPressedOK, NSString *_Nullable passkeyName) { + if (@available(iOS 16.0, macOS 12.0, tvOS 16.0, *)) { + [user startPasskeyEnrollmentWithName:passkeyName completion:^(ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest * _Nullable request, NSError * _Nullable error) { + if (request) { + ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests: [NSMutableArray arrayWithObject:request]]; + controller.delegate = self; + controller.presentationContextProvider = self; + [controller performRequests]; + } else if (error) { + [self logFailure:@"Passkey enrollment failed" error:error]; + } + }]; + } else { + [self log:@"OS version is not supported for this action."]; + } + }]; + +} + +- (void)passkeySignIn { + if (@available(iOS 16.0, macOS 12.0, tvOS 16.0, *)) { + [[AppManager auth] startPasskeySignInWithCompletion:^(ASAuthorizationPlatformPublicKeyCredentialAssertionRequest * _Nullable request, NSError * _Nullable error) { + if (request) { + ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests: [NSMutableArray arrayWithObject:request]]; + controller.delegate = self; + controller.presentationContextProvider = self; + [controller performRequestsWithOptions:ASAuthorizationControllerRequestOptionPreferImmediatelyAvailableCredentials]; + } + }]; + } else { + [self log:@"OS version is not supported for this action."]; + } + +} + +- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){ + + return self.view.window; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Tests/Sample/Sample/MainViewController.m b/FirebaseAuth/Tests/Sample/Sample/MainViewController.m index 1b53c911a86..706cb5bf428 100644 --- a/FirebaseAuth/Tests/Sample/Sample/MainViewController.m +++ b/FirebaseAuth/Tests/Sample/Sample/MainViewController.m @@ -40,6 +40,7 @@ #import "UIViewController+Alerts.h" #import "UserInfoViewController.h" #import "UserTableViewCell.h" +#import "MainViewController+Passkey.h" NS_ASSUME_NONNULL_BEGIN @@ -252,6 +253,8 @@ - (void)updateTable { [weakSelf oobSection], // Auto Tests [weakSelf autoTestsSection], + // Passkey + [weakSelf passkeySection], ]]; } diff --git a/FirebaseAuth/Tests/Sample/Sample/UserInfoViewController.m b/FirebaseAuth/Tests/Sample/Sample/UserInfoViewController.m index fbf44ae9634..0c2b0bbe2e2 100644 --- a/FirebaseAuth/Tests/Sample/Sample/UserInfoViewController.m +++ b/FirebaseAuth/Tests/Sample/Sample/UserInfoViewController.m @@ -17,6 +17,7 @@ #import "UserInfoViewController.h" #import +#import #import #import #import "StaticContentTableViewManager.h" @@ -74,6 +75,8 @@ - (void)loadTableView { value:stringWithBool(_user.emailVerified)], [StaticContentTableViewCell cellWithTitle:@"refresh token" value:_user.refreshToken], [StaticContentTableViewCell cellWithTitle:@"multi factor" value:[self multiFactorString]], + [StaticContentTableViewCell cellWithTitle:@"passkeys" value:[self passkeysString]], + ]] ] mutableCopy]; [sections addObject:[self sectionWithUserInfo:_user]]; @@ -108,4 +111,16 @@ - (NSString *)multiFactorString { return string; } +- (NSString *)passkeysString { + NSMutableString *string = [NSMutableString string]; + + for (FIRPasskeyInfo *info in _user.enrolledPasskeys) { + [string appendString:info.name]; + [string appendString:@" - "]; + [string appendString:info.credentialID]; + } + + return string; +} + @end From 363e228c200be20a20ecc50ad58bb84e78a884c3 Mon Sep 17 00:00:00 2001 From: Liubin Jiang Date: Tue, 21 Nov 2023 15:48:52 -0800 Subject: [PATCH 2/3] remove trailing space --- FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m b/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m index eb7c3bb085e..81b2e811882 100644 --- a/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m +++ b/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m @@ -49,7 +49,6 @@ - (void)passkeySignUp { [self passkeyEnroll]; } }]; - } - (void)passkeyEnroll { @@ -92,7 +91,6 @@ - (void)passkeySignIn { } else { [self log:@"OS version is not supported for this action."]; } - } - (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){ From 9d151aff0bed487ab32aca3a3f04b570a112da68 Mon Sep 17 00:00:00 2001 From: Liubin Jiang Date: Wed, 22 Nov 2023 10:07:34 -0800 Subject: [PATCH 3/3] remove PresentationAnchor --- .../Tests/Sample/Sample/MainViewController+Passkey.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m b/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m index 81b2e811882..a40cd044495 100644 --- a/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m +++ b/FirebaseAuth/Tests/Sample/Sample/MainViewController+Passkey.m @@ -93,10 +93,10 @@ - (void)passkeySignIn { } } -- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){ - - return self.view.window; -} +//- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){ +// +// return self.view.window; +//} @end