Authenticator Apps (TOTP) via Backend SDKs
This guide is meant for developers that are NOT using Descope on the frontend to design login screens and authentication methods.
If you'd like to use Descope Flows, Quick Start should be your starting point. If you'd like to use our Client SDKs, refer to our Client SDK docs.
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.
Backend SDK
Install SDK
npm i --save @descope/node-sdkpip3 install descopego get github.com/descope/go-sdk// Include the following in your `pom.xml` (Maven)
<dependency>
<artifactId>java-sdk</artifactId>
<groupId>com.descope</groupId>
<version>sdk-version</version> // Check https://github.com/descope/descope-java/releases for the latest versions
</dependency>gem install descopecomposer require descope/descope-phpdotnet add package descopeImport and initialize SDK
import DescopeClient from '@descope/node-sdk';
try{
// baseUrl="<URL>" // When initializing the Descope client, you can also configure the baseUrl ex: https://auth.company.com - this is useful when you utilize a custom domain within your Descope project.
const descopeClient = DescopeClient({ projectId: '__ProjectID__' });
} catch (error) {
// handle the error
console.log("failed to initialize: " + error)
}from descope import (
REFRESH_SESSION_TOKEN_NAME,
SESSION_TOKEN_NAME,
AuthException,
DeliveryMethod,
DescopeClient,
AssociatedTenant,
RoleMapping,
AttributeMapping,
LoginOptions
)
try:
# You can configure the baseURL by setting the env variable Ex: export DESCOPE_BASE_URI="https://auth.company.com - this is useful when you utilize custom domain within your Descope project."
descope_client = DescopeClient(project_id='__ProjectID__')
except Exception as error:
# handle the error
print ("failed to initialize. Error:")
print (error)import "github.com/descope/go-sdk/descope"
import "github.com/descope/go-sdk/descope/client"
// Utilizing the context package allows for the transmission of context capabilities like cancellation
// signals during the function call. In cases where context is absent, the context.Background()
// function serves as a viable alternative.
// Utilizing context within the Descope GO SDK is supported within versions 1.6.0 and higher.
import (
"context"
)
// DescopeBaseURL // within the client.Config, you can also configure the baseUrl ex: https://auth.company.com - this is useful when you utilize a custom domain within your Descope project.
descopeClient, err := client.NewWithConfig(&client.Config{ProjectID:"__ProjectID__"})
if err != nil {
// handle the error
log.Println("failed to initialize: " + err.Error())
}import com.descope.client.Config;
import com.descope.client.DescopeClient;
var descopeClient = new DescopeClient(Config.builder().projectId("__ProjectID__").build());require 'descope'
@project_id = ENV['__ProjectID__']
@client = Descope::Client.new({ project_id: @project_id})require 'vendor/autoload.php';
use Descope\SDK\DescopeSDK;
$descopeSDK = new DescopeSDK([
'projectId' => $_ENV['__ProjectID__'],
]);// appsettings.json
{
"Descope": {
"ProjectId": "__ProjectID__",
"ManagementKey": "DESCOPE_MANAGEMENT_KEY"
}
}
// Program.cs
using Descope;
using Microsoft.Extensions.Configuration;
// ... In your setup code
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var descopeProjectId = config["Descope:ProjectId"];
var descopeManagementKey = config["Descope:ManagementKey"];
var descopeConfig = new DescopeConfig(projectId: descopeProjectId);
var descopeClient = new DescopeClient(descopeConfig)
{
ManagementKey = descopeManagementKey,
};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 resp = await descopeClient.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.
user = { "name": "Joe Person", "phone": "+15555555555", "email": "email@company.com"}
# login_id: email or phone - becomes the login_id or the user from here on and also used for delivery
login_id = "email@company.com"
try:
resp = descope_client.totp.sign_up(login_id=login_id, user=user)
print ("Successfully initialized TOTP signup.")
print (resp)
except AuthException as error:
print ("Failed to initialize TOTP signup")
print ("Status Code: " + str(error.status_code))
print ("Error: " + str(error.error_message))// Args:
// ctx: context.Context - Application context for the transmission of context capabilities like
// cancellation signals during the function call. In cases where context is absent, the context.Background()
// function serves as a viable alternative.
// Utilizing context within the Descope GO SDK is supported within versions 1.6.0 and higher.
ctx := context.Background()
// loginID: email, phone or any other unique value - becomes the loginID for the user from here on
loginID := "email@company.com"
// user: Optional user object to populate new user information.
user := &auth.User{Name:"Joe", Email:"email@company.com", Phone:"+15555555555"}
resp, err := descopeClient.Auth.TOTP.SignUp(ctx, loginID, user)
if (err != nil){
fmt.Println("Failed to initialize TOTP signup: ", err)
} else {
fmt.Println("Successfully initialized TOTP signup: ", resp)
}// Every user must have a loginID. All other user information is optional
String loginId = "email@company.com"
User user = User.builder()
.name("Joe Person")
.phone("+15555555555")
.email(loginId)
.build();
TOTPService ts = descopeClient.getAuthenticationServices().getTOTPService();
try {
TOTPResponse resp = ts.signUp(loginId, user);
} catch (DescopeException de) {
// Handle the error
}
// Use one of the provided options to have the user add their credentials to the authenticator
// resp.getProvisioningURL()
// resp.getImage()
// resp.getKey()# Every user must have a login ID. All other user information is optional
email = 'desmond@descope.com'
user = {name: 'Desmond Copeland', phone: '+15555555555', email: 'someone@example.com'}
totp_response = descope_client.totp_sign_up(method: Descope::Mixins::Common::DeliveryMethod::EMAIL
, login_id: 'someone@example.com', user: user)
# Use one of the provided options to have the user add their credentials to the authenticator
provisioning_url = totp_response['provisioningURL']
image = totp_response['image']
key = totp_response['key']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 resp = await descopeClient.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:
# login_id: email or phone - must be same as provided at the time of signup.
login_id = "email@company.com"
# code: code entered by the user from the authenticator application.
code = "xxxx"
# login_options (LoginOptions): this allows you to configure behavior during the authentication process.
login_options = {
"stepup": false,
"mfa": false,
"custom_claims": {"claim": "Value1"},
"template_options": {"option": "Value1"}
}
# refresh_token (optional): the user's current refresh token in the event of stepup/mfa
refresh_token = "xxxx"
# audience (str | Iterable[str] | None): Optional audience to validate against the session token's aud claim
audience = "xxxx"
try:
resp = descope_client.totp.sign_in_code(login_id=login_id, code=verify_code, login_options=login_options, refresh_token=refresh_token, audience=audience)
print ("Successfully signed in via TOTP.")
print (resp)
except AuthException as error:
print ("Failed to Sign-In via TOTP")
print ("Status Code: " + str(error.status_code))
print ("Error: " + str(error.error_message))// Args:
// ctx: context.Context - Application context for the transmission of context capabilities like
// cancellation signals during the function call. In cases where context is absent, the context.Background()
// function serves as a viable alternative.
// Utilizing context within the Descope GO SDK is supported within versions 1.6.0 and higher.
ctx := context.Background()
// loginID: mail or phone - must be same as provided at the time of signup.
loginID := "email@company.com"
// code (string): code entered by the user from the authenticator application.
code := "xxxxxx"
// r: HttpRequest for the update call. This request should contain refresh token for the authenticated user.
// loginOptions: this allows you to configure behavior during the authentication process.
loginOptions := &descope.LoginOptions{
Stepup: true,
MFA: true,
CustomClaims: map[string]any{}{"test": "testClaim"},
TemplateOptions: map[string]any{"option": "Value1"}
}
// w: ResponseWriter to update with correct session details. You can return this to your client for setting the cookies which are used for session validation
authInfo, err := descopeClient.Auth.TOTP.SignInCode(ctx, loginID, code, r, loginOptions, w)
if (err != nil){
fmt.Println("Failed to Sign-In via TOTP: ", err)
} else {
fmt.Println("Successfully signed in via TOTP: ", authInfo)
}// The optional `w http.ResponseWriter` adds the session and refresh cookies to the response automatically.
// Otherwise they're available via authInfo
TOTPService ts = descopeClient.getAuthenticationServices().getTOTPService();
var loginOptions = LoginOptions.builder()
var loginOptions = LoginOptions.builder()
.stepUp(true)
.mfa(true)
.customClaims(
new HashMap<String, Object>() {{
put("custom-key1", "custom-value1");}}
)
.templateOptions(
new HashMap<String, String>() {{
put("option", "Value1");}}
)
.build();
var code = "xxx";
try {
AuthenticationInfo info = ts.signInCode(loginId, code, loginOptions);
} catch (DescopeException de) {
// Handle the error
}jwt_response = descope_client.totp_sign_in_code(
login_id: 'someone@example.com',
code: '123456' # Code from authenticator app
)
session_token = jwt_response[Descope::Mixins::Common::SESSION_TOKEN_NAME].fetch('jwt')
refresh_token = jwt_response[Descope::Mixins::Common::REFRESH_SESSION_TOKEN_NAME].fetch('jwt')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"
// refresh_token: string with the refresh token of the user. This should be extracted from cookies sent with the query.
const refreshToken = "xxxxxxxx"
const resp = await descopeClient.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:
# login_id: email, phone or username for the authenticated user.
login_id = "email@company.com"
# refresh_token: string with the refresh token of the user. This should be extracted from cookies sent with the query.
refresh_token: "xxxxxxx"
try:
resp = descope_client.totp.update_user(login_id=login_id, refresh_token=verify_code)
print ("Successfully initialized updating user's TOTP.")
print (resp)
except AuthException as error:
print ("Failed to initialized updating user's TOTP")
print ("Status Code: " + str(error.status_code))
print ("Error: " + str(error.error_message))// Args:
// ctx: context.Context - Application context for the transmission of context capabilities like
// cancellation signals during the function call. In cases where context is absent, the context.Background()
// function serves as a viable alternative.
// Utilizing context within the Descope GO SDK is supported within versions 1.6.0 and higher.
ctx := context.Background()
// loginID: email, phone or username of the authenticated user
loginID := "email@company.com"
// r: HttpRequest for the update call. This request should contain refresh token for the authenticated user.
resp, err := descopeClient.Auth.TOTP.UpdateUser(ctx, loginID, r)
if (err != nil){
fmt.Println("Failed to initialized updating user's TOTP: ", err)
} else {
fmt.Println("Successfully initialized updating user's TOTP: ", resp)
}// loginId: email, phone or username of the authenticated user
String loginId = "email@company.com";
// refreshToken: string with the refresh token of the user. This should be extracted from cookies sent with the query.
String refreshToken = "xxxxxxx"
TOTPService ts = descopeClient.getAuthenticationServices().getTOTPService();
try {
AuthenticationInfo info = ts.updateUser(loginId, refreshToken);
} catch (DescopeException de) {
// Handle the error
}descope_client.totp_add_update_key(login_id: login_id, refresh_token: refresh_token)Session Validation
The final step of completing the authentication with Descope is to validate the user session. Descope provides rich session management capabilities, including configurable session timeouts and logout functions. You can find the details and sample code for backend session validation here.
Checkpoint
Your application is now integrated with Descope. Please test with sign-up or sign-in use case.