Step-up Authentication
Step-up authentication is a security mechanism that allows you to add additional authentication requirements for sensitive operations in your application. It works by requiring users to re-authenticate when accessing high-risk features, even if they're already logged in.
How Step-up Authentication Works
When a user needs to perform a sensitive operation (like making a purchase or accessing personal data), you can trigger step-up authentication. This requires the user to verify their identity again, typically using a stronger authentication method than their initial login.
After successful step-up authentication, the user's session token is updated with a su (step-up) claim set to true. This allows your application to verify that the user has completed the additional authentication step. This stepped-up token will be valid for the duration of the
Step Up Token Timeout defined in Project Settings.
{
"amr": ["xxxx"],
"drn": "xx",
"exp": xxxxx,
"iat": xxxxx,
"iss": "xxxxx",
"rexp": "xxxxx",
"su": true,
"sub": "xxxxx"
}Use Cases
Step-up authentication is particularly useful for:
- Financial transactions (e.g., making purchases, transferring money)
- Accessing sensitive personal information
- Administrative operations
- Changing account settings
- Any high-risk operation that requires additional verification
Implementation Options
You can implement step-up authentication using our Flows, Client SDKs, or Backend SDKs.
Option 1: Using Descope Flows
Descope provides a pre-built step-up flow that you can easily integrate into your application. This flow:
- Loads the user from their refresh token
- Marks the flow as a step-up authentication
- Presents authentication options (magic link, passkeys, social login)
- Updates the session token upon successful authentication

You can integrate a step-up flow in your application in the same way you would integrate a regular authentication flow. After the step-up flow succeeds,
the user's JWT will be updated with the su claim.
Option 2: Using Client SDKs
To perform step-up authentication using our Client SDKs, you use the same "Sign In" or "Sign Up Or In" functions as you would for regular authentication. You just have to specify that you are performing step-up using the Login Options parameter.
The below example implements step-up authentication via OTP Sign-In after the user has already authenticated using another authentication method.
In loginOptions, stepup is set to true, indicating that this is a step-up authentication action. On success of the sign in function, the user's JWT will include the su claim.
// Args:
// loginId: email or phone - becomes the loginId for the user from here on and also used for delivery
const loginId = "email@company.com"
// deliveryMethod: Delivery method to use to send OTP. Supported values include "email", "voice, or "sms"
const deliveryMethod = "email"
// loginOptions: login options for MFA, stepup, or custom claims. Ex: {stepup: true, mfa: false, customClaims: {}}
const loginOptions = {stepup: true}
// token: refresh token from the successful sign-in of the user
const token = "xxxxxx"
const resp = await descopeClient.otp.signIn[deliveryMethod](loginId, loginOptions, token);
if (!resp.ok) {
console.log("Failed to initialize step-up flow")
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 step-up flow")
}Option 3: Using Backend SDKs
To perform step-up authentication using our Backend SDKs, you use the same "Sign In" or "Sign Up Or In" functions as you would for regular authentication. You just have to specify that you are performing step-up using the Login Options parameter.
The below example implements step-up authentication via OTP Sign In after the user has already authenticated using another authentication method.
In loginOptions, stepup is set to true, indicating that this is a step-up authentication action. On success of the sign in function, the user's JWT will include the su claim.
// Args:
// deliveryMethod: Delivery method to use to send OTP. Supported values include "email", "voice, or "sms"
const deliveryMethod = "email"
// loginId: email or phone - email or phone - the loginId for the user
const loginId = "email@company.com"
// loginOptions: login options for MFA, stepup, or custom claims. Ex: {stepup: true, mfa: false, customClaims: {}}
const loginOptions = {stepup: true}
// token: refresh token from the successful sign-in of the user
const token = "xxxx"
var resp = await descopeClient.otp.signIn[delivery_method](loginId, loginOptions, token);
if (!resp.ok) {
console.log("Failed to initialize step-up flow")
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 step-up flow")
console.log(resp.data)
}# Args:
# delivery_method: Delivery method to use to send OTP. Supported values include DeliveryMethod.SMS, DeliveryMethod.Voice, or DeliveryMethod.EMAIL
delivery_method = DeliveryMethod.EMAIL
# login_id: email or phone - email or phone - the loginId for the user
login_id = "email@company.com"
# login_options (LoginOptions): login options for MFA, stepup, or custom claims. Ex: LoginOptions(stepup: True, mfa: False, customClaims: {})
login_options = LoginOptions(stepup=True)
# refresh_token: refresh token from the successful sign-in of the user
refresh_token = "xxxxx"
try:
resp = descope_client.otp.sign_in(method=delivery_method, login_id=login_id, login_options=login_options, refresh_token=refresh_token)
print ("Successfully initialized Step-up flow")
except AuthException as error:
print ("Failed to initialize Step-up flow")
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()
// deliveryMethod: Delivery method to use to send OTP. Supported values include descope.MethodEmail, descope.MethodVoice, or descope.MethodSMS
deliveryMethod := descope.MethodEmail
// loginID: email or phone - the loginId for the user
loginID := "email@company.com"
// r: HttpRequest for the update call. This request should contain refresh token for the authenticated user.
// loginOptions: Optional login options for MFA, stepup, or custom claims.
loginOptions := &descope.LoginOptions{Stepup: true}
err := descopeClient.Auth.OTP().SignIn(ctx, deliveryMethod, loginID, r, loginOptions)
if (err != nil){
fmt.Println("Failed to initialize step-up flow: ", err)
} else {
fmt.Println("Successfully initialized step-up flow")
}// 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();
var loginOptions = LoginOptions.builder()
.stepUp(true)
.build();
OTPService otps = descopeClient.getAuthenticationServices().getOtpService();
try {
String maskedAddress = otps.signIn(DeliveryMethod.EMAIL, loginId, loginOptions);
} catch (DescopeException de) {
// Handle the error
}Step-Up Validation
To validate that the session was successfully stepped up, you can utilize the Backend SDKs to validate the session and check that the su claim is true.
// Args:
// sessionToken (str): The session token, which contains the signature that will be validated
const sessionToken="xxxx"
try {
const authInfo = await descopeClient.validateSession(sessionToken);
console.log("Successfully validated user session:");
console.log(authInfo);
if ("su" in authInfo.token) {
console.log("Session is stepped up.");
}
} catch (error) {
console.log ("Could not validate user session " + error);
}# Args:
# session_token (str): The session token, which contains the signature that will be validated
session_token="xxxx"
try:
jwt_response = descope_client.validate_session(session_token=session_token)
print ("Successfully validated user session:")
print (jwt_response)
if "su" in resp:
if resp["su"] == True:
print("Session is stepped up.")
else:
print("Session is not stepped up.")
except Exception as error:
print ("Could not validate user session. Error:")
print (error)// 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()
// sessionToken (str): The session token, which contains the signature that will be validated
sessionToken = "xxxx"
authorized, userToken, err := descopeClient.Auth.ValidateSessionWithToken(ctx, sessionToken)
if (err != nil){
fmt.Println("Could not validate user session: ", err)
} else {
fmt.Println("Successfully validated user session: ", userToken, authorized)
val, ok := userToken.Claims["su"]
if (ok == true && val == true) {
fmt.Println("Session is stepped up.")
}
}// Validate the session. Will return an error if expired
AuthenticationService as = descopeClient.getAuthenticationServices().getAuthenticationService();
try {
Token t = as.validateSessionWithToken(sessionToken);
Boolean su = (Boolean) t.getClaims().get("su");
if (su != null && su) {
System.out.println("Session is stepped up.");
}
} catch (DescopeException de) {
// Handle the unauthorized error
}Resources
The B2C Retail Sample App, Tee-Hee Tees demonstrates a practical implementation of step-up authentication. Users can browse products and add items to their cart with basic authentication. When the user proceeds to checkout, step-up authentication is required.
Check out our learning center article for more examples, use cases, and guidelines on implementing step-up authentication.
Adaptive MFA
Learn how to implement adaptive MFA within your Descope flows. This guide has examples of trusted device, IP reputation, and impossible traveler adaptive MFA.
Fingerprinting
This guide explains the fingerprinting capabilities available in Descope, including device fingerprinting, risk-based authentication, and bot detection.