Skip to main content

Implementation Guide

This implementation guide provides a step by step description of how to implement an authentication flow using Passlinks and the Hanko Authentication API.

Prerequisites#

Before you start implementing an authentication flow based on Passlinks, there are some prerequisites that must be met.

Setting up your account#

First, you need to set up an account with Hanko. This includes setting up an organization and a relying party. Head over to our getting started section to learn how to set these up.

Accessing the Hanko API#

In order to use the Hanko API you need your relying party API base URL and an API Key. Head over to our "Getting started" section to learn how to obtain your API base URL and to generate an API Key. API keys are required to make authenticated calls to the Hanko API. In order to do so, you must provide an authorization header as described in the API reference.

What we will build#

The following diagram provides an overview of the steps to take to implement a basic Passlink authentication flow with the Hanko API:

Figure 1: Passlink login flow sequence diagram

As can be seen from the diagram, an authentication flow using Passlinks consists of:

  1. Initialization: The initialization phase involves making a request to the Hanko API to create a Passlink and send a message containing the Passlink to the user.
  2. Confirmation: By clicking the Passlink contained in the received message, the user approves the authentication request with the Hanko API. The API marks the Passlink as confirmed and redirects the user to a URL in the scope of the relying party application. The ID of the confirmed Passlink is appended to the redirect URL as a query parameter.
  3. Finalization: The target identified by the redirect URL is responsible for finalizing the flow through triggering a request to the appropriate finalization endpoint of the Hanko API. To identify the correct Passlink to finalize, the Passlink ID given in the query parameter of the redirect URL (see Step 2) is used. The redirect URL target must be implemented by the relying party.

Step 1: Initialize and send Passlink#

The very first step of the entire authentication flow is a login request initiated by a user. We assume your application has a login screen that allows users provide their email address through submitting a form. Once a user has submitted this form, your application backend must issue a POST request to the Passlink initialization endpoint of the Hanko API (v1/passlink/initialize - see also the API reference). Because this endpoint is protected, requests must originate from a trusted client with an appropriate authorization header containing valid API credentials (see the Getting started guide on how to obtain API credentials).

The request body must include:

  • user_id: Serves as an identifier for the user a Passlink is created and sent to
  • transport: Currently, Passlinks can be sent via email only, so this value should be set to email
  • email: The email address of the recipient of the message containing the Passlink

In this example we additionally set a redirect URL via the redirect_to attribute in the initialization request. The target of this URL is where the user will be redirected once she confirms a Passlink (see Step 2). The target is responsible for implementing the finalization of the authentication flow. We will describe how to handle the redirect URL in Step 3.

On redirect URLs

The redirect URL given via the redirect_to attribute must be a URL that has been registered as a valid redirect URL in the Passlink settings in the Hanko Console. This ensures users cannot be redirected to arbitrary locations.

You can register multiple redirect URLs through the Hanko Console settings to allow for handling of specific use cases (i.e. account registration, verification, and recovery). You can also designate one of the configured redirect URLs as a default redirect URL. This default will be used if no redirect URL is given via the redirect_to attribute in the initialization request.

This is example uses the Hanko Go SDK.

Example Passlink initialization using the Hanko Go SDK
import (
"github.com/teamhanko/hanko-go/passlink"
"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 = passlink.NewClient(apiUrl, apiSecret).WithHmac(apiKeyId)
// Mock user store to lookup user data
var userStore = ...
func PasslinkInitializationHandler(w http.ResponseWriter, r *http.Request) {
// We assume submission of a login form with a single email input here
// and extract the value from the request
_ = r.ParseForm()
email := r.FormValue("email")
// Look up the user in your user store based on the given email address
user := userStore.FindByEmail(email)
if user != nil {
request := &passlink.LinkRequest{
UserID: user.ID,
Email: user.Email,
Transport: "email",
// We will implement the handler for this URL in Step 3
RedirectTo: "https://example.com/passlink/finalize"
}
// InitializePasslink issues the POST request to the /v1/passlink/initialize endpoint (cf. Figure 1)
passlink, apiErr := apiClient.InitializePasslink(passlinkRequest)
if apiErr != nil {
// Handle error
}
}
// Provide a generic response for both cases where a user does and does not exist
// for a given email in order to avoid user enumeration
w.WriteHeader(http.StatusOK)
}
On message customization

Passlink initialization also allows you to provide a number of options that allow customization of the message that is sent to the user (see the API reference). These options include:

  • template: This is the name of a template to use for the message. The template determines the styling and content of the message sent to the user. We currently provide templates for different use cases: login (login), account verification (verification) and registration through invitation (invitation). If the template attribute is omitted in the request - like we did in the example above - the default value for the template name will be login.
  • locale: This is a locale string of the form Language_Territory where Language is a two-letter ISO 639-1 language code, and Territory is a two-letter ISO 3166-1 alpha-2 territory code. The Language part determines the requested language of the template that is used to construct the message sent to the user. If no template is found for the given language the API will return an error response. The Territory part is used to appropriately format datetime strings in messages sent to the user. For these purposes the locale value should be one of the values listed in the API reference. If an unknown value is used or the locale attribute is omitted, it will default to en_GB.
  • timezone: This is a Timezone name of the form Area/Location as defined in the IANA Time Zone Database. If provided, it is used to appropriately format datetime strings (e.g. the expiration date of a Passlink) in messages. This value defaults to UTC if the given timezone name cannot be resolved.
  • salutation: This allows you set a custom salutation for the message. If omitted, a default salutation as configured for the given template name (see template attribute) and language (see locale attribute) will be used.

A successful initialization will result in two things:

  1. The Hanko API will send an email containing a Passlink to the recipient specified by the email attribute in the request.
  2. The Hanko API will respond with a JSON representation of the created Passlink. At this point, the Passlink is in a pending status awaiting confirmation through the user. The valid_until response attribute determines the time at which this link will expire. By default Passlinks expire after 15 minutes. This expiration can be configured in the initialization request through the ttl attribute (see the API reference for details).
Example JSON response: Pending Passlink
{
"id": "c7c13373-05d4-4620-966e-701930272a80",
"user_id": "e3be22a7-13cf-4235-a09c-380dfd44ac04",
"valid_until": "2021-09-28T08:27:51.77931Z",
"status": "pending"
}
note

After a successful initialization, display a message to inform the user that a message containing the Passlink has been sent to the user's email address and to follow the instructions in the message. To mitigate user enumeration, provide a generic message regardless of whether a user for a given email address exists in your application.

Additionally, following a Passlink by clicking the link will usually result in opening the target in a new tab in the user's default browser, and it is this browsing context in which the remainder of the authentication flow will take place (i.e. finalization as described in Step 3 below, creation of a session with your application, and a redirect to a profile page or the like). As such, it might be helpful to inform the user that the tab from which the authentication was initialized can be closed.

Step 2: Confirm Passlink#

If initialization was successful, the user will receive a message through the requested communication channel (as already mentioned, the Hanko API currently can deliver Passlinks via email only). To confirm a Passlink, the user simply clicks the link or manually copies and pastes the link in the address bar of a new browser tab.

This results in a GET request to the confirmation endpoint of the Hanko API (v1/passlink/{id}/confirm - see also the API reference) and redirects the user to either the default relying party redirect URL as configured via the Hanko Console or to the URL given by the redirect_to attribute in the initialization request (again, note that a URL given in the initialization request also must be registered as a valid URL in the Hanko Console).

Step 3: Finalize Passlink#

To complete the authentication flow, confirmed Passlinks must be finalized (note that only confirmed Passlinks can be finalized and any attempt to finalize a Passlink that is in a pending status, i.e. has just been created through Passlink initialization in Step 1, will result in an error response).

To finalize a Passlink we need to implement the handler for the redirect URL target given via the redirect_to attribute in the initialization request (see Step 1). This handler must issue a PATCH request to the Passlink finalization endpoint of the Hanko API (v1/passlink/{id}/finalize - see also the API reference). The endpoint requires a Passlink ID as a path parameter. The redirect URL includes the correlated Passlink ID as a query parameter (link_id). Extract this ID and use it in the finalization request. The following example shows an implementation of the handler for the redirect URL we specified in the initialization request in Step 1 (i.e. the handler for https://example.com/passlink/finalize):

This is example uses the Hanko Go SDK.

Example Passlink finalization using the Hanko Go SDK
import (
"github.com/teamhanko/hanko-go/webauthn"
"encoding/json"
"net/http"
)
// Client instantiated in Step 1
var apiClient = ...
// This is the handler for the redirect URL given in the
// `redirect_to` attribute during initialization (either explicitly or implicitly
// using the configured default redirect URL in the Hanko Console) in Step 1
func PasslinkFinalizationHandler(w http.ResponseWriter, r *http.Request) {
// Extract the ID of the Passlink to finalize from the request.
linkId := r.URL.Query().Get("link_id")
// FinalizePasslink Issues the PATCH request to the /v1/passlink/{id}/finalize endpoint (cf. Figure 1)
passlink, apiErr := apiClient.FinalizePasslink(linkId)
if apiErr != nil {
// Handle error
}
// Validate the passlink has been finished
if passlink != nil && passlink.Status == 'finished' {
// Create session for user
// Handle success by, for example, redirecting to a profile page
http.Redirect(w, r, "https://example.com/profile", 301)
}
// Handle nil Passlink or unknown status case
}

On successful finalization the Hanko API will respond with a representation of the created Passlink. The Passlink is now in a finished status. Once a Passlink has reached this status, it cannot be used again.

Example JSON response: Finished Passlink
{
"id": "c7c13373-05d4-4620-966e-701930272a80",
"user_id": "e3be22a7-13cf-4235-a09c-380dfd44ac04",
"valid_until": "2021-09-28T08:27:51.77931Z",
"status": "finished"
}

Congratulations, you have now completed your first Passlink authentication flow!