Highlight
Garrett from Apple’s Authenticated Experience team provides a comprehensive introduction to Passkey, a next-generation authentication technology based on FIDO Alliance standards.Passkey is officially available to all users in macOS Ventura and iOS 16 (it was a developer preview last year).
Core Content
There are two long-standing problems with password logins.It is difficult for users to create strong passwords for each account, and the server must save password derivatives.Even if apps and websites get the process right, phishing, password reuse, and credential leaks can still compromise accounts (00:33).
Passkey changes login credentials into a pair of keys per account.The device generates a key pair, the public key is stored on the server, and the private key remains on the user device.When logging in, the server issues a one-time challenge, the device uses the private key to sign locally, and the server uses the public key to verify the signature (26:06).The server has no private key to leak, and users have no string to be deceived by phishing websites.
The focus of this session is implementation.Apple recommends putting the passkey into existing username input fields and the AutoFill process.What the user sees is still the familiar login form; if the account already has a passkey, QuickType bar will directly give suggestions, click and log in through Face ID or Touch ID (09:15).
Detailed Content
First associate the App with the website
(11:30) Before the App uses passkey, it needs to configure Associated Domains (associated domain names) and declare them in the site’s Apple App Site Association filewebcredentialsServe.
{
"webcredentials": {
"apps": [ "A1B2C3D4E5.com.example.Shiny" ]
}
}
Key points:
webcredentialsIndicates that this domain name allows the app to use website credentials, including passwords and passkeys.appsThe array is written to Team ID plus Bundle ID, in the exampleA1B2C3D4E5.com.example.ShinyCorresponds to Shiny App.- This step lets the system know that the App is
example.comBelong to the same login domain.
Next, mark the username input boxtextContentType.The system uses this flag to decide where to display passkey suggestions (11:47).
override func viewDidLoad() {
super.viewDidLoad()
//Additional setup…
userNameField.textContentType = .username
}
Key points:
viewDidLoad()It is the location where the view is initialized, suitable for setting the input box properties.super.viewDidLoad()Keep UIKit’s default initialization flow.userNameField.textContentType = .usernameTells the system that this is the username field, and that passkey and password suggestions will appear in this input location.
Use AutoFill to start passkey login
(11:59) Native App passedAuthenticationServicesinsideASAuthorizationPlatformPublicKeyCredentialProviderInitiate a passkey request.Apple recommends enabling AutoFill-assisted requests as early as possible so users can see suggestions when focusing on the username field (13:05).
// AutoFill-assisted passkey request
func signIn() {
let challenge: Data = … // Fetched from server
let provider =
ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier: "example.com")
let request =
provider.createCredentialAssertionRequest(
challenge: challenge)
let controller =
ASAuthorizationController(
authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
// Start the request
controller.performAutoFillAssistedRequests()
}
Key points:
challengeIt must be obtained from the server and corresponds to a single challenge in the WebAuthn login request.ASAuthorizationPlatformPublicKeyCredentialProviderIs the provider that handles passkey requests.relyingPartyIdentifier: "example.com"Points to the login domain to which passkey belongs.createCredentialAssertionRequest(challenge:)Create an assertion request for login.ASAuthorizationControllerResponsible for executing authorization requests.delegateReceive success or failure callbacks.presentationContextProviderTell the system which window the authorization interface should be hung on.performAutoFillAssistedRequests()Starts the AutoFill assist request; after the user selects the passkey in the QuickType bar, the system triggers Face ID or Touch ID.
After success, nothing will be filled in the username input box.The App will receive a unifiedASAuthorizationControllerDelegateCallback and get passkey assertion (13:29).
// Completing a passkey sign in
func authorizationController(controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization) {
guard let passkeyAssertion = authorization.credential as?
ASAuthorizationPlatformPublicKeyCredentialAssertion
else { … }
let signature = passkeyAssertion.signature
let clientDataJSON = passkeyAssertion.rawClientDataJSON
// Pass these values to your server, and complete the sign in
…
}
Key points:
didCompleteWithAuthorizationis allASAuthorizationUnified entrance after success.authorization.credentialNeed to be converted firstASAuthorizationPlatformPublicKeyCredentialAssertion。signatureIt is the signature generated by the device using the private key for the challenge.rawClientDataJSONIs client data required for WebAuthn authentication.- The App does not locally determine successful login, but passes these values to the server to complete WebAuthn verification.
After the user enters the username, switch to modal login.
(15:30) If the user does not click on the AutoFill suggestion but enters the username manually, Apple recommends canceling the AutoFill request and popping up the modal passkey login form.The code changes are very small, just replace the last step withperformRequests()。
// Modal passkey request
func signIn() {
let challenge: Data = … // Fetched from server
let provider =
ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier: "example.com")
let request =
provider.createCredentialAssertionRequest(
challenge: challenge)
let controller =
ASAuthorizationController(
authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
// Start the request
controller.performRequests()
}
Key points:
- The first half is still the same challenge, provider and assertion request.
performRequests()A modal form will be displayed listing available passkeys.- This form will also provide access to the nearby device passkey.
- The same delegate callback is still used after success, so the backend verification logic can be reused.
When there are multiple passkeys on the device, you can find the corresponding credential ID from the server based on the entered user name, and then set the allow list (20:55).
// Modal request with allow list
func signIn(userName: String) {
let challenge: Data = … // Fetched from server
let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier:"example.com")
let request = provider.createCredentialAssertionRequest(
challenge: challenge)
let credentialIDs: [Data] = … // Fetched from server for provided userName
request.allowedCredentials = credentialIDs.map(
ASAuthorizationPlatformPublicKeyCredentialDescriptor.init(credentialID:))
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
// Start the request
controller.performRequests()
}
Key points:
userNameIt is the account name that the user has entered.credentialIDsFrom the server; the WebAuthn server should be able to find the credential ID by user name.allowedCredentialsLimit the modal form to display only matching passkeys.ASAuthorizationPlatformPublicKeyCredentialDescriptor.init(credentialID:)Convert the original ID to the descriptor required for the request.- This mode is suitable for scenarios where the user has provided account context.
When there is no local passkey, decide whether to fall back silently
(22:56) The default modal request will display a QR code when there is no native passkey, allowing the user to log in with a nearby device.If you just want to test whether there are available credentials on this machine first, you can use.preferImmediatelyAvailableCredentials.When there are no matching credentials, the system silently falls back via an error callback.
// Modal passkey request, silent fallback
func signIn() {
let challenge: Data = … // Fetched from server
let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier:"example.com")
let request = provider.createCredentialAssertionRequest(
challenge: challenge)
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
// Start the request
controller.performRequests(options: .preferImmediatelyAvailableCredentials)
}
Key points:
performRequests(options:)Use the same passkey request and only change the display strategy..preferImmediatelyAvailableCredentialsAsk the system to prioritize credentials that are immediately available on the current device.- If there are no available credentials, the system will not directly display the cross-device QR code, but will enter an error callback.
- Apple reminder: Apps should still provide AutoFill requests or default modal requests elsewhere to make login entrances for nearby devices accessible (23:48).
// Handling a silent fallback
func authorizationController(controller: ASAuthorizationController,
didCompleteWithError error: Error) {
guard let error = error as? ASAuthorizationError else { … }
if error.code == .canceled {
// Either the user canceled the sheet, or there were no credentials available.
showSignInForm()
}
}
Key points:
didCompleteWithErrorIs the delegate callback when authorization fails or is cancelled.ASAuthorizationErrorProvide structured error codes..canceledThere are two sources: the user closes the form, or.preferImmediatelyAvailableCredentialsNative credentials not found.showSignInForm()Is the fallback path to display the traditional login form.
Web terminal uses conditional mediation to access AutoFill
(16:53) The web side uses the standard WebAuthn API.The first step is to declare both on the username input boxusernameandwebauthnAutofill token.
<input type="text" id="username-field" autocomplete="username webauthn" >
Key points:
autocomplete="username webauthn"Let the browser display password and passkey suggestions on the username field.- This mark corresponds to the
.usernametext content type。 - This is a prerequisite for AutoFill-assisted WebAuthn requests to appear in the correct location.
Next, check whether conditional mediation is available in JavaScript and usenavigator.credentials.getInitiate request (17:09).
// AutoFill-assisted WebAuthn request (JavaScript)
function signIn() {
if (!PublicKeyCredential.isConditionalMediationAvailable ||
!PublicKeyCredential.isConditionalMediationAvailable()) {
// Browser doesn't support AutoFill-assisted requests.
return;
}
const options = {
"publicKey": {
challenge: … // Fetched from server
},
mediation: "conditional"
};
navigator.credentials.get(options)
.then(assertion => {
// Pass the assertion to your server.
});
}
Key points:
PublicKeyCredential.isConditionalMediationAvailableIt is the standard feature detection entrance.- Return directly when detection fails to avoid initiating an error process on unsupported browsers.
challengeAlso from the server side.mediation: "conditional"Turn WebAuthn requests into AutoFill assisted mode.navigator.credentials.get(options)Return Promise.assertionIt needs to be sent back to the server for verification, and the browser does not save the login conclusion.
If you want to display a modal WebAuthn form, just remove mediation: "conditional" (18:14).
// Modal WebAuthn request (JavaScript)
function signIn() {
var options = {
"publicKey": {
challenge: … // Fetched from server
}
};
navigator.credentials.get(options)
.then(function (assertion) {
// Pass the assertion to your server.
});
}
Key points:
options.publicKey.challengeIt is still a WebAuthn challenge generated by the server.- No
mediationparameters, the browser displays a modal request. - Apple recommends that modal WebAuthn requests be triggered by user gestures, such as clicking a button (19:25).
- returned
assertionLike the AutoFill process, server-side validation is passed back.
Multiple credentials can be placed in the same authorization request
(24:40) Reality migration does not happen in one step.Users may have one or more credentials from passkey, password, Sign in with Apple.ASAuthorizationControllerMultiple credentials can be requested in the same modal form.
// Combined credential modal request
func signIn() {
let challenge: Data = … // Fetched from server
let passkeyProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier:"example.com")
let passkeyRequest = passkeyProvider.createCredentialAssertionRequest(
challenge: challenge)
let passwordRequest = ASAuthorizationPasswordProvider().createRequest()
let signInWithAppleRequest = ASAuthorizationAppleIDProvider().createRequest()
let controller = ASAuthorizationController(
authorizationRequests: [passkeyRequest, passwordRequest, signInWithAppleRequest])
controller.delegate = self
controller.presentationContextProvider = self
// Start the request
controller.performRequests()
}
Key points:
passkeyRequestHandle existing passkey login.ASAuthorizationPasswordProvider().createRequest()Requests saved password credentials.ASAuthorizationAppleIDProvider().createRequest()Request Sign in with Apple credentials.authorizationRequestsThe array delivers the three requests to the same controller.- The system form only displays credentials available on the current device.
The callback needs to be distributed to different processing logic according to the credential type (25:02).
// Completing a combined credential request
func authorizationController(controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization) {
switch authorization.credential {
case let passkeyAssertion as ASAuthorizationPlatformPublicKeyCredentialAssertion:
finishSignIn(with: passkeyAssertion)
case let signInWithAppleCredential as ASAuthorizationAppleIDCredential:
finishSignIn(with: signInWithAppleCredential)
case let passwordCredential as ASPasswordCredential:
finishSignIn(with: passwordCredential)
default:
// Handle other credential types
break
}
}
Key points:
switch authorization.credentialUse the runtime type to determine which credentials the user selected.ASAuthorizationPlatformPublicKeyCredentialAssertionUse passkey verification.ASAuthorizationAppleIDCredentialGo to Sign in with Apple.ASPasswordCredentialLog in with password.defaultReserve entry for future or other credential type processing.
Cross-device login is completed by the system
(28:33) Passkey supports logging in on devices without native credentials.The client displays the QR code, the device holding the passkey scans the QR code, and then the two devices complete local key negotiation and prove physical proximity via Bluetooth.
This process has several security boundaries: a pair of one-time encryption keys is encoded in the QR code; the mobile phone broadcast contains relay server routing information; after the local exchange is completed, the network communication is end-to-end encrypted and the relay server cannot read the content; the website does not participate in cross-device communication (29:49).Therefore, remotely sending the QR code to the victim or generating the QR code on a fake website cannot complete Bluetooth proximity proof.
Core Takeaways
-
What to do: Add passkey AutoFill to existing login page. Why it’s worth doing: Session clearly recommends putting passkey into the username field instead of creating a new login interface. How to get started: Configuration
webcredentialsAssociated domain name, set for user name input box.username,start upperformAutoFillAssistedRequests()。 -
What to do: After the user enters the username, only the passkey of the corresponding account is displayed. Why it’s worth doing: Multiple accounts may be saved on the device; the allow list can reduce the chance of selecting the wrong account. How to start: The server returns the credential ID according to the user name, and the client maps them to
ASAuthorizationPlatformPublicKeyCredentialDescriptorand set torequest.allowedCredentials。 -
What: When the app starts, it tries native credentials before displaying the traditional login form. Why it’s worth doing: Users with local credentials can enter the system form directly; users without credentials will not see the QR code first. How to start: Call
performRequests(options: .preferImmediatelyAvailableCredentials),exist.canceledDisplay the login form in the callback. -
What to do: Put passkey, password and Sign in with Apple into the same modal form. Why it’s worth doing: The credential types of users during the migration period are inconsistent, and unified requests can reduce entry fragmentation. How to get started: Create
passkeyRequest、passwordRequest、signInWithAppleRequest, pass togetherASAuthorizationController。 -
What to do: Add conditional mediation for website login. Why it’s worth doing: The web client can also display passkey suggestions in the username field, so users don’t have to choose a complicated login method first. How to start: Set the input box
autocomplete="username webauthn", detectPublicKeyCredential.isConditionalMediationAvailable(), and then call the bandmediation: "conditional"ofnavigator.credentials.get()。
Related Sessions
- Enhance your Sign in with Apple experience — Also based on
ASAuthorization, talking about account deduplication, credential query and Sign in with Apple field processing. - Replace CAPTCHAs with Private Access Tokens — Discuss replacing traditional web security challenges with device and account trust capabilities.
- Improve DNS security for apps and servers — Reduce phishing and hijacking risks from the network layer, suitable for consideration together with passkey login.
- Discover Managed Device Attestation — Talk about how the server verifies the identity of the managed device, which belongs to the same access control infrastructure as credential login.
Comments
GitHub Issues · utterances