-7
. See this list for all available options.platform
for iOS and Android. Note that browser / OS may choose to ignore this.
$user = new User($userId)
$challenge = array_slice(shuffle(range(1, 255)),0,64); // 64 random ints between 1 and 255
$user->saveChallenge($challenge);
return [
"challenge" => $challenge,
"rp" => [
"name" => "Your website",
"id" => "example.com",
],
"user" => [
"id" => $user->getUuid(),
"name" => $user->getName(),
"displayName" => $user->getDisplayName(),
],
"pubKeyCredParams" => [
["alg" => -7, "type" => "public-key"],
["alg" => -257, "type" => "public-key"],
],
"authenticatorSelection" => [
"authenticatorAttachment" => "platform"
],
"timeout" => 30000,
"attestation" => "direct"
];
Uint8Array
values
var options = api.request('/get-attestation-options?userId=789');
// you will need to convert the challenge from a string to a byte array
options.challenge = Uint8Array.from(options.challenge);
options.user.id = Uint8Array.from(options.user.id, c => c.charCodeAt(0));
const credential = await navigator.credentials.create({
publicKey: options
})
// base64Url encode the credential before sending
credential = window.prepareCredentialForApi(credential);
api.request('/save-credential?userId=789', credential);
$user = new User($userId);
$credential = $requestBody['credential'];
if ($credential->challenge == $user->getChallenge()) {
// you now need to be able to parse out values from the credential
// you will need to use libraries to decode CBOR and handle COSE keys
// before proceeding you also need to carry out other checks such as:
// credential type is webauthn.create
// the relying party ID in the credential matches the expected domain
// the user verified to create the credential
// the credential ID does not already exist in your database
$user->clearChallenge();
$user->addCredential($credential);
return ['success'];
}
internal
transport for in-built device keys e.g. iOS and Android
$user = new User($userId);
$challenge = array_slice(shuffle(range(1, 255)),0,64); // 64 random ints between 1 and 255
$user->setChallenge($challenge);
$allowCredentials = [];
foreach($user->getCredentials() as $credential) {
$allowCredentials[] = [
"id" => $credential->getId(),
"type" => "public-key",
"transports" => ["internal"]
]
}
return [
"challenge" => $challenge,
"allowCredentials" => $allowCredentials,
"timeout" => 60000,
]
var options = api.request('/get-assertion-options?userId=789');
// you will need to convert these values from a string to a byte array
options.challenge = Uint8Array.from(options.challenge);
options.allowCredentials.forEach(element => element.id = new Uint8Array(window.base64url.decode(element.id)));
const credential = await navigator.credentials.get({
publicKey: options
});
// base64Url encode the credential before sending
credential = window.prepareCredentialForApi(credential);
api.request('/verify-credential?userId=789', credential);
$user = new User($userID)
$challengeMatches = ($user->getChallenge() == $credential->challenge);
// you need to again be able to parse out values from the credential
// you will need to use libraries to decode CBOR and handle COSE keys
$dataToVerify = // parse this out from the data in the credential
$signature = // parse this out from the data in the credential
$keyMatches = openssl_verify($dataToVerify, $signature, $user->getCredential($credential->id));
if($challengeMatches && $keyMatches) {
$user->clearChallenge();
return ['success'];
}
prepareCredentialForApi: async function(credential) {
const publicKeyCredential = {
id: credential.id,
type: credential.type,
rawId: window.base64url.encode(new Uint8Array(credential.rawId)),
response: {
clientDataJSON: window.base64url.encode(
new Uint8Array(credential.response.clientDataJSON)
),
},
};
if (credential.response.attestationObject !== undefined) {
publicKeyCredential.response.attestationObject = window.base64url.encode(
new Uint8Array(credential.response.attestationObject)
);
}
if (credential.response.authenticatorData !== undefined) {
publicKeyCredential.response.authenticatorData = window.base64url.encode(
new Uint8Array(credential.response.authenticatorData)
);
}
if (credential.response.signature !== undefined) {
publicKeyCredential.response.signature = window.base64url.encode(
new Uint8Array(credential.response.signature)
);
}
if (credential.response.userHandle !== undefined) {
publicKeyCredential.response.userHandle = window.base64url.encode(
new Uint8Array(credential.response.userHandle)
);
}
return publicKeyCredential;
}
Web Authentication is widely supported on modern Android and iOS devices. See Can I use - Webauthn, Can I use - Credenial Management and MDN Browser compatibility for more details on browser support.
Important notes:
authenticatorAttachments
were supported - meaning external security keys. Platform authenticators - meaning use of the iPhone device itself as an authenticator - was only supported from iOS 14.If you are interested in trying out Web Authentication, the information and code samples on this site and others should give you plenty of direction to get started with. However, there is no doubt it is a complex process - in order to process the credentials your front-end will generate, you need complex server-side code to parse and verify them. However, there are tools available to help with this!
Presence is a cloud tool that is offers a ready-made Web Authentication sandbox for use in your applications and prototypes. With this tool you will receive an API key which you can use to submit and verify credentials in minutes.
Learn more about Presence - including a free trial without requiring card details.