UMFA APIs for Browser Applications

The Universal Multi-Factor Authentication (UMFA) module provides a flexible solution for handling multi-factor authentication processes. It allows the configuration to be provided either as a JSON string or via a URL pointing to a configuration file. The authentication steps are executed using dynamically loaded modules.


Interface Definitions

Note that the following interface definitions presuppose that you have already imported the UMFAClient class from the ZSM Client SDK (@ideem/zsm-client-sdk). If you have not yet done so, please refer to the Web Framework Setup documentation for instructions on how to import the ZSM Client SDK.


Check Enrollment UMFAClient.checkEnrollment() (Check For Existing Credentials)

The checkEnrollment method of the UMFAClient class examines the user's enrollment status for the Universal Multi-Factor Authentication (UMFA) process on the current device. This allows the application to determine whether the user has already enrolled in the UMFA process, allowing the developer to skip the enrollment process, should the user already be enrolled, or perform additional authentication prior to enrollment.

Parameters

Parameter NameData TypeDescription
userIdentifierStringThe unique identifier for the user. This is typically the user's email address or a UUID.

Returns

Parameter NameData TypeDescription
responsePromiseReturns a promise containing the results of the checkEnrollment query. This can include any one of the following:
credentialID (String): The local credential set's credential ID (user HAS enrolled)
false (Boolean): Indicating user does not have a local ZSM UMFA credential set
error ( Error ): An Error occurred while attempting to check the enrollment status

Usage

JavaScript

const enrollmentStatus = await umfaClient.checkEnrollment(userIdentifier);
if(enrollmentStatus instanceof Error) throw(enrollmentStatus);                   // Error Condition
if(enrollmentStatus !== false) {
    console.log(`${user} is enrolled with credential_ID: ${enrollmentStatus}`);  // Credentials Present
} else {
    console.log(`${user} is not enrolled`);                                      // Credentials Absent
}

TypeScript

const enrollmentStatus:string|boolean|Error = await umfaClient.checkEnrollment(userIdentifier);
if (enrollmentStatus instanceof Error) throw(enrollmentStatus);                  // Error Condition
if (typeof enrollmentStatus === 'string') {
    console.log(`${user} is enrolled with credential_ID: ${enrollmentStatus}`);  // Credentials Present
} else {
    console.log(`${user} is not enrolled`);                                      // Credentials Absent
}


Enroll UMFAClient.enroll() (Enroll in UMFA Process)

The enroll method of the UMFAClient class initiates the enrollment process for the Universal Multi-Factor Authentication (UMFA) module. This process allows the user to enroll in the UMFA process, which is required for subsequent authentication operations.

Parameters

Parameter NameData TypeDescription
userIdentifierStringThe unique identifier for the user. This is typically the user's email address or a UUID.

Returns

Parameter NameData TypeDescription
responsePromiseReturns a promise containing the results of the checkEnrollment query. This can include any one of the following:
credential (Object) : The credential object containing the user's authentication information*.
false (Boolean) : Indicating user is already enrolled and has a valid local credential set
error ( Error ) : An Error occurred while attempting to enroll the user

(to be stored for use in verification later. Note this is a JSON object, and may need to be JSON.stringify'd depending on your storage mechanism.)

Usage

JavaScript

const userAuthenticationJWT = await UMFAClient.enroll(userIdentifier);
if (userAuthenticationJWT instanceof Error) throw(userAuthenticationJWT);                            // Error Condition
if(userAuthenticationJWT !== false) {
    console.log(`Enrollment of ${userIdentifier} successful! JWT Token: ${userAuthenticationJWT}`);  // Successful Enrollment
} else {
    console.error('Enrollment failed.');                                                             // Enrollment Failed
}

TypeScript

const userAuthenticationJWT:string|boolean|Error = await UMFAClient.enroll(userIdentifier);
if (userAuthenticationJWT instanceof Error) throw(userAuthenticationJWT);                            // Error Condition
if (typeof userAuthenticationJWT === 'string') {
    console.log(`Enrollment of ${userIdentifier} successful! JWT Token: ${userAuthenticationJWT}`);  // Successful Enrollment
} else {
    console.error('Enrollment failed.');                                                             // Enrollment Failed
}


Authenticate UMFAClient.authenticate() (Execute UMFA Process)

Performs an authentication operation using the Universal Multi-Factor Authentication (UMFA) module, using the credential currently stored on the device for the specified user. If the specified user's credential is not present on the device, authentication will fail.

Parameters

Parameter NameData TypeDescription
userIdentifierStringThe unique identifier for the user. This is typically the user's email address or a UUID.

Returns

Parameter NameData TypeDescription
responsePromiseReturns a promise containing the results of the checkEnrollment query. This can include any one of the following:
credential (Object) : The credential object containing the user's authentication information*.
error ( Error ): "${userIdentifier} is not enrolled." (if the user is not enrolled)
error ( Error ): An Error occurred while attempting to enroll the user

(to be stored for use in verification later. Note this is a JSON object, and may need to be JSON.stringify'd depending on your storage mechanism.)

Usage

JavaScript

const userAuthenticationJWT = await UMFAClient.authenticate(userIdentifier);
if (userAuthenticationJWT instanceof Error) throw(userAuthenticationJWT);                            // Error Condition
console.log(`${userIdentifier} successfully authorized! JWT Token: ${userAuthenticationJWT}`);       // Successful Authorization

TypeScript

const userAuthenticationJWT:string|Error = await UMFAClient.authenticate(userIdentifier);
if (userAuthenticationJWT instanceof Error) throw(userAuthenticationJWT);                            // Error Condition
console.log(`${userIdentifier} successfully authorized! JWT Token: ${userAuthenticationJWT}`);       // Successful Authorization


Unenroll UMFAClient.unenroll() (Remove user's enrollment from the device)

The unenroll method of the UMFAClient class removes the specified, enrolled user's credentials from the device the user is actively logged in with. This effectively deletes any credentials associated with the user, preventing future authentication using those credentials, until the user re-enrolls on said device.

Parameters

Parameter NameData TypeDescription
userIdentifierStringThe unique identifier for the user. This is typically the user's email address or a UUID.

Returns

Parameter NameData TypeDescription
responsePromiseReturns a promise containing the results of the unenrollment. This can include any one of the following:
true (boolean) : The user's credentials were successfully removed from the device
false (boolean): The user was not enrolled and/or had no valid local credential set
error ( Error ): Unable to unenroll 's identity from this device.

Usage

JavaScript

const unenrollResult = await UMFAClient.unenroll(userIdentifier);
if (unenrollResult instanceof Error) throw(unenrollResult);                // Error Condition
console.log(`${userIdentifier} successfully unenrolled!`);                 // Successful Unenrollment

TypeScript

const unenrollResult:string|Error = await UMFAClient.unenroll(userIdentifier);
if (unenrollResult instanceof Error) throw(unenrollResult);                // Error Condition
console.log(`${userIdentifier} successfully unenrolled!`);                 // Successful Unenrollment


Event: UMFAClientReady

The UMFAClientReady event is dispatched when the UMFAClient is ready to be used. This event is emitted from the worker thread to the main thread's window object. This event can be listened for - to ensure that the client is fully initialized before attempting to call methods contained within it - and the listener can, upon its firing, be used to trigger any necessary actions or UI updates in your application.

Details

In some implementations, the UMFAClient may take a short time (typically measured in tens of milliseconds, depending on client system specifications) to initialize, especially if it is being loaded from a remote source or if it is being loaded JIT (Just-In-Time) immediately preceding its use.

As such, a listener for this event can be added to the window object to ensure that the UMFAClient is fully initialized before attempting to call methods contained within it.

Payload

ParameterTypeDescription
detailBoolean (true)The detail object inside the event will always contain the value true, indicating that the UMFAClient is ready to be used, but since the event is only emitted once it's already ready, this is a formality and can be ignored.

Usage

JavaScript

window.addEventListener('UMFAClientReady', function (event) {
    console.info("UMFAClientReady event received!");

    // Perform any additional actions that involve the UMFAClient, assured that it is ready to be used
});

TypeScript

window.addEventListener('UMFAClientReady', function (event: CustomEvent) {
    console.info("UMFAClientReady event received!");

    // Perform any additional actions that involve the UMFAClient, assured that it is ready to be used
});
Note...

The UMFAClientReady event is a custom event and is not part of the standard DOM events. It is specific to the UMFAClient implementation and should be handled accordingly. In most implementations, this event listener should not be necessary, as the UMFAClient is typically initialized during the page load and will be long-since ready prior to its use. However, in some specific cases, this event may be useful to ensure that the client is fully initialized before attempting to call methods contained within it.


Out-of-Band Token Validation

For out-of-band validation of the JWT returned by enroll and authenticate, use the /api/umfa/validate-token endpoint.

HTTP Method

POST

URL

$ZSM_AUTHENTICATOR_HOST/api/umfa/validate-token

Request Headers

HeaderValueDescription
Content-Typeapplication/jsonIndicates the payload format
AuthorizationBearer XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXAuthorizes the API (API Key expected)

Request Body

FieldTypeRequiredDescription
application_idstringYesThe unique identifier for the request's (server-to-server) application, parsed as a UUID.
user_idstringYesThe unique identifier for the user.
tokenstringYesThe token received from a previous UMFA authentication operation.
token_typestringNoAn optional token type specifying the type of validation. Can be "credential" to validate a get() PublicKeyCredential.
trace_idstringNoAn optional identifier for the validate-token operation. If a trace_id is not provided, then a random trace_id will be generated.

Successful Response (HTTP 200)

{
  "user_id": "janedoe@gmail.com",
  "trace_id": "7a626fe9-ce25-4b87-8eb2-b12a7ee20143"
}
FieldTypeDescription
user_idstringThe unique identifier for the user that was validated.
trace_idstringThe trace identifier for the validate-token operation.

Error Responses

{
  "status": 400,
  "trace_id": "7a626fe9-ce25-4b87-8eb2-b12a7ee20143",
  "message": "Validate token failed with: MFA login JWT was invalid: Invalid JWT: There is no user_id claim"
}
FieldTypeDescription
statusintegerThe HTTP response code.
trace_idstringThe trace identifier for the verification operation.
messagestringAdditional information about the validation failure.
Status CodeDescriptionExample Response
400 Bad RequestIncorrectly formed request{ ... "message": "No data provided." }
401 UnauthorizedInvalid or expired token{ ... "message": "Validate token failed with: MFA login JWT was invalid: Invalid JWT: There is no webauthn_time claim" }
500 Internal Server ErrorServer encountered an issue{ ... "message": "Server encountered an internal error" }

Example cURL Commands

HTTP Success (200) JWT Validation

$ curl -s - X POST -H "Content-Type: application/json"
-H "Authorization: Bearer XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
-d '{"application_id": "bf468b21-308f-49d2-9031-83556e0781d2", 
"user_id": "janedoe@gmail.com", "token": "eyJ0eX ... Nk9uWg"}' 
$ZSM_AUTHENTICATOR_HOST/api/umfa/validate-token | jq
{
  "user_id": "c7d7d44b-385e-4e83-bdd5-37e4fb3c8b7d",
  "trace_id": "7a626fe9-ce25-4b87-8eb2-b12a7ee20143"
}

HTTP Success (200) Credential Validation

$ curl -s - X POST -H "Content-Type: application/json"
-H "Authorization: Bearer XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
-d '{"application_id": "bf468b21-308f-49d2-9031-83556e0781d2", 
"user_id": "janedoe@gmail.com", 
"token":
{
  "id":"",
  "rawId":"rF2kHiKUQCO0d0Y4Wek9kA",
  "response":
  {
    "clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiNWtMb2tjOXpkZzhuU2RFT2hzV1o5MUwzZTNGdjlqbERlRU9KcF93SnRkayIsIm9yaWdpbiI6Imh0dHBzOi8venNtLmFwcCJ9","authenticatorData":"ZxcNEwlh6TBKTTC5FMSFPTOboOZWzGeOSiYY7rm67WUFAAAAAQ","signature":"UOOoSBAwemLSPvqLVG2MDw41cqKLcHEUp4LAFGuvrVPsR1GWBYTWtmpqG_Jtjn-DXq5tGGAE58SYvu7uvcw7oHqquoNG4VEdm4Tz7UNe5kdSoc3RFpEGGDLCIz28iKXaBPbv3jdHi4xGoCIJKIIeHyh0-g7LUb4ZjYFIZHyXds7cdH9ozXRt5ERWUVvH1axDnPDKpntGQXG8FC4VXd0Rc01-4bBklNSGHOVgbO-Rpm8HgeFj3J4uOZDJ0xP7pnIkwOo5Uw_0ZO9xI66S8NQEtVzXVUKXs98f38LpLiLGEPlWtr_RIdf9xgsmHx-oVRJxC37gzV2ydSGKJaV6bNsXOw"},
    "type":"public-key"
  }
},
"token-type": "credential"}' 
$ZSM_AUTHENTICATOR_HOST/api/umfa/validate-token | jq
{
  "user_id": "c7d7d44b-385e-4e83-bdd5-37e4fb3c8b7d",
  "trace_id": "7a626fe9-ce25-4b87-8eb2-b12a7ee20143"
}

Bad Request (400)

$ curl -s - X POST -H "Content-Type: application/json"
-H "Authorization: Bearer XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
-d '{"application_id": "bf468b21-308f-49d2-9031-83556e0781d2", 
"user_id": "janedoe@gmail.com"}' 
$ZSM_AUTHENTICATOR_HOST/api/umfa/validate-token | jq
{
  "status": 400,
  "trace_id": "7a626fe9-ce25-4b87-8eb2-b12a7ee20143",
  "message": "Invalid data provided" 
}

Unauthorized (401)

$ curl -s - X POST -H "Content-Type: application/json"
-H "Authorization: Bearer XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
-d '{"application_id": "bf468b21-308f-49d2-9031-83556e0781d2", 
"user_id": "janedoe@gmail.com", "token": "eyJ0eX ... Nk9uWg"}' 
$ZSM_AUTHENTICATOR_HOST/api/umfa/validate-token | jq
{
  "status": 401,
  "trace_id": "7a626fe9-ce25-4b87-8eb2-b12a7ee20143",
  "message": "Validate token failed with: MFA login JWT was invalid: Invalid JWT: Invalid claim: The token has expired: 2024-12-10 18:41:03.0 +00:00:00" 
}

Validate Token Failures

The server's token validation can fail for two general reasons:

  • HTTP 400 Response: The request was malformed (i.e., token was not included)
  • HTTP 401 Response: The token was invalid, which could have various causes
    • Token's user_id did not match the supplied user_id
    • Token has expired (the token's "exp" claim has passed)
    • Token was missing required claims ("iss", "sub", "iat", "exp", "user_id", "webauthn_time")
    • Token was not signed by the expected ZSM server's certificate
    • Token was not a valid PublicKeyCredential when supplied "token_type" = "credential"

Decoded JWT

Below, we illustrate an example of a decoded UMFA JWT (Header and Payload). The validation performs standard JWT validation like signature, liveness, and structure. The validation also ensures that the payload claim user_id matches that of the supplied user_id.

{
  "typ": "JWT",
  "alg": "RS256",
  "iss": "Ideem::Authenticator"
}
{
  "sub": "UMFA_login",
  "iss": "Ideem::Authenticator",
  "aud": [
    "Ideem::Authenticator",
    "Ideem::ZSM",
    "Ideem::ZSM_CLI"
  ],
  "iat": 1729280408,
  "exp": 1729366808,
  "jti": "042142c1-40c8-4d9b-bf5e-1fa84e0f3f03",
  "user_id": "c7d7d44b-385e-4e83-bdd5-37e4fb3c8b7d",
  "userpw_time": "2024-10-18T19:40:07.053364351+00:00",
  "webauthn_time": "2024-10-18T19:40:08.053364351+00:00"
}