Skip to main content

Authenticate with a credential

Now that we have registered a credential we can use it to authenticate with the Hanko API. The authentication operation is very similar to the registration operation - it's just three simple steps.

The Authentication ceremony#

The authentication ceremony basically corresponds to logging in a user using a previously-registered credential.

  • When a user requests authentication with an existing credential, your relying party application requests generation of credential request options, including a challenge to which the user must respond using a cryptographically signed statement identifying the user.
  • The user provides consent through a local authorization gesture (e.g. through a PIN or biometrics) to use an existing credential's private key to create and cryptographically sign an assertion.
  • The authenticator assertion response is sent to the Hanko Authentication API.
  • The Hanko Authentication API validates the assertion using the stored credential public key.

Step 1: Initialize Authentication at Hanko API#

Just like on registration, the first step to authenticating with a credential is to initialize the authentication ceremony. Initialization consists of retrieving credential request options from the Hanko API in order to trigger the discovery and use of an existing credential on an authenticator for generating an assertion.

Frontend - Trigger authentication initialization#

Authentication is usually triggered following the submission of a login form which provides the username of the user on whose behalf authentication should be performed. In your frontend, initialize an authentication by making a request to your application backend with the username (e.g. as a query parameter). To process the request you will need to implement a handler. In this case, we assume your backend handler responds to requests issued to the /webauthn/authentication/initialize endpoint:

async function initializeAuthentication(userName) {
const response = await fetch(`/webauthn/authentication/initialize?user_name=${userName}`);
if (!response.ok) {
// handle fetch error
}
return response.json();
}

Backend - Retrieve credential request options#

Your backend handler must then issue a POST request to the authentication initialization endpoint of the Hanko API (/v1/webauthn/authentication/initialize - see also the API reference). Unlike in the case of registration, the only user data you need to provide is the user id.

note

When studying the authentication initialization endpoint description in the API reference you might have noticed that the user is not required for the operation. In this case, provision of a user is necessary because the credential registered in the previous guide was not registered as a resident key (learn more about resident keys here).

Example authentication initialization request body - /v1/webauthn/authentication/initialize
{
"user": {
"id": "e3be22a7-13cf-4235-a09c-380dfd44ac04"
}
}
note

Just like on registration initialization, you can specify additional options for the authentication operation. For simplicity, this guide will not make use of these options. In a more advanced implementation relying parties can

On successful initialization the Hanko API will respond with the generated credentialRequestOptions. You will use these in the next step to generate the assertion through the Web Authentication API.

Example JSON response: credential request options
{
"publicKey": {
"challenge": "k3jADDKPpeXRf45G_nZ_S2rXnr6eLcVudPlSf4Cql7U",
"timeout": 10000,
"rpId": "hanko.io",
"allowCredentials": [
{
"type": "public-key",
"id": "4mcZyHLLCDNWdExkadB7JCGCtFWAZKFC4dRovdbqdLg0z52amUdfP2HGkNdMgulTUEfBqACPAcRk6QVVCTQncw"
}
],
"userVerification": "preferred"
}
}

Step 2: Get credentials and generate assertion#

In order create an assertion and authenticate with an existing credential, your application frontend must pass the credentialRequestOptions obtained in the previous step to the browser through the Web Authentication API. To make things as convenient as possible, you can use the Hanko JavaScript SDK to accomplish this.

// Get credentialRequestOptions (Step 1)
const credentialRequestOptions = await initializeAuthentication(userName)
// Get the credential using the credentialRequestOptions
const webAuthnResponse = await hankoWebAuthn.get(credentialRequestOptions)

Like on registration, this will trigger a native browser dialog prompting the user to perform an authorization gesture. Once a user has given consent by performing the gesture, the authenticator generates an assertion.

Step 3: Finalize authentication with the Hanko API#

The last step is to finalize the authentication with the Hanko API.

Frontend - Forward Web Authentication API response#

Pass the webAuthnResponse from the previous step to your application backend. Again, you will need a corresponding backend handler. Implement a handler that processes requests issued to the /webauthn/authentication/finalize endpoint.

// Finalize the authentication using the `webAuthnResponse` from Step 2
async function finalizeAuthentication(webAuthnResponse) {
const response = await fetch('/webauthn/authentication/finalize', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(webAuthnResponse)
});
if (!response.ok) {
// handle fetch error
}
return response.json();
}
Full example using plain JavaScript
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@teamhanko/hanko-webauthn@latest/dist/browser-global/hanko-webauthn.browser-global.js"></script>
</head>
<body>
<script type="text/javascript">
async function authenticate(userName) {
try {
const credentialRequestOptions = await initializeAuthentication(userName);
const webAuthnResponse = await hankoWebAuthn.get(credentialRequestOptions);
return await finalizeAuthentication(webAuthnResponse);
} catch (error) {
// handle error
}
}
async function initializeAuthentication(userName) {
const response = await fetch(`/webauthn/authentication/initialize?user_name=${userName}`);
if (!response.ok) {
// handle fetch error
}
return response.json();
}
async function finalizeAuthentication(webAuthnResponse) {
const response = await fetch('/webauthn/authentication/finalize', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(webAuthnResponse)
});
if (!response.ok) {
// handle fetch error
}
return response.json();
}
</script>
</body>
</html>
Full example using JavaScript modules
import {get as getCredential} from '@teamhanko/hanko-webauthn';
async function authenticate(userName) {
try {
const credentialRequestOptions = await initializeAuthentication(userName);
const webAuthnResponse = await getCredential(credentialRequestOptions);
return await finalizeAuthentication(webAuthnResponse);
} catch (error) {
// handle error
}
}
async function initializeAuthentication(userName) {
const response = await fetch(`/webauthn/authentication/initialize?user_name=${userName}`);
if (!response.ok) {
// handle fetch error
}
return response.json();
}
async function finalizeAuthentication(webAuthnResponse) {
const response = await fetch('/webauthn/authentication/finalize', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(webAuthnResponse)
});
if (!response.ok) {
// handle fetch error
}
return response.json();
}

Backend - Finalize with the Hanko API#

Finalize the authentication by forwarding the response sent by your frontend to the Hanko API using a POST request to the authentication finalization endpoint (/v1/webauthn/authentication/finalize, see also the API reference).

This is example uses the Hanko Go SDK.

Example authentication finalization using the Hanko Go SDK
import (
"github.com/teamhanko/hanko-sdk-golang/webauthn"
"encoding/json"
"net/http"
)
// Client instantiated in Step 1
var apiClient = ...
func authenticationFinalizationHandler(w http.ResponseWriter, r *http.Request) {
var request AuthenticationFinalizationRequest
// decode the request body
err := json.NewDecoder(r.Body).Decode(&request)
if err != nil {
// handle error
}
response, apiErr := apiClient.FinalizeAuthentication(request)
if apiErr != nil {
// handle error
}
// return API finalization response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(data)
}

On successful finalization, the Hanko API returns a representation of the credential used for authentication and the user can be considered logged in.

Congratulations, you successfully authenticated a user using the Hanko API!

Try the example application#

Are you looking for a more immersive learning experience? See how it all works by trying out our example application.