Add New Authenticator
Overview
The majority of customers have multiple devices, and they expect to be able to access your application from all of them. In order to add this new device securely, a previously registered device is required to authorize the new device.
Adding a new authenticator requires a backend API call, therefore it is recommended to leverage the LoginID server SDK. However, the LoginID APIs can be called directly if required.
It is best practice to have the end user attempt to log in on their new device, rather than give them an explicit option to say they are on a new device. After authentication is attempted, there will be an error indicating there are no credentials available on the device. Once this error is returned, the user should be prompted to either add this new device as an authenticator, or only use this device once.
At a high level, this flow consists of three main components: generation of a code, authorization of that code, then using that code to add the new authenticator.
Prerequisites
- Create an application on LoginID Dashboard
- Download Client SDK
- Download Server SDK (optional)
With Server SDK
Step 1: Get user_id
The code generation functionality requires the user_id
rather than the username. In the case where you have not stored the user_id
, you can call the below SDK method to get the user_id
.
Server SDK: .getUserID(username)
The response is the user’s profile information (including the user_id) for the provided username.
Step 2: Generate Code
Once you have the user_id
, you must generate a code. This code is what ties the new device and existing device together. To understand which code_type
is best for your use case, please see our guide on choosing a code type. The purpose should be add_credential
.
Server SDK: .generateCode(purpose, code_type, username, authorized)
This returns a code of the specified type as well as its expiry time.
Step 3: Notify User
LoginID does not send the code to the user, as we understand it is important for you to maintain direct contact with your clients. Depending on your desired user experience, you can use the code returned in Step 2 in a variety of ways. For adding a new authenticator, this is frequently done through a QR code or push notification to the already registered device.
Step 4: Authorize Code
The code generated in Step 2 has to be authorized before being able to be used to add the new authenticator. This should be done from an end user’s previously registered device. After the end user logs into their already registered device, they would be able to authorize the code.
Server SDK: .authorizeCode(code, purpose, code_type, user_id)
This returns confirmation the code was authorized, as well as the time the operation was completed.
Optional: Invalidate Code
It may be desired to provide the user the ability to deny the addition of a new device. If so, the code generated in Step 2 can be invalidated.
Server SDK: .invalidateAllCodes(purpose, code_type, user_id)
Step 5: Add New Credential
The authorized code can now be used to add the new device as an authenticator. The user has the option to specify a credential name for their new device at this point. If no credential name is specified, a default name will be populated based on the user agent.
Client SDK: .addFido2Credential(user_id, code, code_type, credential_name)
This returns a jwt containing the id_token. At this point, the user is now authenticated on the new device and that device has been added as an authenticator.
Calling APIs Directly
Step 1: Retrieve user_id
The code generation functionality requires the user_id
rather than the username. In the case where you have not stored the user_id
, you can call the below endpoint to get the user_id
.
The required scope of the service token for this request is users.retrieve
Request:
POST /api/native/manage/users/retrieve
Header:
Authorization: Bearer {authorization_token}
Body:
{
"username": "string"
}
The response is the user’s profile information (including the user_id
) for the provided username.
Step 2: Generate Code
Once you have the user_id
, you must generate a code. This code is what ties the new device and existing device together. To understand which code_type
is best for your use case, please see our guide on choosing a code type.
The scope of the authorization token is codes.generate
Request:
POST /api/native/codes/{type}/generate
Header:
Authorization: Bearer {authorization_token}
Body:
{
"client_id": <dashboard-generated client_id>,
"user_id": <user_id_to_be_authenticated>,
"purpose": "add_credential",
"authorize": <bool>
}
Where:
client_id
: generated on the LoginID dashboarduser_id
: ID of an already registered user. In this case it would be theuser_id
of the person trying to add a new devicepurpose
: the reason for generating a code. In this case it would always beadd_credential
authorize
: Whether the request to generate the code is pre-authorized. In this case it would be false as the new device has not yet been authorized
Response:
{
"code": <string>,
"expires_at": <ISO8061 timestamp>
"is_authorized": false
}
Step 3: Notify User
LoginID does not send the code to the user, as we understand it is important for you to maintain direct contact with your clients. Depending on your desired user experience, you can use the code returned in Step 2 in a variety of ways. For adding a new authenticator, this is frequently done through a QR code or push notification to the already registered device.
Step 4: Authorize Code
The code generated in Step 2 has to be authorized before being able to be used to add the new authenticator. This should be done from an end user’s previously registered device. After the end user logs into their already registered device, they would be able to authorize the code.
The scope of the authorization token is codes.authorize
Request:
POST /api/native/codes/{type}/authorize
Authorization:
Authorization: Bearer {authorization_token}
Body:
{
"client_id": <dashboard-generated client_id>,
"user_id": <user_id to be authenticated>,
"purpose": "add_credential",
"code": <user-entered code as string>
}
Response:
{
"expires_at": <ISO8061 timestamp>,
"is_authorized": true
}
The user_id is returned in the JWT in the /authenticate/complete call.
Optional: Invalidate Code
It may be desired to provide the user the ability to deny the addition of a new device. If so, the code generated in Step 2 can be invalidated.
The scope of the authorization token is codes.invalidate
Request:
POST /api/native/codes/{code_type}/invalidate-all
Authorization:
Authorization: Bearer {authorization_token}
Body:
{
"client_id": <dashboard-generated client_id>,
"user_id": <user_id_to_be_authenticated>,
"purpose": "add_credential"
}
Response:
{
"deleted_at": <ISO8061 timestamp>
}
Step 5: Initiate Addition of New Authenticator
The authorized code can now be used to add the new device as an authenticator. The user has the option to specify a credential name for their new device at this point. If no credential name is specified, a default name will be populated based on the user agent.
The scope of the authorization token is credentials.add
Request:
POST /credentials/fido2/init/code
Authorization:
Authorization: Bearer {authorization_token}
Body:
{
"client_id": "string",
"user_id": "string",
"authentication_code": {
"code": <code from Step 1 as string>,
"type": <type from Step 1 as string>
}
}
This call should be made by the front end, but requires an authorization token.
The response will be the standard registration initialization payload (i.e. same as /register/fido2/init
).
Step 6: Credential Addition Completion
Once the credential addition flow has been initialized, the user has the ability to enter a custom device name. After entering the device name, this would be submitted on the /credentials/fido2/complete
call. The payload structure follows the same pattern as the normal registration flow.
After a successful credential addition, similar to the registration flow, a JWT will be returned indicating the successful registration of the new authenticator.