Skip to content

Commit

Permalink
Restructured Platform and Security Key selection on iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
f-23 committed Sep 6, 2024
1 parent 2a6779d commit 3a008cd
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 25 deletions.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,21 @@ try {
}
```

### Security Keys (iOS-specific)
### Force Platform or Security Key (iOS-specific)

You can allow or disallow users to register using a Security Key (like [Yubikey](https://www.yubico.com/)).
You can force users to register and authenticate using either a platform key, a security key (like [Yubikey](https://www.yubico.com/)) or allow both using the following methods. This only works on iOS, Android will ignore these instructions.

For this just set the `authenticatorAttachment` field in your Passkey request to `platform` or `cross-platform`, depending on your preference.
#### Create Passkey

- `Passkey.create()` - Allow the user to choose between platform and security passkey
- `Passkey.createPlatformKey()` - Force the user to create a platform passkey
- `Passkey.createSecurityKey()` - Force the user to create a security passkey

#### Get Passkey

- `Passkey.get()` - Allow the user to choose between platform and security passkey
- `Passkey.getPlatformKey()` - Force the user to authenticate using a platform passkey
- `Passkey.getSecurityKey()` - Force the user to authenticate using a security passkey

### Extensions

Expand Down
4 changes: 2 additions & 2 deletions android/src/main/java/com/reactnativepasskey/PasskeyModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class PasskeyModule(reactContext: ReactApplicationContext) : ReactContextBaseJav
}

@ReactMethod
fun create(requestJson: String, promise: Promise) {
fun create(requestJson: String, forcePlatformKey: Boolean, forceSecurityKey: Boolean, promise: Promise) {
val credentialManager = CredentialManager.create(reactApplicationContext.applicationContext)
val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest(requestJson)

Expand Down Expand Up @@ -70,7 +70,7 @@ class PasskeyModule(reactContext: ReactApplicationContext) : ReactContextBaseJav
}

@ReactMethod
fun get(requestJson: String, promise: Promise) {
fun get(requestJson: String, forcePlatformKey: Boolean, forceSecurityKey: Boolean, promise: Promise) {
val credentialManager = CredentialManager.create(reactApplicationContext.applicationContext)
val getCredentialRequest =
GetCredentialRequest(listOf(GetPublicKeyCredentialOption(requestJson)))
Expand Down
8 changes: 4 additions & 4 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ PODS:
- React-jsinspector (0.71.18)
- React-logger (0.71.18):
- glog
- react-native-passkey (2.1.1):
- react-native-passkey (3.0.0-beta2):
- React-Core
- React-perflogger (0.71.18)
- React-RCTActionSheet (0.71.18):
Expand Down Expand Up @@ -573,7 +573,7 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"

SPEC CHECKSUMS:
boost: a7c83b31436843459a1961bfd74b96033dc77234
boost: 7dcd2de282d72e344012f7d6564d024930a6a440
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: 5f81939bc6d6bcd3e71bba3744753c2a88991395
Expand All @@ -592,7 +592,7 @@ SPEC CHECKSUMS:
hermes-engine: 251dcc3511fc68678e2ee96dd7d175d9489b781e
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCTRequired: 467cd79889cbcfd008d0ff7c697f73765d4578c3
RCTTypeSafety: 71c8b219dc633684896452fee38a1e442d167dd6
React: b807b09e4402c12dcbc44176e50533dca10d01f0
Expand All @@ -606,7 +606,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: a60e1117c011cbb6a95418f4bf67d6dfbbf81772
React-jsinspector: 7218c92923292e20326a1eed3104fe90393ca449
React-logger: 166cf6649b5ea9e26ab816073dd0e68020561d65
react-native-passkey: b2125378c7fe3638349acc7dc37e90721d22fb8a
react-native-passkey: 88d3bb48dc38bdc69f0baf019b0faf355c27e6ec
React-perflogger: c8849042b03392681ebd5d99a0775dbadfad2c74
React-RCTActionSheet: 6bd3b502da266d69942e9ce4bc13666f90544620
React-RCTAnimation: 63fbec04ae279b387ac786898bd5a918b8875f48
Expand Down
4 changes: 4 additions & 0 deletions ios/Passkey.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
@interface RCT_EXTERN_MODULE(Passkey, NSObject)

RCT_EXTERN_METHOD(create:(NSString)request
withForcePlatformKey:(BOOL)forcePlatformKey
withForceSecurityKey:(BOOL)forceSecurityKey
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject);

RCT_EXTERN_METHOD(get:(NSString)request
withForcePlatformKey:(BOOL)forcePlatformKey
withForceSecurityKey:(BOOL)forceSecurityKey
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject);

Expand Down
28 changes: 14 additions & 14 deletions ios/Passkey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class Passkey: NSObject, RNPasskeyResultHandler {
/**
Main create entrypoint
*/
@objc(create:withResolver:withRejecter:)
func create(_ request: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
@objc(create:withForcePlatformKey:withForceSecurityKey:withResolver:withRejecter:)
func create(_ request: String, forcePlatformKey: Bool, forceSecurityKey: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
do {
passkeyHandler = RNPasskeyHandler(resolve, reject);

Expand All @@ -46,7 +46,7 @@ class Passkey: NSObject, RNPasskeyResultHandler {
let securityKeyRequest: ASAuthorizationRequest = self.configureCreateSecurityKeyRequest(challenge: challenge, userId: userId, request: requestJSON);

// Get authorization controller
let authController: ASAuthorizationController = self.configureAuthController(authenticatorAttachement: requestJSON.authenticatorSelection?.authenticatorAttachment, platformKeyRequest: platformKeyRequest, securityKeyRequest: securityKeyRequest);
let authController: ASAuthorizationController = self.configureAuthController(forcePlatformKey: forcePlatformKey, forceSecurityKey: forceSecurityKey, platformKeyRequest: platformKeyRequest, securityKeyRequest: securityKeyRequest);

let passkeyDelegate = PasskeyDelegate(completionHandler: self);

Expand All @@ -65,8 +65,8 @@ class Passkey: NSObject, RNPasskeyResultHandler {
/**
Main get entrypoint
*/
@objc(get:withResolver:withRejecter:)
func get(_ request: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
@objc(get:withForcePlatformKey:withForceSecurityKey:withResolver:withRejecter:)
func get(_ request: String, forcePlatformKey: Bool, forceSecurityKey: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
do {
passkeyHandler = RNPasskeyHandler(resolve, reject);

Expand All @@ -84,7 +84,7 @@ class Passkey: NSObject, RNPasskeyResultHandler {
let securityKeyRequest: ASAuthorizationRequest = self.configureGetSecurityKeyRequest(challenge: challenge, request: requestJSON);

// Get authorization controller
let authController: ASAuthorizationController = self.configureAuthController(platformKeyRequest: platformKeyRequest, securityKeyRequest: securityKeyRequest);
let authController: ASAuthorizationController = self.configureAuthController(forcePlatformKey: forcePlatformKey, forceSecurityKey: forceSecurityKey, platformKeyRequest: platformKeyRequest, securityKeyRequest: securityKeyRequest);

let passkeyDelegate = PasskeyDelegate(completionHandler: self);

Expand Down Expand Up @@ -240,16 +240,16 @@ class Passkey: NSObject, RNPasskeyResultHandler {
/**
Creates and returns authorization controller depending on selected request types
*/
private func configureAuthController(authenticatorAttachement: AuthenticatorAttachment? = .crossPlatform, platformKeyRequest: ASAuthorizationRequest, securityKeyRequest: ASAuthorizationRequest) -> ASAuthorizationController {
// Determine if we show platformKeyRequest, securityKeyRequest, or both
var authorizationRequests: [ASAuthorizationRequest] = [platformKeyRequest]

if (authenticatorAttachement == .crossPlatform) {
authorizationRequests.append(securityKeyRequest);
private func configureAuthController(forcePlatformKey: Bool, forceSecurityKey: Bool, platformKeyRequest: ASAuthorizationRequest, securityKeyRequest: ASAuthorizationRequest) -> ASAuthorizationController {
if (forcePlatformKey) {
return ASAuthorizationController(authorizationRequests: [platformKeyRequest]);
}

// Create auth controller
return ASAuthorizationController(authorizationRequests: authorizationRequests);
if (forceSecurityKey) {
return ASAuthorizationController(authorizationRequests: [securityKeyRequest]);
}

return ASAuthorizationController(authorizationRequests: [platformKeyRequest, securityKeyRequest]);
}

/**
Expand Down
124 changes: 122 additions & 2 deletions src/Passkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,67 @@ export class Passkey {

try {
const response: PasskeyCreateResult = (await NativePasskey.create(
JSON.stringify(request)
JSON.stringify(request),
false, // forcePlatformKey
false // forceSecurityKey
)) as PasskeyCreateResult;

return response;
} catch (error) {
throw handleNativeError(error);
}
}

/**
* Creates a new Passkey
* Forces the usage of a platform authenticator on iOS
*
* @param request The FIDO2 Attestation Request in JSON format
* @param options An object containing options for the registration process
* @returns The FIDO2 Attestation Result in JSON format
* @throws
*/
public static async createPlatformKey(
request: PasskeyCreateRequest
): Promise<PasskeyCreateResult> {
if (!Passkey.isSupported()) {
throw NotSupportedError;
}

try {
const response: PasskeyCreateResult = (await NativePasskey.create(
JSON.stringify(request),
true, // forcePlatformKey
false // forceSecurityKey
)) as PasskeyCreateResult;

return response;
} catch (error) {
throw handleNativeError(error);
}
}

/**
* Creates a new Passkey
* Forces the usage of a security authenticator on iOS
*
* @param request The FIDO2 Attestation Request in JSON format
* @param options An object containing options for the registration process
* @returns The FIDO2 Attestation Result in JSON format
* @throws
*/
public static async createSecurityKey(
request: PasskeyCreateRequest
): Promise<PasskeyCreateResult> {
if (!Passkey.isSupported()) {
throw NotSupportedError;
}

try {
const response: PasskeyCreateResult = (await NativePasskey.create(
JSON.stringify(request),
false, // forcePlatformKey
true // forceSecurityKey
)) as PasskeyCreateResult;

return response;
Expand All @@ -52,7 +112,67 @@ export class Passkey {

try {
const response: PasskeyGetResult = (await NativePasskey.get(
JSON.stringify(request)
JSON.stringify(request),
false, // forcePlatformKey
false // forceSecurityKey
)) as PasskeyGetResult;

return response;
} catch (error) {
throw handleNativeError(error);
}
}

/**
* Authenticates using an existing Passkey
* Forces the usage of a platform authenticator on iOS
*
* @param request The FIDO2 Assertion Request in JSON format
* @param options An object containing options for the authentication process
* @returns The FIDO2 Assertion Result in JSON format
* @throws
*/
public static async getPlatformKey(
request: PasskeyGetRequest
): Promise<PasskeyGetResult> {
if (!Passkey.isSupported()) {
throw NotSupportedError;
}

try {
const response: PasskeyGetResult = (await NativePasskey.get(
JSON.stringify(request),
true, // forcePlatformKey
false // forceSecurityKey
)) as PasskeyGetResult;

return response;
} catch (error) {
throw handleNativeError(error);
}
}

/**
* Authenticates using an existing Passkey
* Forces the usage of a security authenticator on iOS
*
* @param request The FIDO2 Assertion Request in JSON format
* @param options An object containing options for the authentication process
* @returns The FIDO2 Assertion Result in JSON format
* @throws
*/
public static async getSecurityKey(
request: PasskeyGetRequest
): Promise<PasskeyGetResult> {
if (!Passkey.isSupported()) {
throw NotSupportedError;
}

try {
const response: PasskeyGetResult = (await NativePasskey.get(
JSON.stringify(request),
false, // forcePlatformKey
true // forceSecurityKey
)) as PasskeyGetResult;

return response;
Expand Down

0 comments on commit 3a008cd

Please sign in to comment.