Skip to content
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
5 changes: 5 additions & 0 deletions ios/Passkey.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,9 @@ + (BOOL)requiresMainQueueSetup
return NO;
}

- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}

@end
19 changes: 15 additions & 4 deletions ios/Passkey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class Passkey: NSObject, RNPasskeyResultHandler {
*/
@objc(create:withForcePlatformKey:withForceSecurityKey:withResolver:withRejecter:)
func create(_ request: String, forcePlatformKey: Bool, forceSecurityKey: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
guard passkeyHandler == nil else {
reject("RequestFailed", "Another passkey request is already in progress", nil);
return;
}
do {
passkeyHandler = RNPasskeyHandler(resolve, reject);
// Create never uses immediate mediation; reset so a stale flag from a prior
Expand Down Expand Up @@ -72,6 +76,10 @@ class Passkey: NSObject, RNPasskeyResultHandler {
*/
@objc(get:withForcePlatformKey:withForceSecurityKey:withPreferImmediatelyAvailable:withResolver:withRejecter:)
func get(_ request: String, forcePlatformKey: Bool, forceSecurityKey: Bool, preferImmediatelyAvailable: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
guard passkeyHandler == nil else {
reject("RequestFailed", "Another passkey request is already in progress", nil);
return;
}
do {
passkeyHandler = RNPasskeyHandler(resolve, reject);
self.preferImmediatelyAvailable = preferImmediatelyAvailable;
Expand Down Expand Up @@ -186,6 +194,8 @@ class Passkey: NSObject, RNPasskeyResultHandler {
print("passkeyHandler was nil");
return
}
passkeyHandler = nil;
passkeyDelegate = nil;

do {
switch data {
Expand All @@ -211,7 +221,7 @@ class Passkey: NSObject, RNPasskeyResultHandler {
*/
private func configureCreateSecurityKeyRequest(challenge: Data, userId: Data, request: RNPasskeyCredentialCreationOptions) -> ASAuthorizationSecurityKeyPublicKeyCredentialRegistrationRequest {

let securityKeyProvider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: request.rp.id!);
let securityKeyProvider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: request.rp.id);

let authRequest = securityKeyProvider.createCredentialRegistrationRequest(challenge: challenge,
displayName: request.user.displayName,
Expand Down Expand Up @@ -245,7 +255,7 @@ class Passkey: NSObject, RNPasskeyResultHandler {
*/
private func configureCreatePlatformRequest(challenge: Data, userId: Data, request: RNPasskeyCredentialCreationOptions) throws -> ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest {

let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: request.rp.id!);
let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: request.rp.id);

let authRequest = platformProvider.createCredentialRegistrationRequest(challenge: challenge,
name: request.user.name,
Expand Down Expand Up @@ -315,7 +325,7 @@ class Passkey: NSObject, RNPasskeyResultHandler {
if prf.evalByCredential != nil {
// If evalByCredential is present and allowCredentials is empty we throw an "Unsupported" error as specified in the WebAuthn standard

if let allowCredentials = request.allowCredentials, allowCredentials.isEmpty {
if request.allowCredentials?.isEmpty != false {
throw NSError(domain: "PRF Issue", code: 1)
}

Expand Down Expand Up @@ -437,8 +447,9 @@ class Passkey: NSObject, RNPasskeyResultHandler {
print("passkeyHandler was nil");
return
}
passkeyHandler = nil;
passkeyDelegate = nil;

handler.reject(error.type.rawValue, error.message, nil);
}
}

60 changes: 41 additions & 19 deletions ios/PasskeyDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import AuthenticationServices
import CryptoKit

@available(iOS 15.0, *)
protocol RNPasskeyResultHandler {
protocol RNPasskeyResultHandler: AnyObject {
func onSuccess(_ data: PublicKeyCredentialJSON)
func onError(_ error: Error)
}

@objc(PasskeyDelegate)
@available(iOS 15.0, *)
class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
private let _completionHandler: RNPasskeyResultHandler
private weak var _completionHandler: RNPasskeyResultHandler?

/**
Whether the system asked us for a window to present the credential sheet in.
Expand All @@ -30,6 +30,18 @@ class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizat
_completionHandler = completionHandler;
}

private func finishWithError(_ error: Error) {
let handler = _completionHandler;
_completionHandler = nil;
handler?.onError(error);
}

private func finishWithSuccess(_ data: PublicKeyCredentialJSON) {
let handler = _completionHandler;
_completionHandler = nil;
handler?.onSuccess(data);
}

// Perform the authorization request for a given ASAuthorizationController instance
func performAuthForController(controller: ASAuthorizationController, preferImmediatelyAvailable: Bool = false) {
controller.delegate = self;
Expand Down Expand Up @@ -62,7 +74,7 @@ class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizat
didCompleteWithError error: Error
) {
// Authorization request returned an error
_completionHandler.onError(error);
finishWithError(error);
}

func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
Expand All @@ -80,13 +92,14 @@ class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizat
case let credential as ASAuthorizationSecurityKeyPublicKeyCredentialAssertion:
self.handleSecurityKeyPublicKeyAssertionResponse(credential: credential);
default:
_completionHandler.onError(ASAuthorizationError(ASAuthorizationError.invalidResponse));
finishWithError(ASAuthorizationError(ASAuthorizationError.invalidResponse));
}
}

func handlePlatformPublicKeyRegistrationResponse(credential: ASAuthorizationPlatformPublicKeyCredentialRegistration) -> Void {
if credential.rawAttestationObject == nil {
_completionHandler.onError(ASAuthorizationError(ASAuthorizationError.invalidResponse));
guard let attestationObject = credential.rawAttestationObject else {
finishWithError(ASAuthorizationError(ASAuthorizationError.invalidResponse));
return;
}

// LargeBlob Extension
Expand Down Expand Up @@ -117,7 +130,7 @@ class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizat

let response = AuthenticatorAttestationResponseJSON(
clientDataJSON: credential.rawClientDataJSON.toBase64URLEncodedString(),
attestationObject: credential.rawAttestationObject!.toBase64URLEncodedString()
attestationObject: attestationObject.toBase64URLEncodedString()
);

let createResponse = RNPasskeyCreateResponseJSON(
Expand All @@ -127,12 +140,13 @@ class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizat
clientExtensionResults: clientExtensionResults
);

_completionHandler.onSuccess(.create(createResponse));
finishWithSuccess(.create(createResponse));
}

func handleSecurityKeyPublicKeyRegistrationResponse(credential: ASAuthorizationSecurityKeyPublicKeyCredentialRegistration) -> Void {
if credential.rawAttestationObject == nil {
_completionHandler.onError((ASAuthorizationError(ASAuthorizationError.Code.failed)));
guard let attestationObject = credential.rawAttestationObject else {
finishWithError((ASAuthorizationError(ASAuthorizationError.Code.failed)));
return;
}

var transports: [AuthenticatorTransport] = [];
Expand All @@ -148,7 +162,7 @@ class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizat
let response = AuthenticatorAttestationResponseJSON(
clientDataJSON: credential.rawClientDataJSON.toBase64URLEncodedString(),
transports: transports,
attestationObject: credential.rawAttestationObject!.toBase64URLEncodedString()
attestationObject: attestationObject.toBase64URLEncodedString()
);

let createResponse = RNPasskeyCreateResponseJSON(
Expand All @@ -157,19 +171,23 @@ class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizat
response: response
);

_completionHandler.onSuccess(.create(createResponse));
finishWithSuccess(.create(createResponse));
}

func handlePlatformPublicKeyAssertionResponse(credential: ASAuthorizationPlatformPublicKeyCredentialAssertion) -> Void {
guard let signature = credential.signature else {
finishWithError(ASAuthorizationError(ASAuthorizationError.invalidResponse));
return;
}
var largeBlob: AuthenticationExtensionsLargeBlobOutputsJSON?;
if #available(iOS 17.0, *), let result = credential.largeBlob?.result {
largeBlob = AuthenticationExtensionsLargeBlobOutputsJSON()
switch (result) {
case .read(data: let blobData):
if let blob = blobData {
// get uIntArray, then transform to a dictionary RN can work with
largeBlob?.blob = Dictionary(uniqueKeysWithValues: blob.uIntArray.enumerated().map { (index, value) in
(String(index + 1), Int(value))
// Preserve each byte and expose the same zero-based indices as Uint8Array.
largeBlob?.blob = Dictionary(uniqueKeysWithValues: blob.enumerated().map { (index, value) in
(String(index), Int(value))
})
}
case .write(success: let successfullyWritten):
Expand Down Expand Up @@ -197,7 +215,7 @@ class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizat
let response = AuthenticatorAssertionResponseJSON(
authenticatorData: credential.rawAuthenticatorData.toBase64URLEncodedString(),
clientDataJSON: credential.rawClientDataJSON.toBase64URLEncodedString(),
signature: credential.signature!.toBase64URLEncodedString(),
signature: signature.toBase64URLEncodedString(),
userHandle: userHandle
);

Expand All @@ -208,16 +226,20 @@ class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizat
clientExtensionResults: clientExtensionResults
);

_completionHandler.onSuccess(.get(getResponse));
finishWithSuccess(.get(getResponse));
}

func handleSecurityKeyPublicKeyAssertionResponse(credential: ASAuthorizationSecurityKeyPublicKeyCredentialAssertion) -> Void {
guard let signature = credential.signature else {
finishWithError(ASAuthorizationError(ASAuthorizationError.invalidResponse));
return;
}
let userHandle: String? = credential.userID?.toBase64URLEncodedString();

let response = AuthenticatorAssertionResponseJSON(
authenticatorData: credential.rawAuthenticatorData.toBase64URLEncodedString(),
clientDataJSON: credential.rawClientDataJSON.toBase64URLEncodedString(),
signature: credential.signature!.toBase64URLEncodedString(),
signature: signature.toBase64URLEncodedString(),
userHandle: userHandle
);

Expand All @@ -227,6 +249,6 @@ class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizat
response: response
);

_completionHandler.onSuccess(.get(getResponse));
finishWithSuccess(.get(getResponse));
}
}
Loading