# Passkeys

A passkey is a public/private key pair. For platform passkeys the private key
sits in iCloud Keychain and syncs across the person's devices; the
relying-party server keeps the public credential material and verifies every
ceremony. Back to the [skill overview](../SKILL.md).

## Before writing code

1. Turn on the **Associated Domains** capability for the target.
2. Add `webcredentials:<domain>` for the relying party, for example
   `webcredentials:accounts.tasknest.app`.
3. Host the `apple-app-site-association` JSON on the domain, with the app's ID listed under `webcredentials`.
4. Get every registration and assertion challenge from the server.

The relying party identifier is normally the bare domain, such as
`tasknest.app`, not the bundle ID.

## Registering a passkey

The server creates the challenge and a stable user ID (bytes, not an email)
for the account.

```swift
import AuthenticationServices

@MainActor
final class PasskeyEnrollment: NSObject {
    private let relyingParty = "tasknest.app"
    weak var anchorProvider: ASAuthorizationControllerPresentationContextProviding?
    weak var resultHandler: ASAuthorizationControllerDelegate?

    func enroll(username: String) async throws {
        let options = try await PasskeyAPI.registrationOptions(for: username)  // challenge + userID
        let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: relyingParty)
        let request = provider.createCredentialRegistrationRequest(
            challenge: options.challenge,
            name: username,
            userID: options.userID
        )
        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = resultHandler
        controller.presentationContextProvider = anchorProvider
        controller.performRequests()
    }
}
```

When the registration credential comes back, post it to the server. The server
checks the challenge, validates the attestation it asked for, and stores the
public credential.

## Signing in

```swift
func signInRequest(challenge: Data) -> ASAuthorizationPlatformPublicKeyCredentialAssertionRequest {
    let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: "tasknest.app")
    return provider.createCredentialAssertionRequest(challenge: challenge)
}
```

Use a fresh challenge from the server each time, optionally scoped by a
username hint. If the person has no passkey for this relying party the request
fails; offer to create one, or fall back to a password or Sign in with Apple.

## One delegate for every credential type

```swift
func authorizationController(controller: ASAuthorizationController,
                             didCompleteWithAuthorization authorization: ASAuthorization) {
    Task {
        switch authorization.credential {
        case let created as ASAuthorizationPlatformPublicKeyCredentialRegistration:
            try await PasskeyAPI.finishRegistration(
                credentialID: created.credentialID,
                clientData: created.rawClientDataJSON,
                attestation: created.rawAttestationObject)
        case let proof as ASAuthorizationPlatformPublicKeyCredentialAssertion:
            try await PasskeyAPI.finishSignIn(
                credentialID: proof.credentialID,
                clientData: proof.rawClientDataJSON,
                authenticatorData: proof.rawAuthenticatorData,
                signature: proof.signature,
                userID: proof.userID)
        case let apple as ASAuthorizationAppleIDCredential:
            try await SessionAPI.exchange(token: apple.identityToken, code: apple.authorizationCode)
        case let saved as ASPasswordCredential:
            try await SessionAPI.passwordLogin(user: saved.user, password: saved.password)
        default:
            break
        }
    }
}
```

What arrives on the device is not the final word. Issue a session only after
the server has verified the challenge and the signed response.

## Inline suggestions with AutoFill

If the login form includes a username box, let the QuickType bar suggest passkeys
instead of opening a modal right away:

```swift
typealias PasskeyUIHost = ASAuthorizationControllerDelegate & ASAuthorizationControllerPresentationContextProviding

@MainActor
func armPasskeySuggestions(for field: UITextField, host: PasskeyUIHost) async throws -> ASAuthorizationController {
    field.textContentType = .username
    let assertion = signInRequest(challenge: try await PasskeyAPI.freshChallenge())
    let controller = ASAuthorizationController(authorizationRequests: [assertion])
    controller.delegate = host
    controller.presentationContextProvider = host
    controller.performAutoFillAssistedRequests()
    return controller          // keep a reference while the login screen is visible
}
```

The system shows its UI once a field with the matching content type gains
focus, which usually feels better than an immediate sheet.

## Physical security keys

`ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier:)`
covers hardware FIDO keys over NFC, USB or Lightning. Use it only when a
physical key is a requirement and keep it on its own code path: the provider,
request type and user experience all differ. When the relying party accepts
both, a single controller can carry a platform assertion request and a
security-key assertion request together.

## Failure modes

- No `webcredentials:` entitlement, or the domain's association file does not list the app.
- Reusing a challenge, or generating challenges on the device.
- Treating the assertion credential as a finished login before server verification.
- Sending the bundle ID as the relying-party identifier while the backend registered a web domain.
- Forgetting `performAutoFillAssistedRequests()`, so no inline suggestions appear.
- Choosing the platform provider even though policy calls for a hardware key.

## Apple documentation

- [Supporting passkeys](https://developer.apple.com/documentation/authenticationservices/supporting-passkeys)
- [ASAuthorizationPlatformPublicKeyCredentialProvider](https://developer.apple.com/documentation/authenticationservices/asauthorizationplatformpublickeycredentialprovider)
- [ASAuthorizationPlatformPublicKeyCredentialRegistration](https://developer.apple.com/documentation/authenticationservices/asauthorizationplatformpublickeycredentialregistration)
- [ASAuthorizationPlatformPublicKeyCredentialAssertion](https://developer.apple.com/documentation/authenticationservices/asauthorizationplatformpublickeycredentialassertion)
- [ASAuthorizationSecurityKeyPublicKeyCredentialProvider](https://developer.apple.com/documentation/authenticationservices/asauthorizationsecuritykeypublickeycredentialprovider)
- [performAutoFillAssistedRequests()](https://developer.apple.com/documentation/authenticationservices/asauthorizationcontroller/performautofillassistedrequests())
