preferImmediatelyAvailableCredentials doesn't work for Passkey

I tried to use preferImmediatelyAvailableCredentials option in my sign in via Passkey process and expected to see this logic: If passkey isn’t available (for some reason, for example, user deleted it) the sign up modal sheet appears. But instead of this I got ‘Choose how you’d like in sign in’ sheet. As I understand, preferImmediatelyAvailableCredentials should allow me to fallback to sign up and skip this step but it doesn’t. My code is:

    func signIn(credId: String) {
        currentAuthController?.cancel()
        let securityKeyProvider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: domain)
        let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: domain)
        
        let assertionRequest = publicKeyCredentialProvider.createCredentialAssertionRequest(challenge: challengeData)
        let securityKeyRequest = securityKeyProvider.createCredentialAssertionRequest(challenge: challengeData)
        if let data = Data(base64urlEncoded: credId) {
            let cred = ASAuthorizationPlatformPublicKeyCredentialDescriptor(credentialID: data)
            assertionRequest.allowedCredentials = [cred]
        }
        let requests = [assertionRequest, securityKeyRequest]
        let authController = ASAuthorizationController(authorizationRequests: requests)
        authController.delegate = self
        authController.presentationContextProvider = self
        
        currentAuthController = authController

        authController.performRequests(options: .preferImmediatelyAvailableCredentials)
    }

Accepted Reply

The preferImmediatelyAvailableCredentials option does not fall back to registration (it couldn't without changing the API - registration takes different parameters). That option is meant to suppress the UI entirely if there are no local passkeys available (unless your request also includes security keys).

Some history and examples:

Prior to passkeys

The ASAuthorization API family initially supported only passwords and Sign in with Apple. If you make a request for either or both of those, the sheet comes up only if you actually have credentials to pick from. Both of these credential types exist locally on-device, so credentials are always "available immediately". If you have no matching credentials, the request is entirely silent (no system UI at all), and immediately returns an error to the delegate. This is a feature to allow apps to, for example, display a nice, consistent sign in prompt near app launch or on top of their existing sign in page, only for users that already have credentials, without interrupting new users.

With passkeys

With the introduction of passkeys and the cross-device sign in model, now suddenly the user may have credentials on a nearby device, rather than just the device they're currently on. As always with passkeys, it is extremely important to not regress the user experience from passwords - you can always type a password from a nearby device, so you need to be able to use a passkey from a nearby device as well. To that end, whenever you make passkey registration or assertion request, we will always offer the cross-device option alongside any existing credentials.

However, this means we now want to both: allow users to easily sign in from other devices, and also continue to allow developers to make requests that are silent when the user has no credentials. The preferImmediatelyAvailableCredentials option is the middle ground: it will be silent if there are no on-device credentials, but will still allow cross-device sign in whenever the sheet is shown.

With security keys

Security keys now complicate this matter further: because they're external devices, the credentials are never immediately available. As of right now, we assume that if you're including a security key request, then you always want to show the sheet, in order to indicate to the user that they need to present their key. Therefore passing both a security key request and preferImmediatelyAvailableCredentials at the same time is contradictory, and we currently ignore preferImmediatelyAvailableCredentials in that case.

  • @garrett-davidson Thanks for the feedback. Is there any possibility to not to show this 'external auth' sheet during passkey flow? I have a requirement to do sign up process if there's no local credential during login and if somehow I could get some 'no credential' error I would switch to sign up manaully.

  • If a user has a credential they want to use and you're not allowing that, you've created a regression from passwords. We've been stuck with passwords (which everyone universally agrees are terrible and actively hurt people) for so long because they're so incredibly easy to use and work everywhere. If we want passkeys to actually succeed as the successor to passwords, our top priority must be to meet the incredibly ease-of-use bar of the password.

  • The correct way to handle situations like this is not to block people from signing in how they want to, but to detect how they sign in and offer a faster solution for next time. You can check the attachment field on ASAuthorizationPlatformPublicKeyCredentialAssertion to see if the used passkey was on this device or another device. If it was another device, you can offer for the user to create an additional passkey on the current device.

Add a Comment

Replies

The preferImmediatelyAvailableCredentials option does not fall back to registration (it couldn't without changing the API - registration takes different parameters). That option is meant to suppress the UI entirely if there are no local passkeys available (unless your request also includes security keys).

Some history and examples:

Prior to passkeys

The ASAuthorization API family initially supported only passwords and Sign in with Apple. If you make a request for either or both of those, the sheet comes up only if you actually have credentials to pick from. Both of these credential types exist locally on-device, so credentials are always "available immediately". If you have no matching credentials, the request is entirely silent (no system UI at all), and immediately returns an error to the delegate. This is a feature to allow apps to, for example, display a nice, consistent sign in prompt near app launch or on top of their existing sign in page, only for users that already have credentials, without interrupting new users.

With passkeys

With the introduction of passkeys and the cross-device sign in model, now suddenly the user may have credentials on a nearby device, rather than just the device they're currently on. As always with passkeys, it is extremely important to not regress the user experience from passwords - you can always type a password from a nearby device, so you need to be able to use a passkey from a nearby device as well. To that end, whenever you make passkey registration or assertion request, we will always offer the cross-device option alongside any existing credentials.

However, this means we now want to both: allow users to easily sign in from other devices, and also continue to allow developers to make requests that are silent when the user has no credentials. The preferImmediatelyAvailableCredentials option is the middle ground: it will be silent if there are no on-device credentials, but will still allow cross-device sign in whenever the sheet is shown.

With security keys

Security keys now complicate this matter further: because they're external devices, the credentials are never immediately available. As of right now, we assume that if you're including a security key request, then you always want to show the sheet, in order to indicate to the user that they need to present their key. Therefore passing both a security key request and preferImmediatelyAvailableCredentials at the same time is contradictory, and we currently ignore preferImmediatelyAvailableCredentials in that case.

  • @garrett-davidson Thanks for the feedback. Is there any possibility to not to show this 'external auth' sheet during passkey flow? I have a requirement to do sign up process if there's no local credential during login and if somehow I could get some 'no credential' error I would switch to sign up manaully.

  • If a user has a credential they want to use and you're not allowing that, you've created a regression from passwords. We've been stuck with passwords (which everyone universally agrees are terrible and actively hurt people) for so long because they're so incredibly easy to use and work everywhere. If we want passkeys to actually succeed as the successor to passwords, our top priority must be to meet the incredibly ease-of-use bar of the password.

  • The correct way to handle situations like this is not to block people from signing in how they want to, but to detect how they sign in and offer a faster solution for next time. You can check the attachment field on ASAuthorizationPlatformPublicKeyCredentialAssertion to see if the used passkey was on this device or another device. If it was another device, you can offer for the user to create an additional passkey on the current device.

Add a Comment