Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Verify Passkeys Overview


(information)

Info

This Verify feature is currently in Pilot, which means that we're actively looking for early adopters to try it out and give feedback. Contact Twilio Sales(link takes you to an external page) to try it out for free!


What are Passkeys

what-are-passkeys page anchor

Passkeys, also known as FIDO/WebAuthn, is an industry-standard(link takes you to an external page) authentication method that is more seamless and secure than passwords. Many consumer apps are adding support for Passkeys, including Google making Passkeys its default sign-in option.(link takes you to an external page)

2023-11-21 at 10.49.36 PM.

Twilio Verify's support for Passkeys

twilio-verifys-support-for-passkeys page anchor

Verify enables developers to easily add Passkeys into their existing authentication flows. The Verify API supports passkey registration, public key storage, and auth flows.

Verify Passkeys also offers client-side supported SDKs for iOS and Android that helps you verify users by adding a low-friction, secure, cost-effective, device approval factor into your own mobile application. Get early access to the SDKs.

Twilio is a member of the FIDO alliance(link takes you to an external page) that created the Passkeys standard.


Resource definitions

resource-definitions page anchor
  • Service : an organization or environment (e.g. stage, prod). Contains configurations for all verification methods available through the Verify platform (SMS OTP, Voice OTP, Email OTP, Passkeys, Push Verification, TOTP). A Twilio [sub]account can have multiple Services. Each Service contains multiple Entities that are not shared across Services.
  • Entity : a user or other identity that needs verification. An Entity can contain multiple Factors.
  • Factor : a verification method, which involves an exchange of secrets via a communication channel. For factor_type: passkeys , which follows the AES256(link takes you to an external page) algorithm, the Factor contains the (Relying Party) that is used to generate the Passkey. A Factor contains multiple Challenges.

The system follows a client-server architecture where the client generates or receives Passkeys and communicates with the server for verification.

Verify Passkeys involves two main sequences that are shown in the diagrams below:

Passkeys creation and registration: Register (sign up) a user by creating a unique passkey.

Passkeys Authentication: Verify (Sign In) the user by authenticating the registered passkey.

register-user-public-docs-sequence-diagram-Verify_Passkeys_Sequence_Diagram.
verify-user-public-docs-sequence-diagram-Verify_Passkeys_Sequence_Diagram.

Quickstart guide: Verify Passkeys API

quickstart-guide-verify-passkeys-api page anchor

This quick tutorial will cover how to register and authenticate with Passkeys. There are two main components for working with Passkeys:

Browser (Client) APIs. These are standardized and available in modern browsers.

Server APIs. Twilio Verify is providing the server side passkey storage and validation.

Register Passkey

register-passkey page anchor

The steps to register a passkey are:

[Server - Twilio] Create a passkey factor

[Client - Browser] Register a passkey with the factor details

[Server - Twilio] Verify passkey registration

1. Start Passkey registration

1-start-passkey-registration page anchor

Initiate the process of creating a new Passkey registration.


_10
POST https:https://www.comms.twilio.com/preview/Factors

NameTypeDescription
friendly_nameStringA human-readable description of the Factor. Maximum length is 255 characters. (🏢 not PII )
toObjectThe end user that is enrolling the factor. (🏢 PII )
to.user_identifierObjectA custom string that uniquely identifies a person or end-user. For example, this can be the end-user's username or a UUID4 string or an SHA256 hash. (🏢 PII )
contentObjectThe configuration options of the Factor according to the Factor type.
content.relying_party.id (required)ObjectThe relying party identifier. This should generally be the origin without a scheme and port.
content.relying_party.nameObjectThe relying party name that the authenticator will show during the registration/authentication process.
content.relying_party.originsObjectList of Relying Party Server Origins or App IDs that are permitted.
content.authenticator_criteria.authenticatior_attachmentObjectDefault: "any" Enum: "platform" "cross-platform" "any" A flag indicating a requirement to attach only to a certain type of authenticator.
content.authenticator_criteria.discoverable_credentialsObjectDefault: "preferred" Enum: "required" "preferred" "discouraged" A flag indicating the level of preference for discoverable credentials.
content.authenticator_criteria.user_verificationObjectDefault: "preferred" Enum: "required" "preferred" "discouraged" Whether user identity verification (via biometrics or PIN) is required.

_22
curl --location 'https://comms.twilio.com/preview/Factors' \
_22
--header 'Content-Type: application/json' \
_22
--header 'Authorization: Basic ***' \
_22
--data '{
_22
"friendly_name": "ACME CORP",
_22
_22
"to": {
_22
"user_identifier": "passkeyuser001"
_22
},
_22
"content": {
_22
"relying_party": {
_22
"id": "acme.com",
_22
"name": "ACME",
_22
"origins": []
_22
},
_22
"authenticator_criteria": {
_22
"authenticator_attachment": "platform",
_22
"discoverable_credentials": "preferred",
_22
"user_verification": "preferred"
_22
}
_22
}
_22
}


_59
{
_59
"contact_id": "comms_contact_751yj9086a8ddjer531ancqdnr",
_59
"content": {
_59
"authenticator_criteria": {
_59
"authenticator_attachment": "platform",
_59
"discoverable_credentials": "preferred",
_59
"user_verification": "preferred"
_59
},
_59
"credential": {
_59
"authenticator_metadata": null,
_59
"credential_id": null,
_59
"credential_public_key": null,
_59
"flags": [],
_59
"transports": []
_59
},
_59
"relying_party": {
_59
"id": "acme.com",
_59
"name": "ACME",
_59
"origins": []
_59
}
_59
},
_59
"created_at": "2024-05-24T09:23:20.385660582Z",
_59
"deleted_at": null,
_59
"friendly_name": "ACME CORP",
_59
"id": "comms_factor_04x93pexgrjmjn1zsx6f9r2d1t",
_59
"next_step": {
_59
"attestation": "none",
_59
"authenticatorSelection": {
_59
"authenticatorAttachment": "platform",
_59
"requireResidentKey": false,
_59
"residentKey": "preferred",
_59
"userVerification": "preferred"
_59
},
_59
"challenge": "WUYwNGVhNDc2Nzc2MTg5NTI1NTBmZjNkMzNkMzgxMzQzYQ",
_59
"excludeCredentials": [],
_59
"pubKeyCredParams": [
_59
{
_59
"alg": -7,
_59
"type": "public-key"
_59
}
_59
],
_59
"rp": {
_59
"id": "acme.com",
_59
"name": "ACME"
_59
},
_59
"timeout": 600000,
_59
"user": {
_59
"displayName": null,
_59
"id": "WUVlNTBmYTQ5MDIwY2E0MzViMjc2MGEzMGFhYWNiYjZiOA",
_59
"name": "new-user-factor"
_59
}
_59
},
_59
"related": [],
_59
"status": "pending",
_59
"tags": {},
_59
"type": "passkey",
_59
"updated_at": "2024-05-24T09:23:20.385660582Z",
_59
"user_identifier": "passkeyuser001"
_59
}

2. Create Passkey from the browser

2--create-passkey-from-the-browser page anchor

Use the next_step response to create a passkey in the browser with navigator.credentials.create()(link takes you to an external page). The navigator.credentials.create method requires certain parameters to be encoded.

We recommend using the create() wrapper provided by the webauthn-json library(link takes you to an external page) for easier encoding and decoding. The capability provided by this library will eventually be browser native. Open the developer console for your browser while on a twilio.com page (domain matching is part of the Passkeys spec) and copy the following code:


_10
const { create, get, parseCreationOptionsFromJSON, parseRequestOptionsFromJSON, supported } = await
_10
import('https://cdn.jsdelivr.net/npm/@github/webauthn-json/dist/esm/webauthn-json.browser-ponyfill.js')

This loads the webauthn-json library into your browser console. Assign the JSON response from step 1 to a variable:


_10
let { next_step: publicKey } = <paste JSON response from step 1>

Next, copy the following code to the browser console create a passkey credential:


_10
// converts challenge and user.id to ArrayBuffers
_10
let creationOptions = parseCreationOptionsFromJSON({publicKey});
_10
// wrapper for navigator.credentials.create
_10
let credential = await create(creationOptions);

Follow the prompts to register a passkey with the password manager of your choice then copy the credential to use in the next request. In the browser console run the following to copy the JSON request body to your clipboard:


_10
copy({ "content" : credential });

3. Approve Passkey Registration

3-approve-passkey-registration page anchor

Complete the Passkey registration by passing the response from the navigator.credentials.create()(link takes you to an external page) request to below endpoint with the content parameter.


_10
POST https://www.comms.twilio.com/preview/Factors/Approve

NameTypeDescription
factor_idStringA reference to a Factor. (🏢 not PII )
content (required)objectThe payload required to approve a Factor
content.typeStringValue: "public-key" The valid credential types supported by the API. The values of this enumeration are used for versioning the AuthenticatorAssertion and AuthenticatorAttestation structures according to the type of the authenticator.
content.idStringA base64url encoded representation of rawId.
content.rawIdStringThe globally unique identifier for this PublicKeyCredential.
content.authenticatorAttachmentStringEnum: "platform" "cross-platform" A string that indicates the mechanism by which the WebAuthn implementation is attached to the authenticator at the time the associated navigator.credentials.create() or navigator.credentials.get() call completes.
content.responseobjectThe result of a WebAuthn credential registration via navigator.credentials.create(), as specified in AuthenticatorAttestationResponse.
content.response.clientDataJSON (required)StringThis property contains the JSON-compatible serialization of the data passed from the browser to the authenticator in order to generate this credential.
content.response.attestationObject (required)StringThe authenticator data and an attestation statement for a new key pair generated by the authenticator.
content.response.transportsStringItems Enum: "usb" "nfc" "ble" "smart-card" "internal" "hybrid" An array of strings providing hints as to the methods the client could use to communicate with the relevant authenticator of the public key credential to retrieve.

_19
curl --location 'https://comms.twilio.com/preview/Factors/Approve' \
_19
--header 'Content-Type: application/json' \
_19
--header 'Authorization: Basic ***' \
_19
--data '{
_19
"factor_id": "comms_factor_04nwt37vgrb7j21yn7g55z98qw",
_19
"content": {
_19
"type": "public-key",
_19
"id": "4-O54pnhw12mMAz8rvDcZ3pvEWwEZzSluVVK-cHjbXs",
_19
"rawId": "4-O54pnhw12mMAz8rvDcZ3pvEWwEZzSluVVK-cHjbXs",
_19
"authenticatorAttachment": "platform",
_19
"response": {
_19
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiV1VZd05HRm1NelF6TTJWbE1UZzFPV1UwTWpCbVlXRTNPREUwWW1ZMFlUSm1ZdyIsIm9yaWdpbiI6Imh0dHBzOi8vNTdmOTJhZGI1YzAzLm5ncm9rLmFwcCIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
_19
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikw7izEZUWvBs_gwvj5FDoWndf0jzEJpSCztwEIi9HgONFAAAAAK3OAAI1vMYKZIsLJfHwVQMAIOPjueKZ4cNdpjAM_K7w3Gd6bxFsBGc0pblVSvnB4217pQECAyYgASFYIP_2j2eHQVMka1OtAibT6LtNJPbRmTMX0bOXocijGFWOIlggCOnyYGFzN-yCLwTn9se3xreIBHRv6HipD4QKs4N9MkY",
_19
"transports": [
_19
"internal"
_19
]
_19
}
_19
}
_19
}'


_72
{
_72
"contact_id": "comms_contact_5hq9cznnc8dnvrrhz1m1pbfmv5",
_72
"content": {
_72
"authenticator_criteria": {
_72
"authenticator_attachment": "platform",
_72
"discoverable_credentials": "preferred",
_72
"user_verification": "preferred"
_72
},
_72
"credential": {
_72
"authenticator_metadata": {
_72
"AAGUID": "adce0002-35bc-c60a-648b-0b25f1f05503",
_72
"authenticator_attachment": "platform",
_72
"clone_warning": false,
_72
"sign_count": 0
_72
},
_72
"credential_id": "4-O54pnhw12mMAz8rvDcZ3pvEWwEZzSluVVK-cHjbXs",
_72
"credential_public_key": "pQECAyYgASFYIP_2j2eHQVMka1OtAibT6LtNJPbRmTMX0bOXocijGFWOIlggCOnyYGFzN-yCLwTn9se3xreIBHRv6HipD4QKs4N9MkY",
_72
"flags": [
_72
"user-present",
_72
"user-verified",
_72
"attested-credential-data"
_72
],
_72
"transports": [
_72
"internal"
_72
]
_72
},
_72
"relying_party": {
_72
"id": "57f92adb5c03.ngrok.app",
_72
"name": "ACME",
_72
"origins": [
_72
"https://acme.com"
_72
]
_72
}
_72
},
_72
"created_at": "2024-05-24T09:43:42.281167662Z",
_72
"deleted_at": null,
_72
"friendly_name": "ACME",
_72
"id": "comms_factor_04nwt37vgrb7j21yn7g55z98qw",
_72
"next_step": {
_72
"attestation": "none",
_72
"authenticatorSelection": {
_72
"authenticatorAttachment": "platform",
_72
"requireResidentKey": false,
_72
"residentKey": "preferred",
_72
"userVerification": "preferred"
_72
},
_72
"challenge": "WUYwNGFmMzQzM2VlMTg1OWU0MjBmYWE3ODE0YmY0YTJmYw",
_72
"excludeCredentials": [],
_72
"pubKeyCredParams": [
_72
{
_72
"alg": -7,
_72
"type": "public-key"
_72
}
_72
],
_72
"rp": {
_72
"id": "acme.com",
_72
"name": "ACME Corporation"
_72
},
_72
"timeout": 600000,
_72
"user": {
_72
"displayName": "ACME",
_72
"id": "WUViMWJhNTlmYWQ1ODg2ZDc3OGM0N2UxYTA2Y2I3ZDM2NQ",
_72
"name": "ACME"
_72
}
_72
},
_72
"related": [],
_72
"status": "approved",
_72
"tags": {},
_72
"type": "passkey",
_72
"updated_at": "2024-05-24T09:43:42.281167662Z",
_72
"user_identifier": "passkeysuser001"
_72
}

Authenticate with Passkey

authenticate-with-passkey page anchor

The steps to authenticate with a Passkey are:

[Server - Twilio] Create an authentication challenge.

[Client - Browser] Fetch the passkey with the challenge data. The browser will sign the challenge with the passkey.

[Server - Twilio] Validate the signature and approve the authentication challenge.

1. Create a passkey authentication challenge

1-create-a-passkey-authentication-challenge page anchor

_10
POST https://www.comms.twilio.com/preview/Verifications

NameTypeDescription
to (required)objectThe user to Verify. (🏢 PII )
to.user_identifierStringA custom string that uniquely identifies a person or end-user. For example, this can be the end-user's username or a UUID4 string or an SHA256 hash. (🏢 PII )
content (required)objectThe content of the Verification communication.
content.rp_idStringThe relying party identifier.
content.user_verificationStringEnum: "required" "preferred" "discouraged" A string that specifies the extent to which the relying party desires to authenticate the user to the client, and the extent to which the client should request that the user be authenticated.

_12
curl --location 'https://comms.dev.twilio.com/preview/Verifications' \
_12
--header 'Content-Type: application/json' \
_12
--header 'Authorization: Basic ***'
_12
--data '{
_12
"to": {
_12
"factor_id": "comms_factor_04j1end14yz8m3XXXXX"
_12
},
_12
"content": {
_12
"rp_id": "acme.com",
_12
"user_verification": "preferred"
_12
},
_12
}'


_35
{
_35
"created_at": "2024-07-15T09:46:10.217023412Z",
_35
"deleted_at": null,
_35
"id": "comms_verification_04zdtzzx9tj1gXXXXXX",
_35
"next_step": {
_35
"publicKey": {
_35
"allowCredentials": [
_35
{
_35
"id": "wmDTDMVE6qzrTlM8sG7vgTfDPZ2lnT0AURGXXXXX",
_35
"transports": [],
_35
"type": "public-key"
_35
}
_35
],
_35
"challenge": "WUMwNGZiNzVmZmY1M2E5MDYxN2MyOWIyMmJkNzQ0YzU0Nw",
_35
"extensions": {},
_35
"rpId": "acme.com",
_35
"timeout": 300000,
_35
"userVerification": "preferred"
_35
}
_35
},
_35
"related": [],
_35
"status": "pending",
_35
"tags": {},
_35
"to": {
_35
"address": null,
_35
"address_extension": null,
_35
"channel": "passkey",
_35
"contact_id": "comms_contact_01j0k8sf0se8n9dpjxXXXX",
_35
"device_ip": null,
_35
"factor_id": "comms_factor_04j1end14yz8m3jse90XXXX",
_35
"otp_type": null,
_35
"user_identifier": "testuser001"
_35
},
_35
"updated_at": "2024-07-15T09:46:10.217023412Z"
_35
}

2. Fetch the passkey in the browser

2-fetch-the-passkey-in-the-browser page anchor

Use the next_step response to get the passkey in the browser with navigator.credentials.get(). Like registration, we recommend using the get() wrapper provided by the webauthn-json library for easier encoding and decoding. In the developer console, paste the following code to assign the JSON response from step 1 to a variable.


_10
let { next_step: { publicKey } } = <paste JSON response from step 1>

Next, add the following code to the browser console to get the passkey credential:


_10
// converts challenge and user.id to ArrayBuffers
_10
let requestOptions = parseRequestOptionsFromJSON({publicKey});
_10
// wrapper for navigator.credentials.create
_10
let credential = await get(requestOptions);

Follow the prompts to register a passkey with the password manager of your choice then copy the credential to use in the next request. In the browser console run the following to copy the JSON request body to your clipboard:


_10
copy({ "content" : credential });

3. Validate the passkey

3-validate-the-passkey page anchor

Complete the authentication of a Passkey registration by passing the response from the get() request to the passkey verification check endpoint with the content parameter.


_10
POST https://www.comms.twilio.com/preview/Verifications/Check

NameTypeDescription
content (required)objectThe set of properties to check against the Verification, according to the Verification channel or factor.
content.idStringA base64url encoded representation of rawId.
content.rawIdStringThe globally unique identifier for this PublicKeyCredential.
content.authenticatorAttachmentString (Optional)Enum: "platform" "cross-platform" A string that indicates the mechanism by which the WebAuthn implementation is attached to the authenticator at the time the associated navigator.credentials.create() or navigator.credentials.get() call completes.
content.typeStringValue: "public-key" The valid credential types supported by the API. The values of this enumeration are used for versioning the AuthenticatorAssertion and AuthenticatorAttestation structures according to the type of the authenticator.
content.response (required)objectThe result of a WebAuthn authentication via a navigator.credentials.get() request, as specified in AuthenticatorAttestationResponse.
content.response.authenticatorDataStringA CBOR-encoded string, in CTAP2 canonical CBOR encoding form.
content.response.clientDataJSONStringThis property contains the JSON-compatible serialization of the data passed from the browser to the authenticator in order to generate this credential.
content.response.signatureStringAn assertion signature over authenticatorData and clientDataJSON. The assertion signature is created with the private key of the key pair that was created during the originating navigator.credentials.create() call and verified using the public key of that same key pair.
content.response.userHandleStringThe user handle stored in the authenticator, specified as user.id in the options passed to the originating navigator.credentials.create() call. This property should contain a base64url-encoded contact ID.

_19
curl --location 'https://comms.dev.twilio.com/preview/Verifications/Check' \
_19
--header 'Content-Type: application/json' \
_19
--header 'Authorization: Basic ***'
_19
--data '{
_19
"verification_id": "comms_verification_04zdtzzx9tj1gqradj5fbm9ha7",
_19
"content": {
_19
"id": "wmDTDMVE6qzrTlM8sG7vgTfDPZ2lnT0AURGlAHG9B7k",
_19
"rawId": "wmDTDMVE6qzrTlM8sG7vgTfDPZ2lnT0AURGlAHG9B7k",
_19
"authenticatorAttachment": "platform",
_19
"type": "public-key",
_19
"clientExtensionResults": {},
_19
"response": {
_19
"authenticatorData": "PsiNdKDyTjYfuluswFpAJmUc71Ul9PPRGf7ZtyMXERIFAAAAAg",
_19
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiV1VNd05HWmlOelZtWm1ZMU0yRTVNRFl4TjJNeU9XSXlNbUprTnpRMFl6VTBOdyIsIm9yaWdpbiI6Imh0dHBzOi8vdmlydHVhbC1hdXRoZW50aWNhdG9yLWUyZS10ZXN0LnR3aWxpby5jb20iLCJjcm9zc09yaWdpbiI6ZmFsc2V9",
_19
"signature": "MEUCIF3dZrka0ahPSylH_tRif9L0pZh_jBJlpzSE5T0feGevAiEA-q5KSFAt-5YTdXQP4PbrnRu4a7ux6v55go-wtK94P0E",
_19
"userHandle": "WUU2MDg1MzU1MzVkYzA1NDMwMTY2MDljNDExODAwMWQyYg"
_19
}
_19
}
_19
}'


_35
{
_35
"created_at": "2024-07-15T09:46:10.217023412Z",
_35
"deleted_at": null,
_35
"id": "comms_verification_04zdtzzx9tj1gqradXXXX",
_35
"next_step": {
_35
"publicKey": {
_35
"allowCredentials": [
_35
{
_35
"id": "wmDTDMVE6qzrTlM8sG7vgTfDPZ2lnT0AURGlAHG9B7k",
_35
"transports": [],
_35
"type": "public-key"
_35
}
_35
],
_35
"challenge": "WUMwNGZiNzVmZmY1M2E5MDYxN2MyOWIyMmJkNzQ0YzU0Nw",
_35
"extensions": {},
_35
"rpId": "acme.com",
_35
"timeout": 300000,
_35
"userVerification": "preferred"
_35
}
_35
},
_35
"related": [],
_35
"status": "approved",
_35
"tags": {},
_35
"to": {
_35
"address": null,
_35
"address_extension": null,
_35
"channel": "passkey",
_35
"contact_id": "comms_contact_01j0k8sf0se8n9dpjx9db3gjrd",
_35
"device_ip": null,
_35
"factor_id": "comms_factor_04j1end14yz8m3jse90zhcd4vn",
_35
"otp_type": null,
_35
"user_identifier": "testuser"
_35
},
_35
"updated_at": "2024-07-15T09:46:10.445005034Z"
_35
}


Rate this page: