The examples here call functions defined by the @github/webauthn-json/browser-ponyfill package (available at https://ga.jspm.io/npm:@github/webauthn-json@2.1.0/dist/esm/webauthn-json.browser-ponyfill.js).
Registration endpoints are for signed-in users. Provide the user's JWT as usual.
To start, you need to initiate a registration challenge by making a POST to /users/passkeys/create_challenge. The response will be similar to:
{
"challenge": "Ab0eFvIq_yqxsAqxtUABy0iRLoWJVVXrdQjyzzVeKOQ",
"timeout": 120000,
"extensions":
{},
"rp":
{
"name": "Displet API"
},
"user":
{
"name": "shantell3@effertz-purdy.com",
"id": "025bf1fd-180b-42be-99f1-9bb1c782bf1d",
"displayName": "shantell3@effertz-purdy.com"
},
"pubKeyCredParams":
[
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -37
},
{
"type": "public-key",
"alg": -257
}
],
"authenticatorSelection":
{
"residentKey": "required",
"userVerification": "required"
},
"excludeCredentials":
[]
}You'll then feed the JSON from the API request into two package-defined functions, parseCreationOptionsFromJSON and create:
let challengeFetch = fetch('https://api-v3.displet.com/users/passkeys/create_challenge', {
method: "POST",
headers: {
"Accept": "application/json"
}
})
const challengeJSON = await(await challengeFetch).json()
const credentialCreationOptions = parseCreationOptionsFromJSON({publicKey: challengeJSON})
const credentialCreationResponse = await create(credentialCreationOptions)Finally, you'll submit a request to create the passkey with a POST to /users/passkeys, passing the result of JSON.stringify(credentialCreationResponse) from the example as the credential param. You can additionally pass an optional label with a friendly name that will be returned in the serialized response.
Authentication endpoints do not require a JWT as these will be used by users who aren't yet signed-in.
To start, you need to initiate an authentication challenge by making a POST to /users/passkeys/auth_challenge. The response will be similar to:
{
"challenge": "j4DJ3jtHeGcZdzAeAYtmN6gBe007c1CN1JwZ1vZUq_o",
"timeout": 120000,
"extensions":
{},
"allowCredentials":
[],
"userVerification": "required"
}You'll then feed the JSON from the API request into two package-defined functions, parseRequestOptionsFromJSON and get:
let challengeFetch = fetch('https://api-v3.displet.com/users/passkeys/auth_challenge', {
method: "POST",
headers: {
"Accept": "application/json"
}
})
const challengeJSON = await(await challengeFetch).json()
const credentialAuthenticationOptions = parseRequestOptionsFromJSON({publicKey: challengeJSON})
const credentialAuthenticationResponse = await get(credentialAuthenticationOptions)Finally, you'll submit a request to authenticate the passkey with a POST to /users/passkeys/auth, passing the result of JSON.stringify(credentialAuthenticationResponse) from the example as the credential param.
It's possible to begin the authentication flow at any point - that could be following a button click, partial form completion, or on initial page load as seen at https://admin-v3.displet.com/admins/login. The Displet admin UI applies "conditional mediation" to determine if passkey authentication is an option:
class ConditionalMediationNotSupportedError extends Error {
constructor(message) {
super(message)
this.name = "ConditionalMediationNotSupportedError"
}
}
let conditionalMediationAvailable = async function(){
if (
typeof window.PublicKeyCredential !== 'undefined'
&& typeof window.PublicKeyCredential.isConditionalMediationAvailable === 'function'
) {
return await PublicKeyCredential.isConditionalMediationAvailable()
} else {
return Promise.reject(new ConditionalMediationNotSupportedError('Browser does not support Conditional Mediation'))
}
}
let startConditionalMediation = async function(form){
const available = await conditionalMediationAvailable()
if(!available){ return }
// Starts passkey authentication flow
getChallengeAndSubmitCredential(form)
}
To list passkeys:
GET /users/passkeys
To delete a passkey:
DELETE /users/passkeys/:id