Register a credential
In this guide you will learn how to create a credential with the Hanko API in just three simple steps.
The Registration ceremony
We assume that a user has already created an account with your service (the relying party).
- When a user requests registration of a credential, your relying party application communicates basic user info to the Hanko Authentication API.
- The API generates credential creation options, including a challenge to which the user must respond using a cryptographically signed statement identifying the user.
- For this purpose, the user provides consent to generate a public/private credential key pair on the authenticator device using a local authorization gesture (e.g. through a PIN or biometrics).
- The private key is associated with the relying party and stored on a secure hardware element on the authenticator device. Unlike a password, the private key is never transmitted over the network. The public key is sent to the Hanko Authentication API along with the signed statement.
- The Hanko Authentication API validates the statement and stores the public key, associating it with a user ID.
Step 1: Initialize Registration at Hanko API
The first step to registering a credential is to initialize the registration ceremony. Initialization consists of retrieving credential creation options from the Hanko API in order to trigger credential creation on an authenticator through the Web Authentication API.
Frontend - Trigger registration initialization
In your frontend, trigger registration initialization by first making a request to your application backend. 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/registration/initialize
endpoint:
async function initializeRegistration() {
const response = await fetch('/webauthn/registration/initialize');
if (!response.ok) {
// handle error
}
return response.json();
}
Backend - Retrieve credential creation options
Your backend handler must then issue a POST
request to the registration initialization endpoint of the Hanko API
(/v1/webauthn/registration/initialize
- see also the API reference). The request body must include data about the user entity
on whose behalf registration is performed. The Hanko API does not manage user entities so user data must originate from
an existing relying party user store. In this guide we assume a user already has an existing account and valid session
with your application such that your backend is able to retrieve data from the user store and can construct an
appropriate request body. The user data object used as a request body must include:
- an
id
: this is used to map a credential to a specific user account. The userid
must not contain personally identifying information. - a
name
and adisplayName
: these are human-readable descriptions of the user that are used for display purposes only
- JSON
- Go
- Java
{
"user": {
"id": "e3be22a7-13cf-4235-a09c-380dfd44ac04",
"name": "John Doe",
"displayName": "johndoe123"
}
}
This is example uses the Hanko Go SDK.
import (
"github.com/teamhanko/hanko-sdk-golang/webauthn"
"encoding/json"
"net/http"
)
// For demonstration purposes only. Do not hardcode sensitive information!
var apiUrl = ... // Your relying party's API base URL
var apiSecret = ... // Your relying party's API secret
var apiKeyId = ... // Your relying party's API key ID
// Instantiate an API client
var apiClient = webauthn.NewClient(apiUrl, apiSecret).WithHmac(apiKeyId)
// Mock user store to lookup user data
var userStore = ...
func registrationInitializationHandler(w http.ResponseWriter, r *http.Request) {
// Mock function to look up user in your existing user store. The user model
// must include an ID, Name and DisplayName
user := userStore.getCurrentUser()
// Build initialization request
registrationUser := webauthn.NewRegistrationInitializationUser(user.ID, user.Name)
.WithDisplayName(user.DisplayName)
request := webauthn.NewRegistrationInitializationRequest(registrationUser)
// Initialize registration
credentialCreationOptions, apiErr := apiClient.InitializeRegistration(request)
if apiErr != nil {
// Handle error
}
// Return API initialization response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(credentialCreationOptions)
}
This is example uses the Hanko Java SDK.
@RestController
public class HankoController {
private final HankoWebAuthnClient apiClient;
private final UserStore userStore;
public HankoController() {
// For demonstration purposes only. Do not hardcode sensitive information!
String apiUrl = "..."; // Your relying party's API base URL
String apiSecret = "..."; // Your relying party's API secret
String apiKeyId = "..."; // Your relying party's API key ID
HankoClientConfig config = new HankoClientConfig(apiUrl, apiSecret, apiKeyId);
// Instantiate an API client
this.apiClient = new HankoWebAuthnClient(config);
this.userStore = new UserStore();
}
@PostMapping("/webauthn/registration/initialize")
public String initializeRegistration() {
// Mock User type and function to look up user in your existing user store. The user model
// must include an id, name and displayName
User user = userStore.getCurrentUser();
// Build initialization request
RegistrationInitializationUser regUser = new RegistrationInitializationUser(
user.getId(),
user.getName(),
user.getDisplayName());
RegistrationInitializationRequest request = new RegistrationInitializationRequest(regUser);
String credentialCreationOptions = null;
// Handle error explicitly or use, for example, a global exception
// handler using Spring's ControllerAdvice
try {
credentialCreationOptions = apiClient.initializeRegistration(request);
} catch (final HankoException ex) {
// Handle error
}
return credentialCreationOptions;
}
}
}
note
During registration initialization you can also specify additional options for the registration operation. For simplicity, this guide will not make use of these options and use only parameters that are required for creating a credential. In a more advanced implementation relying parties can
- require a specific type of authenticator device (see also Platform vs. Roaming authenticators)
- require user verification (see also User verification vs. user presence)
- require attestation of the device (see also Authenticator trust model)
- require credentials to be created as resident keys to enable usernameless authentication flows (see also Resident Keys )
On successful initialization the Hanko API will respond with the generated credentialCreationOptions
. You will use
these in the next step to trigger credential creation through the Web Authentication API.
Example JSON response: credential creation options
{
"publicKey": {
"challenge": "POjDn1rKqktubP8F9wkIdW_QbrFayNPwSUG9JF_P9IM",
"rp": {
"name": "Hanko GmbH",
"icon": "https://hanko.io/logo.png",
"id": "hanko.io"
},
"user": {
"name": "john.doe@example.com",
"displayName": "John Doe",
"id": "ZTNiZTIyYTctMTNjZi00MjM1LWEwOWMtMzgwZGZkNDRhYzA0"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -35
},
{
"type": "public-key",
"alg": -36
},
{
"type": "public-key",
"alg": -257
},
{
"type": "public-key",
"alg": -258
},
{
"type": "public-key",
"alg": -259
},
{
"type": "public-key",
"alg": -37
},
{
"type": "public-key",
"alg": -38
},
{
"type": "public-key",
"alg": -39
},
{
"type": "public-key",
"alg": -8
}
],
"authenticatorSelection": {},
"timeout": 10000
}
}
Step 2: Create the credentials
In order to create a new credential, your application frontend must pass the credentialCreationOptions
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. First, install the
SDK:
- CDN
- NPM
- Yarn
Include the SDK as a script in your HTML:
<script src="https://cdn.jsdelivr.net/npm/@teamhanko/hanko-webauthn@latest/dist/browser-global/hanko-webauthn.browser-global.js"></script>
This will set a browser global hankoWebAuthn
variable.
npm install --save @teamhanko/hanko-webauthn
yarn add @teamhanko/hanko-webauthn
Then use the SDK to create a credential through the WebAuthn API:
- JavaScript
- JS module
// Get credentialCreationOptions (Step 1)
const credentialCreationOptions = await initializeRegistration()
// Create the credentials using the credentialCreationOptions
const webAuthnResponse = await hankoWebAuthn.create(credentialCreationOptions)
import { create as createCredential } from '@teamhanko/hanko-webauthn'
// Get credentialCreationOptions (Step 1)
const credentialCreationOptions = await initializeRegistration()
// Create the credentials using the credentialCreationOptions
const webAuthnResponse = await createCredential(credentialCreationOptions);
This will trigger a native browser dialog prompting the user to perform an authorization gesture. The dialog prompts differ depending on the available and connected authenticators. Once a user has given consent by performing the gesture, the authenticator generates a public/private credential key pair and returns the public key as part of the response.
Step 3: Finalize registration with the Hanko API
The last step is to finalize the registration 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/registration/finalize
endpoint.
// Finalize the registration using the `webAuthnResponse` from Step 2
async function finalizeRegistration(webAuthnResponse) {
const response = await fetch('/webauthn/registration/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 register() {
try {
const credentialCreationOptions = await initializeRegistration();
const webAuthnResponse = await hankoWebAuthn.create(credentialCreationOptions);
return await finalizeRegistration(webAuthnResponse);
} catch (error) {
// handle error
}
}
async function initializeRegistration() {
const response = await fetch('/webauthn/registration/initialize');
if (!response.ok) {
// handle fetch error
}
return response.json();
}
async function finalizeRegistration(webAuthnResponse) {
const response = await fetch('/webauthn/registration/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 {create as createCredential} from '@teamhanko/hanko-webauthn';
async function register() {
try {
const credentialCreationOptions = await initializeRegistration();
const webAuthnResponse = await createCredential(credentialCreationOptions);
return await finalizeRegistration(webAuthnResponse);
} catch (error) {
// handle error
}
}
async function initializeRegistration() {
const response = await fetch('/webauthn/registration/initialize');
if (!response.ok) {
// handle fetch error
}
return response.json();
}
async function finalizeRegistration(webAuthnResponse) {
const response = await fetch('/webauthn/registration/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 registration by forwarding the response sent by your frontend to the Hanko API using a POST
request to
the registration finalization endpoint (/v1/webauthn/registration/finalize
, see also the API
reference).
- JSON
- Go
- Java
{
"type": "public-key",
"id": "9vmRvoxvs2tpqi9VCvInW7TOu98",
"rawId": "9vmRvoxvs2tpqi9VCvInW7TOu98",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV...",
"attestationObject": "o2NmbXRkbm9uZWdh..."
}
}
This is example uses the Hanko Go SDK.
import (
"github.com/teamhanko/hanko-sdk-golang/webauthn"
"encoding/json"
"net/http"
)
// Client instantiated in Step 1
var apiClient = ...
func registrationFinalizationHandler(w http.ResponseWriter, r *http.Request) {
// Decode the request body
var request RegistrationFinalizationRequest
err := json.NewDecoder(r.Body).Decode(&request)
if err != nil {
// Handle error
}
response, apiErr := apiClient.FinalizeRegistration(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(response)
}
This is example uses the Hanko Java SDK.
@RestController
public class HankoController {
private final HankoWebAuthnClient apiClient;
private final UserStore userStore;
public HankoController() {
// Initialization code omitted, see constructor from Step 1
}
@PostMapping("/webauthn/registration/finalize")
public FinalizationResponse finalizeRegistration(
// Extract webAuthnResponse from request body
@RequestBody final String webAuthnResponse
) {
FinalizationResponse finalizationResponse = null;
// Handle error explicitly or use, for example, a global exception
// handler using Spring's ControllerAdvice
try {
finalizationResponse = apiClient.finalizeRegistration(webAuthnResponse);
} catch (final HankoException ex) {
// Handle error
}
return finalizationResponse;
}
}
On successful finalization, the Hanko API returns a representation of the registered credential. This credential data can be used, for example, for display and management of credentials in a user profile. Congratulations, you have registered your first credential with the Hanko API!
Example JSON response: created credential
{
"credential": {
"id": "AUKybEpjFAmx-IcvgF...",
"createdAt": "2021-05-18T14:16:45.551437Z",
"lastUsed": "2021-05-18T14:16:45.551435Z",
"name": "Initial Name",
"userVerification": true,
"isResidentKey": false,
"authenticator": {
"aaguid": "adce0002-35bc-c60a-648b-0b25f1f05503",
"attachment": "platform"
},
"user": {
"id": "e3be22a7-13cf-4235-a09c-380dfd44ac04"
}
}
}
Try the example application
Are you looking for a more immersive learning experience? See how it all works by trying out our example application.