The following is the list of all the relevant Silent MFA APIs that the customer backend needs to call according to the purpose of the customer.
Scenario Flow |
HTTP Method Type |
Path |
Readme.io Link |
---|---|---|---|
Add User |
PUT |
/mfa/user |
|
Add Device |
POST |
/mfa/device |
https://haventec.readme.io/v1.0.1/reference/registermfadevice |
Verify User or Verify Device |
PATCH |
/mfa/verify |
https://haventec.readme.io/v1.0.1/reference/verifyuserordevice |
Add User’s Device or Activate Device |
PATCH |
/mfa/user/device/activate |
https://haventec.readme.io/v1.0.1/reference/addoractivatemfadevice |
Login Flow |
POST |
/authentication/login |
|
Customer Backend
The backend will need to provide APIs for the MFA front-end SDK to connect to and make external API calls to Haventec Authenticate. The following APIs should exist on the customer backend. The URI of each API may be changed as the Silent MFA Front-end SDK can be configured to hit different URIs. However, the API must contain the JSON properties specified in the Readme.io links. Additional properties may be added, however, none may be removed.
POST |
/register |
|
POST |
/check |
|
GET |
/magic-link |
|
POST |
/login |
It should be noted the front-end SDK does NOT call the magic link api. The URI of these endpoints can be configured in the SDK eg the backend might have /login-user
instead of /login
and the SDK can be configured to use that API instead.
Register API (Customer Backend)
The register endpoint is for creating a new MFA user or registering an MFA device to an existing end user. This is the most complex of the Silent MFA APIs as there are several scenarios to take into account:
-
End user does not exist in Haventec Authenticate
-
End user exists in Haventec Authenticate, but their current device does not.
API contract
The contract is specified here.
an HTTP status of 200 and the response body should be structured like the following:
{
"userUuid": "String formatted",
"deviceUuid": "String formatted",
"authSessionDetails": {
"uuid": "uuid formatted string",
"expiryDate": "ISO 8601 date-time"
},
activationDetails: {
"link": "Base64UrlSafeString"
}
}
While userUuid or deviceUuid can be null, at least one of them will have a non-null value depending on whether it is user registration or device registration.
Behaviour
Assuming the customer does not store whether an Authenticate user exists or not, we first call Authenticate Create Mfa Device API; if the API response is 200 we can proceed to store the activation details. However, if the response is a 400 with a response body like the following:
{ "responseStatus": { "code": "AN-AUTH-1017", "message": "User not found in application" } }
we have to register the user, as the user does not exist.
The end user can be registered by calling Haventec Authenticate Create MFA user, you will need to send in Haventec Authenticate applicationId and API key via headers, and provide a username in the request body.
The system must store the response, after hashing the activationDetails.link
, as this value is a secret. It is recommended that Argon2id hashing algorithm is used. Additionally, the system must be able to retrieve the userUuid
, authSessionDetails.uuid,
and authSessionDetails.expiryDate
.
An example SQL table that can hold these details is:
create table mfa_activation
(
id uuid
constraint mfa_activation_primary
primary key,
session_id uuid,
-- When the session expires
expiry_time timestamp,
-- Optional columns for auditing purposes
create_date timestamp,
date_updated timestamp
);
Next, we need to email or SMS the user their magic link, which might look something like
https://customer-domain/api/magic-link?activation={activationLink}&id={id}
where {activationLink
} is the value from the register response activationDetails.link
; and {id}
is the primary key of the mfa_activation
table.
When the user clicks the link it should trigger the API described here.
Note: In order to send the magic link to the user via email or SMS, the customer’s backend needs to map the username entered by the end user to his/her mobile number or email id.
If the SMS/Email sending is successful then return a 200 response back to the end-user, along with the following responseBody:
{
"userUuid": "uuid",
"authSession": {
"id": "session uuid",
"expiryDate": "ISO 8601"
}
}
The values here are taken from Haventec Authenticate’s original 200 responses. As you can see we have removed the activationDetails
, as the user will receive it in their Email or SMS.
Additionally, the customer may add a CRON job to remove expired sessions from their database table periodically.
Magic Link API (Customer Backend)
The magic-link API verifies end-user activity (registering a new end-user or end-user device). The request URI contains the activationLink
sent to the user via email or SMS. The customer's backend needs to identify the corresponding session and call the verify API on Haventec Authenticate.
API contract
The activation token needs to be accepted as a query parameter eg:
https://customer-domain/api/magic-link?activation={activationLink}&id={id}
where {activationLink
} is the activationDetails.link
value from the Register MFA User or Register MFA Device API response body. {id}
is the primary key of the mfa_activation
table.
Behaviour
The customer’s system takes the {id}
value and calls the database to retrieve MFA session details. After extracting the sessionId
system can call Haventec Authenticate to verify the endpoint, passing in the sessionId
and the activationLink
.
Authenticate will respond with a 200 success if the end-users action has been verified, or 403 if the session has expired or if the activatationLink was incorrect.
Upon a 200 success, the next Check API call will return MFA device credentials.
Check API (Customer Backend)
The Check API is polled by the front end. The request API is proxied through to Haventec Authenticate's verify API. Upon successfully validating a user/device via the magic link, Haventec Authenticate will return device credentials to be stored on the end user's device.
Before the magic link is clicked, it is expected that API calls polling will return 403 as the end-user or end-user device is considered unverified.
API contract
The request body should contain the sessionId
.
{
"sessionId": "sessionId"
}
A successful response should return valid MFA device credentials:
{
"deviceUuid": "string",
"authKey": [
"string"
],
"accessToken": {
"type": "string",
"token": "string"
}
}
Further details can be seen Check Verification endpoint.
Behaviour
The request is proxied through to Haventec Authenticate's activate device endpoint.
Upon a 200 success response, the customer’s system will need to pass the response back to the end user’s device. The backend may remove any stored information from its database.
If the session has expired, the back-end may remove session data from its database or storage.
Login API (Customer Backend)
The login endpoint is to allow a user to log in, at this point it is assumed the front-end contains MFA device credentials. The end user will need to do their regular password verification in addition to logging in to Haventec Authenticate.
API Contract
The request body must at the very least contain MFA credentials in the following format:
{
"username": "string",
"authKey": "string",
"deviceUuid": "string",
// Below Client's own credential verification fields
"password": "string"
}
The response which will be stored by the front-end SDK will look like this
{ "username": "string", "authKey": "string", "deviceUuid": "string" }
See further API details here.
Behaviour
The customer’s backend will need to verify the login request with their regular logic. In addition to this, they will need to proxy the MFA device credentials to Haventec Authenticate via the Login API. A 200 indicates that the device is valid and the user validated.
The customer’s system will need to pass the new MFA credentials from Haventec Authenticate back to the front-end SDK, so the device may update its credentials.
Frontend
The Haventec MFA SDK supports Javascript or Typescript front-ends. The front-end will need permissions to use local storage and session storage.
Importing SDK and configuration
Assuming the customer frontend application is running NPM, the Haventec Silent MFA SDK needs to be imported into the package.json
{
"dependencies": {
"@haventec/mfa-client": "1.0.0"
}
}
or run
npm install @haventec/mfa-client
Setting up the client, you would need to run the following code
//Import SDK
import {HaventecMfaClient} from "@haventec/mfa-client";
//Any class can import the MFA client
class App {
{
client: HaventecMfaClient = new HaventecMfaClient({
pollTimeInMs: 5000,
checkUrl: "http://localhost:9003/check",
registerUrl: "http://localhost:9003/register",
loginUrl: "http://localhost:9003/login"
});
}
}
pollTimeInMs
: Configures how often the SDK should check that the device has been verified, by default this value is 5000.
checkUrl:
Should point to the customer backend’s Check API, this is the API that will be polled
registerUrl:
Should point to the customer backend’s Register API; this is the API used to register Haventec Silent MFA User or Silent MFA device.
loginUrl:
Should point to the customer backend’s Login API
Registering a new user or device
In order to register an MFA user or device call HaventecMfaClient.register
eg:
//Import SDK
import {HaventecMfaClient} from "@haventec/mfa-client";
//Any class can import the MFA client
class App {
client: HaventecMfaClient = new HaventecMfaClient({
pollTimeInMs: 5000,
checkUrl: "http://localhost:9003/check",
registerUrl: "http://localhost:9003/register",
loginUrl: "http://localhost:9003/login"
});
public register(username: string, password: string): void {
//Will create an unverified Haventec Authenticate User or device, and then poll
// the check endpoint. When the magic-link is clicked, which verifies the user/device.
//The Check endpoint will then return a 200 with device credentials and the promise will resolve
this.client.register({username, password})
.then(() => {
//MFA device credentials has been stored
})
.catch(() => {
//The session has timed out, before the check poll returning device credentials.
});
}
}
Login
When calling the login API, the SDK will append the authKey
, deviceUuid
properties to the request body.
For example:
this.client.login({username: "username", password: "password", "extraProperty": "Extra"});
Will send the following request body:
{ username: "username", password: "password", "extraProperty": "Extra", authKey: "authKey", deviceUuid: "deviceUuid" }
this.client.login({username: "username", password: "password"});
//Import SDK
import {HaventecMfaClient} from "@haventec/mfa-client";
//Any class can import the MFA client
class App {
client: HaventecMfaClient = new HaventecMfaClient({
pollTimeInMs: 5000,
checkUrl: "http://localhost:9003/check",
registerUrl: "http://localhost:9003/register",
loginUrl: "http://localhost:9003/login"
});
public login(username: string, password: string): void {
this.client.login({username, password})
.then(() => {
//new set of MFA device credentials has been stored
})
.catch(() => {
//Invalid device credentials, invalid regular password or no device credentials stored
});
}
}
additionally, the SDK offers login with register fallback; this method will call the login API if there are device credentials stored. Otherwise, it will call the register API.
//Import SDK
import {HaventecMfaClient} from "@haventec/mfa-client";
//Any class can import the MFA client
class App {
client: HaventecMfaClient = new HaventecMfaClient({
pollTimeInMs: 5000,
checkUrl: "http://localhost:9003/check",
registerUrl: "http://localhost:9003/register",
loginUrl: "http://localhost:9003/login"
});
public register(username: string, password: string): void {
// If device credentials are found in local-storage will call the login API. No polling of the Check API will be initaited.
the promise will resolve or reject depending on the Login API response.
// If no device credentials are found it will create an unverified Haventec Authenticate User or device by calling the register API, and then poll
// the check API. When the magic-link is clicked, which verifies the user/device.
//The Check endpoint will then return a 200 with device credentials and the promise will resolve
this.client.loginWithRegisterFallback({username, password})
.then(() => {
//MFA device credentials has been stored
})
.catch(() => {
//The session has timed out, before the check poll returning device credentials.
});
}
}