Authenticator Apps (TOTP) via Client SDKs
This guide is meant for developers that are NOT using Descope Flows to design login screens and authentication methods.
If you'd like to use Descope Flows, Quick Start should be your starting point.
Descope supports validating sign-up and sign-ins via Authenticator Applications which provide a Time-based One-time Password (TOTP). Google Authenticator, Microsoft Authenticator, and Authy are examples of authenticator apps. Descope generates the required QR code or key (also called a secret or seed) in order to configure new a new Authenticator.
Client SDK
For information on how to install and initialize the Descope Client SDK, please refer to the Client SDK Installation Guide.
User Sign-Up
The first step for implementing TOTP authentication is sign-up. In this step the user registers their TOTP app with the authentication service. Descope will generate a TOTP key (also called a secret or seed) that will be entered into the end user's authenticator app so that TOTP codes can be successfully verified. The new end user will be registered after the full TOTP sign-up flow has been successfully completed.
// Args:
// user: Optional user object to populate new user information.
const user = { "name": "Joe Person", "phone": "+15555555555", "email": "email@company.com"}
// loginId: email or phone - becomes the unique ID for the user from here on and also used for delivery
const loginId = "email@company.com"
const descopeSdk = useDescope();
const resp = await descopeSdk.totp.signUp(loginId, user);
if (!resp.ok) {
console.log("Failed to initialize TOTP signup")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized TOTP signup.")
console.log(resp.data)
}// Args:
// user: Optional user object to populate new user information.
const user = { "name": "Joe Person", "phone": "+15555555555", "email": "email@company.com"}
// loginId: email or phone - becomes the unique ID for the user from here on and also used for delivery
const loginId = "email@company.com"
const resp = await descopeSdk.totp.signUp(loginId, user);
if (!resp.ok) {
console.log("Failed to initialize TOTP signup")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized TOTP signup.")
console.log(resp.data)
}<script>
let descopeSdk = Descope({projectId: '__ProjectID__', persistTokens: true, autoRefresh: true });
async function totpSignUp() {
// Args:
// user: Optional user object to populate new user information.
const user = { "name": document.getElementById("name").value, "phone": document.getElementById("phone").value, "email": document.getElementById("email").value}
// loginId: email or phone - becomes the loginId for the user from here on and also used for delivery
const loginId = document.getElementById("email").value
const resp = await descopeSdk.totp.signUp(loginId, user);
if (!resp.ok) {
window.alert("Failed to initialize signup flow\nStatus Code: " + resp.code
+ "\nError Code: " + resp.error.errorCode + "\nError Description: " + resp.error.errorDescription + "\nError Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized signup flow")
window.location.replace("./totpDisplay?image=" + encodeURIComponent(resp.data.image) + "&key=" + encodeURIComponent(resp.data.key) + "&provisioningURL=" + encodeURIComponent(resp.data.provisioningURL) + "&loginId=" + encodeURIComponent(loginId))
}
}
</script>User Sign-In / Verify
For signing in, your application client must prompt the user for loginId, such as email or phone, and the code from the
authenticator application. Your client will then call the verify function. Upon successful verification, the user will be
logged in and the response will include the JWT information.
// Args:
// loginId: email or phone - must be same as provided at the time of signup.
const loginId = "email@company.com"
// code: code entered by the user from the authenticator application.
const code = "xxxx"
// loginOptions (LoginOptions): this allows you to configure behavior during the authentication process.
const loginOptions = {
"stepup": false,
"mfa": false,
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
// refreshToken (optional): the user's current refresh token in the event of stepup/mfa
const descopeSdk = useDescope();
const resp = await descopeSdk.totp.verify(loginId, code, loginOptions);
if (!resp.ok) {
console.log("Failed to Sign-In via TOTP")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully signed in via TOTP. " + JSON.stringify(resp.data))
}// Args:
// loginId: email or phone - must be same as provided at the time of signup.
const loginId = "email@company.com"
// code: code entered by the user from the authenticator application.
const code = "xxxx"
// loginOptions (LoginOptions): this allows you to configure behavior during the authentication process.
const loginOptions = {
"stepup": false,
"mfa": false,
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
// refreshToken (optional): the user's current refresh token in the event of stepup/mfa
const resp = await descopeSdk.totp.verify(loginId, code, loginOptions);
if (!resp.ok) {
console.log("Failed to Sign-In via TOTP")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully signed in via TOTP. " + JSON.stringify(resp.data))
}<script>
let descopeSdk = Descope({projectId: '__ProjectID__', persistTokens: true, autoRefresh: true });
async function totpVerify(thisLoginId) {
// Args:
// loginId (str): The loginId of the user being validated
const loginId = thisLoginId
// code (str): The authorization code enter by the end user during signup/signin
const code = document.getElementById("totpCode").value
// loginOptions (LoginOptions): this allows you to configure behavior during the authentication process.
const loginOptions = {
"stepup": false,
"mfa": false,
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
// refreshToken (optional): the user's current refresh token in the event of stepup/mfa
const resp = await descopeSdk.totp.verify(loginId, code, loginOptions);
if (!resp.ok) {
window.alert("Failed to verify TOTP code\nStatus Code: " + resp.code
+ "\nError Code: " + resp.error.errorCode + "\nError Description: " + resp.error.errorDescription + "\nError Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully verified TOTP ")
window.location.replace("../loggedIn.html?userId=" + encodeURIComponent(loginId) + "&sessionJwt=" + resp.data.sessionJwt)
}
}
</script>Update User
The update user call is used when you would like to associate a new authenticator method with an existing and authenticated user. You need to pass the refresh token or http request of an authenticated user. The update will work only if the user is authenticated.
// Args:
// loginId: email, phone or username of the authenticated user
const loginId = "email@company.com"
// refreshToken: string with the refresh token of the user. This should be extracted from cookies sent with the query.
const refreshToken = "xxxxxxxx"
const descopeSdk = useDescope();
const resp = await descopeSdk.totp.update(loginId, refreshToken)
if (!resp.ok) {
console.log("Failed to initialized updating user's TOTP")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized updating user's TOTP: " + resp.data)
}// Args:
// loginId: email, phone or username of the authenticated user
const loginId = "email@company.com"
// refreshToken: string with the refresh token of the user. This should be extracted from cookies sent with the query.
const refreshToken = "xxxxxxxx"
const resp = await descopeSdk.totp.update(loginId, refreshToken)
if (!resp.ok) {
console.log("Failed to initialized updating user's TOTP")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized updating user's TOTP: " + resp.data)
}<script>
let descopeSdk = Descope({projectId: '__ProjectID__', persistTokens: true, autoRefresh: true });
async function updateTOTP() {
// Args:
// loginId: email, phone or username of the authenticated user
const loginId = document.getElementById("loginId").value
// refreshToken: string with the refresh token of the user. This should be extracted from cookies sent with the query.
const refreshToken = document.getElementById("refreshToken").value
const resp = await descopeSdk.totp.update(loginId, refreshToken)
if (!resp.ok) {
window.alert("Failed to initialize TOTP update\nStatus Code: " + resp.code
+ "\nError Code: " + resp.error.errorCode + "\nError Description: " + resp.error.errorDescription + "\nError Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully initialized TOTP update")
window.location.replace("./totpDisplay?image=" + encodeURIComponent(resp.data.image) + "&key=" + encodeURIComponent(resp.data.key) + "&provisioningURL=" + encodeURIComponent(resp.data.provisioningURL) + "&loginId=" + encodeURIComponent(loginId))
}
}
</script>Checkpoint
Your application is now integrated with Descope. Please test with sign-up or sign-in use case.