# Getting started with actions Source: https://docs.authsignal.com/actions-rules/actions/getting-started Learn how Authsignal actions enable risk-based authentication and how to implement them in your app. Authsignal actions are the building blocks that let you create contextual, risk-based authentication flows. Actions represent specific user activities or events in your application that might require authentication. These can range from routine operations like sign-in to high-risk activities like withdrawing funds, changing account settings, or making large purchases. ## Creating an action To create an action, navigate to the [actions page](https://portal.authsignal.com/actions) and click the **Create a new action** button. Creating actions in the Authsignal dashboard ## Core components of an action ### 1. Action outcomes Every action in Authsignal results in one of four possible outcomes that determine how to handle the user's request: * **ALLOW:** Let the action proceed without additional authentication * **CHALLENGE:** Require the user to complete an authentication challenge * **REVIEW:** Place the action in a queue for manual review * **BLOCK:** Prevent the action from proceeding entirely Action outcomes Each action has a configurable default outcome that determines what happens when no rules are triggered. However, when you create rules for an action, those rules can override the default outcome. ### 2. Rules The conditional logic that determines which outcome to apply based on risk factors and context. Rules can override the action's default outcome. When you track an action, you provide the context needed for evaluation: ```js theme={null} await authsignal.track({ userId: "0272c312-e181-4cad-a494-43647b503a0a", // Unique identifier for the user action: "withdraw-funds", // The action code (what the user is doing) attributes: { // Contextual information for rule evaluation deviceId: "555c17e1-3837-4f13-81bb-131e5597e168", ipAddress: "203.0.113.42", userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", }, }); ``` ## Setting up your action ### 1. Tracking an action In your app's backend, use an [Authsignal Server SDK](/sdks/server) to track an action which represents what your user is doing (e.g. `withdraw-funds`). This step will return a token which can be passed to a client SDK to perform a challenge for that user. ```ts Node.js theme={null} // Track an action on your backend const result = await authsignal.track({ userId: "0272c312-e181-4cad-a494-43647b503a0a", action: "withdraw-funds", attributes: { deviceId: "555c17e1-3837-4f13-81bb-131e5597e168", ipAddress: "203.0.113.42", }, }); // Handle different action outcomes if (result.state === "CHALLENGE_REQUIRED") { // User needs to complete a challenge return { token: result.token, }; } else if (result.state === "ALLOW") { // Proceed with the action return { success: true }; } else if (result.state === "REVIEW") { // Action requires manual review return { status: "under_review", message: "Your request is being reviewed" }; } else if (result.state === "BLOCK") { // Action is blocked return { error: "This action cannot be completed for security reasons" }; } ``` ```csharp C# theme={null} // Track an action on your backend var request = new TrackRequest( UserId: "0272c312-e181-4cad-a494-43647b503a0a", Action: "withdraw-funds", Attributes: new TrackAttributes( DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168", IpAddress: "203.0.113.42" ) ); var result = await authsignal.Track(request); // Handle different action outcomes if (result.State == "CHALLENGE_REQUIRED") { // User needs to complete a challenge return new { Token = result.Token }; } else if (result.State == "ALLOW") { // Proceed with the action return new { Success = true }; } else if (result.State == "REVIEW") { // Action requires manual review return new { Status = "under_review", Message = "Your request is being reviewed" }; } else if (result.State == "BLOCK") { // Action is blocked return new { Error = "This action cannot be completed for security reasons" }; } ``` ```java Java theme={null} // Track an action on your backend TrackRequest request = new TrackRequest(); request.userId = "0272c312-e181-4cad-a494-43647b503a0a"; request.action = "withdraw-funds"; request.attributes = new TrackAttributes(); request.attributes.deviceId = "555c17e1-3837-4f13-81bb-131e5597e168"; request.attributes.ipAddress = "203.0.113.42"; TrackResponse result = authsignal.track(request).get(); // Handle different action outcomes if ("CHALLENGE_REQUIRED".equals(result.state)) { // User needs to complete a challenge Map response = new HashMap<>(); response.put("token", result.token); return response; } else if ("ALLOW".equals(result.state)) { // Proceed with the action return Map.of("success", true); } else if ("REVIEW".equals(result.state)) { // Action requires manual review return Map.of( "status", "under_review", "message", "Your request is being reviewed" ); } else if ("BLOCK".equals(result.state)) { // Action is blocked return Map.of( "error", "This action cannot be completed for security reasons" ); } ``` ```ruby Ruby theme={null} # Track an action on your backend result = Authsignal.track({ user_id: "0272c312-e181-4cad-a494-43647b503a0a", action: "withdraw-funds", attributes: { device_id: "555c17e1-3837-4f13-81bb-131e5597e168", ip_address: "203.0.113.42", } }) # Handle different action outcomes case result[:state] when "CHALLENGE_REQUIRED" # User needs to complete a challenge { token: result[:token] } when "ALLOW" # Proceed with the action { success: true } when "REVIEW" # Action requires manual review { status: "under_review", message: "Your request is being reviewed" } when "BLOCK" # Action is blocked { error: "This action cannot be completed for security reasons" } end ``` ```python Python theme={null} # Track an action on your backend result = authsignal.track( user_id="0272c312-e181-4cad-a494-43647b503a0a", action="withdraw-funds", attributes={ "deviceId": "555c17e1-3837-4f13-81bb-131e5597e168", "ipAddress": "203.0.113.42" } ) # Handle different action outcomes if result["state"] == "CHALLENGE_REQUIRED": # User needs to complete a challenge return { "token": result["token"] } elif result["state"] == "ALLOW": # Proceed with the action return {"success": True} elif result["state"] == "REVIEW": # Action requires manual review return { "status": "under_review", "message": "Your request is being reviewed" } elif result["state"] == "BLOCK": # Action is blocked return { "error": "This action cannot be completed for security reasons" } ``` ```php PHP theme={null} // Track an action on your backend $result = Authsignal::track([ 'userId' => "0272c312-e181-4cad-a494-43647b503a0a", 'action' => "withdraw-funds", 'attributes' => [ 'deviceId' => "555c17e1-3837-4f13-81bb-131e5597e168", 'ipAddress' => "203.0.113.42" ] ]); // Handle different action outcomes switch ($result["state"]) { case "CHALLENGE_REQUIRED": // User needs to complete a challenge return [ 'token' => $result["token"] ]; case "ALLOW": // Proceed with the action return ['success' => true]; case "REVIEW": // Action requires manual review return [ 'status' => "under_review", 'message' => "Your request is being reviewed" ]; case "BLOCK": // Action is blocked return [ 'error' => "This action cannot be completed for security reasons" ]; } ``` ```go Go theme={null} // Track an action on your backend result, err := client.Track( TrackRequest{ UserId: "0272c312-e181-4cad-a494-43647b503a0a", Action: "withdraw-funds", Attributes: &TrackAttributes{ DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168", IpAddress: "203.0.113.42", }, }, ) if err != nil { return err } // Handle different action outcomes switch result.State { case "CHALLENGE_REQUIRED": // User needs to complete a challenge return map[string]interface{}{ "token": result.Token, } case "ALLOW": // Proceed with the action return map[string]interface{}{"success": true} case "REVIEW": // Action requires manual review return map[string]interface{}{ "status": "under_review", "message": "Your request is being reviewed", } case "BLOCK": // Action is blocked return map[string]interface{}{ "error": "This action cannot be completed for security reasons", } } ``` ```ts Node.js theme={null} // Track an action on your backend const result = await authsignal.track({ userId: "0272c312-e181-4cad-a494-43647b503a0a", action: "withdraw-funds", attributes: { redirectUrl: "https://yourapp.com/callback", deviceId: "deviceId", ipAddress: "ipAddress", }, }); // Handle different action outcomes if (result.state === "CHALLENGE_REQUIRED") { // User needs to complete a challenge return { url: result.url, }; } else if (result.state === "ALLOW") { // Proceed with the action return { success: true }; } else if (result.state === "REVIEW") { // Action requires manual review return { status: "under_review", message: "Your request is being reviewed" }; } else if (result.state === "BLOCK") { // Action is blocked return { error: "This action cannot be completed for security reasons" }; } ``` ```csharp C# theme={null} // Track an action on your backend var request = new TrackRequest( UserId: "0272c312-e181-4cad-a494-43647b503a0a", Action: "withdraw-funds", Attributes: new TrackAttributes( RedirectUrl: "https://yourapp.com/callback", DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168", IpAddress: "203.0.113.42" ) ); var result = await authsignal.Track(request); // Handle different action outcomes if (result.State == "CHALLENGE_REQUIRED") { // User needs to complete a challenge return new { Url = result.Url }; } else if (result.State == "ALLOW") { // Proceed with the action return new { Success = true }; } else if (result.State == "REVIEW") { // Action requires manual review return new { Status = "under_review", Message = "Your request is being reviewed" }; } else if (result.State == "BLOCK") { // Action is blocked return new { Error = "This action cannot be completed for security reasons" }; } ``` ```java Java theme={null} // Track an action on your backend TrackRequest request = new TrackRequest(); request.userId = "0272c312-e181-4cad-a494-43647b503a0a"; request.action = "withdraw-funds"; request.attributes = new TrackAttributes(); request.attributes.redirectUrl = "https://yourapp.com/callback"; request.attributes.deviceId = "555c17e1-3837-4f13-81bb-131e5597e168"; request.attributes.ipAddress = "203.0.113.42"; TrackResponse result = authsignal.track(request).get(); // Handle different action outcomes if ("CHALLENGE_REQUIRED".equals(result.state)) { // User needs to complete a challenge Map response = new HashMap<>(); response.put("url", result.url); return response; } else if ("ALLOW".equals(result.state)) { // Proceed with the action return Map.of("success", true); } else if ("REVIEW".equals(result.state)) { // Action requires manual review return Map.of( "status", "under_review", "message", "Your request is being reviewed" ); } else if ("BLOCK".equals(result.state)) { // Action is blocked return Map.of( "error", "This action cannot be completed for security reasons" ); } ``` ```ruby Ruby theme={null} # Track an action on your backend result = Authsignal.track({ user_id: "0272c312-e181-4cad-a494-43647b503a0a", action: "withdraw-funds", attributes: { redirect_url: "https://yourapp.com/callback", device_id: "555c17e1-3837-4f13-81bb-131e5597e168", ip_address: "203.0.113.42", } }) # Handle different action outcomes case result[:state] when "CHALLENGE_REQUIRED" # User needs to complete a challenge { url: result[:url] } when "ALLOW" # Proceed with the action { success: true } when "REVIEW" # Action requires manual review { status: "under_review", message: "Your request is being reviewed" } when "BLOCK" # Action is blocked { error: "This action cannot be completed for security reasons" } end ``` ```python Python theme={null} # Track an action on your backend result = authsignal.track( user_id="0272c312-e181-4cad-a494-43647b503a0a", action="withdraw-funds", attributes={ "redirectUrl": "https://yourapp.com/callback", "deviceId": "555c17e1-3837-4f13-81bb-131e5597e168", "ipAddress": "203.0.113.42" } ) # Handle different action outcomes if result["state"] == "CHALLENGE_REQUIRED": # User needs to complete a challenge return { "url": result["url"] } elif result["state"] == "ALLOW": # Proceed with the action return {"success": True} elif result["state"] == "REVIEW": # Action requires manual review return { "status": "under_review", "message": "Your request is being reviewed" } elif result["state"] == "BLOCK": # Action is blocked return { "error": "This action cannot be completed for security reasons" } ``` ```php PHP theme={null} // Track an action on your backend $result = Authsignal::track([ 'userId' => "0272c312-e181-4cad-a494-43647b503a0a", 'action' => "withdraw-funds", 'attributes' => [ 'redirectUrl' => "https://yourapp.com/callback", 'deviceId' => "555c17e1-3837-4f13-81bb-131e5597e168", 'ipAddress' => "203.0.113.42" ] ]); // Handle different action outcomes switch ($result["state"]) { case "CHALLENGE_REQUIRED": // User needs to complete a challenge return [ 'url' => $result["url"] ]; case "ALLOW": // Proceed with the action return ['success' => true]; case "REVIEW": // Action requires manual review return [ 'status' => "under_review", 'message' => "Your request is being reviewed" ]; case "BLOCK": // Action is blocked return [ 'error' => "This action cannot be completed for security reasons" ]; } ``` ```go Go theme={null} // Track an action on your backend result, err := client.Track( TrackRequest{ UserId: "0272c312-e181-4cad-a494-43647b503a0a", Action: "withdraw-funds", Attributes: &TrackAttributes{ RedirectUrl: "https://yourapp.com/callback", DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168", IpAddress: "203.0.113.42", }, }, ) if err != nil { return err } // Handle different action outcomes switch result.State { case "CHALLENGE_REQUIRED": // User needs to complete a challenge return map[string]interface{}{ "url": result.Url, } case "ALLOW": // Proceed with the action return map[string]interface{}{"success": true} case "REVIEW": // Action requires manual review return map[string]interface{}{ "status": "under_review", "message": "Your request is being reviewed", } case "BLOCK": // Action is blocked return map[string]interface{}{ "error": "This action cannot be completed for security reasons", } } ``` ### 2. Challenging the user Challenging the user performing an action. In your frontend, call `setToken` with the client token obtained, then use the relevant SDK methods to progress the user through a challenge. ```js theme={null} // Set the token from the track result authsignal.setToken(token); // Show the appropriate challenge based on the user's enrolled methods const result = await authsignal.passkey.signIn({ action: "withdraw-funds", }); // Send the result token back to your server for validation if (result.token) { await validateChallenge(result.token); } ``` In your frontend, pass the url from the `track` call to the [Authsignal Web SDK](/sdks/client/web/setup) to launch an enrollment or re-authentication flow. ```js theme={null} // Launch the Pre-built UI authsignal.launch({ url: challengeUrl, mode: "popup", // or "redirect" }); ``` ### 3. Validating a challenge After the user completes the challenge, you'll receive a `token` that you can validate on your backend to verify the authentication result. For pre-built UI, this `token` is appended to your redirect URL as a query parameter, while for custom UI implementation, you'll get the `token` directly from the challenge completion result. Pass the token obtained from the challenge result to your backend and validate it server-side to complete authentication. ```ts Node.js theme={null} const request = { token: "eyJhbGciOiJ...", // Token from challenge completion }; const response = await authsignal.validateChallenge(request); if (response.state === "CHALLENGE_SUCCEEDED") { // The user completed the challenge successfully // Proceed with authenticated action or create authenticated session return { success: true, userId: response.userId }; } else { // The user did not complete the challenge successfully return { error: "Challenge validation failed" }; } ``` ```csharp C# theme={null} var request = new ValidateChallengeRequest( Token: "eyJhbGciOiJ..." // Token from challenge completion ); var response = await authsignal.ValidateChallenge(request); if (response.State == "CHALLENGE_SUCCEEDED") { // The user completed the challenge successfully // Proceed with authenticated action or create authenticated session return new { Success = true, UserId = response.UserId }; } else { // The user did not complete the challenge successfully return new { Error = "Challenge validation failed" }; } ``` ```java Java theme={null} ValidateChallengeRequest request = new ValidateChallengeRequest(); request.token = "eyJhbGciOiJ..."; // Token from challenge completion ValidateChallengeResponse response = authsignal.validateChallenge(request).get(); if ("CHALLENGE_SUCCEEDED".equals(response.state)) { // The user completed the challenge successfully // Proceed with authenticated action or create authenticated session return Map.of("success", true, "userId", response.userId); } else { // The user did not complete the challenge successfully return Map.of("error", "Challenge validation failed"); } ``` ```ruby Ruby theme={null} response = Authsignal.validate_challenge({ token: "eyJhbGciOiJ..." # Token from challenge completion }) if response[:state] == "CHALLENGE_SUCCEEDED" # The user completed the challenge successfully # Proceed with authenticated action or create authenticated session { success: true, user_id: response[:user_id] } else # The user did not complete the challenge successfully { error: "Challenge validation failed" } end ``` ```python Python theme={null} response = authsignal.validate_challenge( token="eyJhbGciOiJ..." # Token from challenge completion ) if response["state"] == "CHALLENGE_SUCCEEDED": # The user completed the challenge successfully # Proceed with authenticated action or create authenticated session return {"success": True, "userId": response["userId"]} else: # The user did not complete the challenge successfully return {"error": "Challenge validation failed"} ``` ```php PHP theme={null} $response = Authsignal::validateChallenge([ 'token' => "eyJhbGciOiJ..." // Token from challenge completion ]); if ($response["state"] === "CHALLENGE_SUCCEEDED") { // The user completed the challenge successfully // Proceed with authenticated action or create authenticated session return [ 'success' => true, 'userId' => $response["userId"] ]; } else { // The user did not complete the challenge successfully return ['error' => "Challenge validation failed"]; } ``` ```go Go theme={null} response, err := client.ValidateChallenge( ValidateChallengeRequest{ Token: "eyJhbGciOiJ...", // Token from challenge completion }, ) if err != nil { return err } if response.State == "CHALLENGE_SUCCEEDED" { // The user completed the challenge successfully // Proceed with authenticated action or create authenticated session return map[string]interface{}{ "success": true, "userId": response.UserId, } } else { // The user did not complete the challenge successfully return map[string]interface{}{ "error": "Challenge validation failed", } } ``` ```ts Node.js theme={null} const result = await authsignal.validateChallenge({ token: "eyJhbGciOiJ..." // Token from redirect URL query parameter }); if (result.state === "CHALLENGE_SUCCEEDED") { // The user completed the challenge successfully // Proceed with the action return { success: true }; } ``` ```csharp C# theme={null} var result = await authsignal.ValidateChallenge(new ValidateChallengeRequest( Token: "eyJhbGciOiJ..." // Token from redirect URL query parameter )); if (result.State == "CHALLENGE_SUCCEEDED") { // The user completed the challenge successfully // Proceed with the action return new { Success = true }; } ``` ```java Java theme={null} ValidateChallengeRequest request = new ValidateChallengeRequest(); request.token = "eyJhbGciOiJ..."; // Token from redirect URL query parameter ValidateChallengeResponse result = authsignal.validateChallenge(request).get(); if ("CHALLENGE_SUCCEEDED".equals(result.state)) { // The user completed the challenge successfully // Proceed with the action return Map.of("success", true); } ``` ```ruby Ruby theme={null} result = Authsignal.validate_challenge({ token: "eyJhbGciOiJ..." # Token from redirect URL query parameter }) if result[:state] == "CHALLENGE_SUCCEEDED" # The user completed the challenge successfully # Proceed with the action { success: true } end ``` ```python Python theme={null} result = authsignal.validate_challenge( token="eyJhbGciOiJ..." # Token from redirect URL query parameter ) if result["state"] == "CHALLENGE_SUCCEEDED": # The user completed the challenge successfully # Proceed with the action return {"success": True} ``` ```php PHP theme={null} $result = Authsignal::validateChallenge([ 'token' => "eyJhbGciOiJ..." // Token from redirect URL query parameter ]); if ($result["state"] === "CHALLENGE_SUCCEEDED") { // The user completed the challenge successfully // Proceed with the action return ['success' => true]; } ``` ```go Go theme={null} result, err := client.ValidateChallenge( ValidateChallengeRequest{ Token: "eyJhbGciOiJ...", // Token from redirect URL query parameter }, ) if err != nil { return err } if (result.State == "CHALLENGE_SUCCEEDED") { // The user completed the challenge successfully // Proceed with the action return map[string]interface{}{"success": true} } ``` # Implementing MFA Source: https://docs.authsignal.com/actions-rules/actions/implementing-mfa Learn how to implement MFA and step-up authentication across your application using Authsignal actions. Actions are the foundation for implementing multi-factor authentication (MFA) and step-up authentication in your application. By tracking specific user activities as actions, you can apply contextual security policies that challenge users when needed. ## MFA on login The most common MFA scenario is requiring additional authentication after a user's primary credentials (username and password) have been validated. Here's how the flow works with Authsignal: ```mermaid theme={null} sequenceDiagram participant F as Your frontend participant B as Your backend participant A as Authsignal Note left of F: 1st authentication step in your app
(e.g. username + password) F->>B: Primary user credentials Note over B: Validate credentials B->>A: Track action A->>B: Pre-built UI URL B->>F: Pre-built UI URL Note left of F: 2nd authentication step via Authsignal
(e.g. Passkey, TOTP, or SMS OTP) F->>B: Check result B->>A: Validate challenge A->>B: Challenge result Note over B: Create login session ``` ### Implementation 1. **Track the login action** after validating primary credentials: ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { redirectUrl: "https://yourapp.com/callback", }, }; const response = await authsignal.track(request); const url = response.url; ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: user.Id, Action: "signIn", Attributes: new TrackAttributes( RedirectUrl: "https://yourapp.com/callback" ) ); var response = await authsignal.Track(request); var url = response.Url; ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; request.attributes = new TrackAttributes(); request.attributes.redirectUrl = "https://yourapp.com/callback"; TrackResponse response = authsignal.track(request).get(); String url = response.url; ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { redirect_url: "https://yourapp.com/callback", } }) url = response[:url] ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn", attributes={ "redirectUrl": "https://yourapp.com/callback" } ) url = response["url"] ``` ```php PHP theme={null} $response = Authsignal::track([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'action' => "signIn", 'attributes' => [ 'redirectUrl' => "https://yourapp.com/callback" ] ]); $url = $response["url"] ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: &TrackAttributes{ RedirectUrl: "https://yourapp.com/callback", }, }, ) url := response.Url ``` 2. **Handle the response** based on the action state: * If `CHALLENGE_REQUIRED`: Redirect user to the authentication flow * If `ALLOW`: Proceed with login * If `BLOCK`: Deny access * If `REVIEW`: Review the challenge 3. **Follow the standard integration steps** covered in [actions getting started](/actions-rules/actions/getting-started#2-challenging-the-user) to launch the challenge URL and validate the result. ## Step-up authentication Step-up authentication challenges users when they perform sensitive operations, even if they're already logged in. This is ideal for high-risk actions like financial transactions, account settings changes, or data exports. ```mermaid theme={null} sequenceDiagram participant F as Your frontend participant B as Your backend participant A as Authsignal Note left of F: User initiates a sensitive action
(e.g. payment, settings change) F->>B: User action input (e.g. amount) B->>A: Track action A->>B: Pre-built UI URL B->>F: Pre-built UI URL Note left of F: User completes challenge
via Authsignal pre-built UI F->>B: Check result B->>A: Validate challenge A->>B: Challenge result Note over B: Proceed with action
(e.g. process payment) ``` ### Common step-up scenarios * **Financial transactions**: Challenge for payments above a certain threshold * **Account changes**: Require authentication for email/password changes * **Administrative actions**: Challenge admin users for sensitive operations * **Data access**: Authenticate before accessing sensitive information ## Combining with rules for adaptive MFA While actions define *what* to protect, rules define *when* to challenge users. You can create adaptive MFA flows by combining actions with rules: * Challenge only on new devices * Require stronger authentication for high-risk IP addresses * Skip MFA for trusted locations * Apply different requirements based on user risk scores Learn more about implementing adaptive MFA with [rules](/actions-rules/rules/adaptive-mfa). # Passwordless login Source: https://docs.authsignal.com/actions-rules/actions/passwordless-login Learn how to implement passwordless authentication flows using Authsignal actions. Actions can be used to implement passwordless authentication, where Authsignal serves as the primary authentication method instead of traditional passwords. This approach eliminates password-related security risks while providing a smooth user experience. ## Passwordless login flow In a passwordless flow, you track an action to initiate the authentication challenge directly, without validating a password first: ```mermaid theme={null} sequenceDiagram participant F as Your frontend participant B as Your backend participant A as Authsignal F->>B: User identifier (e.g. email or username) Note over B: Lookup user in your DB or IdP B->>A: Track action A->>B: Pre-built UI URL B->>F: Pre-built UI URL Note left of F: User completes authentication
via Authsignal pre-built UI F->>B: Check result B->>A: Validate challenge A->>B: Challenge result Note over B: Create login session ``` ### Implementation 1. **Look up the user** by their identifier (email, username) in your database or external identity provider 2. **Track the authentication action** for the identified user: ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { redirectUrl: "https://yourapp.com/callback", }, }; const response = await authsignal.track(request); const url = response.url; ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: user.Id, Action: "signIn", Attributes: new TrackAttributes( RedirectUrl: "https://yourapp.com/callback" ) ); var response = await authsignal.Track(request); var url = response.Url; ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; request.attributes = new TrackAttributes(); request.attributes.redirectUrl = "https://yourapp.com/callback"; TrackResponse response = authsignal.track(request).get(); String url = response.url; ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { redirect_url: "https://yourapp.com/callback", } }) url = response[:url] ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn", attributes={ "redirectUrl": "https://yourapp.com/callback" } ) url = response["url"] ``` ```php PHP theme={null} $response = Authsignal::track([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'action' => "signIn", 'attributes' => [ 'redirectUrl' => "https://yourapp.com/callback" ] ]); $url = $response["url"] ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: &TrackAttributes{ RedirectUrl: "https://yourapp.com/callback", }, }, ) url := response.Url ``` 3. **Handle the response** based on the action state: * If `CHALLENGE_REQUIRED`: Present the authentication challenge * If `ALLOW`: Complete the login (user may have been auto-authenticated) * If `BLOCK`: Deny access * If `REVIEW`: Review the challenge 4. **Follow the standard integration steps** covered in [actions getting started](/actions-rules/actions/getting-started#2-challenging-the-user) to launch the challenge URL and validate the result ## Combining passwordless with rules You can combine rules with actions to create passwordless flows: ### Progressive authentication * **Low risk**: Email magic link * **Medium risk**: Email OTP or SMS * **High risk**: Passkey authentication ### Context-aware authentication * **Known devices**: Auto-allow or simple OTP * **New devices**: Require passkey or stronger authentication * **Suspicious activity**: Block or require multiple factors # Restricting authenticators for an action Source: https://docs.authsignal.com/actions-rules/actions/restricting-authenticators Learn how to restrict which authenticators a user can select to complete challenges. Not all types of authenticators offer the same level of security. For example, authentication via email magic link is less secure than using a passkey or authenticator app. With this in mind, you might want force your users to use only their most secure authenticators for particularly sensitive actions e.g. changing their password. ## Set up To achieve this behavior, you can go to the **Settings** of your desired action and choose which authenticators you want to permit for completing challenges. Permitted authenticators The pre-built UI will automatically hide any authenticators that are not permitted for the action. If a user doesn't have any of the permitted authenticators, then the pre-built UI will allow them to use their default authenticator to complete the challenge. ## Overriding permitted authenticators with rules In some advanced scenarios, you might want to override the permitted authenticators more granularly when a specific rule is triggered. When editing a rule, you will see an **Advanced settings** section where you can configure permitted authenticators. If this rule is triggered, then the challenge can only be completed using the permitted authenticators. # Adaptive MFA Source: https://docs.authsignal.com/actions-rules/rules/adaptive-mfa Learn how to use rules to create adaptive, risk-based authentication that challenges users only when necessary. Adaptive MFA uses rules to intelligently determine when to challenge users based on risk factors and context. Instead of challenging every user every time, you can create policies that balance security with user experience. For a recorded walk-through and Postman companion steps, see the [Adaptive MFA video walk-through](/knowledge-base/video-walkthroughs/adaptive-mfa). ## How adaptive MFA works Rules evaluate contextual information about each action to determine the appropriate response: ```mermaid theme={null} sequenceDiagram participant F as Your frontend participant B as Your backend participant A as Authsignal B->>A: Track action with context Note over A: Rules engine evaluates:
• Device information
• Location data
• User behavior
• Custom data A->>B: Decision: ALLOW, CHALLENGE, or BLOCK alt Challenge Required B->>F: Pre-built UI URL Note left of F: Present challenge F->>B: Check result B->>A: Validate challenge A->>B: Challenge result else Allow Note over B: Proceed without challenge else Block Note over B: Deny access end ``` ## Common adaptive MFA scenarios ### New device detection Challenge users only when they're signing in from an unrecognized device: New device rule example **Implementation:** ```javascript theme={null} const result = await authsignal.track({ userId: "0272c312-e181-4cad-a494-43647b503a0a", action: "signIn", attributes: { deviceId: "555c17e1-3837-4f13-81bb-131e5597e168", // Required for device tracking userAgent: req.headers['user-agent'], ipAddress: "203.0.113.42" } }); ``` ### Location-based policies Apply different authentication requirements based on user location: * **Known locations**: Allow without challenge * **New countries**: Require MFA * **High-risk regions**: Block or require strong authentication Location-based rule example **Implementation:** ```javascript theme={null} const result = await authsignal.track({ userId: "0272c312-e181-4cad-a494-43647b503a0a", action: "signIn", attributes: { ipAddress: "192.168.1.100", // Required for location detection userAgent: req.headers['user-agent'], } }); ``` ### Risk-based authentication Create rules that consider multiple risk factors: * **User behavior**: Login patterns, time of day, frequency * **Device characteristics**: Known vs unknown devices, device type * **Network information**: IP reputation, VPN detection * **Transaction context**: Amount, recipient, frequency ## Business-specific adaptive MFA You can create rules based on your application's specific data points. For example, challenge users only for high-value transactions: Payment threshold rule **Implementation:** ```javascript theme={null} const result = await authsignal.track({ userId: "0272c312-e181-4cad-a494-43647b503a0a", action: "payment", attributes: { redirectUrl: "https://yourapp.com/callback", custom: { paymentAmount: 1000, }, }, }); const url = result.url; ``` Learn more about creating and using business-specific data in rules with [custom data points](/actions-rules/rules/custom-data-points)**.** ## Rule examples ### Device and location combination ``` Device is new AND (IP country is not United States OR VPN detected is true) ``` New device and location combination **Implementation:** ```javascript theme={null} const result = await authsignal.track({ userId: "0272c312-e181-4cad-a494-43647b503a0a", action: "signIn", attributes: { deviceId: "555c17e1-3837-4f13-81bb-131e5597e168", // Required for device tracking ipAddress: "198.51.100.25", // Required for location and VPN detection userAgent: req.headers['user-agent'] } }); ``` ### Transaction pattern analysis using custom data points ``` Payment amount is greater than 500 AND (daily transaction count is greater than 10) ``` Transaction pattern analysis **Implementation:** ```javascript theme={null} const result = await authsignal.track({ userId: "0272c312-e181-4cad-a494-43647b503a0a", action: "payment", attributes: { custom: { paymentAmount: transactionAmount, dailyTransactionCount: userDailyCount }, deviceId: "555c17e1-3837-4f13-81bb-131e5597e168", ipAddress: "203.0.113.89" } }); ``` # Customizing rules with custom data points Source: https://docs.authsignal.com/actions-rules/rules/custom-data-points Learn how to create custom data points and integrate them into Authsignal rules to create intelligent, policy-driven authentication flows that respond to your unique security requirements While Authsignal provides a powerful set of predefined data points to create rules with, you may find yourself wanting to create rules based on your own data. In this guide we will explore how you can create your own custom data points within the rules builder. Let's create a rule that will challenge a user when they attempt to withdraw funds over a certain amount. ## 1. Create the `withdrawFunds` action * Create the `withdrawFunds` action * Head to the **Rules** section and create a new rule called `Challenge large withdrawals` * Set the **Outcome** to `CHALLENGE` ## 2. Create a custom data point * In the **Conditions** section, click **Add feature** * Click **Select feature**, then head to the **Custom** tab * Click **Create data point** and choose **Action**. * Fill in the **Name** and **Description** for the data point and choose **Type** as `Number`. Create custom data The value you provide to the **Name** field dictates how it must be sent in the track action payload. * You should now see a new condition added to your rule. Now, change the operation from `==` to `>` and set the value to `2000`. Condition Any custom data point you create will be saved in the **Custom** section and available tenant-wide, i.e. you can use it in any of your actions and rules. Custom data created * Finally, click the **Save** button and return to the **Rules** page for your `withdrawFunds` action. You should see your new rule listed. Rule created ## 3. Send the custom data in your code Now that the rule is created, you need to send the `withdrawalAmount` custom data point in the track action payload. When tracking actions, Authsignal allows you to send a `custom` object that contains your custom data points. For our `withdrawFunds` action this will look like: ```ts Node.js {9-11} theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "withdrawFunds", attributes: { deviceId: "555c17e1-3837-4f13-81bb-131e5597e168", userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", ipAddress: "203.0.113.42", redirectUrl: "https://yourapp.com/callback", custom: { withdrawalAmount: 2001, }, }, }; const response = await authsignal.track(request); ``` ```csharp C# {9} theme={null} var request = new TrackRequest( UserId: user.Id, Action: "withdrawFunds", Attributes: new TrackAttributes( DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168", UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", IpAddress: "203.0.113.42", RedirectUrl: "https://yourapp.com/callback", Custom: new Dictionary { { "withdrawalAmount", 2001 } } ) ); var response = await authsignal.Track(request); ``` ```java Java {9-11} theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "withdrawFunds"; request.attributes = new TrackAttributes(); request.attributes.deviceId = "555c17e1-3837-4f13-81bb-131e5597e168"; request.attributes.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"; request.attributes.ipAddress = "203.0.113.42"; request.attributes.redirectUrl = "https://yourapp.com/callback"; request.attributes.custom = new HashMap() {{ put("withdrawalAmount", 2001); }}; TrackResponse response = authsignal.track(request).get(); ``` ```ruby Ruby {9-11} theme={null} response = Authsignal.track({ user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "withdrawFunds", attributes: { device_id: "555c17e1-3837-4f13-81bb-131e5597e168", user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", ip_address: "203.0.113.42", redirect_url: "https://yourapp.com/callback", custom: { withdrawalAmount: 2001, }, } }) ``` ```python Python {9-11} theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="withdrawFunds", attributes={ "device_id": "555c17e1-3837-4f13-81bb-131e5597e168", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "ip_address": "203.0.113.42", "redirect_url": "https://yourapp.com/callback", "custom": { "withdrawalAmount": 2001, }, } ) ``` ```php PHP {9-11} theme={null} $response = Authsignal::track([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'action' => "withdrawFunds", 'attributes' => [ 'device_id' => "555c17e1-3837-4f13-81bb-131e5597e168", 'user_agent' => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", 'ip_address' => "203.0.113.42", 'redirect_url' => "https://yourapp.com/callback", 'custom' => [ 'withdrawalAmount' => 2001, ], ] ]); ``` ```go Go {10-12} theme={null} response := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "withdrawFunds", Attributes: &TrackAttributes{ DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168", UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", IpAddress: "203.0.113.42", RedirectUrl: "https://yourapp.com/callback", Custom: map[string]interface{}{ "withdrawalAmount": 2001, }, }, }, ) ``` That' it! Now when someone tries to withdraw more than \$2000, they'll be challenged. You can verify this worked by checking the Latest activity table in your dashboard. Action details You can manage all your custom data points in one place at [Settings > Rules > Custom data points](https://portal.authsignal.com/organisations/tenants/custom_data_points) in the Authsignal Portal. ## Public custom data points For app verification methods like [push](/authentication-methods/app-verification/push) and [QR code](/authentication-methods/app-verification/qr-code), you often want the verifying device to display transaction context, such as the amount of a payment, so the user can make an informed decision before they approve or reject a challenge. To surface a value on the device, mark its custom data point as public. This will make the value available on [getting push challenges](/authentication-methods/app-verification/push#getting-challenges) and [claiming QR code challenges](/authentication-methods/app-verification/qr-code#claiming-challenges). Condition # Custom lists Source: https://docs.authsignal.com/actions-rules/rules/custom-lists Create lists of information to use in rules. You can create lists of specific types of information and use them in rules. For example, you might want to create rules that use a list of: * **User IDs for trusted users.** Use this list to automatically allow actions by these users. * **Email addresses you have flagged as fraudulent.** Automatically block any action with an email address on this list. * **Suspicious IP addresses.** Block actions that have a matching IP address. Lists make rules more manageable. For example, instead of creating an individual condition for each email address, you can add all the email addresses to a list and reference the list in a single condition. ## Create a custom list To create a custom list, head to the [Custom Lists](https://portal.authsignal.com/organisations/tenants/lists) page inside `settings > rules` in the Authsignal Portal. 1. Click **Create list**. 2. Enter a name for the list. 3. Choose the **Type** of list and create the list. Currently only `string` and `number` lists are supported. Create list 5. Now, add values to the list by clicking **Add item**. 6. Once you have added all the items you want to add, click **Save**. Custom list with items ## Use a custom list in a rule To use a custom list in your rule conditions, create or edit a rule and navigate to the **Conditions** section. 1. Click **Add feature**. 2. Select the feature you want to use in the rule. 3. Select the `In List` or `Not In List` operator. 4. Select the custom list you want to use in the rule. Custom list in rule Only the following features can reference a custom list: **User features:** * `UserId` * `Email address` * `Phone number` **Device features:** * `IP address` **Card Bin** * `Issuer country code` * `Issuer name` **Crypto features:** * `Wallet address` * `Asset Code` All **custom** features can reference a custom list. ## Managing custom lists To manage a custom list, head to the [Custom Lists](https://portal.authsignal.com/organisations/tenants/lists) page in the Authsignal Portal. To delete a custom list, click the **Delete** button. To add more items to a custom list, click **Edit** and then click **Add item**. # Getting started with rules Source: https://docs.authsignal.com/actions-rules/rules/getting-started Learn how Authsignal rules enable risk-based authentication and how to implement them in your app. Authsignal's rules engine transforms static authentication flows into intelligent, risk-based security systems. Rules determine when and how to challenge users based on contextual factors. Rules are conditional statements that evaluate the context of each action to make intelligent security decisions. While actions define what users are doing, rules determine when and how to challenge them based on risk factors, device characteristics, user behavior, and custom business data. ## Creating a rule To create a rule, navigate to your action (e.g., withdraw-funds) and click the Rules tab. Then click "Create rule" and provide a name and description. ## Core components of a rule When you create a rule in Authsignal, you're defining the logic that determines when additional security measures are required. Here's what a rule contains: ### 1. Conditions Define the criteria that will trigger the rule using various data points, for example: * **Device characteristics:** New devices, device count * **IP/Network data:** Anonymous IPs, country codes, impossible travel detection * **Custom data points:** Your business-specific data like transaction amounts, account tiers ### 2. Outcomes When a rule's conditions are met, it can override the action's default outcome with any of the four available action outcomes (`ALLOW`, `CHALLENGE`, `REVIEW`, or `BLOCK`). This allows you to apply outcomes dynamically based on risk assessment rather than using a static default. Rule outcomes ## Rule priority When multiple rules are defined for an action, they are evaluated in priority order. Each rule is assigned a priority number, with lower numbers indicating higher priority. Rule priority ordering All rules are evaluated against the action's context, and if multiple rules match, the highest-priority rule's outcome is used. If no rules match, the action's default outcome is used. You can reorder rules by dragging them in the rules list to adjust their priority. Place your most specific or highest-risk rules at the top to ensure they take precedence over broader rules. For example, if you have a "High risk" rule (priority 1) that challenges transactions above \$10,000, and a "Low risk" rule (priority 2) that challenges transactions above \$1,000, a \$15,000 transaction will match both rules, but the "High risk" rule's outcome will be used because it has a higher priority. ## Using rules with actions Rules work seamlessly with your existing action tracking. When you track an action, Authsignal evaluates all applicable rules and returns the appropriate outcome. ```ts Node.js theme={null} // Track an action - rules are evaluated automatically const result = await authsignal.track({ userId: "0272c312-e181-4cad-a494-43647b503a0a", action: "withdraw-funds", attributes: { deviceId: "555c17e1-3837-4f13-81bb-131e5597e168", ipAddress: "203.0.113.42", userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } }); // Handle the result based on rule evaluation if (result.state === "CHALLENGE_REQUIRED") { // Rule determined a challenge is needed return { token: result.token // For custom UI with SDKs }; } else if (result.state === "ALLOW") { // Rule determined user is trusted return { success: true }; } else if (result.state === "REVIEW") { // Rule determined manual review is needed return { status: "under_review", message: "Your request is being reviewed" }; } else if (result.state === "BLOCK") { // Rule determined this is high-risk return { error: "This action has been blocked for security reasons" }; } ``` ```csharp C# theme={null} // Track an action - rules are evaluated automatically var request = new TrackRequest( UserId: "0272c312-e181-4cad-a494-43647b503a0a", Action: "withdraw-funds", Attributes: new TrackAttributes( DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168", IpAddress: "203.0.113.42", UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" ) ); var result = await authsignal.Track(request); // Handle the result based on rule evaluation if (result.State == "CHALLENGE_REQUIRED") { // Rule determined a challenge is needed return new { Token = result.Token // For custom UI with SDKs }; } else if (result.State == "ALLOW") { // Rule determined user is trusted return new { Success = true }; } else if (result.State == "REVIEW") { // Rule determined manual review is needed return new { Status = "under_review", Message = "Your request is being reviewed" }; } else if (result.State == "BLOCK") { // Rule determined this is high-risk return new { Error = "This action has been blocked for security reasons" }; } ``` ```java Java theme={null} // Track an action - rules are evaluated automatically TrackRequest request = new TrackRequest(); request.userId = "0272c312-e181-4cad-a494-43647b503a0a"; request.action = "withdraw-funds"; request.attributes = new TrackAttributes(); request.attributes.deviceId = "555c17e1-3837-4f13-81bb-131e5597e168"; request.attributes.ipAddress = "203.0.113.42"; request.attributes.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"; TrackResponse result = authsignal.track(request).get(); // Handle the result based on rule evaluation if ("CHALLENGE_REQUIRED".equals(result.state)) { // Rule determined a challenge is needed Map response = new HashMap<>(); response.put("token", result.token); // For custom UI with SDKs return response; } else if ("ALLOW".equals(result.state)) { // Rule determined user is trusted return Map.of("success", true); } else if ("REVIEW".equals(result.state)) { // Rule determined manual review is needed return Map.of( "status", "under_review", "message", "Your request is being reviewed" ); } else if ("BLOCK".equals(result.state)) { // Rule determined this is high-risk return Map.of( "error", "This action has been blocked for security reasons" ); } ``` ```ruby Ruby theme={null} # Track an action - rules are evaluated automatically result = Authsignal.track({ user_id: "0272c312-e181-4cad-a494-43647b503a0a", action: "withdraw-funds", attributes: { device_id: "555c17e1-3837-4f13-81bb-131e5597e168", ip_address: "203.0.113.42", user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } }) # Handle the result based on rule evaluation case result[:state] when "CHALLENGE_REQUIRED" # Rule determined a challenge is needed { token: result[:token] # For custom UI with SDKs } when "ALLOW" # Rule determined user is trusted { success: true } when "REVIEW" # Rule determined manual review is needed { status: "under_review", message: "Your request is being reviewed" } when "BLOCK" # Rule determined this is high-risk { error: "This action has been blocked for security reasons" } end ``` ```python Python theme={null} # Track an action - rules are evaluated automatically result = authsignal.track( user_id="0272c312-e181-4cad-a494-43647b503a0a", action="withdraw-funds", attributes={ "deviceId": "555c17e1-3837-4f13-81bb-131e5597e168", "ipAddress": "203.0.113.42", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } ) # Handle the result based on rule evaluation if result["state"] == "CHALLENGE_REQUIRED": # Rule determined a challenge is needed return { "token": result["token"] # For custom UI with SDKs } elif result["state"] == "ALLOW": # Rule determined user is trusted return {"success": True} elif result["state"] == "REVIEW": # Rule determined manual review is needed return { "status": "under_review", "message": "Your request is being reviewed" } elif result["state"] == "BLOCK": # Rule determined this is high-risk return { "error": "This action has been blocked for security reasons" } ``` ```php PHP theme={null} // Track an action - rules are evaluated automatically $result = Authsignal::track([ 'userId' => "0272c312-e181-4cad-a494-43647b503a0a", 'action' => "withdraw-funds", 'attributes' => [ 'deviceId' => "555c17e1-3837-4f13-81bb-131e5597e168", 'ipAddress' => "203.0.113.42", 'userAgent' => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" ] ]); // Handle the result based on rule evaluation switch ($result["state"]) { case "CHALLENGE_REQUIRED": // Rule determined a challenge is needed return [ 'token' => $result["token"] // For custom UI with SDKs ]; case "ALLOW": // Rule determined user is trusted return ['success' => true]; case "REVIEW": // Rule determined manual review is needed return [ 'status' => "under_review", 'message' => "Your request is being reviewed" ]; case "BLOCK": // Rule determined this is high-risk return [ 'error' => "This action has been blocked for security reasons" ]; } ``` ```go Go theme={null} // Track an action - rules are evaluated automatically result, err := client.Track( TrackRequest{ UserId: "0272c312-e181-4cad-a494-43647b503a0a", Action: "withdraw-funds", Attributes: &TrackAttributes{ DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168", IpAddress: "203.0.113.42", UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", }, }, ) if err != nil { return err } // Handle the result based on rule evaluation switch result.State { case "CHALLENGE_REQUIRED": // Rule determined a challenge is needed return map[string]interface{}{ "token": result.Token, // For custom UI with SDKs } case "ALLOW": // Rule determined user is trusted return map[string]interface{}{"success": true} case "REVIEW": // Rule determined manual review is needed return map[string]interface{}{ "status": "under_review", "message": "Your request is being reviewed", } case "BLOCK": // Rule determined this is high-risk return map[string]interface{}{ "error": "This action has been blocked for security reasons", } } ``` ```ts Node.js theme={null} // Track an action - rules are evaluated automatically const result = await authsignal.track({ userId: "0272c312-e181-4cad-a494-43647b503a0a", action: "withdraw-funds", attributes: { deviceId: "555c17e1-3837-4f13-81bb-131e5597e168", ipAddress: "203.0.113.42", userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", redirectUrl: "https://yourapp.com/callback" } }); // Handle the result based on rule evaluation if (result.state === "CHALLENGE_REQUIRED") { // Rule determined a challenge is needed return { url: result.url // For pre-built UI }; } else if (result.state === "ALLOW") { // Rule determined user is trusted return { success: true }; } else if (result.state === "REVIEW") { // Rule determined manual review is needed return { status: "under_review", message: "Your request is being reviewed" }; } else if (result.state === "BLOCK") { // Rule determined this is high-risk return { error: "This action has been blocked for security reasons" }; } ``` ```csharp C# theme={null} // Track an action - rules are evaluated automatically var request = new TrackRequest( UserId: "0272c312-e181-4cad-a494-43647b503a0a", Action: "withdraw-funds", Attributes: new TrackAttributes( DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168", IpAddress: "203.0.113.42", UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", RedirectUrl: "https://yourapp.com/callback" ) ); var result = await authsignal.Track(request); // Handle the result based on rule evaluation if (result.State == "CHALLENGE_REQUIRED") { // Rule determined a challenge is needed return new { Url = result.Url // For pre-built UI }; } else if (result.State == "ALLOW") { // Rule determined user is trusted return new { Success = true }; } else if (result.State == "REVIEW") { // Rule determined manual review is needed return new { Status = "under_review", Message = "Your request is being reviewed" }; } else if (result.State == "BLOCK") { // Rule determined this is high-risk return new { Error = "This action has been blocked for security reasons" }; } ``` ```java Java theme={null} // Track an action - rules are evaluated automatically TrackRequest request = new TrackRequest(); request.userId = "0272c312-e181-4cad-a494-43647b503a0a"; request.action = "withdraw-funds"; request.attributes = new TrackAttributes(); request.attributes.deviceId = "555c17e1-3837-4f13-81bb-131e5597e168"; request.attributes.ipAddress = "203.0.113.42"; request.attributes.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"; request.attributes.redirectUrl = "https://yourapp.com/callback"; TrackResponse result = authsignal.track(request).get(); // Handle the result based on rule evaluation if ("CHALLENGE_REQUIRED".equals(result.state)) { // Rule determined a challenge is needed Map response = new HashMap<>(); response.put("url", result.url); // For pre-built UI return response; } else if ("ALLOW".equals(result.state)) { // Rule determined user is trusted return Map.of("success", true); } else if ("REVIEW".equals(result.state)) { // Rule determined manual review is needed return Map.of( "status", "under_review", "message", "Your request is being reviewed" ); } else if ("BLOCK".equals(result.state)) { // Rule determined this is high-risk return Map.of( "error", "This action has been blocked for security reasons" ); } ``` ```ruby Ruby theme={null} # Track an action - rules are evaluated automatically result = Authsignal.track({ user_id: "0272c312-e181-4cad-a494-43647b503a0a", action: "withdraw-funds", attributes: { device_id: "555c17e1-3837-4f13-81bb-131e5597e168", ip_address: "203.0.113.42", user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", redirect_url: "https://yourapp.com/callback" } }) # Handle the result based on rule evaluation case result[:state] when "CHALLENGE_REQUIRED" # Rule determined a challenge is needed { url: result[:url] # For pre-built UI } when "ALLOW" # Rule determined user is trusted { success: true } when "REVIEW" # Rule determined manual review is needed { status: "under_review", message: "Your request is being reviewed" } when "BLOCK" # Rule determined this is high-risk { error: "This action has been blocked for security reasons" } end ``` ```python Python theme={null} # Track an action - rules are evaluated automatically result = authsignal.track( user_id="0272c312-e181-4cad-a494-43647b503a0a", action="withdraw-funds", attributes={ "deviceId": "555c17e1-3837-4f13-81bb-131e5597e168", "ipAddress": "203.0.113.42", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "redirectUrl": "https://yourapp.com/callback" } ) # Handle the result based on rule evaluation if result["state"] == "CHALLENGE_REQUIRED": # Rule determined a challenge is needed return { "url": result["url"] # For pre-built UI } elif result["state"] == "ALLOW": # Rule determined user is trusted return {"success": True} elif result["state"] == "REVIEW": # Rule determined manual review is needed return { "status": "under_review", "message": "Your request is being reviewed" } elif result["state"] == "BLOCK": # Rule determined this is high-risk return { "error": "This action has been blocked for security reasons" } ``` ```php PHP theme={null} // Track an action - rules are evaluated automatically $result = Authsignal::track([ 'userId' => "0272c312-e181-4cad-a494-43647b503a0a", 'action' => "withdraw-funds", 'attributes' => [ 'deviceId' => "555c17e1-3837-4f13-81bb-131e5597e168", 'ipAddress' => "203.0.113.42", 'userAgent' => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", 'redirectUrl' => "https://yourapp.com/callback" ] ]); // Handle the result based on rule evaluation switch ($result["state"]) { case "CHALLENGE_REQUIRED": // Rule determined a challenge is needed return [ 'url' => $result["url"] // For pre-built UI ]; case "ALLOW": // Rule determined user is trusted return ['success' => true]; case "REVIEW": // Rule determined manual review is needed return [ 'status' => "under_review", 'message' => "Your request is being reviewed" ]; case "BLOCK": // Rule determined this is high-risk return [ 'error' => "This action has been blocked for security reasons" ]; } ``` ```go Go theme={null} // Track an action - rules are evaluated automatically result, err := client.Track( TrackRequest{ UserId: "0272c312-e181-4cad-a494-43647b503a0a", Action: "withdraw-funds", Attributes: &TrackAttributes{ DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168", IpAddress: "203.0.113.42", UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", RedirectUrl: "https://yourapp.com/callback", }, }, ) if err != nil { return err } // Handle the result based on rule evaluation switch result.State { case "CHALLENGE_REQUIRED": // Rule determined a challenge is needed return map[string]interface{}{ "url": result.Url, // For pre-built UI } case "ALLOW": // Rule determined user is trusted return map[string]interface{}{"success": true} case "REVIEW": // Rule determined manual review is needed return map[string]interface{}{ "status": "under_review", "message": "Your request is being reviewed", } case "BLOCK": // Rule determined this is high-risk return map[string]interface{}{ "error": "This action has been blocked for security reasons", } } ``` ## Creating a rule to challenge high-risk users Let's walk through creating a practical rule that automatically challenges high-risk users while allowing trusted users to access your application seamlessly. ### 1. Create a rule * Navigate to your `signIn` action and click the Rules tab * Click **Create rule** and name it Challenge "high-risk" users * Click Continue to proceed to rule configuration ### 2. Add conditions We'll determine a user as 'high-risk' if they meet any of the following conditions: * Are detected as being a bot * Are on a new device * Are using an anonymous IP address To add these conditions: * Click **Add feature** and then **Select feature** * Choose the **Device** category and select **Device is new** * Repeat for **Device is a bot** (Device category) * Repeat for **IP is anonymous** (IP/Network category) Select feature * Change the conjunction logic from AND to OR so the rule triggers if any condition is met Conditions ### 3. Set the outcome * Set the rule outcome to `CHALLENGE` so high-risk users will be prompted for additional verification, then click **Save**. * Return to the Rules page of your `signIn` action. You should see your new rule listed. Rule created ### 4. Configure default action outcome * Navigate to your action's **Settings** tab * Change the default outcome to `ALLOW` * Click **Save** Default outcome This ensures users who don't trigger the high-risk rule can proceed without challenge. ### 5. Update your track call Now that you have created the rule, you'll need to update your track action call to include some additional fields: `deviceId`, `ipAddress`, and `userAgent`. ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { deviceId: "555c17e1-3837-4f13-81bb-131e5597e168", // From __as_aid cookie if using Authsignal Web SDK userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", ipAddress: "203.0.113.42" } }; const response = await authsignal.track(request); // Handle the response if (response.state === "CHALLENGE_REQUIRED") { // Use token with client SDK return { token: response.token }; } else if (response.state === "ALLOW") { // User is trusted, proceed return { success: true }; } ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: new TrackAttributes( DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168", // From __as_aid cookie if using Authsignal Web SDK UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", IpAddress: "203.0.113.42" ) ); var response = await authsignal.Track(request); // Handle the response if (response.State == "CHALLENGE_REQUIRED") { // Use token with client SDK return new { Token = response.Token }; } else if (response.State == "ALLOW") { // User is trusted, proceed return new { Success = true }; } ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; request.attributes = new TrackAttributes(); request.attributes.deviceId = "555c17e1-3837-4f13-81bb-131e5597e168"; // From __as_aid cookie if using Authsignal Web SDK request.attributes.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"; request.attributes.ipAddress = "203.0.113.42"; TrackResponse response = authsignal.track(request).get(); // Handle the response if ("CHALLENGE_REQUIRED".equals(response.state)) { // Use token with client SDK return Map.of("token", response.token); } else if ("ALLOW".equals(response.state)) { // User is trusted, proceed return Map.of("success", true); } ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { device_id: "555c17e1-3837-4f13-81bb-131e5597e168", # From __as_aid cookie if using Authsignal Web SDK user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", ip_address: "203.0.113.42" } }) # Handle the response case response[:state] when "CHALLENGE_REQUIRED" # Use token with client SDK { token: response[:token] } when "ALLOW" # User is trusted, proceed { success: true } end ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn", attributes={ "deviceId": "555c17e1-3837-4f13-81bb-131e5597e168", # From __as_aid cookie if using Authsignal Web SDK "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "ipAddress": "203.0.113.42" } ) # Handle the response if response["state"] == "CHALLENGE_REQUIRED": # Use token with client SDK return {"token": response["token"]} elif response["state"] == "ALLOW": # User is trusted, proceed return {"success": True} ``` ```php PHP theme={null} $response = Authsignal::track([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'action' => "signIn", 'attributes' => [ 'deviceId' => "555c17e1-3837-4f13-81bb-131e5597e168", // From __as_aid cookie if using Authsignal Web SDK 'userAgent' => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", 'ipAddress' => "203.0.113.42" ] ]); // Handle the response switch ($response["state"]) { case "CHALLENGE_REQUIRED": // Use token with client SDK return ['token' => $response["token"]]; case "ALLOW": // User is trusted, proceed return ['success' => true]; } ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: &TrackAttributes{ DeviceId: "", // From __as_aid cookie if using Authsignal Web SDK UserAgent: "", IpAddress: "", }, }, ) if err != nil { return err } // Handle the response switch response.State { case "CHALLENGE_REQUIRED": // Use token with client SDK return map[string]interface{}{"token": response.Token} case "ALLOW": // User is trusted, proceed return map[string]interface{}{"success": true} } ``` ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { deviceId: "555c17e1-3837-4f13-81bb-131e5597e168", // From __as_aid cookie if using Authsignal Web SDK userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", ipAddress: "203.0.113.42", redirectUrl: "https://yourapp.com/callback" } }; const response = await authsignal.track(request); // Handle the response if (response.state === "CHALLENGE_REQUIRED") { // Redirect to pre-built UI return { url: response.url }; } else if (response.state === "ALLOW") { // User is trusted, proceed return { success: true }; } ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: new TrackAttributes( DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168", // From __as_aid cookie if using Authsignal Web SDK UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", IpAddress: "203.0.113.42", RedirectUrl: "https://yourapp.com/callback" ) ); var response = await authsignal.Track(request); // Handle the response if (response.State == "CHALLENGE_REQUIRED") { // Redirect to pre-built UI return new { Url = response.Url }; } else if (response.State == "ALLOW") { // User is trusted, proceed return new { Success = true }; } ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; request.attributes = new TrackAttributes(); request.attributes.deviceId = "555c17e1-3837-4f13-81bb-131e5597e168"; // From __as_aid cookie if using Authsignal Web SDK request.attributes.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"; request.attributes.ipAddress = "203.0.113.42"; request.attributes.redirectUrl = "https://yourapp.com/callback"; TrackResponse response = authsignal.track(request).get(); // Handle the response if ("CHALLENGE_REQUIRED".equals(response.state)) { // Redirect to pre-built UI return Map.of("url", response.url); } else if ("ALLOW".equals(response.state)) { // User is trusted, proceed return Map.of("success", true); } ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { device_id: "555c17e1-3837-4f13-81bb-131e5597e168", # From __as_aid cookie if using Authsignal Web SDK user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", ip_address: "203.0.113.42", redirect_url: "https://yourapp.com/callback" } }) # Handle the response case response[:state] when "CHALLENGE_REQUIRED" # Redirect to pre-built UI { url: response[:url] } when "ALLOW" # User is trusted, proceed { success: true } end ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn", attributes={ "deviceId": "555c17e1-3837-4f13-81bb-131e5597e168", # From __as_aid cookie if using Authsignal Web SDK "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "ipAddress": "203.0.113.42", "redirectUrl": "https://yourapp.com/callback" } ) # Handle the response if response["state"] == "CHALLENGE_REQUIRED": # Redirect to pre-built UI return {"url": response["url"]} elif response["state"] == "ALLOW": # User is trusted, proceed return {"success": True} ``` ```php PHP theme={null} $response = Authsignal::track([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'action' => "signIn", 'attributes' => [ 'deviceId' => "555c17e1-3837-4f13-81bb-131e5597e168", // From __as_aid cookie if using Authsignal Web SDK 'userAgent' => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", 'ipAddress' => "203.0.113.42", 'redirectUrl' => "https://yourapp.com/callback" ] ]); // Handle the response switch ($response["state"]) { case "CHALLENGE_REQUIRED": // Redirect to pre-built UI return ['url' => $response["url"]]; case "ALLOW": // User is trusted, proceed return ['success' => true]; } ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: &TrackAttributes{ DeviceId: "", // From __as_aid cookie if using Authsignal Web SDK UserAgent: "", IpAddress: "", RedirectUrl: "https://yourapp.com/callback", }, }, ) if err != nil { return err } // Handle the response switch response.State { case "CHALLENGE_REQUIRED": // Redirect to pre-built UI return map[string]interface{}{"url": response.Url} case "ALLOW": // User is trusted, proceed return map[string]interface{}{"success": true} } ``` Note: When using the Authsignal Web SDK, you can obtain the deviceId from the `__as_aid` cookie that's automatically created on the client side. # Testing rules Source: https://docs.authsignal.com/actions-rules/rules/testing-rules Learn how to test your rules with Authsignal's test action feature. For convenience, Authsignal lets you create test actions directly in the portal, without having to track them from within your application. ## How to test your rules ### 1. Start a test action Go to the **Latest activity** tab of your action and click **Track a test action** button. Track a test action button ### 2. Add test data A dialog will popup with default values for `userId`, `email`, `deviceId`, `userAgent`, and `ipAddress`. You can change these to test different scenarios. If you're using a custom data points, you can add them here too . Track a test action payload ### 3. Track the action Click **Track action** and you will see a breakdown of the action response. Track a test action response 1 ## Understanding test results When you track a test action with our **Withdrawing high amount of funds** rule, you should see that the action state is . ### What happens next? If the user hasn't enrolled any authenticators yet, they'll be directed to an enrollment page first, then proceed with the challenge. Click the **Pre-built UI enrollment URL** to set up authentication methods. Once the user has enrolled authenticators, they'll be able to complete the challenge to proceed with the action. Track a test action response 2 ### View the action details If you want to see a full breakdown of a user action, refresh the page and you should see the test action you just created in the **Latest activity** table. Click `View details` to see the full breakdown of the action. View action details Action data # Authenticator binding Source: https://docs.authsignal.com/advanced-usage/authenticator-binding Ensure a strong binding between authenticators when using the pre-built UI or Client SDKs. In order to add new authentication methods, users must first prove their identity by authenticating with any existing methods on their account. ## Using the pre-built UI ### Requiring a challenge with an existing method When using the pre-built UI, strong binding between authenticators is handled automatically. This is because the pre-built UI requires the user to complete a challenge with an existing authentication method in order to add a new method within a limited time window (10 minutes by default). Completing an email OTP challenge to enroll a
passkey ### Skipping the prerequisite challenge It is possible to skip this prerequisite challenge step when launching the pre-built UI if you have already strongly authenticated the user outside of Authsignal. This can be achieved by passing **additional scopes** when tracking an action on your server to generate a short-lived pre-built UI URL. ```ts Node.js {7} theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "enroll", attributes: { redirectUrl: "https://yourapp.com/callback", redirectToSettings: true, scope: "add:authenticators update:authenticators remove:authenticators", }, }; const response = await authsignal.track(request); const url = response.url; ``` ```csharp C# {7} theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "enroll", Attributes: new TrackAttributes( RedirectUrl: "https://yourapp.com/callback", RedirectToSettings: true, Scope: "add:authenticators update:authenticators remove:authenticators" ) ); var response = await authsignal.Track(request); var url = response.Url; ``` ```java Java {7} theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "enroll"; request.attributes = new TrackAttributes(); request.attributes.redirectUrl = https://yourapp.com/callback"; request.attributes.redirectToSettings = true; request.attributes.scope = "add:authenticators update:authenticators remove:authenticators"; TrackResponse response = authsignal.track(request).get(); String url = response.url; ``` ```ruby Ruby {7} theme={null} response = Authsignal.track({ user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833" action: "enroll", attributes: { redirect_url: "https://yourapp.com/callback", redirect_to_settings: true, scope: "add:authenticators update:authenticators remove:authenticators" } }) url = response[:url] ``` ```python Python {7} theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="enroll", attributes={ "redirectUrl": "https://yourapp.com/callback", "redirect_to_settings": true, "scope": "add:authenticators update:authenticators remove:authenticators" } ) url = response["url"] ``` ```php PHP {7} theme={null} $response = Authsignal::track([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'action' => "enroll", 'attributes' => [ 'redirectUrl' => "https://yourapp.com/callback", 'redirectToSettings' => true, 'scope' => "add:authenticators update:authenticators remove:authenticators" ] ]); $url = $response["url"]; ``` ```go Go {8} theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "enroll", Attributes: &TrackAttributes{ RedirectUrl: "https://yourapp.com/callback", RedirectToSettings: true, Scope: "add:authenticators update:authenticators remove:authenticators", }, }, ) url := response.Url ``` With these additional scopes, the pre-built UI will assume that **the user has already been strongly authenticated by another method** and allow them to enroll, update or remove authenticators without first requiring a challenge to be completed. This bypass should only been used **when the user has been strongly authenticated by at least one of their existing authentication methods**. ## Using Client SDKs To ensure a strong binding between authenticators, our [Web SDK](/sdks/client/web/setup) and [Mobile SDK](/sdks/client/mobile/setup) support two different ways of adding new authentication methods. ### Presenting a challenge with an existing method Similar to the pre-built UI, with this option you can present a challenge with an existing method (e.g. passkey) in order to enroll a user in a new method (e.g. authenticator app) within a limited time window (10 minutes by default). ```ts Web theme={null} const response = await authsignal.passkey.signIn({ action: "signInWithPasskey" }); if (response.data?.isVerified) { const totpResponse = await authsignal.totp.enroll(); const totpUri = totpResponse.data.uri; } ``` ```swift iOS theme={null} let response = await authsignal.passkey.signIn(action: "addAuthenticator") if let isVerified = response.data?.isVerified, isVerified { let totpResponse = await authsignal.totp.enroll() let totpUri = totpResponse.data.uri } ``` ```kotlin Android theme={null} val response = authsignal.passkey.signIn(action = "addAuthenticator") if (response.data != null && response.data.isVerified) { val totpResponse = authsignal.totp.enroll() val totpUri = totpResponse.data.uri } ``` ```ts React Native theme={null} const response = await authsignal.passkey.signIn({ action: "addAuthenticator" }); if (response.data?.isVerified) { const totpResponse = await authsignal.totp.enroll(); const totpUri = totpResponse.data.uri; } ``` ```dart Flutter theme={null} final response = await authsignal.passkey.signIn(action: "addAuthenticator"); if (response.data != null && response.data.isVerified) { final totpResponse = await authsignal.totp.enroll(); final totpUri = totpResponse.data.uri; } ``` ### Tracking an action to generate a token You can track an action using a [Server SDK](/sdks/server) to generate a time-limited token (valid for 10 minutes by default). This token can be used to authorize adding the new authenticator. If this is not the user's first authenticator, you **must** specify the scope `add:authenticators` when generating the token. You should only ever generate a token with the `add:authenticators` scope from a context where the user is **strongly authenticated**. The example below demonstrates how a user ID is obtained from an authenticated request context and passed into the track request when using AWS Lambda and API Gateway. ```ts Node.js theme={null} export const handler = async (event) => { // Get the user from the JWT authorizer // This ensures that the token is generated for an authenticated user const userId = event.requestContext.authorizer.jwt.claims.sub as string; const { token } = await authsignal.track({ userId, action: "enrollAuthenticator", attributes: { scope: "add:authenticators", }, }); return { token, }; }; ``` Then pass the token from your backend to the Client SDK. You can pass the token directly to the relevant method when [creating a passkey](/sdks/client/mobile/passkeys#creating-a-passkey) or [adding a push credential](/sdks/client/mobile/push-verification#adding-a-credential) or else you can use the `setToken` method. ```ts Web theme={null} await authsignal.setToken("eyJhbGciOiJ..."); const totpResponse = await authsignal.totp.enroll(); const totpUri = totpResponse.data.uri; ``` ```swift iOS theme={null} authsignal.setToken("eyJhbGciOiJ...") let totpResponse = await authsignal.totp.enroll() let totpUri = totpResponse.data.uri ``` ```kotlin Android theme={null} authsignal.setToken("eyJhbGciOiJ...") val totpResponse = authsignal.totp.enroll() val totpUri = totpResponse.data.uri ``` ```ts React Native theme={null} await authsignal.setToken("eyJhbGciOiJ..."); const totpResponse = await authsignal.totp.enroll(); const totpUri = totpResponse.data.uri; ``` ```dart Flutter theme={null} await authsignal.setToken("eyJhbGciOiJ..."); final totpResponse = await authsignal.totp.enroll(); final totpUri = totpResponse.data.uri; ``` # Custom API domains Source: https://docs.authsignal.com/advanced-usage/custom-api-domains Learn how to use your own custom domain with Authsignal's Client API and SDKs If you are using a custom UI, you can optionally set up a branded domain for any API calls your UI makes to Authsignal. This is also useful if you are using the pre-built UI with a [custom domain](/implementation-options/prebuilt-ui/custom-domains), allowing for a fully branded experience. ## Step 1 Contact Authsignal Support requesting a branded domain for the Client API. Let us know the domain name you want to use. For example, if your app is hosted on `example.com`, you might want to use `api.auth.example.com` as your branded domain. ## Step 2 Authsignal support will provide you with two DNS records to set on your domain. | Record | Type | Value | | ---------------------------------- | ------- | ---------------------------------------- | | `api.auth.example.com` | `CNAME` | `au.authsignal-client.authsignaldns.com` | | `_{randomString}-auth.example.com` | `CNAME` | `{randomString}` | The first CNAME value will be dependent on which Authsignal region your tenant is located in: | Region | DNS Value | | ------------- | ---------------------------------------- | | AU (Sydney) | `au.authsignal-client.authsignaldns.com` | | US (Oregon) | `us.authsignal-client.authsignaldns.com` | | EU (Ireland) | `eu.authsignal-client.authsignaldns.com` | | CA (Montreal) | `ca.authsignal-client.authsignaldns.com` | The second record will be custom provided for you by Authsignal support, which will validate ownership of the domain. ## Step 3: Validation Let Authsignal support know once you have updated your DNS. We will validate the change on our side, and then will enable the new DNS. ## Step 4: Update SDKs Update your Client SDKs to point to the new branded domain. This is dependent on the SDK you are using, but for example in the Web SDK it would look like this: ``` import { Authsignal } from "@authsignal/browser"; const authsignal = new Authsignal({ tenantId: "YOUR_TENANT_ID", baseUrl: "https://api.auth.example.com/v1", }); ``` If you are using the Pre-built UI, Authsignal support can adjust this for you. # Programmatically managing authenticators on behalf of users Source: https://docs.authsignal.com/advanced-usage/programmatic-authenticator-management Learn how to automatically enroll and un-enroll users' authentication methods. Programmatic authenticator management allows you to automatically enroll or remove authenticators on behalf of users without requiring their direct interaction. This is useful for: * **Registration flows** - Auto-enroll users with verified email/SMS during signup * **Admin operations** - Remove compromised or outdated authenticators * **Account migrations** - Transfer existing verified contact methods to Authsignal * **Bulk operations** - Manage authenticators for multiple users programmatically **Use with caution:** Programmatic management bypasses normal user verification flows. Only use these methods when you've already verified user contact information in your own system. ## Adding authenticators ### When to use programmatic enrollment Programmatic enrollment is ideal when: * You've already verified a user's email/phone in your registration flow * You're migrating users from another authentication system * You want to streamline onboarding by pre-enrolling verified methods ### Supported methods You can programmatically enroll the following verification methods: | Method | Required Field | Description | | ------------------ | ------------------------------------- | ------------------------------------- | | `EMAIL_OTP` | `email` | Email-based one-time passwords | | `EMAIL_MAGIC_LINK` | `email` | Email magic links | | `SMS` | `phoneNumber` | SMS-based one-time passwords | | `PASSKEY` | `credentialId`, `credentialPublicKey` | Passkeys (WebAuthn). Optional: `name` | ### Implementation ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", attributes: { verificationMethod: VerificationMethod.SMS, phoneNumber: "+64270000000", }, }; const response = authsignal.enrollVerifiedAuthenticator(request); ``` ```csharp C# theme={null} var request = new EnrollVerifiedAuthenticatorRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Attributes: new EnrollVerifiedAuthenticatorAttributes( VerificationMethod: VerificationMethod.SMS, PhoneNumber: "+64270000000" ) ); var response = await authsignal.EnrollVerifiedAuthenticator(request); ``` ```java Java theme={null} EnrollVerifiedAuthenticatorRequest request = new EnrollVerifiedAuthenticatorRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.attributes = new EnrollVerifiedAuthenticatorAttributes(); request.attributes.verificationMethod = VerificationMethodType.SMS; request.attributes.phoneNumber = "+64270000000"; authsignal.enrollVerifiedAuthenticator(request).get(); ``` ```ruby Ruby theme={null} Authsignal.enroll_verified_authenticator( user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", attributes:{ verification_method: "SMS", phone_number: "+64270000000" } ) ``` ```python Python theme={null} response = authsignal.enroll_verified_authenticator( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", attributes={ "verificationMethod": "SMS", "phoneNumber": "+64270000000" } ) ``` ```php PHP theme={null} $response = Authsignal::enrollVerifiedAuthenticator([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'attributes' => [ "verificationMethod" => "SMS", "phoneNumber" => "+64270000000" ] ]); ``` ```go Go theme={null} response, err := client.EnrollVerifiedAuthenticator( EnrollVerifiedAuthenticatorRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Attributes: &EnrollVerifiedAuthenticatorAttributes{ VerificationMethod: "SMS", PhoneNumber: "+64270000000", }, }, ) ``` This method assumes the user's contact information has already been verified in your system. Never enroll unverified email addresses or phone numbers. ## Removing authenticators ### When to use programmatic removal Administrative removal is appropriate for: * **Security incidents** - Remove compromised authenticators immediately * **User support** - Help users who've lost access to their methods * **Account cleanup** - Remove outdated or duplicate authenticators * **Compliance** - Ensure users only have approved authentication methods ### Implementation ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", userAuthenticatorId: "bf287470-24d9-4aa4-8b29-85683bea703f", }; await authsignal.deleteAuthenticator(request); ``` ```csharp C# theme={null} var request = new DeleteAuthenticatorRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", UserAuthenticatorId: "bf287470-24d9-4aa4-8b29-85683bea703f" ); await authsignal.DeleteAuthenticator(request); ``` ```java Java theme={null} DeleteAuthenticatorRequest request = new DeleteAuthenticatorRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.userAuthenticatorId = "bf287470-24d9-4aa4-8b29-85683bea703f"; authsignal.deleteAuthenticator(request).get(); ``` ```ruby Ruby theme={null} Authsignal.delete_authenticator( user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", user_authenticator_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833" ) ``` ```python Python theme={null} authsignal.delete_authenticator( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", user_authenticator_id="bf287470-24d9-4aa4-8b29-85683bea703f" ) ``` ```php PHP theme={null} Authsignal::deleteAuthenticator([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'userAuthenticatorId' => "bf287470-24d9-4aa4-8b29-85683bea703f" ]); ``` ```go Go theme={null} err := client.DeleteAuthenticator( DeleteAuthenticatorRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", UserAuthenticatorId: "bf287470-24d9-4aa4-8b29-85683bea703f", }, ) ``` **Getting authenticator IDs:** To remove an authenticator, you need its `userAuthenticatorId`. Get this via: ```javascript theme={null} // Get all authenticators for a user const authenticators = await authsignal.getAuthenticators({ userId: "user-id", }); // Find the specific authenticator to remove const emailAuthenticator = authenticators.find((auth) => auth.verificationMethod === "EMAIL_OTP"); // Remove it await authsignal.deleteAuthenticator({ userId: "user-id", userAuthenticatorId: emailAuthenticator.userAuthenticatorId, }); ``` **Account lockout risk:** Removing all of a user's authenticators will prevent them from completing authentication challenges. Always ensure users have at least one working authenticator or a way to re-enroll. ## Next steps * [Understanding authenticator binding](/advanced-usage/authenticator-binding) # Session management Source: https://docs.authsignal.com/advanced-usage/session-management Learn how to use Authsignal SDKs to issue, validate, refresh and revoke access tokens. Authsignal's session APIs can be used to manage an authenticated session for a user. ## Configuration ### Enabling the JWKS URL To use Authsignal session APIs, you must first enable a JWKS URL in the Authsignal Portal under Settings -> API keys. Enabling a JWKS URL Once enabled, a link to the JWKS URL for your tenant will be displayed. Enabling a JWKS URL ### Creating app clients Next, create an app client in the Authsignal Portal under Settings -> App clients. Creating an app client For each client you create, you can configure a separate access token and refresh token duration. The **client ID** will be set as the access token's `aud` claim. App client list ## Creating sessions In order to create an authenticated session, you must first obtain an Authsignal client token either by using a [Client SDK](/sdks/client) or the [pre-built UI](/implementation-options/prebuilt-ui/overview). ### OTP auth (email, SMS, TOTP) For an OTP authentication method such as [Email OTP](/authentication-methods/email-otp) you can follow the integration steps below to create a session. **1. Backend - Track action** In your app's backend, use an [Authsignal Server SDK](/sdks/server) to track an action and obtain an initial client token. ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { email: "jane.smith@authsignal.com", }, }; const response = await authsignal.track(request); const token = response.token; ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: new TrackAttributes( Email: "jane.smith@authsignal.com", ) ); var response = await authsignal.Track(request); var token = response.token; ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; request.attributes = new TrackAttributes(); request.attributes.email = "jane@authsignal.co"; TrackResponse response = authsignal.track(request).get(); String token = response.token; ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { email: "jane.smith@authsignal.com", } }) token = response.token ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn", attributes={ "email": "jane.smith@authsignal.com", } ) token = response.token ``` ```php PHP theme={null} $response = Authsignal::track([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'action' => "signIn", 'attributes' => [ 'email' => "jane.smith@authsignal.com", ] ]); $token = response.token ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: &TrackAttributes{ Email: "jane.smith@authsignal.com", }, }, ) token := response.token ``` **2. Frontend - Use a Client SDK** In your web or mobile app, call `setToken` with the client token obtained in step 1, then use the relevant SDK methods to progress the user through a challenge and obtain a new client token. ```ts Web theme={null} // Set token from the track response authsignal.setToken("eyJhbGciOiJ..."); // Send the user an email OTP code // You can call this multiple times via a 'resend' button await authsignal.email.challenge(); // Verify the inputted code matches the original code const response = await authsignal.email.verify({ code: "123456" }); // Obtain a new token const token = response.token; ``` ```swift iOS theme={null} // Set token from the track response authsignal.setToken("eyJhbGciOiJ...") // Send the user an email OTP code // You can call this multiple times via a 'resend' button await authsignal.email.challenge() // Verify the inputted code matches the original code let response = await authsignal.email.verify(code: "123456") // Obtain a new token let token = response.token ``` ```kotlin Android theme={null} // Set token from the track response authsignal.setToken("eyJhbGciOiJ...") // Send the user an email OTP code // You can call this multiple times via a 'resend' button authsignal.email.challenge() // Verify the inputted code matches the original code val response = authsignal.email.verify(code = "123456") // Obtain a new token val token = response.token ``` ```ts React Native theme={null} // Set token from the track response await authsignal.setToken("eyJhbGciOiJ..."); // Send the user an email OTP code // You can call this multiple times via a 'resend' button await authsignal.email.challenge(); // Verify the inputted code matches the original code const response = await authsignal.email.verify({ code: "123456" }); // Obtain a new token const token = response.token; ``` ```dart Flutter theme={null} // Set token from the track response await authsignal.setToken("eyJhbGciOiJ..."); // Send the user an email OTP code // You can call this multiple times via a 'resend' button await authsignal.email.challenge(); // Verify the inputted code matches the original code final response = await authsignal.email.verify(code: "123456"); // Obtain a new token final token = response.token; ``` **3. Backend - Create session** Pass the client token obtained in step 2 to your backend and exchange it for an access token and refresh token. ```ts Node.js theme={null} const request = { token: "eyJhbGciOiJ...", clientId: "46e10ac5-cb9c-458f-ad86-0f698f67b365", }; const response = await authsignal.createSession(request); const accessToken = response.accessToken; const refreshToken = response.refreshToken; ``` ```csharp C# theme={null} var request = new CreateSessionRequest( Token: "eyJhbGciOiJ...", ClientId: "46e10ac5-cb9c-458f-ad86-0f698f67b365" ); var response = await authsignal.CreateSession(request); var accessToken = response.AccessToken; var refreshToken = response.RefreshToken; ``` ```java Java theme={null} CreateSessionRequest request = new CreateSessionRequest(); request.token = "eyJhbGciOiJ..."; request.clientId = "46e10ac5-cb9c-458f-ad86-0f698f67b365"; CreateSessionResponse response = authsignal.createSession(request).get(); String accessToken = response.accessToken; String refreshToken = response.refreshToken; ``` ```ruby Ruby theme={null} response = Authsignal.create_session( token: "eyJhbGciOiJ...", client_id: "46e10ac5-cb9c-458f-ad86-0f698f67b365", ) access_token = response[:access_token] refresh_token = response[:refresh_token] ``` ```go Go theme={null} response, err := client.CreateSession( CreateSessionRequest{ Token: "eyJhbGciOiJ...", ClientId: "46e10ac5-cb9c-458f-ad86-0f698f67b365", }, ) accessToken := response.AccessToken refreshToken := response.RefreshToken ``` ### Passkeys When using a device-bound authentication method like **passkeys**, only two steps are required to create a session. **1. Frontend - Use a Client SDK** Use our [web SDK](/sdks/client/web/setup) to present a passkey sign-in prompt in the browser, or use one of our [mobile SDKs](/sdks/client/mobile/setup) to present the native passkey UI in an iOS or Android app. ```ts Web theme={null} const response = await authsignal.passkey.signIn({ action: "signInWithPasskey", }); if (response.data?.token) { // Send token to your backend for validation const token = response.data.token; } else { console.error("Passkey sign-in failed:", response.error); } ``` ```ts React Native theme={null} const response = await authsignal.passkey.signIn({ action: "signInWithPasskey", }); if (response.data?.token) { // Send token to your backend for validation const token = response.data.token; } else { Alert.alert("Error", "Passkey sign-in failed"); } ``` ```dart Flutter theme={null} final response = await authsignal.passkey.signIn(action: "signInWithPasskey"); if (response.data?.token != null) { // Send token to your backend for validation final token = response.data.token; } else { print("Passkey sign-in failed: ${response.error}"); } ``` ```swift Swift theme={null} let response = await authsignal.passkey.signIn(action: "signInWithPasskey") if let token = response.data?.token { // Send token to your backend for validation } else { print("Passkey sign-in failed: \(response.error ?? "Unknown error")") } ``` ```kotlin Kotlin theme={null} val response = authsignal.passkey.signIn(action = "signInWithPasskey") if (response.data?.token != null) { // Send token to your backend for validation val token = response.data.token } else { Log.e("Passkey", "Sign-in failed: ${response.error}") } ``` **2. Backend - Create session** Pass the token obtained in step 1 to your backend and exchange it for an access token and refresh token. ```ts Node.js theme={null} const request = { token: "eyJhbGciOiJ...", clientId: "46e10ac5-cb9c-458f-ad86-0f698f67b365", }; const response = await authsignal.createSession(request); const accessToken = response.accessToken; const refreshToken = response.refreshToken; ``` ```csharp C# theme={null} var request = new CreateSessionRequest( Token: "eyJhbGciOiJ...", ClientId: "46e10ac5-cb9c-458f-ad86-0f698f67b365" ); var response = await authsignal.CreateSession(request); var accessToken = response.AccessToken; var refreshToken = response.RefreshToken; ``` ```java Java theme={null} CreateSessionRequest request = new CreateSessionRequest(); request.token = "eyJhbGciOiJ..."; request.clientId = "46e10ac5-cb9c-458f-ad86-0f698f67b365"; CreateSessionResponse response = authsignal.createSession(request).get(); String accessToken = response.accessToken; String refreshToken = response.refreshToken; ``` ```ruby Ruby theme={null} response = Authsignal.create_session( token: "eyJhbGciOiJ...", client_id: "46e10ac5-cb9c-458f-ad86-0f698f67b365", ) access_token = response[:access_token] refresh_token = response[:refresh_token] ``` ```go Go theme={null} response, err := client.CreateSession( CreateSessionRequest{ Token: "eyJhbGciOiJ...", ClientId: "46e10ac5-cb9c-458f-ad86-0f698f67b365", }, ) accessToken := response.AccessToken refreshToken := response.RefreshToken ``` ## Validating sessions ### Using the JWKS URL Access tokens are signed using an RS256 algorithm. A JWKS endpoint for your tenant's keys is available at the following location: ``` ${env:AUTHSIGNAL_URL}/client/public/${env:AUTHSIGNAL_TENANT}/.well-known/jwks ``` * The `AUTHSIGNAL_URL` value is the URL for your [tenant's region](/sdks/server/overview#initialization). * The `AUTHSIGNAL_TENANT` value is your tenant ID. ### Using the SDK You can also use the [Authsignal Server SDK](/sdks/server/overview) to validate an access token. ```ts Node.js theme={null} const request = { accessToken: "eyJhbGciOiJ...", clientIds: ["46e10ac5-cb9c-458f-ad86-0f698f67b365"], }; const response = await authsignal.validateSession(request); const userId = response.user.userId; const email = response.user.email; ``` ```csharp C# theme={null} var request = new ValidateSessionRequest( AccessToken: "eyJhbGciOiJ...", ClientIds: ["46e10ac5-cb9c-458f-ad86-0f698f67b365"] ); var response = await authsignal.ValidateSession(request); var userId = response.User.UserId; var email = response.User.Email; ``` ```java Java theme={null} ValidateSessionRequest request = new ValidateSessionRequest(); request.accessToken = "eyJhbGciOiJ..."; request.clientIds = clientIds = new String[] { "46e10ac5-cb9c-458f-ad86-0f698f67b365" }; ValidateSessionResponse response = authsignal.validateSession(request).get(); String userId = response.user.userId; String email = response.user.email; ``` ```ruby Ruby theme={null} response = Authsignal.validate_session( access_token: "eyJhbGciOiJ...", client_ids: ["46e10ac5-cb9c-458f-ad86-0f698f67b365"], ) user_id = response[:user][:user_id] email = response[:user][:email] ``` ```go Go theme={null} response, err := client.ValidateSession( ValidateSessionRequest{ AccessToken: "eyJhbGciOiJ...", ClientIds: []string{"46e10ac5-cb9c-458f-ad86-0f698f67b365"} }, ) userId := response.User.UserId email := response.User.Email ``` In addition to verifying the access token's signature, the Authsignal SDK's `validateSession` method will also check that the token has not been revoked. ## Refreshing sessions A refresh token can be exchanged for a new access token and refresh token. ```ts Node.js theme={null} const request = { refreshToken: "3f72a26e6834b4da...", }; const response = await authsignal.refreshSession(request); const accessToken = response.accessToken; const refreshToken = response.refreshToken; ``` ```csharp C# theme={null} var request = new RefreshSessionRequest( RefreshToken: "3f72a26e6834b4da...", ); var response = await authsignal.RefreshSession(request); var accessToken = response.AccessToken; var refreshToken = response.RefreshToken; ``` ```java Java theme={null} RefreshSessionRequest request = new RefreshSessionRequest(); request.refreshToken = "3f72a26e6834b4da..."; RefreshSessionResponse response = authsignal.refreshSession(request).get(); String accessToken = response.accessToken; String refreshToken = response.refreshToken; ``` ```ruby Ruby theme={null} response = Authsignal.refresh_session( refresh_token: "3f72a26e6834b4da...", ) access_token = response[:access_token] refresh_token = response[:refresh_token] ``` ```go Go theme={null} response, err := client.RefreshSession( RefreshSessionRequest{ RefreshToken: "3f72a26e6834b4da...", }, ) accessToken := response.AccessToken refreshToken := response.RefreshToken ``` Refresh tokens are single-use and should be replaced with the new refresh token returned in the response. ## Revoking sessions An individual access token can be revoked so that the `validateSession` method will no longer accept it. ```ts Node.js theme={null} const request = { accessToken: "eyJhbGciOiJ...", }; await authsignal.revokeSession(request); ``` ```csharp C# theme={null} var request = new RevokeSessionRequest( AccessToken: "eyJhbGciOiJ..." ); await authsignal.RevokeSession(request); ``` ```java Java theme={null} RevokeSessionRequest request = new RevokeSessionRequest(); request.accessToken = "eyJhbGciOiJ..."; authsignal.revokeSession(request).get(); ``` ```ruby Ruby theme={null} response = Authsignal.revoke_session( access_token: "eyJhbGciOiJ...", ) ``` ```go Go theme={null} err := client.RevokeSession( RevokeSessionRequest{ AccessToken: "eyJhbGciOiJ...", }, ) ``` In addition, you can revoke all currently active tokens for a user. ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", }; await authsignal.revokeUserSessions(request); ``` ```csharp C# theme={null} var request = new RevokeUserSessionsRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833" ); await authsignal.RevokeUserSessions(request); ``` ```java Java theme={null} RevokeUserSessionsRequest request = new RevokeUserSessionsRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; authsignal.revokeUserSessions(request).get(); ``` ```ruby Ruby theme={null} response = Authsignal.revoke_user_sessions( user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", ) ``` ```go Go theme={null} err := client.RevokeUserSessions( RevokeSessionRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", }, ) ``` ## Next steps * [Server SDK session methods](/sdks/server/sessions) # Using Terraform Source: https://docs.authsignal.com/advanced-usage/using-terraform Learn how to use Authsignal's Terraform provider to keep your rule configuration synced across tenants. Authsignal's Terraform provider can be used to set-up and manage action configurations and rules. Detailed information about the available resources can be found on [the Terraform registry](https://registry.terraform.io/providers/authsignal/authsignal/latest). If this is your first time using Terraform we recommend having a read of [the official get started guide](https://developer.hashicorp.com/terraform/tutorials/aws-get-started) for AWS to learn the fundamentals. ## 1. Obtain a Management API secret key You will need to generate an Management API secret key to use with the Terraform provider. The key can be generated in [the admin portal](https://portal.authsignal.com/organisations/tenants/api). ## 2. Create initial Terraform configuration The following 3 variables are needed for the provider to run and can be found on the admin portal page linked above. These can be written directly into your Terraform files but we recommend configuring them as environment variables, especially for production environments. Please note the addition of `/v1/management` on the end of the API host URI. ```javascript theme={null} terraform { required_providers { authsignal = { source = "authsignal/authsignal" version = "1.0.0" } } } provider "authsignal" { host = "https://api.authsignal.com/v1/management" # AUTHSIGNAL_HOST tenant_id = "aaaa-bbbb-cccc-dddd" # AUTHSIGNAL_TENANT_ID api_secret = "" # AUTHSIGNAL_API_SECRET } ``` Run `terraform init` to validate the configuration. From here you can start configuring resources, please refer to [the Terraform registry](https://registry.terraform.io/providers/authsignal/authsignal/latest) to see what resources are available and the related syntax. ## 3. Export Rule JSON from the Admin portal (optional) We recommend configuring some rules in the admin portal by hand and then exporting them into your Terraform configuration, rather than writing out the rules from scratch. After configuring a rule in the portal, you can export rule's JSON by doing the following: 1. Click 'Edit' A screenshot showing where the edit rule button
is 2. Click 'Export' A screenshot showing the export rule json button
is You will need to edit the exported JSON slightly to conform to valid Terraform syntax. Please see [the Terraform registry's docs](https://registry.terraform.io/providers/authsignal/authsignal/latest/docs/resources/rule) for rules for the correct structure and field names. ## 4. Import already-existing resources into your Terraform configuration (optional) Rather than deleting your existing rules in the portal and then adding them back through Terraform, you are able to import rules directly into your Terraform state. You will need to write the corresponding Terraform for your action configurations and rules and then import them one by one. This can be done via the `terraform import` command which is [documented here](https://developer.hashicorp.com/terraform/cli/commands/import). The address syntax can be found under each resource's documentation on [the Terraform registry](https://registry.terraform.io/providers/authsignal/authsignal/latest/docs). ### Importing an action configuration You will need to find your action code in the admin portal, it can be found here: A screenshot showing an example of a rule's json
output Then write the corresponding Terraform for your action configuration: ```javascript theme={null} resource "authsignal_action_configuration" "my_action" { action_code = "My-Action" default_user_action_result = "ALLOW" } ``` You can then import the rule using the following command:\ `terraform import authsignal_action_configuration.my_action My-Action` ### Importing a rule You will need to find your rule's unique identifier in the admin portal, it can be found in the URL when editing a rule: A screenshot showing an example of a rule's json
output Then write the corresponding Terraform for your rule: ```javascript theme={null} resource "authsignal_rule" "my_rule" { action_code = authsignal_action_configuration.my_action name = "My-Rule" is_active = true priority = 0 type = "CHALLENGE" conditions = jsonencode({ "and" : [ { "==" : [ { "var" : "ip.isAnonymous" }, true ] } ] }) } ``` You can then import the rule using the following command:\ `terraform import authsignal_rule.my_rule "My-Action/ccad3b11-14dc-4509-895a-dacac4983ed3"` ### Importing a theme To use a `authsignal_theme` resource you will need to import the current theme settings using `terraform import`. All Authsignal tenants come with some default theme settings. Write the corresponding Terraform for your theme: ```javascript theme={null} resource "authsignal_theme" "my_theme" { name = "My Tenant" logo_url = "" favicon_url = "" watermark_url = "" primary_color = "#ABCD12" borders = { button_border_radius = 1 button_border_width = 2 card_border_radius = 3 card_border_width = 4 input_border_radius = 5 input_border_width = 6 container_border_radius = 7 } colors = { button_primary_text = "#ABCD12" button_primary_border = "#ABCD12" button_secondary_text = "#ABCD12" button_secondary_background = "#ABCD12" button_secondary_border = "#ABCD12" card_background = "#ABCD12" card_border = "#ABCD12" input_background = "#ABCD12" input_border = "#ABCD12" link = "#ABCD12" heading_text = "#ABCD12" body_text = "#ABCD12" container_background = "#ABCD12" container_border = "#ABCD12" divider = "#ABCD12" icon = "#ABCD12" loader = "#ABCD12" positive = "#ABCD12" critical = "#ABCD12" information = "#ABCD12" hover = "#ABCD12" focus = "#ABCD12" } page_background = { background_color = "#ABCD12" background_image_url = "" } container = { content_alignment = "left" padding = 1 logo_alignment = "center" logo_position = "inside" logo_height = 2 position = "outside" } } ``` You can then import the existing theme with the following command. Please note the use of an empty string as the resource ID. A tenant can only have 1 theme resource, which is imported from the `""` ID:\ `terraform import authsignal_theme.my_theme ""` # action.log_created Source: https://docs.authsignal.com/advanced-usage/webhooks/action-log-created A snapshot of an action that has reached a terminal state. Sent in batches for SIEM/data lake forwarding. These webhooks are asynchronous and delivered in batches of up to 500 events, after your tenant's challenge token duration (typically 15 minutes). Delivery is at-least-once, so de-duplicate using the envelope `id`. Configure the webhook URL for log events in [tenant settings](https://portal.authsignal.com/organisations/tenants/settings). ## Batch delivery format ```json theme={null} { "records": [ { "type": "action.log_created", "record": { } }, { "type": "challenge.log_created", "record": { } } ] } ``` ## Payload The ID of the tenant that the action occurred within. The ID of the user that the action was triggered by. The action being evaluated (e.g. `login`, `withdrawal`). Unique per action instance. When the action was first evaluated. When the action record was last updated. Final action state. One of `ALLOW`, `BLOCK`, `CHALLENGE_REQUIRED`, `CHALLENGE_SUCCEEDED`, `CHALLENGE_FAILED`, `REVIEW_REQUIRED`. When state last changed. The rule engine decision. One of `ALLOW`, `BLOCK`, `CHALLENGE`, `REVIEW`. Immutable for the lifetime of the action. The method the user actually used to complete a challenge, if any (e.g. `EMAIL_OTP`, `PASSKEY`, `SMS`, `AUTHENTICATOR_APP`). The set of methods the user was offered for this challenge. Omitted if empty. Rules that fired during evaluation. Omitted if none. The rule that drove the outcome, when multiple rules fired. IP address of the request that initiated the action. Country resolved from the IP. Email on file for the user at the time of the action. Phone number on file for the user at the time of the action. Opaque identifier for the device that initiated the action. Verification methods the user had enrolled at the time of the action. Custom key/value data supplied by your application. Omitted if empty. ```json action.log_created theme={null} { "version": 1, "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", "source": "https://authsignal.com", "time": "2026-04-22T01:08:05.197Z", "tenantId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "type": "action.log_created", "record": { "tenantId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "userId": "user_abc", "actionCode": "login", "idempotencyKey": "bb51e6b9-a7f8-4f03-8044-2940ae574236", "createdAt": "2026-04-22T01:08:05.197Z", "updatedAt": "2026-04-22T01:08:05.197Z", "state": "CHALLENGE_SUCCEEDED", "stateUpdatedAt": "2026-04-22T01:10:34.067Z", "outcome": "CHALLENGE", "verificationMethod": "EMAIL_OTP", "allowedVerificationMethods": ["PASSKEY", "EMAIL_OTP", "AUTHENTICATOR_APP"], "rules": [ { "id": "40fcd205-5318-41d7-b225-3b6122b13654", "name": "challenge nz" } ], "priorityRuleId": "40fcd205-5318-41d7-b225-3b6122b13654", "ipAddress": "192.168.1.1", "countryCode": "NZ", "email": "alice@example.com", "phoneNumber": "+64221234567", "deviceId": "device_xyz", "enrolledVerificationMethods": ["AUTHENTICATOR_APP", "EMAIL_OTP"], "custom": { "plan": "pro" } } } ``` # action.verify Source: https://docs.authsignal.com/advanced-usage/webhooks/action-verify Sent synchronously after a challenge is verified. The webhook response can gate the transition to CHALLENGE_SUCCEEDED. Sent when an action is about to transition to `CHALLENGE_SUCCEEDED`. Run additional checks against your own systems and gate the final outcome. This webhook is synchronous, so a non-2xx response or a timeout blocks the transition. There are no automatic retries, but the user can retry by re-running the verification. Set `verificationWebhookUrl` on the action configuration in the Authsignal Portal, or via the [Create](/api-reference/management-api/create-action-configurations) / [Update](/api-reference/management-api/update-action-configurations) Action Configuration endpoints. ## Payload The ID of the user that the action belongs to. The action code for the action that is about to succeed. The idempotency key of the action that triggered the webhook. The time the verification completed in ISO 8601 format. The pending action state. For `action.verify` this is always `CHALLENGE_SUCCEEDED`. The verification method that completed the challenge. The ID of the user authenticator that completed the challenge, when available. ```json action.verify theme={null} { "version": 1, "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", "source": "https://authsignal.com", "time": "2024-01-01T01:23:45.678Z", "tenantId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "type": "action.verify", "data": { "userId": "11111111-1111-1111-1111-111111111111", "verificationMethod": "PUSH", "action": "signIn", "idempotencyKey": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "verifiedAt": "2024-01-01T01:23:45.678Z", "state": "CHALLENGE_SUCCEEDED", "userAuthenticatorId": "cccccccc-cccc-cccc-cccc-cccccccccccc" } } ``` # authenticator.created Source: https://docs.authsignal.com/advanced-usage/webhooks/authenticator-created Fired when a user enrolls a new authenticator. This webhook is asynchronous. If your endpoint returns a non-2xx response, it is retried up to 3 times, at least 30 seconds apart. Configure the webhook URL for authenticator events in [tenant settings](https://portal.authsignal.com/organisations/tenants/settings). ## Payload The ID of the user the authenticator was created for. The verification method of the authenticator that was created. The time the authenticator was created in ISO 8601 format. A unique ID for the user authenticator that was created. The email address associated with the authenticator. Included for email OTP and magic link authenticators. The phone number associated with the authenticator. Included for SMS and WhatsApp authenticators. The passkey credential ID. Only included for passkey authenticators. The base64url-encoded public key of the passkey credential. Only included for passkey authenticators when `includeCredentialPublicKey` is enabled in webhook settings. The AAGUID of the authenticator that created the passkey. Only included for passkey authenticators. A display name for the passkey authenticator, such as the device or credential manager name. Only included for passkey authenticators. ```json authenticator.created theme={null} { "version": 1, "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", "source": "https://authsignal.com", "time": "2024-01-01T01:23:45.678Z", "tenantId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "type": "authenticator.created", "data": { "userId": "11111111-1111-1111-1111-111111111111", "verificationMethod": "EMAIL_MAGIC_LINK", "createdAt": "2024-01-01T01:23:45.678Z", "userAuthenticatorId": "cccccccc-cccc-cccc-cccc-cccccccccccc", "email": "jane.smith@authsignal.com" } } ``` # authenticator.deleted Source: https://docs.authsignal.com/advanced-usage/webhooks/authenticator-deleted Fired when an authenticator is removed from a user. This webhook is asynchronous. If your endpoint returns a non-2xx response, it is retried up to 3 times, at least 30 seconds apart. Configure the webhook URL for authenticator events in [tenant settings](https://portal.authsignal.com/organisations/tenants/settings). ## Payload The ID of the user the authenticator was deleted for. The verification method of the authenticator that was deleted. The time the authenticator was originally created in ISO 8601 format. The time the authenticator was deleted in ISO 8601 format. A unique ID for the user authenticator that was deleted. The email address associated with the authenticator. Included for email OTP and magic link authenticators. The phone number associated with the authenticator. Included for SMS and WhatsApp authenticators. The passkey credential ID. Only included for passkey authenticators. The AAGUID of the authenticator that created the passkey. Only included for passkey authenticators. A display name for the passkey authenticator, such as the device or credential manager name. Only included for passkey authenticators. ```json authenticator.deleted theme={null} { "version": 1, "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", "source": "https://authsignal.com", "time": "2025-01-01T02:34:56.789Z", "tenantId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "type": "authenticator.deleted", "data": { "userId": "11111111-1111-1111-1111-111111111111", "verificationMethod": "EMAIL_MAGIC_LINK", "createdAt": "2024-01-01T01:23:45.678Z", "deletedAt": "2025-01-01T02:34:56.789Z", "userAuthenticatorId": "cccccccc-cccc-cccc-cccc-cccccccccccc" } } ``` # authenticator.updated Source: https://docs.authsignal.com/advanced-usage/webhooks/authenticator-updated Fired when an authenticator on a user is updated. Fired when an existing authenticator on a user is updated, such as when the user's SMS channel switches between SMS and WhatsApp. This webhook is asynchronous. If your endpoint returns a non-2xx response, it is retried up to 3 times, at least 30 seconds apart. Configure the webhook URL for authenticator events in [tenant settings](https://portal.authsignal.com/organisations/tenants/settings). ## Payload The ID of the user the authenticator was updated for. The verification method of the authenticator that was updated. The time the authenticator was updated in ISO 8601 format. A unique ID for the user authenticator that was updated. The last channel that was used to successfully complete an SMS OTP challenge. Either `DEFAULT` (regular SMS) or `WHATSAPP`. The email address associated with the authenticator. Included for email OTP and magic link authenticators. The phone number associated with the authenticator. Included for SMS and WhatsApp authenticators. The passkey credential ID. Only included for passkey authenticators. The AAGUID of the authenticator that created the passkey. Only included for passkey authenticators. A display name for the passkey authenticator, such as the device or credential manager name. Only included for passkey authenticators. ```json authenticator.updated theme={null} { "version": 1, "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", "source": "https://authsignal.com", "time": "2024-01-01T01:23:45.678Z", "tenantId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "type": "authenticator.updated", "data": { "userId": "11111111-1111-1111-1111-111111111111", "verificationMethod": "SMS", "updatedAt": "2024-01-01T01:23:45.678Z", "userAuthenticatorId": "cccccccc-cccc-cccc-cccc-cccccccccccc", "previousSmsChannel": "WHATSAPP", "phoneNumber": "+12345678901" } } ``` # challenge.log_created Source: https://docs.authsignal.com/advanced-usage/webhooks/challenge-log-created A record of a challenge-related event such as an OTP being sent or verified. Sent in batches for SIEM/data lake forwarding. Records granular challenge lifecycle events such as OTPs being sent, verified, expired, or rate-limited. Pair with [`action.log_created`](/advanced-usage/webhooks/action-log-created) for a full audit trail. These webhooks are asynchronous and delivered in batches of up to 500 events, after your tenant's challenge token duration (typically 15 minutes). Delivery is at-least-once, so de-duplicate using the envelope `id`. Configure the webhook URL for log events in [tenant settings](https://portal.authsignal.com/organisations/tenants/settings). ## Payload The ID of the tenant that the event occurred within. The ID of the user that the action was triggered by. The action being evaluated (e.g. `login`, `withdrawal`). Unique per action instance. When this challenge event occurred. The event type. See [Challenge event types](#challenge-event-types) below for the full list. The verification method involved (e.g. `EMAIL_OTP`, `SMS`, `PASSKEY`). Email address involved in the event (for email-channel events). Phone number involved in the event (for SMS/WhatsApp-channel events). Human-readable error summary for `*_FAILED` events. Upstream HTTP status code for downstream-delivery failure events. Additional structured fields. Omitted if empty. ## Challenge event types A non-exhaustive list of the most commonly seen event types. **Email OTP**: `EMAIL_OTP_SENT`, `EMAIL_OTP_CODE_VALID`, `EMAIL_OTP_INVALID_OR_EXPIRED`, `EMAIL_OTP_MAX_ATTEMPTS_EXCEEDED`, `EMAIL_OTP_RATE_LIMIT_EXCEEDED`, `EMAIL_OTP_SEND_DOWNSTREAM_FAILED` **Email magic link**: `EMAIL_MAGIC_LINK_SENT`, `EMAIL_MAGIC_LINK_INVALID_OR_EXPIRED`, `EMAIL_MAGIC_LINK_RATE_LIMIT_EXCEEDED`, `EMAIL_MAGIC_LINK_SEND_DOWNSTREAM_FAILED` **SMS**: `SMS_SENT`, `SMS_DELIVERED`, `SMS_NOT_DELIVERED`, `SMS_CODE_VALID`, `SMS_CODE_INVALID_OR_EXPIRED`, `SMS_MAX_ATTEMPTS_EXCEEDED`, `SMS_RATE_LIMIT_EXCEEDED`, `SMS_SEND_DOWNSTREAM_FAILED` **WhatsApp**: `WHATSAPP_SENT`, `WHATSAPP_CODE_VALID`, `WHATSAPP_CODE_INVALID_OR_EXPIRED`, `WHATSAPP_MAX_ATTEMPTS_EXCEEDED`, `WHATSAPP_RATE_LIMIT_EXCEEDED`, `WHATSAPP_SEND_DOWNSTREAM_FAILED` **TOTP / authenticator app**: `TOTP_CODE_VALID`, `TOTP_CODE_INVALID_OR_EXPIRED`, `TOTP_MAX_ATTEMPTS_EXCEEDED` **Push**: `PUSH_SENT` ```json challenge.log_created theme={null} { "version": 1, "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", "source": "https://authsignal.com", "time": "2026-04-22T01:10:20.939Z", "tenantId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "type": "challenge.log_created", "record": { "tenantId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "userId": "user_abc", "actionCode": "login", "idempotencyKey": "bb51e6b9-a7f8-4f03-8044-2940ae574236", "createdAt": "2026-04-22T01:10:20.939Z", "type": "EMAIL_OTP_SENT", "verificationMethod": "EMAIL_OTP", "email": "alice@example.com" } } ``` # Delivery and retries Source: https://docs.authsignal.com/advanced-usage/webhooks/delivery How Authsignal delivers webhook events, and what happens when delivery fails. ## Synchronous webhooks Sent during an active request. The response affects the outcome of that request, so respond as quickly as possible and defer slow work to a background job. * **Challenge events** (`email.created`, `sms.created`, `push.created`). A non-2xx fails the challenge and an error is returned to the caller. The [pre-built UI](/implementation-options/prebuilt-ui/overview) prompts the user to retry; the SDK or API surfaces the error to your application. * **Action verification** (`action.verify`). A non-2xx or a timeout blocks the transition to `CHALLENGE_SUCCEEDED`. No automatic retries; the user can retry by re-running the verification. ## Asynchronous webhooks Sent in the background, so they do not block the originating request. * **Authenticator events** (`authenticator.*`). When your endpoint returns a non-2xx response, the event is retried up to **3 times**, with a minimum of **30 seconds** between attempts. * **Log events** (`action.log_created`, `challenge.log_created`). Delivered in batches of up to **500 events** after your tenant's challenge token duration (typically 15 minutes). Failed batches are retried periodically until delivery succeeds. Both types use **at-least-once delivery**, so the same event can arrive more than once. De-duplicate using the envelope `id`. Authsignal also does not guarantee strict ordering, so use the `time` field on the envelope to reconstruct order if you need it. ## IP address allow-listing Authsignal will send webhooks originating from the following IP addresses: | Region | IP Addresses | | ------------- | --------------------------------------------------------------------- | | US (Oregon) | 44.224.97.232
44.230.210.235
44.236.208.22
52.33.85.88 | | AU (Sydney) | 13.210.81.243
3.105.80.107
54.252.129.142 | | EU (Dublin) | 34.247.148.106
34.253.116.90
54.171.116.55 | | CA (Montreal) | 16.52.98.180
16.54.49.43
16.54.18.28 | # email.created Source: https://docs.authsignal.com/advanced-usage/webhooks/email-created Sent when an email OTP or magic link needs to be delivered through your own email provider. Triggered when a challenge is created for an email-based authenticator. The `data` payload contains either `code` (for email OTP) or `url` (for magic link). This webhook is synchronous, so a non-2xx response fails the challenge. Open the Email OTP or Email Magic Link authenticator in [authenticator settings](https://portal.authsignal.com/organisations/tenants/authenticators) and set the email provider to **Webhook**. The URL you enter as part of provider setup is where this event is delivered. ## Payload The email address the OTP or magic link should be delivered to. The email OTP code. Present for email OTP authenticators. The URL of the magic link. Present for email magic link authenticators. The ID of the user the challenge is for. The idempotency key of the request that created the challenge. The action code of the track request that created the challenge. The user agent of the user who requested the challenge. Only present if the user agent was captured when [tracking an action](/api-reference/server-api/track-action). The timezone of the user who requested the challenge. Only present if the IP address was captured when [tracking an action](/api-reference/server-api/track-action). The IP address of the user who requested the challenge. Only present if the IP address was captured when [tracking an action](/api-reference/server-api/track-action). The locale of the user who requested the challenge. Only present if the locale was captured when [tracking an action](/api-reference/server-api/track-action#body-locale) or set on the user via [update user](/api-reference/server-api/update-user#body-locale). ```json Magic link theme={null} { "version": 1, "id": "a3f68e12-7b4c-4d2a-9e5f-1c8b3a6d9e72", "source": "https://authsignal.com", "time": "2024-01-01T01:23:45.678Z", "tenantId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "type": "email.created", "data": { "to": "jane.smith@acme.com", "url": "https://mfa.authsignal.com/api/verify-magic-link?token=...", "userId": "11111111-1111-1111-1111-111111111111", "idempotencyKey": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "actionCode": "sign-in", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", "timezone": "America/New_York", "ipAddress": "203.0.113.42", "locale": "en" } } ``` ```json Email OTP theme={null} { "version": 1, "id": "b7d29a45-3e1f-4c8b-a6d2-9f5e1c3b7a84", "source": "https://authsignal.com", "time": "2024-01-01T01:23:45.678Z", "tenantId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "type": "email.created", "data": { "to": "john.doe@example.org", "code": "847291", "userId": "11111111-1111-1111-1111-111111111111", "idempotencyKey": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "actionCode": "transfer-funds", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "timezone": "Europe/London", "ipAddress": "198.51.100.17", "locale": "en" } } ``` # Event types Source: https://docs.authsignal.com/advanced-usage/webhooks/event-types The full list of events Authsignal can send to your webhook endpoints. ## Authenticator challenge events Sent so you can deliver a challenge through your own provider. **Synchronous**: a non-2xx response fails the challenge. | Event | Description | | --------------------------------------------------------- | -------------------------------------------- | | [`email.created`](/advanced-usage/webhooks/email-created) | An email OTP or magic link needs to be sent. | | [`sms.created`](/advanced-usage/webhooks/sms-created) | An SMS OTP needs to be sent. | | [`push.created`](/advanced-usage/webhooks/push-created) | A push notification needs to be delivered. | ## Authenticator events Fired when a user adds, updates, or removes an authenticator. These can be used to send custom notifications so that users are aware of changes to the factors protecting their account, and to synchronise user details with third-party systems such as CRMs. **Asynchronous**, with up to 3 retries. | Event | Description | | ------------------------------------------------------------------------- | ----------------------------------------- | | [`authenticator.created`](/advanced-usage/webhooks/authenticator-created) | A user enrolled a new authenticator. | | [`authenticator.updated`](/advanced-usage/webhooks/authenticator-updated) | An authenticator on a user was updated. | | [`authenticator.deleted`](/advanced-usage/webhooks/authenticator-deleted) | An authenticator was removed from a user. | ## Action verification events Sent during action evaluation. These can be used to trigger additional blocking checks while a challenge is being verified. **Synchronous**: a non-2xx response blocks the outcome. | Event | Description | | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | [`action.verify`](/advanced-usage/webhooks/action-verify) | A challenge is about to transition to `CHALLENGE_SUCCEEDED`. The webhook can block the transition. | ## Log events Audit records for actions and challenges, delivered in batches for SIEM or data-lake forwarding. **Asynchronous**, batched, at-least-once. | Event | Description | | ------------------------------------------------------------------------- | ----------------------------------------------------------------- | | [`action.log_created`](/advanced-usage/webhooks/action-log-created) | A snapshot of an action that has reached a terminal state. | | [`challenge.log_created`](/advanced-usage/webhooks/challenge-log-created) | A challenge-related event, such as an OTP being sent or verified. | ## Envelope schema Every webhook delivery shares a common envelope. Event-specific fields live under `data` (or `record`, for log events). ## Event metadata A unique identifier for the event. The source of the event. This is always `https://authsignal.com`. The time the event was created in ISO 8601 format. The type of the event. Each type has a different schema for the `data` field. The version of the event. The ID of the tenant that the event is intended for. The event-specific data. See below for the schema of the `data` field for each event type. # Webhooks Source: https://docs.authsignal.com/advanced-usage/webhooks/introduction Use webhooks to receive real-time events from Authsignal and integrate with your own systems. Authsignal sends webhooks as signed JSON over HTTPS whenever a relevant event happens in your tenant. Your endpoint returns a 2xx response to acknowledge the event. ## How to receive webhooks ### 1. Create an endpoint In your application, create a route that accepts `POST` requests over HTTPS. For local development, expose your endpoint with a tunnel such as [ngrok](https://ngrok.com) or [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/). ### 2. Configure the webhook URL Webhook URLs are configured in different places depending on the event type. | Event type | Where to configure | | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authenticator events (`authenticator.created`, `authenticator.updated`, `authenticator.deleted`) | [Tenant settings](https://portal.authsignal.com/organisations/tenants/settings) → Webhooks → **Authenticator events webhook URL** | | Log events (`action.log_created`, `challenge.log_created`) | [Tenant settings](https://portal.authsignal.com/organisations/tenants/settings) → Webhooks → **Log events webhook URL** | | `email.created`, `sms.created`, `push.created` | [Authenticator settings](https://portal.authsignal.com/organisations/tenants/authenticators) → open the Email, SMS, or Push authenticator → set the delivery method to **Webhook** and enter the URL there | | `action.verify` | Action configuration (Portal or [Management API](/api-reference/management-api/create-action-configurations) `verificationWebhookUrl`) | ### 3. Verify the signature Every request is signed with an `X-Signature-V2` header. Verify it before trusting the payload. See [Verify webhook requests](/advanced-usage/webhooks/verify-requests). ### 4. Handle the event Inspect the envelope `type` and route to the right handler. Each event's `data` schema is documented on its own page in this section. # push.created Source: https://docs.authsignal.com/advanced-usage/webhooks/push-created Sent when a push notification challenge needs to be delivered through your own push provider. Triggered when a challenge is created for the Push authenticator. This webhook is synchronous, so a non-2xx response fails the challenge. Open the Push authenticator in [authenticator settings](https://portal.authsignal.com/organisations/tenants/authenticators) and enter the **Webhook URL**. ## Payload The challenge ID to be completed using push notification. The ID of the user the push challenge is for. The idempotency key of the request that created the push notification. The action code of the track request that created the push notification. The user agent of the user who requested the push notification. Only present if the user agent was captured when [tracking an action](/api-reference/server-api/track-action). The timezone of the user who requested the push notification. Only present if the IP address was captured when [tracking an action](/api-reference/server-api/track-action). The IP address of the user who requested the push notification. Only present if the IP address was captured when [tracking an action](/api-reference/server-api/track-action). ```json push.created theme={null} { "version": 1, "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", "source": "https://authsignal.com", "time": "2024-01-01T01:23:45.678Z", "tenantId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "type": "push.created", "data": { "challengeId": "61b5b44bea582c5f2c7e2c93f1f41d7d8f8e9fba8582a319be6a3aee696b018cc59f7d043acaaabab705c6d2b93ea1ef", "userId": "11111111-1111-1111-1111-111111111111", "idempotencyKey": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "actionCode": "sign-in", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "timezone": "Europe/London", "ipAddress": "12.34.56.78" } } ``` # sms.created Source: https://docs.authsignal.com/advanced-usage/webhooks/sms-created Sent when an SMS OTP needs to be delivered through your own SMS provider. Triggered when a challenge is created for the SMS OTP authenticator. This webhook is synchronous, so a non-2xx response fails the challenge. Open the SMS OTP authenticator in [authenticator settings](https://portal.authsignal.com/organisations/tenants/authenticators) and set the SMS provider to **Webhook**. The URL you enter as part of provider setup is where this event is delivered. ## Payload The phone number the SMS should be delivered to (E.164 format). The SMS OTP code. The ID of the user the challenge is for. The idempotency key of the request that created the SMS code. The action code of the track request that created the SMS code. The locale of the user who requested the SMS code. Only present if the locale was captured when [tracking an action](/api-reference/server-api/track-action#body-locale) or set on the user via [update user](/api-reference/server-api/update-user#body-locale). ```json sms.created theme={null} { "version": 1, "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", "source": "https://authsignal.com", "time": "2024-01-01T01:23:45.678Z", "tenantId": "dddddddd-dddd-dddd-dddd-dddddddddddd", "type": "sms.created", "data": { "to": "+123456789", "code": "987654", "userId": "11111111-1111-1111-1111-111111111111", "idempotencyKey": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "actionCode": "sign-in", "locale": "en" } } ``` # Verify webhook requests Source: https://docs.authsignal.com/advanced-usage/webhooks/verify-requests Validate that incoming webhook requests really came from Authsignal. It is critical to verify that incoming requests to your webhook were sent by Authsignal, and to reject any that were not. The recommended approach is to use an [Authsignal Server SDK](/sdks/server/webhooks) to handle the incoming request. ```ts Node.js theme={null} const authsignal = new Authsignal({ apiSecretKey: "YOUR_SERVER_API_SECRET_KEY" }); // Obtain raw request body and signature header // For example using Express (https://expressjs.com/) const payload = req.body; const sig = req.headers["X-Signature-V2"]; const event = authsignal.webhook.constructEvent(payload, sig); ``` ```csharp C# theme={null} var authsignal = new AuthsignalClient("YOUR_SERVER_API_SECRET_KEY"); // Obtain raw request body and signature header var payload = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); var sig = Request.Headers["X-Signature-V2"]; var webhookEvent = authsignal.Webhook.ConstructEvent(payload, sig); ``` ```java Java theme={null} AuthsignalClient authsignal = new AuthsignalClient("YOUR_SERVER_API_SECRET_KEY"); // Obtain raw request body and signature header // For example using the Spark framework (http://sparkjava.com) String payload = request.body(); String sig = request.headers("X-Signature-V2"); WebhookEvent event = authsignal.webhook.constructEvent(payload, sig); ``` ```ruby Ruby theme={null} authsignal = Authsignal::Client.new(api_secret_key: 'YOUR_SERVER_API_SECRET_KEY') # Obtain raw request body and signature header payload = request.body.read sig = request.headers['X-Signature-V2'] event = authsignal.webhook.construct_event(payload, sig) ``` ```python Python theme={null} from authsignal import AuthsignalClient authsignal = AuthsignalClient(api_secret_key="YOUR_SERVER_API_SECRET_KEY") # Obtain raw request body and signature header payload = request.get_data(as_text=True) sig = request.headers.get("X-Signature-V2") event = authsignal.webhook.construct_event(payload, sig) ``` ```PHP PHP theme={null} $authsignal = new Authsignal\Client('YOUR_SERVER_API_SECRET_KEY'); // Obtain raw request body and signature header $payload = file_get_contents('php://input'); $sig = $_SERVER['HTTP_X_SIGNATURE_V2']; $event = $authsignal->webhook()->constructEvent($payload, $sig); ``` ```go Go theme={null} webhook := NewWebhook("YOUR_SERVER_API_SECRET_KEY") // Obtain raw request body and signature header payload := string(body) sig := r.Header.Get("X-Signature-V2") event, err := webhook.ConstructEventWithDefaultTolerance(payload, sig) ``` By passing the raw request body along with the `X-Signature-V2` header to the SDK, it verifies that the request is valid and constructs the event for you to handle. The Authsignal SDK requires the **raw body** of the request to verify the signature. If you're using a framework, make sure it doesn't manipulate the raw body, as this will cause the signature verification to fail. ## Replay protection The Authsignal Server SDKs reject events older than **5 minutes** by default. Customize the window by passing a `tolerance` value (in minutes). ```ts Node.js theme={null} const payload = req.body; const sig = req.headers["X-Signature-V2"]; const tolerance = 10; const event = authsignal.webhook.constructEvent(payload, sig, tolerance); ``` ```csharp C# theme={null} var payload = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); var sig = Request.Headers["X-Signature-V2"]; var tolerance = 10; var webhookEvent = authsignal.Webhook.ConstructEvent(payload, sig, tolerance); ``` ```java Java theme={null} String payload = request.body(); String sig = request.headers("X-Signature-V2"); int tolerance = 10; WebhookEvent event = authsignal.webhook.constructEvent(payload, sig, tolerance); ``` ```ruby Ruby theme={null} payload = request.body.read sig = request.headers['X-Signature-V2'] tolerance = 10 event = authsignal.webhook.construct_event(payload, sig, tolerance) ``` ```python Python theme={null} payload = request.get_data(as_text=True) sig = request.headers.get("X-Signature-V2") tolerance = 10 event = authsignal.webhook.construct_event(payload, sig, tolerance) ``` ```PHP PHP theme={null} $payload = file_get_contents('php://input'); $sig = $_SERVER['HTTP_X_SIGNATURE_V2']; $tolerance = 10; $event = $authsignal->webhook()->constructEvent($payload, $sig, $tolerance); ``` ```go Go theme={null} payload := string(body) sig := r.Header.Get("X-Signature-V2") tolerance := 10 event, err := webhook.ConstructEvent(payload, sig, tolerance) ``` # Finish Call Connect Session Source: https://docs.authsignal.com/api-reference/call-connect-api/finish-call-connect call-connect-api POST /call/finish Finish an existing Call Connect authentication session. # Call Connect API - Overview Source: https://docs.authsignal.com/api-reference/call-connect-api/overview Learn how to integrate with the Authsignal Call Connect API - an Authsignal service for call center customer authentication. The Call Connect API is a service that enables caller authentication during active calls or omni-channel customer interactions. It can be integrated with: * Interactive Voice Response (IVR) systems * Customer support workflows * Digital channels (e.g., chatbots, email) ### Endpoint selection Select the API endpoint which correlates with your tenant's region selection. ```bash US (Oregon) theme={null} https://us-connect.authsignal.com ``` ```bash EU (Ireland) theme={null} https://eu-connect.authsignal.com ``` ```bash AU (Sydney) theme={null} https://au-connect.authsignal.com ``` ```bash CA (Montreal) theme={null} https://ca-connect.authsignal.com ``` ## Authentication The Authsignal Call Connect API uses a secret key to authenticate requests. **This key is different from the one you use to authenticate to other APIs.** Please keep this key secure, and never expose it publicly. You can find the Call Connect API secret key in the [Call Connect section of the Authsignal Portal settings](https://portal.authsignal.com/organisations/tenants/call_connect). ### Example usage ```bash US (Oregon) theme={null} curl -u "YOUR_SECRET_KEY:" https://us-connect.authsignal.com/call/start # The colon prevents curl from asking for a password. ``` ```bash EU (Ireland) theme={null} curl -u "YOUR_SECRET_KEY:" https://eu-connect.authsignal.com/call/start # The colon prevents curl from asking for a password. ``` ```bash AU (Sydney) theme={null} curl -u "YOUR_SECRET_KEY:" https://au-connect.authsignal.com/call/start # The colon prevents curl from asking for a password. ``` ```bash CA (Montreal) theme={null} curl -u "YOUR_SECRET_KEY:" https://ca-connect.authsignal.com/call/start # The colon prevents curl from asking for a password. ``` Following the basic authentication protocol, the Call Connect API expects requests to include an Authorization header containing the word `Basic` followed by a space and a **base64-encoded** `username:password` string. The `username` value should be your Call Connect API secret and the `password` value should be empty. ## Sequence Diagram ```mermaid theme={null} sequenceDiagram participant U as User participant C as IVR/Contact Center System participant A as Authsignal U->>C: Initiates call or session Note over C: System matches the user during flow C->>A: Call Connect Start API is called Note over C: User can be placed in on-hold state A->>U: Authentication request sent via SMS/WhatsApp/Email Note over C: User completes authentication challenge (e.g., Passkeys, ID Verification, Face Biometrics) A->>C: Returns authentication outcome via Webhook Note over C: System updates user authentication status and routes call appropriately ``` # Start Call Connect Session Source: https://docs.authsignal.com/api-reference/call-connect-api/start-call-connect call-connect-api POST /call/start Start a Call Connect authentication session, triggering a user verification challenge based on the configured channels. # Finalize Email Magic Link Challenge Source: https://docs.authsignal.com/api-reference/client-api/finalize-email-magic-link-challenge client-api POST /verify/email-magic-link/finalize The email magic link challenge is verified when the user clicks on the link sent to their email. Use this endpoint to finalize the challenge, checking if the user has clicked on the magic link (e.g. via polling). # Generate Passkey Authentication Options Source: https://docs.authsignal.com/api-reference/client-api/generate-passkey-authentication-options client-api POST /user-authenticators/passkey/authentication-options Generate options to authenticate with an existing passkey. # Generate Passkey Registration Options Source: https://docs.authsignal.com/api-reference/client-api/generate-passkey-registration-options client-api POST /user-authenticators/passkey/registration-options Generate options to register a new passkey. # Get Authenticator Configurations Source: https://docs.authsignal.com/api-reference/client-api/get-authenticator-configurations client-api GET /authenticators Get a list of the authenticators configured for the tenant. # Get Authenticators Source: https://docs.authsignal.com/api-reference/client-api/get-authenticators client-api GET /user-authenticators Get a list of the user's enrolled authenticators. # Initiate Challenge Source: https://docs.authsignal.com/api-reference/client-api/initiate-challenge client-api POST /challenge Initiate a challenge for an action. Call this before Generate Authentication Options to customize the action code for passkey sign-in. # iProov API - Overview Source: https://docs.authsignal.com/api-reference/client-api/iproov-overview The Authsignal iProov integration allows for high assurance facial biometric verification and re-authentication of users. The [Client API](/api-reference/client-api) can be used to initiate and verify an iProov challenge session in a headless manner not using the Authsignal Pre-built UI. This approach also applies when initiating the iProov Client SDKs in native mobile apps. The `iproovToken` returned from the [Start iProov Challenge](/api-reference/client-api/start-iproov-challenge) endpoint can be used to initiate the iProov client side flow. Pass this to the [iProov Client SDKs](https://docs.iproov.com/implementation_A-Z/front_end/introduction). ## Authentication The Client API iProov endpoints are authenticated via [Bearer token auth](/api-reference/client-api/overview#authentication). A token can be obtained by [tracking an action](/api-reference/client-api/overview#1-track-an-action). ## Endpoints * [Start iProov Challenge](/api-reference/client-api/start-iproov-challenge) * [Verify iProov Challenge](/api-reference/client-api/verify-iproov-challenge) ## Sequence diagram The sequence starts with a Server API request to [track an action](/api-reference/server-api/track-action) (1) and ends with a Server API request to [validate the challenge](/api-reference/server-api/validate-challenge) (12). The Client API iProov endpoints can be called to [initiate the challenge](/api-reference/client-api/start-iproov-challenge) (3) which returns an `iproovToken` and a `challengeId`. The `iproovToken` is used to launch the iProov Client SDK (4) and the `challengeId` is used to [verify the challenge](/api-reference/client-api/verify-iproov-challenge) (7). ```mermaid theme={null} sequenceDiagram autonumber participant I as iProov participant F as Your client frontend participant AC as Authsignal Client API participant B as Your web backend participant A as Authsignal Server API B->>A: Track Action A->>F: token F->>AC: Start iProov Challenge AC->>F: challengeId, iproovToken rect rgb(191, 223, 255) note left of I: Using iProov Front-end SDKs F->>I: Launch iProov Client SDK using iproovToken Note over I: The user is prompted to complete the iProov Facial Biometric
Verification flow I->>F: iProov Client SDK's return status callback end F->>AC: Verify iProov Challenge (challengeId) AC-->>F: (Verify iProov Challenge response)
isVerified F->>B: accessToken B->>A: Validate Challenge A->>B: Challenge result Note over B: Proceed with login or
authenticated transaction ``` # Client API - Overview Source: https://docs.authsignal.com/api-reference/client-api/overview Learn how to use the Authsignal Client API to build custom authentication flows. The Authsignal Client API can be used to perform challenges using different authentication methods and verify users. This API is designed to be used if you're not using Authsignal's [pre-built UI](./../../implementation-options/prebuilt-ui/overview) and are building your own web or native app UI. ## Endpoint selection Select the API endpoint which correlates with your tenant's region selection. ```bash US (Oregon) theme={null} https://api.authsignal.com/v1/client ``` ```bash EU (Ireland) theme={null} https://eu.api.authsignal.com/v1/client ``` ```bash AU (Sydney) theme={null} https://au.api.authsignal.com/v1/client ``` ```bash CA (Montreal) theme={null} https://ca.api.authsignal.com/v1/client ``` ## Authentication The Authsignal Client API uses bearer authentication with a short-lived token obtained from the [Server API](/api-reference/server-api/track-action) or via a [Server SDK](/sdks/server). ### 1. Track an action You should first [track an action](/api-reference/server-api/track-action) that represents what the user is doing (e.g. "signIn") and get a token which is valid for 10 minutes. ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", }; const response = await authsignal.track(request); const token = response.token; ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn" ); var response = await authsignal.Track(request); var token = response.Token; ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; TrackResponse response = authsignal.track(request).get(); String token = response.token; ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", }) token = response[:token] ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn", ) token = response["token"] ``` ```php PHP theme={null} $response = Authsignal::track([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'action' => "signIn", ]); $token = $response["token"]; ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", }, ) token := response.Token ``` When tracking an action to enroll an authenticator, the scope `add:authenticators` must be explicitly specified if the user is already enrolled with at least one authentication method. Learn more about scopes for [authenticator binding](/advanced-usage/authenticator-binding). ### 2. Use bearer auth This token can then be used to authenticate to the Client API using bearer auth. ```bash US (Oregon) theme={null} curl https://api.authsignal.com/v1/client/user-authenticators \ -H "Accept: application/json" \ -H "Authorization: Bearer TOKEN_RETURNED_FROM_SERVER_API" ``` ```bash EU (Ireland) theme={null} curl https://eu.api.authsignal.com/v1/client/user-authenticators \ -H "Accept: application/json" \ -H "Authorization: Bearer TOKEN_RETURNED_FROM_SERVER_API" ``` ```bash AU (Sydney) theme={null} curl https://au.api.authsignal.com/v1/client/user-authenticators \ -H "Accept: application/json" \ -H "Authorization: Bearer TOKEN_RETURNED_FROM_SERVER_API" ``` ```bash CA (Montreal) theme={null} curl https://ca.api.authsignal.com/v1/client/user-authenticators \ -H "Accept: application/json" \ -H "Authorization: Bearer TOKEN_RETURNED_FROM_SERVER_API" ``` The authentication model is designed so you can call the Authsignal Client API directly from your web browser or mobile app (though you can also communicate via your backend where convenient). # Overview Source: https://docs.authsignal.com/api-reference/client-api/passkey-overview We highly recommend using our [Web SDK](/sdks/client/web/setup) or [Mobile SDKs](/sdks/client/mobile/setup) as the quickest and simplest way to start implementing passkeys in your app. However, if you prefer to avoid using SDKs and integrate only using REST APIs, then the documentation below outlines the Client API's passkey endpoints and their authentication model. ## Creating a passkey The following endpoints should be called in sequence when creating a passkey. 1. [Generate Passkey Registration Options](/api-reference/client-api/generate-passkey-registration-options) 2. [Verify Passkey Registration](/api-reference/client-api/verify-passkey-registration) These endpoints must be authenticated using [bearer auth with a token](/api-reference/client-api/overview#authentication). ## Using a passkey ### For MFA When using passkeys as a **secondary factor**, i.e. in a context where the user has already been authenticated with a primary factor, you should use the following endpoints in sequence. 1. [Generate Passkey Authentication Options](/api-reference/client-api/generate-passkey-authentication-options) 2. [Verify Passkey Authentication](/api-reference/client-api/verify-passkey-authentication) These endpoints should be authenticated using [bearer auth with a token](/api-reference/client-api/overview#authentication). ### For sign-in When using passkeys as the **primary factor** for sign-in, you should use the following endpoints. 1. [Initiate Challenge](/api-reference/client-api/initiate-challenge) 2. [Generate Passkey Authentication Options](/api-reference/client-api/generate-passkey-authentication-options) 3. [Verify Passkey Authentication](/api-reference/client-api/verify-passkey-authentication) These endpoints should be authenticated using [basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication) with your tenant ID as the username and an empty password value. ```bash theme={null} curl https://api.authsignal.com/v1/client/user-authenticators/passkey/authentication-options \ -X POST \ -H "Content-Type: application/json" \ -u "YOUR_TENANT_ID:" ``` Make sure to include the `:` after your tenant ID to set an empty password value. This authentication model allows the passkey endpoints to be called when signing in from an unauthenticated context. Typically in this flow you would present the passkey prompt based on whatever credentials are available on the device, then use the result of the [Verify Passkey Authentication](/api-reference/client-api/verify-passkey-authentication) call to lookup the user account corresponding to the passkey credential which was used. For more information on how to optimize your sign-in UX with passkeys, refer to our guides on implementing best practice UX for [web](/authentication-methods/passkey/best-practices-web) and [mobile](/authentication-methods/passkey/best-practices-mobile). # Push API - Overview Source: https://docs.authsignal.com/api-reference/client-api/push-overview The [Client API](/api-reference/client-api) can be used to initiate and verify a push challenge. This typically involves sending a push notification to a user's mobile app - though the model assumes push notification delivery is not guaranteed or required. The user is presented with a dialog to approve or reject the request and, if approval is given, they are authenticated on the app which initiated the challenge (e.g. a website). ## Authentication The Client API push endpoints are authenticated via [Bearer token auth](/api-reference/client-api/overview#authentication). A token can be obtained by [tracking an action](/api-reference/client-api/overview#1-track-an-action). ## Endpoints * [Start Push Challenge](/api-reference/client-api/start-push-challenge) * [Verify Push Challenge](/api-reference/client-api/verify-push-challenge) ## Sequence diagram The sequence starts with a Server API request to [track an action](/api-reference/server-api/track-action) (1) and ends with a Server API request to [validate the challenge](/api-reference/server-api/validate-challenge) (12). The Client API push endpoints can be called to [initiate the challenge](/api-reference/client-api/start-push-challenge) (3) by sending a push notification and then to [poll for the result](/api-reference/client-api/verify-push-challenge) (6). ```mermaid theme={null} sequenceDiagram autonumber participant M as User mobile device participant W as Your webhook participant AC as Authsignal Client API participant F as Your web frontend participant B as Your web backend participant A as Authsignal Server API B->>A: Track Action A->>F: token F->>AC: Start Push Challenge AC->>W: challengeId W->>M: Send push notification F-->>AC: Verify Push Challenge
(Start polling) rect rgb(191, 223, 255) note right of M: Using Authsignal SDK M->>AC: Get Challenge AC->>M: challengeId Note over M: Present approve/reject prompt M->>AC: Update Challenge end AC-->>F: (Verify Push Challenge response)
isConsumed
isVerified
accessToken F->>B: accessToken B->>A: Validate Challenge A->>B: Challenge result Note over B: Proceed with login or
authenticated transaction ``` This sequence assumes you are using the [iOS SDK](/sdks/client/mobile/setup) and [Android SDK](/sdks/client/mobile/setup) (or our cross-platform SDKs for [React Native](/sdks/client/mobile/setup) or [Flutter](/sdks/client/mobile/setup)). These SDKs also call the Client API under the hood but they use an authentication model based on digital signature verification with a private/public key pair. # Overview Source: https://docs.authsignal.com/api-reference/client-api/qr-code-overview We highly recommend using our [Web SDK](/sdks/client/web/qr-code-verification) as the quickest and simplest way to start implementing QR code authentication in your app. The implementation steps using the Web SDK can be found in the [using device credentials with QR codes](/authentication-methods/qr-code) guide. If you prefer to use the API directly, then the documentation below outlines the Client API's QR code endpoints and their authentication model. We recommend using the WebSocket API for real-time feedback, falling back to REST API polling if WebSocket connections are not supported in your environment. ## WebSocket API (Recommended) For real-time QR code authentication with immediate updates when users scan and interact with QR codes, use the [WebSocket API](/api-reference/client-api/qr-code-websocket). The WebSocket API provides: * Real-time state updates as users scan and respond to QR codes * Immediate notifications for challenge approval or rejection * Lower latency for time-sensitive authentication flows Details on how to authenticate with the WebSocket API can be found in the [WebSocket API](/api-reference/client-api/qr-code-websocket) documentation. ## REST API 1. [Start QR Code Challenge](/api-reference/client-api/start-qr-code-challenge) - Generates a QR code challenge 2. [Verify QR Code Challenge](/api-reference/client-api/verify-qr-code-challenge) - Verifies the challenge was completed These endpoints must be authenticated using [bearer auth with a token](/api-reference/client-api/overview#authentication). # WebSocket Source: https://docs.authsignal.com/api-reference/client-api/qr-code-websocket Real-time QR code challenge management via WebSocket connections ## Connection Connect to the WebSocket endpoint for the region of your Tenant. | Region | API URL | | ------------- | ------------------------------------------------ | | US (Oregon) | `wss://api-ws.authsignal.com/ws-v1-challenge` | | AU (Sydney) | `wss://au.api-ws.authsignal.com/ws-v1-challenge` | | EU (Ireland) | `wss://eu.api-ws.authsignal.com/ws-v1-challenge` | | CA (Montreal) | `wss://ca.api-ws.authsignal.com/ws-v1-challenge` | ## Authentication To authenticate with the WebSocket API, include the required values in the `Sec-WebSocket-Protocol` header: 1. `authsignal-ws` - identifies the Authsignal WebSocket sub-protocol. 2. Exactly one of the following, depending on the type of challenge you are creating: * `x.authsignal.tenant.` - use when the challenge is **anonymous** (user is not known yet). * `x.authsignal.token.` - use when the challenge is for a **known user**; supply the token returned by the [track](/api-reference/server-api/track-action) request. ## Message Flow The WebSocket communication follows this pattern: 1. **Client connects** to the WebSocket endpoint 2. **Client sends** `CREATE_CHALLENGE` message to initiate a challenge 3. **Server responds** with `CHALLENGE_CREATED` confirmation 4. **Server sends** `STATE_CHANGE` messages as the challenge progresses through states ## Challenge States Challenges progress through the following states: * `unclaimed` - Challenge created but not yet claimed by a user (This state will not be included in the state change messages) * `claimed` - User has claimed the challenge on their device * `approved` - User has approved the challenge * `rejected` - User has rejected the challenge ## Example ```javascript theme={null} // Connect to WebSocket const ws = new WebSocket("wss://api-ws.authsignal.com/ws-v1-challenge", [ "authsignal-ws", "x.authsignal.tenant.5e1eebc0-31c5-42db-b86f-3981f0081ac6", ]); // Send challenge creation request ws.send( JSON.stringify({ type: "CREATE_CHALLENGE", data: { challengeType: "QR_CODE", actionCode: "kiosk-login", }, }) ); // Listen for responses ws.onmessage = (event) => { const message = JSON.parse(event.data); switch (message.type) { case "CHALLENGE_CREATED": console.log("Challenge created:", message.data.challengeId); break; case "STATE_CHANGE": console.log("State changed:", message.data.state); if (message.data.state === "approved" && message.data.accessToken) { console.log("Access token:", message.data.accessToken); // Validate the token in your backend } break; } }; ``` # Start Authenticator App Enrollment Source: https://docs.authsignal.com/api-reference/client-api/start-authenticator-app-enrollment client-api POST /user-authenticators/totp Enroll a new authenticator app authenticator for a user # Start Email Magic Link Challenge Source: https://docs.authsignal.com/api-reference/client-api/start-email-magic-link-challenge client-api POST /challenge/email-magic-link Start a challenge when the user is already enrolled with at least one email magic link authenticator # Start Email Magic Link Enrollment Source: https://docs.authsignal.com/api-reference/client-api/start-email-magic-link-enrollment client-api POST /user-authenticators/email-magic-link Start a challenge to enroll a new email magic link authenticator for a given email. # Start Email OTP Challenge Source: https://docs.authsignal.com/api-reference/client-api/start-email-otp-challenge client-api POST /challenge/email-otp Start a challenge when the user is already enrolled with at least one email OTP authenticator # Start Email OTP Enrollment Source: https://docs.authsignal.com/api-reference/client-api/start-email-otp-enrollment client-api POST /user-authenticators/email-otp Start a challenge to enroll a new email OTP authenticator for a given email. # Start iProov Challenge Source: https://docs.authsignal.com/api-reference/client-api/start-iproov-challenge client-api POST /challenge/iproov Start an iProov challenge. # Start Push Challenge Source: https://docs.authsignal.com/api-reference/client-api/start-push-challenge client-api POST /challenge/push Start a push challenge by sending a notification to the user's mobile device. # Start QR Code Challenge Source: https://docs.authsignal.com/api-reference/client-api/start-qr-code-challenge client-api POST /challenge/qr-code Start an anonymous QR code challenge to be completed by a user's mobile device. This is called an anonymous challenge because it is not associated with a user. # Start SMS Challenge Source: https://docs.authsignal.com/api-reference/client-api/start-sms-challenge client-api POST /challenge/sms Start a challenge when the user is already enrolled with at least one SMS authenticator. # Start SMS Enrollment Source: https://docs.authsignal.com/api-reference/client-api/start-sms-enrollment client-api POST /user-authenticators/sms Start a challenge to enroll a new SMS authenticator for a given phone number. # Start WhatsApp Challenge Source: https://docs.authsignal.com/api-reference/client-api/start-whatsapp-challenge client-api POST /challenge/whatsapp Start a challenge by sending a WhatsApp message containing an OTP code. # Verify Authenticator App Challenge Source: https://docs.authsignal.com/api-reference/client-api/verify-authenticator-app-challenge client-api POST /verify/totp Verify a challenge when enrolling a authenticator app authenticator or when re-authenticating with an existing authenticator app authenticator # Verify Email OTP Challenge Source: https://docs.authsignal.com/api-reference/client-api/verify-email-otp-challenge client-api POST /verify/email-otp Verify a challenge when enrolling a new email OTP authenticator or when re-authenticating with an existing email OTP authenticator # Verify iProov Challenge Source: https://docs.authsignal.com/api-reference/client-api/verify-iproov-challenge client-api POST /verify/iproov Verify a completed iProov challenge. Call this end-point after the iProov Client SDK has completed a verification flow. # Verify Passkey Authentication Source: https://docs.authsignal.com/api-reference/client-api/verify-passkey-authentication client-api POST /verify/passkey Finish the process of authenticating with an existing passkey authenticator. # Verify Passkey Registration Source: https://docs.authsignal.com/api-reference/client-api/verify-passkey-registration client-api POST /user-authenticators/passkey Finish the process of registering a new passkey authenticator. # Verify Push Challenge Source: https://docs.authsignal.com/api-reference/client-api/verify-push-challenge client-api POST /verify/push Verify a challenge to a user's mobile device. Use this endpoint from the app which initiated the challenge to determine if the user approved or rejected the request (e.g. via polling). # Verify QR Code Challenge Source: https://docs.authsignal.com/api-reference/client-api/verify-qr-code-challenge client-api POST /verify/qr-code Verify a QR code challenge. Use this endpoint from the app which initiated the challenge to determine if the user approved or rejected the request (e.g. via polling). # Verify SMS Challenge Source: https://docs.authsignal.com/api-reference/client-api/verify-sms-challenge client-api POST /verify/sms Verify a challenge when enrolling a new SMS authenticator or when re-authenticating with an existing SMS authenticator. # Verify WhatsApp Challenge Source: https://docs.authsignal.com/api-reference/client-api/verify-whatsapp-challenge client-api POST /verify/whatsapp Verify a challenge when re-authenticating with a WhatsApp OTP authenticator. # Create Action Configuration Source: https://docs.authsignal.com/api-reference/management-api/create-action-configurations management-api POST /action-configurations Creates an action configuration from an action code. # Create Custom Data Point Source: https://docs.authsignal.com/api-reference/management-api/create-custom-data-points management-api POST /custom-data-points Creates a custom data point. # Create Custom List Source: https://docs.authsignal.com/api-reference/management-api/create-custom-lists management-api POST /value-lists Creates a custom list of values. # Create Rule Source: https://docs.authsignal.com/api-reference/management-api/create-rules management-api POST /action_configurations/{action_code}/rules Creates a rule for an action configuration. # Delete Action Configuration Source: https://docs.authsignal.com/api-reference/management-api/delete-action-configurations management-api DELETE /action-configurations/{action_code} Deletes an action configuration by action code. # Delete Custom Data Point Source: https://docs.authsignal.com/api-reference/management-api/delete-custom-data-points management-api DELETE /custom-data-points/{id} Deletes an custom list by its alias. # Delete Custom List Source: https://docs.authsignal.com/api-reference/management-api/delete-custom-lists management-api DELETE /value-lists/{value_list_alias} Deletes an custom list by its alias. # Delete Rule Source: https://docs.authsignal.com/api-reference/management-api/delete-rules management-api DELETE /action_configurations/{action_code}/rules/{rule_id} Deletes a rule by action code and rule id. # Get Action Configuration Source: https://docs.authsignal.com/api-reference/management-api/get-action-configurations management-api GET /action-configurations/{action_code} Retrieves an action configuration by action code. # Get Authenticator Configurations Source: https://docs.authsignal.com/api-reference/management-api/get-authenticator-configurations management-api GET /authenticator-configurations Retrieves a list of the authenticator configurations for the tenant. # Get Custom Data Point Source: https://docs.authsignal.com/api-reference/management-api/get-custom-data-points management-api GET /custom-data-points/{id} Retrieves a custom data point by its ID. # Get Custom List Source: https://docs.authsignal.com/api-reference/management-api/get-custom-lists management-api GET /value-lists/{value_list_alias} Retrieves a custom list by its alias. # Get Rule Source: https://docs.authsignal.com/api-reference/management-api/get-rules management-api GET /action_configurations/{action_code}/rules/{rule_id} Gets a rule by action code and rule ID. # Get Tenant Source: https://docs.authsignal.com/api-reference/management-api/get-tenant management-api GET /tenant Retrieves tenant information. # Get Theme Source: https://docs.authsignal.com/api-reference/management-api/get-theme management-api GET /theme Retrieves a tenant's pre-built UI theme. # Management API - Overview Source: https://docs.authsignal.com/api-reference/management-api/overview Configure Authsignal tenants, actions, and rules using the management API. ### Endpoint selection Select the API endpoint which correlates with your tenant's region selection. ```bash US (Oregon) theme={null} https://api.authsignal.com/v1/management ``` ```bash EU (Ireland) theme={null} https://eu.api.authsignal.com/v1/management ``` ```bash AU (Sydney) theme={null} https://au.api.authsignal.com/v1/management ``` ```bash CA (Montreal) theme={null} https://ca.api.authsignal.com/v1/management ``` ## Authentication The Authsignal Management API uses a secret key to authenticate requests. **This key is different from the one you use to authenticate to the Server API.** Please keep this key secure, and never expose it publicly. You can find the Management API secret key in the [API Keys section of the Authsignal Portal settings](https://portal.authsignal.com/organisations/tenants/api). ### Example usage ```bash US (Oregon) theme={null} curl -u "YOUR_SECRET_KEY:" https://api.authsignal.com/v1/management/tenant # The colon prevents curl from asking for a password. ``` ```bash EU (Ireland) theme={null} curl -u "YOUR_SECRET_KEY:" https://eu.api.authsignal.com/v1/management/tenant # The colon prevents curl from asking for a password. ``` ```bash AU (Sydney) theme={null} curl -u "YOUR_SECRET_KEY:" https://au.api.authsignal.com/v1/management/tenant # The colon prevents curl from asking for a password. ``` ```bash CA (Montreal) theme={null} curl -u "YOUR_SECRET_KEY:" https://ca.api.authsignal.com/v1/management/tenant # The colon prevents curl from asking for a password. ``` Following the basic authentication protocol, the Management API expects requests to include an Authorization header containing the word `Basic` followed by a space and a **base64-encoded** `username:password` string. The `username` value should be your Management API secret key and the `password` value should be empty. # Update Action Configuration Source: https://docs.authsignal.com/api-reference/management-api/update-action-configurations management-api PATCH /action-configurations/{action_code} Updates an action configuration by action code. # Update Authenticator Configuration Source: https://docs.authsignal.com/api-reference/management-api/update-authenticator-configuration management-api PATCH /authenticator-configurations/{authenticator_id} Updates an authenticator configuration by authenticator ID. # Update Custom Data Point Source: https://docs.authsignal.com/api-reference/management-api/update-custom-data-points management-api PATCH /custom-data-points/{id} Updates a custom data point. Currently supports toggling whether its value is public. # Update Custom List Source: https://docs.authsignal.com/api-reference/management-api/update-custom-lists management-api PATCH /value-lists/{value_list_alias} Updates an custom list by its alias. # Update Rule Source: https://docs.authsignal.com/api-reference/management-api/update-rules management-api PATCH /action_configurations/{action_code}/rules/{rule_id} Updates a rule by action code and rule ID. # Update Tenant Source: https://docs.authsignal.com/api-reference/management-api/update-tenant management-api PATCH /tenant Updates tenant information. # Update Theme Source: https://docs.authsignal.com/api-reference/management-api/update-theme management-api PATCH /theme Updates a tenant's pre-built UI theme. # Authsignal APIs Source: https://docs.authsignal.com/api-reference/overview Get started with Authsignal APIs Authsignal maintains the following APIs for developers to call in their applications. ## Server API The [Server API](/api-reference/server-api) is the primary API required for any Authsignal integration. **Key features:** * Obtain a short-lived URL for the [pre-built UI](/implementation-options/prebuilt-ui/overview) * Obtain a short-lived token for a [Client SDK](/sdks/client) or the [Client API](/api-reference/client-api/overview) * Perform CRUD operations to programmatically manage a user's authenticators * Can be called directly or via a [Server SDK](/sdks/server) ## Client API The [Client API](/api-reference/client-api) is available as an alternative to the [pre-built UI](/implementation-options/prebuilt-ui/overview) if you prefer to implement your own custom or native UI. If using passkeys or push authentication, we recommend using our [Web SDK](/sdks/client/web/setup) or [Mobile SDKs](/sdks/client/mobile/setup) rather than using our Client API directly. These SDKs provide an implementation layer on top of the Client API and simplify the integration model. **Key features:** * Suitable for both web apps and native mobile apps * Supports a variety of authentication methods including passkeys, authenticator app, SMS or WhatsApp OTP, email OTP or magic link, and push authentication * Must be used together with the [Server API](/api-reference/server-api) for a complete integration * Can be called directly or via a [Client SDK](/sdks/client) ## Management API The [Management API](/api-reference/management-api) can be used to manage your tenant configuration. **Key features:** * Manage tenant configuration * Manage action and rule configuration * Manage theme and branding configuration * Can be called directly or via our [terraform provider](/advanced-usage/using-terraform) # Batch Enroll Verified Authenticators Source: https://docs.authsignal.com/api-reference/server-api/batch-enroll-verified-authenticators server-api POST /users/authenticators Enroll multiple authenticators across multiple users in a single request. This endpoint is idempotent — re-enrolling the same credential for the same user is a no-op. Each item is processed independently; failures do not affect other items in the batch. # Claim Challenge Source: https://docs.authsignal.com/api-reference/server-api/claim-challenge server-api POST /claim Claim an SMS or email-based challenge on behalf of a user, associating it with an ID for a user record in your system. # Create Session Source: https://docs.authsignal.com/api-reference/server-api/create-session server-api POST /sessions Create a session for a user based on a client token obtained after successfully completing a challenge. This will return an access token and refresh token which can be used to manage a session. # Delete Authenticator Source: https://docs.authsignal.com/api-reference/server-api/delete-authenticator server-api DELETE /users/{userId}/authenticators/{userAuthenticatorId} Delete a user's enrolled authenticator. # Delete User Source: https://docs.authsignal.com/api-reference/server-api/delete-user server-api DELETE /users/{userId} Permanently deletes a user and all their associated data, including all of their authenticators and actions. This operation is asynchronous and may take a few minutes to complete after the request returns. # Enroll Verified Authenticator Source: https://docs.authsignal.com/api-reference/server-api/enroll-verified-authenticator server-api POST /users/{userId}/authenticators Enroll an authenticator on behalf of a user. This operation should only be used in cases where you have already verified a user's email address or phone number in your own system. # Get Action Source: https://docs.authsignal.com/api-reference/server-api/get-action server-api GET /users/{userId}/actions/{action}/{idempotencyKey} Get detailed information on a tracked action. # Get Authenticator Configurations Source: https://docs.authsignal.com/api-reference/server-api/get-authenticator-configurations server-api GET /authenticators Gets a list of the authenticators configured for the tenant. # Get Authenticators Source: https://docs.authsignal.com/api-reference/server-api/get-authenticators server-api GET /users/{userId}/authenticators Gets a list of the user's currently enrolled authenticators. # Get Challenge Source: https://docs.authsignal.com/api-reference/server-api/get-challenge server-api GET /challenges Get information about a challenge. # Get Devices Source: https://docs.authsignal.com/api-reference/server-api/get-devices server-api GET /users/{userId}/devices Get a list of devices associated with a user. # Get User Source: https://docs.authsignal.com/api-reference/server-api/get-user server-api GET /users/{userId} Retrieve information about a user. # Initiate Challenge Source: https://docs.authsignal.com/api-reference/server-api/initiate-challenge server-api POST /challenge Initiate a challenge by sending a verification code to an email address or phone number. # Invalidate Device Source: https://docs.authsignal.com/api-reference/server-api/invalidate-device server-api POST /users/{userId}/devices/{deviceId}/invalidate Invalidate a user's device, treating it as if it were a new device on subsequent actions. # Server API - Overview Source: https://docs.authsignal.com/api-reference/server-api/overview Learn how to integrate with Authsignal's server-side REST API for tracking events performed by users and initiating authentication challenges. This REST API can be called directly with an HTTP client in your server-side language, or by using an [Authsignal Server SDK](/sdks/server/overview). ### Endpoint selection Select the API endpoint which correlates with your tenant's region selection. ```bash US (Oregon) theme={null} https://api.authsignal.com/v1 ``` ```bash EU (Ireland) theme={null} https://eu.api.authsignal.com/v1 ``` ```bash AU (Sydney) theme={null} https://au.api.authsignal.com/v1 ``` ```bash CA (Montreal) theme={null} https://ca.api.authsignal.com/v1 ``` ## Authentication The Authsignal Server API uses your secret key to authenticate requests using [basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication). You should only ever call the Authsignal Server API from your server where your secret key can be stored securely. Your secret key should never be exposed to any public client. You can find your secret key in the [API Keys section of the Authsignal Portal settings](https://portal.authsignal.com/organisations/tenants/api). ### Example usage ```bash US (Oregon) theme={null} curl -u "YOUR_SECRET_KEY:" https://api.authsignal.com/v1/users/{userId}/actions/{action}/{idempotencyKey} # The colon prevents curl from asking for a password. ``` ```bash EU (Ireland) theme={null} curl -u "YOUR_SECRET_KEY:" https://eu.api.authsignal.com/v1/users/{userId}/actions/{action}/{idempotencyKey} # The colon prevents curl from asking for a password. ``` ```bash AU (Sydney) theme={null} curl -u "YOUR_SECRET_KEY:" https://au.api.authsignal.com/v1/users/{userId}/actions/{action}/{idempotencyKey} # The colon prevents curl from asking for a password. ``` ```bash CA (Montreal) theme={null} curl -u "YOUR_SECRET_KEY:" https://ca.api.authsignal.com/v1/users/{userId}/actions/{action}/{idempotencyKey} # The colon prevents curl from asking for a password. ``` Following the basic authentication protocol, the Server API expects requests to include an Authorization header containing the word `Basic` followed by a space and a **base64-encoded** `username:password` string. The `username` value should be your Server API secret key and the `password` value should be empty. ## Next steps * [Track action](/api-reference/server-api/track-action) # Query Actions Source: https://docs.authsignal.com/api-reference/server-api/query-actions server-api GET /users/{userId}/actions Query actions for a user. # Query Users Source: https://docs.authsignal.com/api-reference/server-api/query-users server-api GET /users Query users by a set of filters. # Refresh Session Source: https://docs.authsignal.com/api-reference/server-api/refresh-session server-api POST /sessions/refresh Refresh a session for a given refresh token. This will return a new access token and refresh token, revoking any previously issued tokens. # Revoke Session Source: https://docs.authsignal.com/api-reference/server-api/revoke-session server-api POST /sessions/revoke Revoke a session for a given access token. # Revoke User Sessions Source: https://docs.authsignal.com/api-reference/server-api/revoke-user-sessions server-api POST /sessions/user/revoke Revoke all sessions for a given user. # Track Action Source: https://docs.authsignal.com/api-reference/server-api/track-action server-api POST /users/{userId}/actions/{action} Record authentication events performed by users and initiate challenges via Authsignal's pre-built UI or Authsignal Client SDKs. # Update Action Source: https://docs.authsignal.com/api-reference/server-api/update-action server-api PATCH /users/{userId}/actions/{action}/{idempotencyKey} Update an action. # Update Device Source: https://docs.authsignal.com/api-reference/server-api/update-device server-api PATCH /users/{userId}/devices/{deviceId} Update a user's device. # Update User Source: https://docs.authsignal.com/api-reference/server-api/update-user server-api PATCH /users/{userId} Update information about a user. Any fields which are omitted in the request will be left unchanged. # Validate Action Source: https://docs.authsignal.com/api-reference/server-api/validate-action server-api POST /validate Validate the result of an action using a token obtained after redirecting back from the pre-built UI or returned by an Authsignal SDK. # Validate Session Source: https://docs.authsignal.com/api-reference/server-api/validate-session server-api POST /sessions/validate Validate a session for a given access token. This will ensure both that the token signature is valid and that the token has not been revoked. # Verify Challenge Source: https://docs.authsignal.com/api-reference/server-api/verify-challenge server-api POST /verify Verify an SMS or email-based challenge by submitting a verification code. # Create Payment Session Source: https://docs.authsignal.com/api-reference/terminal-api/create-payment-session terminal-api POST /terminal/payment-session Initialization of a payment session will trigger a payment authorization user experience on the paired Authsignal payment terminal application. # Get Payment Session Source: https://docs.authsignal.com/api-reference/terminal-api/get-payment-session terminal-api GET /terminal/payment-session/{challengeId} Poll for a payment session result using the `challengeId` obtained from creating a payment session. # Terminal API - Overview Source: https://docs.authsignal.com/api-reference/terminal-api/overview How to integrate and interact with the Authsignal Biometric Terminal service ## Terminal Pairing To integrate an Authsignal terminal with your point of sale system, you'll need to obtain the `terminalId` from an on-boarded device. This unique identifier is required to authenticate requests to the payment session APIs from your point of sale terminal. ### Terminal Request Identification The Authsignal Payment Session API uses your `terminalId` as a value in the `x-authsignal-terminal` HTTP request header to identify requests. ```bash theme={null} curl --header 'x-authsignal-terminal: YOUR_TERMINAL_ID' https://api.authsignal.com/v1 ``` ### Region selection Authsignal's server SDKs default to the US (Oregon) region. If your tenant is in a different region, a different API URL must be configured. | Region | API URL | | ------------- | ---------------------------------- | | US (Oregon) | `https://api.authsignal.com/v1` | | AU (Sydney) | `https://au.api.authsignal.com/v1` | | EU (Ireland) | `https://eu.api.authsignal.com/v1` | | CA (Montreal) | `https://ca.api.authsignal.com/v1` | # Attestation Source: https://docs.authsignal.com/authentication-methods/app-verification/attestation Verify that app credentials are enrolled from legitimate apps running on real devices. Attestation ensures that app credentials are enrolled from a legitimate, unmodified version of your app running on a real device. This applies to [in-app verification](/authentication-methods/app-verification/in-app), [push verification](/authentication-methods/app-verification/push), and [QR code verification](/authentication-methods/app-verification/qr-code) methods. When enabled, the Authsignal [Mobile SDK](/sdks/client/mobile/setup) generates a platform-specific attestation during credential enrollment which is verified server-side by Authsignal. * **iOS**: Uses Apple's [App Attest](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity) service to generate an attestation bound to the device. * **Android**: Uses Google's [Play Integrity](https://developer.android.com/google/play/integrity) API to generate an integrity token. Authsignal uses the [classic request](https://developer.android.com/google/play/integrity/classic) type, which is suited to one-off checks of high-value actions such as credential enrollment. Attestation requires the following minimum SDK versions: **iOS 2.5.0**, **Android 3.6.0**, **React Native 2.9.0**. ## Portal setup Enable attestation for your tenant in the [Authsignal Portal](https://portal.authsignal.com/organisations/tenants/device_integrity). ### Configuration Controls how the server handles attestation results during credential enrollment. * **Block** - Enrollment is rejected if attestation fails or is not provided. When [validating the challenge](/sdks/server/challenges#validate-challenge) on your backend, the `state` will be `BLOCK` - your backend should handle this by denying access or preventing the user from proceeding. * **Review required** - Enrollment is allowed but the credential is flagged for review if attestation fails. When [validating the challenge](/sdks/server/challenges#validate-challenge) on your backend, the `state` will be `REVIEW_REQUIRED` - your backend should handle this appropriately, for example by restricting access to certain features or flagging the user for manual review. When enabled, accepts attestations from development builds. This should be enabled during development and testing, and disabled in production. * **iOS**: Accepts attestations from apps signed with a development provisioning profile (App Attest sandbox environment). * **Android**: Not applicable - Play Integrity requires distribution via the Google Play Store (including internal testing tracks). For local development builds not distributed via Play, set `performAttestation` to `false` in your SDK calls. ### Platform-specific configuration Your Apple Developer Team ID. Found in the [Apple Developer Portal](https://developer.apple.com/account) under Membership details. The bundle identifiers of apps which are permitted to enroll credentials. Must match the bundle ID of your app as configured in Xcode. A Google Cloud service account key with access to the Play Integrity API. This is used by Authsignal to verify integrity tokens server-side via the [Google Play Integrity API](https://developer.android.com/google/play/integrity/setup). To create a service account key, follow Google's guide on [setting up Play Integrity](https://developer.android.com/google/play/integrity/setup) Play Integrity requires your app to be distributed through the **Google Play Store**. This includes internal testing tracks - your app does not need to be publicly available. ## SDK integration To enable attestation, pass `performAttestation: true` when adding a credential. The `token` parameter is a short-lived token obtained by [tracking an action](/sdks/server/actions#track-action) from your backend. On the client side, call `addCredential` with `performAttestation: true`. The example below uses the `push` namespace. If you're implementing QR code or in-app verification, replace `push` with `qr` or `inapp`. ```swift iOS theme={null} await authsignal.push.addCredential(token: "eyJhbGciOiJ...", performAttestation: true) ``` ```kotlin Android theme={null} authsignal.push.addCredential(token = "eyJhbGciOiJ...", performAttestation = true) ``` ```ts React Native theme={null} await authsignal.push.addCredential({ token: "eyJhbGciOiJ...", performAttestation: true }); ``` The SDK handles all platform-specific attestation generation internally. No additional configuration is required in the mobile app. On your backend, you can [validate the challenge](/sdks/server/challenges#validate-challenge) to check the outcome of the attestation. For more details on the `addCredential` method, refer to the SDK documentation for [in-app verification](/sdks/client/mobile/in-app-verification#adding-a-credential) or [push verification](/sdks/client/mobile/push-verification#adding-a-credential). ## Handling client errors If attestation fails (e.g. unsupported device, emulator, or network issue), `addCredential` will return an error. You can safely ignore this error on the client side - the action will remain in a non-`CHALLENGE_SUCCEEDED` state, and your backend will handle it when calling [validate challenge](/sdks/server/challenges#validate-challenge). ## Server-side validation Authsignal verifies the attestation server-side during credential enrollment. However, it is up to **your backend** to decide what to do with the result. After a user completes app verification, call [validate challenge](#server-side-validation) from your backend using the enrollment token. The response includes a `state` field that reflects the outcome of the attestation. Your backend should use this state to make business logic decisions - for example, provisioning services, issuing vouchers, or granting access to sensitive features. ```ts Node.js theme={null} const response = await authsignal.validateChallenge({ action: "addCredential", token: "eyJhbGciOiJIUzI....", }); switch (response.state) { case "CHALLENGE_SUCCEEDED": // Attestation check passed // Safe to provision services, issue vouchers, grant access, etc. break; case "BLOCK": // Attestation failed and enforcement mode is set to Block // Do not provision - the request may not be from a legitimate device break; case "REVIEW_REQUIRED": // Attestation failed but enforcement mode allows enrollment // The credential was enrolled but you may want to flag for manual review // or restrict access to certain features until reviewed break; default: // Handle unexpected states break; } ``` ```go Go theme={null} response, err := client.ValidateChallenge( ValidateChallengeRequest{ Action: "addCredential", Token: "eyJhbGciOiJ...", }, ) if response.State == "CHALLENGE_SUCCEEDED" { // Attestation check passed // Safe to provision services, issue vouchers, grant access, etc. } else if response.State == "BLOCK" { // Do not provision - the request may not be from a legitimate device } else if response.State == "REVIEW_REQUIRED" { // Credential enrolled but flagged - consider restricting access } ``` ```ruby Ruby theme={null} response = Authsignal.validate_challenge(action: "addCredential", token: "eyJhbGciOiJIUzI....") case response[:state] when "CHALLENGE_SUCCEEDED" # Attestation check passed # Safe to provision services, issue vouchers, grant access, etc. when "BLOCK" # Do not provision - the request may not be from a legitimate device when "REVIEW_REQUIRED" # Credential enrolled but flagged - consider restricting access end ``` ```python Python theme={null} response = authsignal.validate_challenge(action="addCredential", token="eyJhbGciOiJIUzI....") if response["state"] == "CHALLENGE_SUCCEEDED": # Attestation check passed # Safe to provision services, issue vouchers, grant access, etc. elif response["state"] == "BLOCK": # Do not provision - the request may not be from a legitimate device elif response["state"] == "REVIEW_REQUIRED": # Credential enrolled but flagged - consider restricting access ``` ### Attestation result On your server, after calling [validate challenge](/sdks/server/challenges#validate-challenge), you can retrieve the full attestation result by calling [getAction](/api-reference/server-api/get-action) before allowing the user to continue. The following fields are returned in the `output.device.attestationResult` object of the [getAction](/api-reference/server-api/get-action) response. The fields present depend on the platform and what verdicts are enabled. #### Fraud risk metric You can optionally configure a DeviceCheck-enabled key to fetch a fraud risk metric from Apple after attestation. To enable this, add your DeviceCheck Key ID and private key in the [Authsignal Portal](https://portal.authsignal.com/organisations/tenants/device_integrity) under the iOS (App Attest) configuration. Create the key in the [Apple Developer portal](https://developer.apple.com/account/resources/authkeys/list) under Keys with the DeviceCheck service enabled. When a device passes attestation, the risk metric is included in the action's event timeline, which you can view in the Authsignal Portal. #### Response ```json getAction response: output.device.attestationResult theme={null} { "attestationResult": { "verdict": "VALID", "provider": "APP_ATTEST", "deviceIntegrity": true, "appIntegrity": true, "riskMetric": 10, "keyId": "abc123...", "bundleId": "com.example.myapp" } } ``` | Field | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `verdict` | Overall attestation verdict: `VALID`, `FAILED_INTEGRITY`, `FAILED_APP_IDENTITY`, `FAILED_DEVICE`, or `ERROR`. | | `provider` | Always `APP_ATTEST` for iOS. | | `deviceIntegrity` | Whether the device passed integrity checks. | | `appIntegrity` | Whether the app matches the expected bundle ID. | | `riskMetric` | Approximate count of unique attestations for this device over the past 30 days. Only present if a DeviceCheck key is configured. | | `keyId` | The App Attest key identifier. | | `bundleId` | The matched bundle identifier. | | `error` | An error message if attestation verification failed. Only present when `verdict` is `ERROR`. | #### Enabling verdict responses To receive detailed integrity verdicts, enable the responses you want in the [Google Play Console](https://play.google.com/console) under **App integrity > Integrity API settings > Change responses**. Authsignal flattens the [Play Integrity API](https://developer.android.com/google/play/integrity/verdicts) response into a single `attestationResult` object. Fields like `deviceRecognitionVerdict` and `playProtectVerdict` are only present when the corresponding verdict is enabled in the Google Play Console. #### Response ```json getAction response: output.device.attestationResult theme={null} { "attestationResult": { "verdict": "VALID", "provider": "PLAY_INTEGRITY", "deviceIntegrity": true, "appIntegrity": true, "deviceRecognitionVerdict": [ "MEETS_BASIC_INTEGRITY", "MEETS_DEVICE_INTEGRITY" ], "appRecognitionVerdict": "PLAY_RECOGNIZED", "appLicensingVerdict": "LICENSED", "deviceActivityLevel": "LEVEL_1", "playProtectVerdict": "NO_ISSUES", "appAccessRiskVerdict": [ "KNOWN_INSTALLED", "UNKNOWN_INSTALLED" ], "sdkVersion": 34, "requestPackageName": "com.example.myapp", "versionCode": "11" } } ``` | Field | Values | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `verdict` | `VALID`, `FAILED_INTEGRITY`, `FAILED_APP_IDENTITY`, `FAILED_DEVICE`, `ERROR` | | `provider` | `PLAY_INTEGRITY` | | `deviceIntegrity` | `true`, `false` | | `appIntegrity` | `true`, `false` | | `deviceRecognitionVerdict` | `MEETS_BASIC_INTEGRITY`, `MEETS_DEVICE_INTEGRITY`, `MEETS_STRONG_INTEGRITY` | | `appRecognitionVerdict` | `PLAY_RECOGNIZED`, `UNRECOGNIZED_VERSION`, `UNEVALUATED` | | `appLicensingVerdict` | `LICENSED`, `UNLICENSED`, `UNEVALUATED` | | `deviceActivityLevel` | `LEVEL_1` to `LEVEL_4` | | `playProtectVerdict` | `NO_ISSUES`, `MEDIUM_RISK`, `HIGH_RISK`, `POSSIBLE_RISK` | | `appAccessRiskVerdict` | `KNOWN_INSTALLED`, `KNOWN_CAPTURING`, `KNOWN_CONTROLLING`, `UNKNOWN_INSTALLED`, `UNKNOWN_CAPTURING`, `UNKNOWN_CONTROLLING` | | `sdkVersion` | e.g. `34` | | `requestPackageName` | e.g. `com.example.myapp` | | `versionCode` | e.g. `11` | | `error` | Error message string | For full details on each verdict, see Google's [Play Integrity verdicts documentation](https://developer.android.com/google/play/integrity/verdicts). ## Next steps * **[In-app verification](/authentication-methods/app-verification/in-app)** - Set up in-app verification for your mobile app * **[Push verification](/authentication-methods/app-verification/push)** - Set up push verification for your mobile app * **[Enrollment lifecycle](/authentication-methods/app-verification/enrollment-lifecycle)** - Learn when to enroll and un-enroll users * **[Adaptive MFA](/actions-rules/rules/adaptive-mfa)** - Set up rules to trigger authentication based on risk # Enrollment lifecycle Source: https://docs.authsignal.com/authentication-methods/app-verification/enrollment-lifecycle Enroll and un-enroll users for app verification as part of your mobile app's authentication lifecycle. Whether you are implementing app verification for [push](/authentication-methods/app-verification/push), [QR code](/authentication-methods/app-verification/qr-code), or [in-app](/authentication-methods/app-verification/in-app) flows, you'll need to consider when to enroll and un-enroll users in your mobile app. This guide will cover the appropriate points in your app's authentication lifecycle to enroll and un-enroll users by adding and removing device credentials. The code examples below use the `push` namespace. If you're implementing QR code or in-app verification, replace `push` with `qr` or `inapp`. ## When to enroll App verification enrollment for users is typically an invisible process which happens automatically when they're signed in to your mobile app. Your app uses one of our [Mobile SDKs](/sdks/client/mobile/setup) to generate a cryptographic device credential and bind it to an authenticated user. The recommended place to enroll users by adding a device credential is within a **post-authentication handler**. This handler should be triggered in the following two scenarios. 1. Immediately after the user signs in to the app. 2. When the app is launched and the user is already authenticated (e.g. due to a persistent session). If your app uses long-lived sessions and doesn’t require frequent sign-ins, the second scenario is especially important. It will ensure that users who launch the app after installing your updated version are enrolled for app verification without explicitly having to sign in again. The following code should be run in both of the above scenarios to enroll a new credential if none exists on the device. ```swift iOS theme={null} func enrollCredential() async { let response = await authsignal.push.getCredential() if response.data != nil { // A credential already exists return } // Fetch an enrollment token from your backend let token = try await getEnrollmentToken() // Silently enroll a new device credential with attestation await authsignal.push.addCredential(token: token, performAttestation: true) } ``` ```kotlin Android theme={null} suspend fun enrollCredential() { val response = authsignal.push.getCredential() if (response.data != null) { // A credential already exists return } // Fetch an enrollment token from your backend val token = getEnrollmentToken() // Silently enroll a new device credential with attestation authsignal.push.addCredential(token = token, performAttestation = true) } ``` ```ts React Native theme={null} async function enrollCredential() { const response = await authsignal.push.getCredential(); if (response.data) { // A credential already exists return; } // Fetch an enrollment token from your backend by calling track const token = await getEnrollmentToken(); // Silently enroll a new device credential with attestation await authsignal.push.addCredential({ token, performAttestation: true }); } ``` ```dart Flutter theme={null} Future enrollCredential() async { final response = await authsignal.push.getCredential(); if (response.data != null) { // A credential already exists return; } // Fetch an enrollment token from your backend final token = await getEnrollmentToken(); // Silently enroll a new device credential await authsignal.push.addCredential(token); } ``` This code should be run after a user signs in. ```swift iOS theme={null} func postSignInHandler() async { await enrollCredential() } ``` ```kotlin Android theme={null} suspend fun postSignInHandler() { enrollCredential() } ``` ```ts React Native theme={null} async function postSignInHandler() { await enrollCredential(); } ``` ```dart Flutter theme={null} Future postSignInHandler() async { await enrollCredential(); } ``` It should also be run when the user launches the app while already authenticated. ```swift iOS theme={null} func postLaunchHandler() async { if isAuthenticated { await enrollCredential() } } ``` ```kotlin Android theme={null} suspend fun postLaunchHandler() { if (isAuthenticated) { enrollCredential() } } ``` ```ts React Native theme={null} async function postLaunchHandler() { if (isAuthenticated) { await enrollCredential(); } } ``` ```dart Flutter theme={null} Future postLaunchHandler() async { if (isAuthenticated) { await enrollCredential(); } } ``` Your backend code to generate the enrollment token should be implemented within an authenticated endpoint. ```js Node.js theme={null} // A mock example of an endpoint for generating enrollment tokens app.post("/generate-enrollment-token", async (req, res) => { // Determine user from authenticated request context const userId = req.user.sub; const { token } = await authsignal.track({ userId, action: "addCredential", attributes: { scope: "add:authenticators", }, }); res.json({ token }); }); ``` The **add:authenticators** scope is required here since we are enrolling a new authentication method in an [authenticated context](/advanced-usage/authenticator-binding). The **action** can be any value which describes the enrollment activity and will be used for observability in the Authsignal Portal. ### Handling app updates A common question is how to enroll users who are **already signed in and just received an app update** that introduces app verification. The post-launch handler shown above covers this scenario automatically. No separate flow is required. When an existing authenticated user opens the updated app for the first time: 1. The post-launch handler runs because the user's session is still valid. 2. `getCredential` returns no credential (since the device hasn't been enrolled yet on this version). 3. A new device credential is silently enrolled via `addCredential`. For users who already had a credential from a prior version, `getCredential` short-circuits and no re-enrollment occurs, so the flow is safe to run on every launch. ## Keeping credentials alive By default a device credential never expires. You can optionally configure a **credential lifetime** on the push authenticator so that credentials expire if the device goes quiet for too long. This is useful for pruning credentials belonging to devices a user no longer has. Set the lifetime in the Authsignal Portal under the push authenticator configuration. When it's unset, credentials never expire and no keep-alive is needed. When a lifetime is configured, a credential expires if it isn't kept alive within that window. An expired credential is removed: it's excluded from push challenges and `getCredential` returns no credential. To stay enrolled, the device extends its own lease by calling `updateCredential` with `resetExpiry` set to `true`. ```swift iOS theme={null} await authsignal.push.updateCredential(resetExpiry: true) ``` ```kotlin Android theme={null} authsignal.push.updateCredential(resetExpiry = true) ``` ```ts React Native theme={null} await authsignal.push.updateCredential({ resetExpiry: true }); ``` The natural place to make this call is the same post-launch handler you already use to enroll, since it runs whenever an authenticated user opens the app. And because `getCredential` returns no credential once one expires, the `enrollCredential` function shown above already handles the expired case: it enrolls a fresh credential when none is found. ## When to un-enroll The recommended point in your app to un-enroll users for app verification is after sign-out. This means that once signed out users will no longer be enrolled for app verification on that device. ```swift iOS theme={null} func postSignOutHandler() async { await authsignal.push.removeCredential() } ``` ```kotlin Android theme={null} suspend fun postSignOutHandler() { authsignal.push.removeCredential() } ``` ```ts React Native theme={null} function postSignOutHandler() { await authsignal.push.removeCredential(); } ``` ```dart Flutter theme={null} Future postSignOutHandler() async { await authsignal.push.removeCredential(); } ``` # In-app verification Source: https://docs.authsignal.com/authentication-methods/app-verification/in-app Verify high-risk actions in mobile apps using cryptographic device credentials. In-app verification In-app verification uses device credentials to verify that high-risk actions are performed on authorized devices. This method leverages public key cryptography where private keys are securely stored on the user's device. The [Mobile SDK](/sdks/client/mobile/setup) is used for two key steps: 1. Registering a mobile device for in-app verification by [adding a credential](/sdks/client/mobile/in-app-verification#adding-a-credential). This step creates a new public/private key pair. 2. Verifying an action. This step uses the device's private key to sign a message which is verified on the server using the public key. ## Sequence diagram ```mermaid theme={null} sequenceDiagram participant M as Mobile App (Authenticated) participant B as Your Backend participant C as Authsignal Client API participant S as Authsignal Server API B->>S: Track Action S->>B: Return challenge result B->>M: Return challenge result note left of M: Present challenge dialog M->>C: Verify device using credentials C->>M: Return token M->>B: Pass token to validate challenge B->>S: Pass token to validate challenge S->>B: Return challenge result ``` ## Portal setup Enable [in-app verification](https://portal.authsignal.com/organisations/tenants/authenticators/in_app) for your tenant. ## SDK setup ### Server SDK Initialize the SDK using your Server API secret key from the [API keys page](https://portal.authsignal.com/organisations/tenants/api) and the [API URL](/sdks/server/setup#regions) for your region. ```ts Node.js theme={null} import { Authsignal } from "@authsignal/node"; const authsignal = new Authsignal({ apiSecretKey: "YOUR_SECRET_KEY", apiUrl: "YOUR_API_URL", }); ``` ```csharp C# theme={null} using Authsignal; var authsignal = new AuthsignalClient( apiSecretKey: "YOUR_SECRET_KEY", apiUrl: "YOUR_API_URL" ); ``` ```java Java theme={null} import com.authsignal.AuthsignalClient; AuthsignalClient authsignal = new AuthsignalClient( "YOUR_SECRET_KEY", "YOUR_API_URL" ); ``` ```ruby Ruby theme={null} require 'authsignal' Authsignal.setup do |config| config.api_secret_key = ENV["YOUR_SECRET_KEY"] config.api_url = "YOUR_API_URL" end ``` ```python Python theme={null} from authsignal.client import AuthsignalClient authsignal = AuthsignalClient( api_secret_key="YOUR_SECRET_KEY", api_url="YOUR_API_URL" ) ``` ```php PHP theme={null} Authsignal::setApiSecretKey("YOUR_SECRET_KEY"); Authsignal::setApiUrl("YOUR_API_URL"); ``` ```go Go theme={null} import "github.com/authsignal/authsignalgo/v2/client" client := NewAuthsignalClient( "YOUR_SECRET_KEY", "YOUR_API_URL", ) ``` ### Mobile SDK Initialize the [Mobile SDK](/sdks/client/mobile/setup) using your tenant ID from the [API keys page](https://portal.authsignal.com/organisations/tenants/api) and your [API URL](/sdks/server/setup#regions). ```swift iOS theme={null} import Authsignal let authsignal = Authsignal( tenantID: "YOUR_TENANT_ID", baseURL: "YOUR_API_URL" ) ``` ```kotlin Android theme={null} import com.authsignal.Authsignal val authsignal = Authsignal( tenantID = "YOUR_TENANT_ID", baseURL = "YOUR_API_URL", ) ``` ```ts React Native theme={null} import { Authsignal } from "react-native-authsignal"; const authsignal = new Authsignal({ tenantID: "YOUR_TENANT_ID", baseURL: "YOUR_API_URL", }); ``` ```dart Flutter theme={null} import 'package:authsignal_flutter/authsignal_flutter.dart'; final authsignal = Authsignal( "YOUR_TENANT_ID", baseURL: "YOUR_API_URL" ); ``` ## Enrollment **Scenario** - Enroll users for in-app verification so it can be used later to authorize a high-risk action. ### 1. Generate enrollment token In your backend, track an action for a user (e.g. "addAuthenticator") to generate a short-lived token. This token will be used to authorize enrolling a new authentication method on their mobile device. The **add:authenticators** scope is required to enroll a new authentication factor for an existing user. This scope should only be used **when the user is in an already authenticated state**. For more information on using scopes safely refer to our documentation on [authenticator binding](/advanced-usage/authenticator-binding). ### 2. Add credential Use the token obtained in step 1 to enroll a new device credential for the user in the mobile app. Setting `performAttestation` to `true` enables platform-specific attestation ([App Attest](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity) on iOS, [Play Integrity](https://developer.android.com/google/play/integrity) on Android) which verifies that the credential is being enrolled from a legitimate app on a real device. ```swift iOS theme={null} await authsignal.inapp.addCredential(token: "eyJhbGciOiJ...") ``` ```kotlin Android theme={null} authsignal.inapp.addCredential(token = "eyJhbGciOiJ...") ``` ```ts React Native theme={null} await authsignal.inapp.addCredential({ token: "eyJhbGciOiJ..." }); ``` ## Authentication **Scenario** - Strongly authenticate actions performed by users with in-app verification. ### 1. Track action Track an action from your backend which reflects the activity that the user is performing (e.g. authorizing a payment). This step can apply [rules](/actions-rules/rules/getting-started) to determine if additional strong authentication is required. ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "authorizePayment", }; const response = await authsignal.track(request); if (response.state === "CHALLENGE_REQUIRED") { // Obtain token to present challenge const token = response.token; } ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "authorizePayment" ); var response = await authsignal.Track(request); if (response.State == UserActionState.CHALLENGE_REQUIRED) { // Obtain token to present challenge var token = response.Token; } ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "authorizePayment"; TrackResponse response = authsignal.track(request).get(); if (response.state.equals(UserActionState.CHALLENGE_REQUIRED)) { // Obtain token to present challenge String token = response.token; } ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', action: 'authorizePayment' }) case response[:state] when "CHALLENGE_REQUIRED" # Obtain token to present challenge token = response[:token] end ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="authorizePayment" ) if response["state"] == "CHALLENGE_REQUIRED": # Obtain token to present challenge token = response["token"] ``` ```php PHP theme={null} $response = $authsignal->track([ 'userId' => 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', 'action' => 'authorizePayment' ]); switch ($response['state']) { case 'CHALLENGE_REQUIRED': // Obtain token to present challenge $token = $response['token']; } ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "authorizePayment", }, ) switch response.State { case "CHALLENGE_REQUIRED": // Obtain token to present challenge token := response.Token } ``` Return the token to your mobile app and set it via the [Mobile SDK](/sdks/client/mobile/setup). ```swift iOS theme={null} authsignal.setToken(token: "eyJhbGciOiJ...") ``` ```kotlin Android theme={null} authsignal.setToken(token = "eyJhbGciOiJ...") ``` ```ts React Native theme={null} await authsignal.setToken("eyJhbGciOiJ..."); ``` ```dart Flutter theme={null} await authsignal.setToken("eyJhbGciOiJ..."); ``` ### 2. Verify action in app Use the [Mobile SDK](/sdks/client/mobile/in-app-verification#verifying-an-action) to verify the action. ```swift iOS theme={null} let response = await authsignal.inapp.verify() let token = response.data?.token ``` ```kotlin Android theme={null} val response = authsignal.inapp.verify() val token = response.data?.token ``` ```ts React Native theme={null} const response = await authsignal.inapp.verify(); const token = response.data?.token; ``` ```dart Flutter theme={null} final response = await authsignal.inapp.verify(); final token = response.data?.token; ``` If the device credentials were created without using the `userAuthenticationRequired` flag, you may optionally present your own challenge dialog such as a PIN screen prior to calling the `verify` method. ### 3. Complete authentication Once the user has verified the action in the app, you will obtain a new token in the app which can be passed to your backend in order to [validate the action](/sdks/server/challenges#validate-challenge) and complete authentication. ```ts Node.js theme={null} const response = await authsignal.validateChallenge({ action: "authorizePayment", token: "eyJhbGciOiJIUzI....", }); if (response.state === "CHALLENGE_SUCCEEDED") { // User completed challenge successfully } ``` ```csharp C# theme={null} var request = new ValidateChallengeRequest( Action: "authorizePayment", Token: "eyJhbGciOiJIUzI...." ); var response = await authsignal.ValidateChallenge(request); if (response.State == UserActionState.CHALLENGE_SUCCEEDED) { // User completed challenge successfully } ``` ```java Java theme={null} ValidateChallengeRequest request = new ValidateChallengeRequest(); request.action = "authorizePayment"; request.token = "eyJhbGciOiJIUzI...."; ValidateChallengeResponse response = authsignal.validateChallenge(request).get(); if (response.state == UserActionState.CHALLENGE_SUCCEEDED) { // User completed challenge successfully } ``` ```ruby Ruby theme={null} response = Authsignal.validate_challenge(action: "authorizePayment", token: "eyJhbGciOiJIUzI....") if response[:state] == "CHALLENGE_SUCCEEDED" # User completed challenge successfully end ``` ```python Python theme={null} response = authsignal.validate_challenge(action="authorizePayment", token="eyJhbGciOiJIUzI....") if response["state"] == "CHALLENGE_SUCCEEDED": # User completed challenge successfully ``` ```php PHP theme={null} $response = Authsignal::validateChallenge(action: "authorizePayment", token: "eyJhbGciOiJIUzI...."); if ($response["state"] === "CHALLENGE_SUCCEEDED") { # User completed challenge successfully } ``` ```go Go theme={null} response, err := client.ValidateChallenge( ValidateChallengeRequest{ Action: "authorizePayment", Token: "eyJhbGciOiJ...", }, ) if response.State == "CHALLENGE_SUCCEEDED" { // User completed challenge successfully } ``` ## Next steps * **[Passkeys](/authentication-methods/passkey/custom-ui)** - Offer the most secure and user-friendly passwordless authentication * **[Adaptive MFA](/actions-rules/rules/adaptive-mfa)** - Set up smart rules to trigger authentication based on risk # Configuring push notifications Source: https://docs.authsignal.com/authentication-methods/app-verification/managed-push Let Authsignal deliver push notifications for you by connecting your APNs and FCM credentials, instead of running your own webhook. By default, push verification relies on a [webhook](/authentication-methods/app-verification/push#portal-setup): when a challenge starts, Authsignal calls your endpoint and you are responsible for sending the push notification to the user's device. Instead, you can have Authsignal send the notification for you. You connect your Apple Push Notification service (APNs) and/or Firebase Cloud Messaging (FCM) credentials in the portal, and Authsignal delivers the notification directly to Apple's and Google's push services. There is no webhook to build or host. Push delivery is **best-effort**. A challenge can always be completed from inside your app without a notification - the notification is only a prompt to open the app. If a send fails, the challenge is not failed; the failure is recorded as a timeline event so you can monitor delivery. ## Choosing a provider When you enable push verification you choose a push notification provider. The managed options are **Default** and **Firebase**. (The **Webhook** and **Airship** options keep the existing self-hosted delivery behavior - see [Push verification](/authentication-methods/app-verification/push).) | Provider | How it delivers | When to use it | | ------------ | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | **Default** | Authsignal connects directly to APNs for iOS devices and FCM for Android devices. | You want Authsignal to talk to Apple and Google directly. Configure APNs for iOS, FCM for Android, or both. | | **Firebase** | Authsignal sends every notification through Firebase Cloud Messaging only. | You already use FCM as a cross-platform solution (including for iOS). | With the **Default** provider, the device platform decides the route: iOS devices are sent via APNs and all other devices via FCM. You only need to configure credentials for the platforms you target - APNs-only and FCM-only configurations are both valid. ## Portal setup Enable [push verification](https://portal.authsignal.com/organisations/tenants/authenticators/push) for your tenant, then choose a push notification provider. Set up push verification - choosing the Default provider ### Default provider Select **Default** to have Authsignal connect directly to Apple's and Google's push services. Configure the credentials for the platforms you support. #### Apple Push Notification service (APNs) Required for iOS. Authsignal uses token-based authentication with a `.p8` auth key downloaded from the [Apple Developer Console](https://developer.apple.com/account/resources/authkeys/list). | Field | Description | | ----------------------- | -------------------------------------------------------------------------------------------------- | | **Team ID** | Your Apple Developer Team ID. | | **Key ID** | The Key ID of the `.p8` auth key. | | **App bundle ID** | The bundle identifier of your app (for example `com.acme.app`). Used as the APNs topic. | | **APNs environment** | `Production` or `Sandbox`. Sandbox routes to Apple's sandbox push endpoint for development builds. | | **APNs auth key (.p8)** | The contents of your `.p8` private key. Paste the key, drop the file, or upload it. | To create the key, open the [Apple Developer Console](https://developer.apple.com/account/resources/authkeys/list) and go to **Certificates, Identifiers & Profiles > Keys**, then register a new key with **Apple Push Notifications service (APNs)** enabled. Download the `.p8` file when prompted, and note the **Key ID** shown alongside the key. Apple lets you download the `.p8` file only once. Store it securely, you cannot retrieve it again later. #### Firebase Cloud Messaging (FCM) Required for Android. Authsignal uses the FCM HTTP v1 API with a service account JSON downloaded from the [Firebase Console](https://console.firebase.google.com/). | Field | Description | | ------------------------ | --------------------------------------------------------------------------- | | **Service account JSON** | The full service account JSON. Paste the JSON, drop the file, or upload it. | To get the JSON, open your project in the [Firebase Console](https://console.firebase.google.com/) and go to **Project settings > Service accounts**, then click **Generate new private key**. ### Firebase provider Select **Firebase** if you use FCM as a cross-platform solution. Authsignal sends every notification - including to iOS devices - through Firebase Cloud Messaging. Set up push verification - configuring the Firebase provider Upload a service account JSON downloaded from the [Firebase Console](https://console.firebase.google.com/). This is the only credential required. Go to **Project settings > Service accounts** and click **Generate new private key** to download it. ## Registering push tokens For Authsignal to deliver a notification, each enrolled device must have a push token registered against its credential. When you enroll a device for push verification with the [Mobile SDK](/sdks/client/mobile/push-verification#adding-a-credential), pass the device's push token to `addCredential`. Authsignal stores the token with the credential and uses it, along with the device platform, to route the notification to the correct service. ### Which token to register The token you register must match the provider configured for your tenant: * **Default provider** - register the raw device push token. On iOS this is a raw APNs token, on Android it is an FCM token. Authsignal sends to APNs directly on iOS and FCM on Android. * **Firebase provider** - register an FCM registration token on both platforms, including iOS. Your app obtains the token from the OS push APIs: ```swift iOS theme={null} // Default provider: the raw APNs device token, delivered to your AppDelegate // after registering for remote notifications. func application( _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { let pushToken = deviceToken.map { String(format: "%02x", $0) }.joined() } // Firebase provider: the FCM registration token. Messaging.messaging().token { token, error in let pushToken = token } ``` ```kotlin Android theme={null} // Default and Firebase providers both use the FCM registration token on Android. FirebaseMessaging.getInstance().token.addOnCompleteListener { task -> if (task.isSuccessful) { val pushToken = task.result } } ``` ```ts React Native theme={null} import * as Notifications from "expo-notifications"; import messaging from "@react-native-firebase/messaging"; // Default provider: raw APNs token (iOS) / FCM token (Android). const { data: pushToken } = await Notifications.getDevicePushTokenAsync(); // Firebase provider: FCM registration token (both platforms). const pushToken = await messaging().getToken(); ``` ```dart Flutter theme={null} import 'dart:io' show Platform; import 'package:firebase_messaging/firebase_messaging.dart'; // Default provider: raw APNs token (iOS) / FCM token (Android). final pushToken = Platform.isIOS ? await FirebaseMessaging.instance.getAPNSToken() : await FirebaseMessaging.instance.getToken(); // Firebase provider: FCM registration token (both platforms). final pushToken = await FirebaseMessaging.instance.getToken(); ``` ### Passing the token to the SDK Pass the token you obtained to `addCredential`: ```swift iOS theme={null} await authsignal.push.addCredential( token: "eyJhbGciOiJ...", pushToken: "" ) ``` ```kotlin Android theme={null} authsignal.push.addCredential( token = "eyJhbGciOiJ...", pushToken = "" ) ``` ```ts React Native theme={null} await authsignal.push.addCredential({ token: "eyJhbGciOiJ...", pushToken: "", }); ``` ```dart Flutter theme={null} await authsignal.push.addCredential( token: "eyJhbGciOiJ...", pushToken: "", ); ``` If a credential has no push token, the notification is skipped for that device. The challenge can still be completed by opening the app, because delivery is best-effort. Passing the push token is supported on the iOS SDK from `v2.7.1`, the Android SDK from `v3.8.1`, the React Native SDK from `v2.12.0`, and the Flutter SDK from `v2.6.0`. ### Keeping the token current Push tokens rotate over time - on reinstall, restore, or expiry - and a notification sent to a stale token is silently dropped. Both [Apple](https://developer.apple.com/documentation/usernotifications/registering-your-app-with-apns) and [Google](https://firebase.google.com/docs/cloud-messaging/manage-tokens) recommend registering the new token with your server as soon as the OS hands it to you. With Authsignal, register it (typically from a token-refresh callback and on app launch) using the SDK's [Update credential](/sdks/client/mobile/push-verification#updating-a-credential) method, which refreshes the token on the existing credential without re-enrolling the device. Updating a credential's push token requires the iOS SDK `v2.11.0`, the Android SDK `v4.1.0`, or the React Native SDK `v3.1.0` (or later). ## What gets sent The notification payload is intentionally minimal. It carries a generic alert and the `challengeId` of the pending challenge: * **APNs** - an `alert` notification with a default sound, plus the `challengeId` in the payload. * **FCM** - a notification with the title `Authentication request`, plus the `challengeId` in the message data. Your app uses the `challengeId` to fetch the pending challenge with the Mobile SDK's [Get Challenge](/sdks/client/mobile/push-verification#getting-a-challenge) method, then presents the approve/reject prompt as described in [Push verification](/authentication-methods/app-verification/push#3-check-for-pending-challenge-in-mobile-app). ## Monitoring delivery Because delivery is best-effort, failed sends never fail a challenge. Instead, Authsignal records a timeline event when a send fails - including the downstream provider name, status code, and an error description from APNs or FCM - so you can monitor and debug delivery issues. A successful send is also recorded as a `PUSH_SENT` event. You can see these events on the user's [event timeline](/knowledge-base/administration/view-user-activity) in the portal. Expand **View more** on a failed send to see the provider, status code, and error description returned by APNs or FCM. Event timeline showing failed push sends ## Next steps * **[Push verification](/authentication-methods/app-verification/push)** - The full push verification flow, including enrollment and adaptive MFA * **[Enrollment lifecycle](/authentication-methods/app-verification/enrollment-lifecycle)** - When to add credentials and register push tokens in your app * **[Adaptive MFA](/actions-rules/rules/adaptive-mfa)** - Trigger push verification based on risk # Push verification Source: https://docs.authsignal.com/authentication-methods/app-verification/push Verify users by adding a secure "push" authentication method to your mobile apps. Push authentication Push verification allows users to authenticate in a web browser by sending an authentication request to a mobile app. Push notifications are used to improve the UX by prompting users to open their app but they are **not required for the method to work**. The authentication mechanism is based on **public key cryptography** where private keys are securely stored on the user's mobile device and are used to sign messages which can be verified with the public key. The [Mobile SDK](/sdks/client/mobile/setup) is used in push verification for two key steps: 1. Enrolling a user for push verification by [adding a credential](/sdks/client/mobile/push-verification#adding-a-credential) in the mobile app. This step creates a new public/private key pair. 2. Responding to an authentication challenge in the mobile app by [approving or denying it](/sdks/client/mobile/push-verification#updating-a-challenge). This step uses the mobile device's private key to sign a message which is verified on the server using the public key. ## Sequence diagram The diagram below illustrates the sequence for a push verification challenge. ```mermaid theme={null} sequenceDiagram participant M as Mobile app participant W as Webhook participant AC as Authsignal Client API participant F as Your web frontend participant B as Your web backend participant A as Authsignal Server API B->>A: Track Action A->>F: token rect rgba(221, 237, 253, 1) note left of F: Authsignal Web SDK F->>AC: Start Push Challenge AC->>W: challengeId W->>M: Send push notification F-->>AC: Verify Push Challenge
(Start polling) rect rgb(191, 223, 255) note right of M: Authsignal Mobile SDK M->>AC: Get Device Challenge AC->>M: challengeId Note over M: Present approve/reject prompt M->>AC: Update Device Challenge end AC-->>F: (Verify Push Challenge response)
isConsumed
isVerified
token end F->>B: token B->>A: Validate Challenge A->>B: Challenge result Note over B: Proceed with login or
authenticated transaction ``` ## Portal setup Enable [push verification](https://portal.authsignal.com/organisations/tenants/authenticators/push) for your tenant and configure a webhook endpoint for sending push notifications to your app. For more information on the payload which Authsignal sends to your webhook, refer to the [webhook schema documentation for push](/advanced-usage/webhooks/push-created). Prefer not to run your own webhook? Authsignal can deliver push notifications for you - just connect your APNs and/or FCM credentials in the portal. See [Managed push notifications](/authentication-methods/app-verification/managed-push). ## SDK setup ### Server SDK Initialize the SDK using your Server API secret key from the [API keys page](https://portal.authsignal.com/organisations/tenants/api) and the [API URL](/sdks/server/setup#regions) for your region. ```ts Node.js theme={null} import { Authsignal } from "@authsignal/node"; const authsignal = new Authsignal({ apiSecretKey: "YOUR_SECRET_KEY", apiUrl: "YOUR_API_URL", }); ``` ```csharp C# theme={null} using Authsignal; var authsignal = new AuthsignalClient( apiSecretKey: "YOUR_SECRET_KEY", apiUrl: "YOUR_API_URL" ); ``` ```java Java theme={null} import com.authsignal.AuthsignalClient; AuthsignalClient authsignal = new AuthsignalClient( "YOUR_SECRET_KEY", "YOUR_API_URL" ); ``` ```ruby Ruby theme={null} require 'authsignal' Authsignal.setup do |config| config.api_secret_key = ENV["YOUR_SECRET_KEY"] config.api_url = "YOUR_API_URL" end ``` ```python Python theme={null} from authsignal.client import AuthsignalClient authsignal = AuthsignalClient( api_secret_key="YOUR_SECRET_KEY", api_url="YOUR_API_URL" ) ``` ```php PHP theme={null} Authsignal::setApiSecretKey("YOUR_SECRET_KEY"); Authsignal::setApiUrl("YOUR_API_URL"); ``` ```go Go theme={null} import "github.com/authsignal/authsignalgo/v2/client" client := NewAuthsignalClient( "YOUR_SECRET_KEY", "YOUR_API_URL", ) ``` ### Web and Mobile SDKs Initialize the [Web SDK](/sdks/client/web/setup) and [Mobile SDK](/sdks/client/mobile/setup) using your tenant ID from the [API keys page](https://portal.authsignal.com/organisations/tenants/api) and your [API URL](/sdks/server/setup#regions). ```ts Web theme={null} import { Authsignal } from "@authsignal/browser"; const authsignal = new Authsignal({ tenantId: "YOUR_TENANT_ID", baseUrl: "YOUR_API_URL", }); ``` ```swift iOS theme={null} import Authsignal let authsignal = Authsignal( tenantID: "YOUR_TENANT_ID", baseURL: "YOUR_API_URL" ) ``` ```kotlin Android theme={null} import com.authsignal.Authsignal val authsignal = Authsignal( tenantID = "YOUR_TENANT_ID", baseURL = "YOUR_API_URL", ) ``` ```ts React Native theme={null} import { Authsignal } from "react-native-authsignal"; const authsignal = new Authsignal({ tenantID: "YOUR_TENANT_ID", baseURL: "YOUR_API_URL", }); ``` ```dart Flutter theme={null} import 'package:authsignal_flutter/authsignal_flutter.dart'; final authsignal = Authsignal( "YOUR_TENANT_ID", baseURL: "YOUR_API_URL" ); ``` ## Enrollment **Scenario** - Enroll users for push verification in your mobile app so it can be used later as a method for adaptive MFA. ### 1. Generate enrollment token In your backend, track an action for a user (e.g. "addAuthenticator") to generate a short-lived token. ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "addAuthenticator", attributes: { scope: "add:authenticators", }, }; const response = await authsignal.track(request); const token = response.token; ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "addAuthenticator", Attributes: new TrackAttributes( Scope: "add:authenticators", ) ); var response = await authsignal.Track(request); var token = response.Token; ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "addAuthenticator"; request.attributes = new TrackAttributes(); request.attributes.scope = "add:authenticators"; TrackResponse response = authsignal.track(request).get(); String token = response.token; ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "addAuthenticator", attributes: { scope: "add:authenticators", } }) token = response[:token] ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="addAuthenticator", attributes={ "scope": "add:authenticators", } ) token = response["token"] ``` ```php PHP theme={null} $response = Authsignal::track([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'action' => "addAuthenticator", 'attributes' => [ 'scope' => "add:authenticators", ] ]); $token = $response["token"] ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "addAuthenticator", Attributes: &TrackAttributes{ scope: "add:authenticators", }, }, ) token := response.Token ``` This token will be used to authorize enrolling a new push authentication method on their mobile device. The **add:authenticators** scope is required to enroll a new authentication factor for an existing user. This scope should only be used **when the user is in an already authenticated state**. For more information on using scopes safely refer to our documentation on [authenticator binding](/advanced-usage/authenticator-binding). ### 2. Add credential Use the token obtained in step 1 to enroll a new device credential for the user in the mobile app. ```swift iOS theme={null} await authsignal.push.addCredential(token: "eyJhbGciOiJ...") ``` ```kotlin Android theme={null} authsignal.push.addCredential(token = "eyJhbGciOiJ...") ``` ```ts React Native theme={null} await authsignal.push.addCredential({ token: "eyJhbGciOiJ..." }); ``` ```dart Flutter theme={null} await authsignal.push.addCredential("eyJhbGciOiJ..."); ``` For more information on when in your app to add credentials refer to our documentation on the [enrollment lifecycle](/authentication-methods/app-verification/enrollment-lifecycle) for app verification. ## Adaptive MFA **Scenario** - Strongly authenticate users in a web browser with push verification and use rules to decide when and where to trigger the authentication. ### 1. Track action When a user performs an action that requires push verification, your backend should track an action (e.g. "signIn") using our [Server SDK](/sdks/server/actions#track-action). ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", }; const response = await authsignal.track(request); if (response.state === "CHALLENGE_REQUIRED") { // Obtain token to present challenge const token = response.token; } ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn" ); var response = await authsignal.Track(request); if (response.State == UserActionState.CHALLENGE_REQUIRED) { // Obtain token to present challenge var token = response.Token; } ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; TrackResponse response = authsignal.track(request).get(); if (response.state.equals(UserActionState.CHALLENGE_REQUIRED)) { // Obtain token to present challenge String token = response.token; } ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', action: 'signIn' }) case response[:state] when "CHALLENGE_REQUIRED" # Obtain token to present challenge token = response[:token] end ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn" ) if response["state"] == "CHALLENGE_REQUIRED": # Obtain token to present challenge token = response["token"] ``` ```php PHP theme={null} $response = $authsignal->track([ 'userId' => 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', 'action' => 'signIn' ]); switch ($response['state']) { case 'CHALLENGE_REQUIRED': // Obtain token to present challenge $token = $response['token']; } ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", }, ) switch response.State { case "CHALLENGE_REQUIRED": // Obtain token to present challenge token := response.Token } ``` ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { redirectUrl: "https://yourapp.com/callback", }, }; const response = await authsignal.track(request); if (response.state === "CHALLENGE_REQUIRED") { // Return URL to your frontend to launch pre-built UI const url = response.url; } ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: new TrackAttributes( RedirectUrl: "https://yourapp.com/callback", ) ); var response = await authsignal.Track(request); if (response.State == UserActionState.CHALLENGE_REQUIRED) { // Return URL to your frontend to launch pre-built UI var url = response.Url; } ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; request.attributes = new TrackAttributes(); request.attributes.redirectUrl = "https://yourapp.com/callback"; TrackResponse response = authsignal.track(trackRequest).get(); if (response.state.equals(UserActionState.CHALLENGE_REQUIRED)) { // Return URL to your frontend to launch pre-built UI String url = response.url; } ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', action: 'signIn', attributes: { redirect_url: "https://yourapp.com/callback" } }) case response[:state] when "CHALLENGE_REQUIRED" # Return URL to your frontend to launch pre-built UI url = response[:url] end ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn", attributes={ "redirectUrl": "https://yourapp.com/callback" } ) if response["state"] == "CHALLENGE_REQUIRED": # Return URL to your frontend to launch pre-built UI url = response["url"] ``` ```php PHP theme={null} $response = $authsignal->track([ 'userId' => 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', 'action' => 'signIn', 'attributes' => [ 'redirectUrl' => 'https://yourapp.com/callback' ] ]); switch ($response['state']) { case 'CHALLENGE_REQUIRED': // Return URL to your frontend to launch pre-built UI $url = $response['url']; } ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: &TrackAttributes{ RedirectUrl: "https://yourapp.com/callback", }, }, ) switch response.State { case "CHALLENGE_REQUIRED": // Return URL to your frontend to launch pre-built UI url := response.Url } ``` ### 2. Initiate challenge in browser In the browser, use our [Web SDK](/sdks/client/web/push-verification#start-a-push-challenge) to initiate a push challenge. This will trigger a call to your webhook with the [push event schema](/advanced-usage/webhooks/push-created) so that it can send the user a push notification. If a user has enrolled more than one push authenticator, you can target a specific one by passing its `userAuthenticatorId` to the [push challenge](/sdks/client/web/push-verification#start-a-push-challenge). Only that authenticator receives the challenge and can approve it. ```ts theme={null} // Set the token obtained from the track response authsignal.setToken(token); // Initiate the push challenge const response = await authsignal.push.challenge(); // Obtain a challenge ID to poll for the result in the next step const challengeId = response.data?.challengeId; ``` Then you can immediately start polling for the challenge result. ```ts theme={null} // Poll for the challenge result const { data, error } = await authsignal.push.verify({ challengeId }); if (error || !data) { // Handle error } else if (!data.isConsumed) { // The challenge has not yet been consumed // Continue polling } else { // The challenge has been consumed // Stop polling and check if approved or rejected if (data.isVerified) { // Challenge has been approved // Obtain new token to send to backend to validate action const validationToken = data.token; } else { // Challenge has been rejected } } ``` ```js theme={null} // Launch the pre-built UI with the URL from the track response const result = await authsignal.launch(url, { popup: true }); // Get a new token to validate the action on your backend if (result.token) { const validationToken = result.token; } ``` ### 3. Check for pending challenge in mobile app Use the mobile SDK's [Get Challenge](/sdks/client/mobile/push-verification#getting-a-challenge) method to check if there is a pending challenge for the device. ```swift iOS theme={null} let response = await authsignal.push.getChallenge() if let error = result.error { // The credential stored on the device is invalid } else if let challenge = result.data { // A pending challenge request is available // Present the user with a prompt to approve or deny the request let challengeId = challenge.challengeId } else { // No pending challenge request } ``` ```kotlin Android theme={null} val response = authsignal.push.getChallenge() if (response.error != null) { // The credential stored on the device is invalid } else if (response.data != null) { // A pending challenge request is available // Present the user with a prompt to approve or deny the request val challengeId = response.data.challengeId } else { // No pending challenge request } ``` ```ts React Native theme={null} const {data, error} = await authsignal.push.getChallenge(); if (error) { // The credential stored on the device is invalid } else if (data) { // A pending challenge request is available // Present the user with a prompt to approve or deny the request val challengeId = data.challengeId } else { // No pending challenge request } ``` ```dart Flutter theme={null} final response = await authsignal.push.getChallenge(); if (response.error != null) { // The credential stored on the device is invalid } else if (response.data != null) { // A pending challenge request is available // Present the user with a prompt to approve or deny the request final challengeId = response.data.challengeId; } else { // No pending challenge request } ``` This check should be done when **launching** or **foregrounding** the app - either from a push notification or when opened manually - as well as when the app receives a push notification while it is **already foregrounded**. To display custom data points passed into the initial track request, mark the relevant [custom data points](/actions-rules/rules/custom-data-points#public-custom-data-points) as public. ### 4. Present challenge in mobile app If there is a pending challenge, present a dialog to allow the user to approve or reject the challenge. To approve or reject the challenge, use the mobile SDK's [Update Challenge](/sdks/client/mobile/push-verification#updating-a-challenge) method. ```swift iOS theme={null} await authsignal.push.updateChallenge( challengeId: challengeId, approved: true ) ``` ```kotlin Android theme={null} authsignal.push.updateChallenge( challengeId = challengeId, approved = true ) ``` ```ts React Native theme={null} await authsignal.push.updateChallenge({ challengeId, approved: true, }); ``` ```dart Flutter theme={null} await authsignal.push.updateChallenge( challengeId, true, ); ``` ### 5. Complete authentication in browser Once the challenge is approved, the polling request initiated in the web browser in step 2 will return a token that should be passed to your web browser app's backend to [validate the action](/sdks/server/challenges#validate-challenge) in order to complete the authentication flow. ```ts Node.js theme={null} const response = await authsignal.validateChallenge({ action: "signIn", token: "eyJhbGciOiJIUzI....", }); if (response.state === "CHALLENGE_SUCCEEDED") { // User completed challenge successfully } ``` ```csharp C# theme={null} var request = new ValidateChallengeRequest( Action: "signIn", Token: "eyJhbGciOiJIUzI...." ); var response = await authsignal.ValidateChallenge(request); if (response.State == UserActionState.CHALLENGE_SUCCEEDED) { // User completed challenge successfully } ``` ```java Java theme={null} ValidateChallengeRequest request = new ValidateChallengeRequest(); request.action = "signIn"; request.token = "eyJhbGciOiJIUzI...."; ValidateChallengeResponse response = authsignal.validateChallenge(request).get(); if (response.state == UserActionState.CHALLENGE_SUCCEEDED) { // User completed challenge successfully } ``` ```ruby Ruby theme={null} response = Authsignal.validate_challenge(action: "signIn", token: "eyJhbGciOiJIUzI....") if response[:state] == "CHALLENGE_SUCCEEDED" # User completed challenge successfully end ``` ```python Python theme={null} response = authsignal.validate_challenge(action="signIn", token="eyJhbGciOiJIUzI....") if response["state"] == "CHALLENGE_SUCCEEDED": # User completed challenge successfully ``` ```php PHP theme={null} $response = Authsignal::validateChallenge(action: "signIn", token: "eyJhbGciOiJIUzI...."); if ($response["state"] === "CHALLENGE_SUCCEEDED") { # User completed challenge successfully } ``` ```go Go theme={null} response, err := client.ValidateChallenge( ValidateChallengeRequest{ Action: "signIn", Token: "eyJhbGciOiJ...", }, ) if response.State == "CHALLENGE_SUCCEEDED" { // User completed challenge successfully } ``` ## Next steps * **[Adaptive MFA](/actions-rules/rules/adaptive-mfa)** - Set up smart rules to trigger authentication based on risk * **[QR code](/authentication-methods/qr-code)** - Implement QR code authentication * **[Trusted device](/authentication-methods/trusted-device)** - Implement trusted device authentication * **[Passkeys](/authentication-methods/passkey/custom-ui)** - Offer the most secure and user-friendly passwordless authentication # QR code verification Source: https://docs.authsignal.com/authentication-methods/app-verification/qr-code Enable cross-device authentication by scanning QR codes with mobile apps. QR code verification QR code verification uses app credentials to enable cross-device authentication. This method leverages public key cryptography where private keys are securely stored on the user's device. The [Mobile SDK](/sdks/client/mobile) is used for two key steps: 1. Registering a mobile device for QR code verification by [adding a credential](/sdks/client/mobile/qr-code-verification#adding-a-credential). This step creates a new public/private key pair. 2. Responding to an authentication request by [approving or rejecting a challenge](/sdks/client/mobile/qr-code-verification#updating-a-challenge). This step uses the device's private key to sign a message which is verified on the server using the public key. ## Use cases * Log into a desktop or web application from an application that is typically used on mobile * Identify a user on a kiosk in a quick service restaurant (QSR) to load loyalty programs, rewards and offers * Log into an application running on a TV * Complete a payment via a terminal ## Sequence diagram ```mermaid theme={null} sequenceDiagram participant M as Mobile App (Authenticated) participant W as Desktop / Kiosk / Terminal participant B as Your Backend participant C as Authsignal Client API participant S as Authsignal Server API rect rgb(191, 223, 255) note right of W: Using Authsignal's Web SDK W->>C: Request QR Code Challenge activate C C->>W: Return Challenge deactivate C W-->>C: Poll Challenge Result end W-->>M: Display Challenge Id via QR Code M-->>W: Scan QR code to get Challenge Id rect rgb(191, 223, 255) note right of M: Using Authsignal's Mobile SDK M->>C: Claim Challenge activate C C->>M: Return Challenge Context deactivate C M->>C: Approve/Decline Challenge end C-->>W: Return Challenge Result W->>B: Validate Challenge B->>S: Validate Challenge activate S S->>B: Challenge Result deactivate S ``` The general flow for QR code verification is as follows: 1. Generate a challenge 2. Display the QR code to the user 3. Wait for the user to scan and respond on their mobile device 4. Handle the authentication result (approved/rejected) ## Portal setup Enable [QR code verification](https://portal.authsignal.com/organisations/tenants/authenticators/qr_code) for your tenant. ## SDK setup ### Server SDK Initialize the SDK using your Server API secret key from the [API keys page](https://portal.authsignal.com/organisations/tenants/api) and the [API URL](/sdks/server/setup#regions) for your region. ```ts Node.js theme={null} import { Authsignal } from "@authsignal/node"; const authsignal = new Authsignal({ apiSecretKey: "YOUR_SECRET_KEY", apiUrl: "YOUR_API_URL", }); ``` ```csharp C# theme={null} using Authsignal; var authsignal = new AuthsignalClient( apiSecretKey: "YOUR_SECRET_KEY", apiUrl: "YOUR_API_URL" ); ``` ```java Java theme={null} import com.authsignal.AuthsignalClient; AuthsignalClient authsignal = new AuthsignalClient( "YOUR_SECRET_KEY", "YOUR_API_URL" ); ``` ```ruby Ruby theme={null} require 'authsignal' Authsignal.setup do |config| config.api_secret_key = ENV["YOUR_SECRET_KEY"] config.api_url = "YOUR_API_URL" end ``` ```python Python theme={null} from authsignal.client import AuthsignalClient authsignal = AuthsignalClient( api_secret_key="YOUR_SECRET_KEY", api_url="YOUR_API_URL" ) ``` ```php PHP theme={null} Authsignal::setApiSecretKey("YOUR_SECRET_KEY"); Authsignal::setApiUrl("YOUR_API_URL"); ``` ```go Go theme={null} import "github.com/authsignal/authsignalgo/v2/client" client := NewAuthsignalClient( "YOUR_SECRET_KEY", "YOUR_API_URL", ) ``` ### Web and Mobile SDKs The [Web SDK](/sdks/client/web/setup) is used to initiate the challenge in a browser by generating a QR code. The [Mobile SDK](/sdks/client/mobile/setup) is used to respond to the challenge in a mobile app by scanning the QR code. Initialize both SDKs using your tenant ID from the [API keys page](https://portal.authsignal.com/organisations/tenants/api) and your [API URL](/sdks/server/setup#regions). ```ts Web theme={null} import { Authsignal } from "@authsignal/browser"; const authsignal = new Authsignal({ tenantId: "YOUR_TENANT_ID", baseUrl: "YOUR_API_URL", }); ``` ```swift iOS theme={null} import Authsignal let authsignal = Authsignal( tenantID: "YOUR_TENANT_ID", baseURL: "YOUR_API_URL" ) ``` ```kotlin Android theme={null} import com.authsignal.Authsignal val authsignal = Authsignal( tenantID = "YOUR_TENANT_ID", baseURL = "YOUR_API_URL", ) ``` ```ts React Native theme={null} import { Authsignal } from "react-native-authsignal"; const authsignal = new Authsignal({ tenantID: "YOUR_TENANT_ID", baseURL: "YOUR_API_URL", }); ``` ```dart Flutter theme={null} import 'package:authsignal_flutter/authsignal_flutter.dart'; final authsignal = Authsignal( "YOUR_TENANT_ID", baseURL: "YOUR_API_URL" ); ``` ## Enrollment **Scenario** - Enroll users for QR code verification in your mobile app so it can be used as a method for authenticating on another device. ### 1. Generate enrollment token In your backend, track an action for a user (e.g. "addAuthenticator") to generate a short-lived token. ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "addAuthenticator", attributes: { scope: "add:authenticators", }, }; const response = await authsignal.track(request); const token = response.token; ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "addAuthenticator", Attributes: new TrackAttributes( Scope: "add:authenticators", ) ); var response = await authsignal.Track(request); var token = response.Token; ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "addAuthenticator"; request.attributes = new TrackAttributes(); request.attributes.scope = "add:authenticators"; TrackResponse response = authsignal.track(request).get(); String token = response.token; ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "addAuthenticator", attributes: { scope: "add:authenticators", } }) token = response[:token] ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="addAuthenticator", attributes={ "scope": "add:authenticators", } ) token = response["token"] ``` ```php PHP theme={null} $response = Authsignal::track([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'action' => "addAuthenticator", 'attributes' => [ 'scope' => "add:authenticators", ] ]); $token = $response["token"] ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "addAuthenticator", Attributes: &TrackAttributes{ scope: "add:authenticators", }, }, ) token := response.Token ``` This token will be used to authorize enrolling a new authentication method on their mobile device. The **add:authenticators** scope is required to enroll a new authentication factor for an existing user. This scope should only be used **when the user is in an already authenticated state**. For more information on using scopes safely refer to our documentation on [authenticator binding](/advanced-usage/authenticator-binding). ### 2. Add credential Use the token obtained in step 1 to enroll a new device credential for the user in the mobile app. ```swift iOS theme={null} await authsignal.qr.addCredential(token: "eyJhbGciOiJ...") ``` ```kotlin Android theme={null} authsignal.qr.addCredential(token = "eyJhbGciOiJ...") ``` ```ts React Native theme={null} await authsignal.qr.addCredential({ token: "eyJhbGciOiJ..." }); ``` ```dart Flutter theme={null} await authsignal.qr.addCredential("eyJhbGciOiJ..."); ``` ## Authentication **Scenario** - Let users scan a QR code with their mobile app to authenticate on another device. ### 1. Track action (optional) You may optionally track an action from your backend if you want to create a QR code challenge that can only be completed by a specific user or run rules on the action for [adaptive MFA](/actions-rules/rules/adaptive-mfa). If you want to start a QR code challenge that can be claimed and completed by any user, skip to [step 2](#2-present-qr-code). Track an action from your backend using our [Server SDK](/sdks/server/overview). ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", }; const response = await authsignal.track(request); if (response.state === "CHALLENGE_REQUIRED") { // Obtain token to present challenge const token = response.token; } ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn" ); var response = await authsignal.Track(request); if (response.State == UserActionState.CHALLENGE_REQUIRED) { // Obtain token to present challenge var token = response.Token; } ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; TrackResponse response = authsignal.track(request).get(); if (response.state.equals(UserActionState.CHALLENGE_REQUIRED)) { // Obtain token to present challenge String token = response.token; } ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', action: 'signIn' }) case response[:state] when "CHALLENGE_REQUIRED" # Obtain token to present challenge token = response[:token] end ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn" ) if response["state"] == "CHALLENGE_REQUIRED": # Obtain token to present challenge token = response["token"] ``` ```php PHP theme={null} $response = $authsignal->track([ 'userId' => 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', 'action' => 'signIn' ]); switch ($response['state']) { case 'CHALLENGE_REQUIRED': // Obtain token to present challenge $token = $response['token']; } ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", }, ) switch response.State { case "CHALLENGE_REQUIRED": // Obtain token to present challenge token := response.Token } ``` Return the token to your frontend and call the Web SDK's `setToken` method. ```js theme={null} authsignal.setToken(response.token); ``` When creating a QR code challenge after tracking an action, the custom data and action code must be provided in the track request and cannot be overridden in the challenge request from the frontend. ### 2. Present QR code Use our [Web SDK](/sdks/client/web/qr-code-verification) to initiate a QR code challenge. **Initiate QR code challenge** Use the [qrCode.challenge()](/sdks/client/web/qr-code-verification#start-a-qr-code-challenge) method to initiate a challenge. The SDK will handle all the complexity of managing the challenge lifecycle, including automatic refreshing and state updates. By default the SDK will use WebSocket connections. If your environment does not support WebSockets, you can fall back to polling using REST API calls by setting `polling` to `true`. ```js theme={null} const { data } = await authsignal.qrCode.challenge({ action: "signIn", onStateChange: (state, token) => { switch (state) { case "claimed": // User has scanned the QR code - blur/hide it console.log("QR code claimed by user"); break; case "approved": // Challenge approved - validate the token on your backend if (token) { validateChallengeOnBackend(token); // Continue with the application flow } break; case "rejected": // Challenge rejected - show error and offer retry console.log("Challenge rejected by user"); break; } }, onRefresh: ({ challengeId, expiresAt }) => { // Update your QR code display with the new challengeId updateQRCode(challengeId); }, // Optional: Add context that will be shown on the user's device. Only available for anonymous challenges. custom: { deviceInfo: "Terminal A", }, refreshInterval: 540000, // 9 minutes (default) polling: false, // Use WebSocket for real-time updates (default) }); // Display the QR code using the challengeId if (data?.challengeId) { displayQRCode(data.challengeId); } ``` **State changes** After calling `qrCode.challenge()`, the QR code is ready to be scanned. Display it to the user. When `onStateChange` is called with `state: "claimed"`, the user has scanned the QR code. You can use this state change to indicate progress in your UI. When `onStateChange` is called with `state: "approved"` and a `token`, the user has approved the challenge. Pass the token to your backend for validation. When `onStateChange` is called with `state: "rejected"`, the user has declined the challenge. Show an error message and provide a way to retry. **Refresh a challenge (optional)** If you need to manually refresh a challenge you can use the [qrCode.refresh()](/sdks/client/web/qr-code-verification#refresh-a-qr-code-challenge) method: ```js theme={null} // Manually refresh with optional updated context await authsignal.qrCode.refresh({ custom: { deviceInfo: "Terminal B", }, }); ``` This must be called after `qrCode.challenge()` method has been called. The original callbacks will be used with the new challenge. ### 3. Scan QR code Once the user scans the QR code, use the mobile sdk [Claim Challenge](/sdks/client/mobile/qr-code-verification#claiming-a-challenge) method to set the user attempting to complete the challenge. The Claim Challenge method will return some context about the desktop or kiosk initiating the challenge such as ip address, location, user agent and custom data. This data can be shown to the user to help them decide if they want to approve or decline the challenge. To display custom data points passed into the initial track request, mark the relevant [custom data points](/actions-rules/rules/custom-data-points#public-custom-data-points) as public. ```swift iOS theme={null} await authsignal.qr.claimChallenge( challengeId: challengeId ) ``` ```kotlin Android theme={null} authsignal.qr.claimChallenge( challengeId = challengeId ) ``` ```ts React Native theme={null} await authsignal.qr.claimChallenge({ challengeId, }); ``` ```dart Flutter theme={null} await authsignal.qr.claimChallenge( challengeId, ); ``` ### 4. Approve or decline the challenge Present a dialog to allow the user to review the challenge context and approve or decline the challenge by calling the mobile sdk [Update Challenge](/sdks/client/mobile/qr-code-verification#updating-a-challenge) method. ```swift iOS theme={null} await authsignal.qr.updateChallenge( challengeId: challengeId, approved: true ) ``` ```kotlin Android theme={null} authsignal.qr.updateChallenge( challengeId = challengeId, approved = true ) ``` ```ts React Native theme={null} await authsignal.qr.updateChallenge({ challengeId, approved: true, }); ``` ```dart Flutter theme={null} await authsignal.qr.updateChallenge( challengeId, true, ); ``` ### 5. Complete authentication Once the challenge is approved, the `onStateChange` callback will return a `token` that should be passed to your backend to [validate the challenge for the action](/sdks/server/challenges#validate-challenge) and complete the authentication flow. ```ts Node.js theme={null} const response = await authsignal.validateChallenge({ action: "signIn", token: "eyJhbGciOiJIUzI....", }); if (response.state === "CHALLENGE_SUCCEEDED") { // User completed challenge successfully } ``` ```csharp C# theme={null} var request = new ValidateChallengeRequest( Action: "signIn", Token: "eyJhbGciOiJIUzI...." ); var response = await authsignal.ValidateChallenge(request); if (response.State == UserActionState.CHALLENGE_SUCCEEDED) { // User completed challenge successfully } ``` ```java Java theme={null} ValidateChallengeRequest request = new ValidateChallengeRequest(); request.action = "signIn"; request.token = "eyJhbGciOiJIUzI...."; ValidateChallengeResponse response = authsignal.validateChallenge(request).get(); if (response.state == UserActionState.CHALLENGE_SUCCEEDED) { // User completed challenge successfully } ``` ```ruby Ruby theme={null} response = Authsignal.validate_challenge(action: "signIn", token: "eyJhbGciOiJIUzI....") if response[:state] == "CHALLENGE_SUCCEEDED" # User completed challenge successfully end ``` ```python Python theme={null} response = authsignal.validate_challenge(action="signIn", token="eyJhbGciOiJIUzI....") if response["state"] == "CHALLENGE_SUCCEEDED": # User completed challenge successfully ``` ```php PHP theme={null} $response = Authsignal::validateChallenge(action: "signIn", token: "eyJhbGciOiJIUzI...."); if ($response["state"] === "CHALLENGE_SUCCEEDED") { # User completed challenge successfully } ``` ```go Go theme={null} response, err := client.ValidateChallenge( ValidateChallengeRequest{ Action: "signIn", Token: "eyJhbGciOiJ...", }, ) if response.State == "CHALLENGE_SUCCEEDED" { // User completed challenge successfully } ``` ## Next steps * **[Passkeys](/authentication-methods/passkey/custom-ui)** - Offer the most secure and user-friendly passwordless authentication # Biometric auth Source: https://docs.authsignal.com/authentication-methods/biometrics Verify user identity using advanced biometric verification providers Authsignal offers biometric authentication with advanced identity verification for high-security use cases. Choose from our integrated biometric verification partners to add an extra layer of security to your authentication flow. ## Biometric providers ### iProov iProov provides real-time face verification technology that ensures the person is the right person, a real person, and that they are authenticating right now. #### Enable iProov in the Portal 1. Navigate to the [Authenticators section](https://portal.authsignal.com/organisations/tenants/authenticators) in your Authsignal Portal 2. Find **iProov** in the biometric authenticators list 3. Click **Setup iProov**. 4. Enter your iProov credentials, configure your verification settings and activate the authenticator. #### Pre-built UI Experience When enabled, iProov biometric verification integrates seamlessly into the Authsignal pre-built UI, providing users with a smooth face verification experience. *** ### Veriff Veriff offers comprehensive identity verification with advanced biometric checks, document verification, and fraud detection capabilities. #### Enable Veriff in the Portal 1. Navigate to the [Authenticators section](https://portal.authsignal.com/organisations/tenants/authenticators) in your Authsignal Portal 2. Find **Veriff** in the biometric authenticators list 3. Click **Setup Veriff**. 4. Enter your Veriff API credentials and activate the authenticator. #### Pre-built UI Experience Veriff's verification flow integrates directly into the Authsignal pre-built UI, guiding users through document capture and biometric verification steps. *** ### IDVerse IDVerse provides AI-powered identity verification with biometric matching, liveness detection, and document authentication. #### Enable IDVerse in the Portal 1. Navigate to the [Authenticators section](https://portal.authsignal.com/organisations/tenants/authenticators) in your Authsignal Portal 2. Find **IDVerse** in the biometric authenticators list 3. Click **Configure** and follow the setup instructions 4. Enter your IDVerse credentials and set up your verification parameters #### Pre-built UI Experience IDVerse verification is embedded in the Authsignal pre-built UI, offering users an intuitive biometric verification process with real-time feedback. *** ### Onfido Onfido (Entrust) provides document and biometric verification through [Onfido Studio](https://documentation.identity.entrust.com/) workflows. Authsignal uses Onfido's authentication pattern, which relies on two distinct workflows: an enrollment workflow that captures the user's biometric, and an authentication workflow that re-verifies the user against the stored biometric. Onfido is enabled as part of a paid subscription. Contact your [Authsignal account manager](mailto:support@authsignal.com) to have it added to your plan. #### Prerequisites In your Onfido (Entrust) Dashboard, you'll need: * An **API token** issued for your Onfido data region (EU, US, or CA). * An activated **enrollment workflow** (Document & Motion) that enrolls the user's biometric. * An activated **authentication workflow** that re-verifies the user against the stored biometric. You configure both workflows in Onfido Studio. Note the workflow ID of each, since you'll need them during setup. #### Enable Onfido in the Portal 1. Navigate to the [Authenticators section](https://portal.authsignal.com/organisations/tenants/authenticators) in your Authsignal Portal 2. Find **Onfido** in the biometric authenticators list 3. Click **Setup Onfido** 4. Select your **Region**. This must match the region your API token was issued for. 5. Enter your **API token**. 6. Copy the **Webhook URL** shown in the Portal. In your Onfido dashboard, create a webhook for the `workflow_run.completed` event pointing at this URL, then paste the **webhook signing token** that Onfido shows back into the Portal. 7. Enter your **Enrollment workflow** ID and **Authentication workflow** ID. 8. Click **Activate Onfido**. #### Pre-built UI Experience Onfido's verification flow integrates directly into the Authsignal pre-built UI, guiding users through document capture and biometric verification. ## Implementation Once you've configured your preferred biometric provider in the Portal, you can use it like any other Authsignal authenticator: * **Track actions** that require biometric verification using our [Server SDK](/sdks/server/actions#track-action) or [Server API](/api-reference/server-api/track-action) * **Launch the pre-built UI** to present the biometric verification challenge to users * **Validate challenges** on your backend to confirm successful verification The biometric verification flow integrates seamlessly with Authsignal's [rules engine](/actions-rules/rules/getting-started), allowing you to trigger biometric authentication based on risk factors, user behavior, or specific business requirements. ## Next steps Contact us to learn more about how to implement biometric authentication. * Email: [support@authsignal.com](mailto:support@authsignal.com) * Schedule a demo: [Book a call with our team](https://authsignal.com/contact) # Email magic link Source: https://docs.authsignal.com/authentication-methods/email/magic-link Send magic links via email for authentication and for verifying users' email addresses. ## Email provider setup Navigate to [Authenticators](https://portal.authsignal.com/organisations/tenants/authenticators) in the Authsignal Portal, click on **Email magic link**, and choose an email provider. 1. Log in to your [Bird account](https://bird.com/) 2. Get your **Access key** and **Workspace ID** from your Bird settings 3. Create or locate an email channel and note the **Channel ID** 4. Create an email template in Bird and note the **Project ID/Template ID** 5. Publish your template and note the **Published version ID** 6. In the Authsignal Portal, select **Bird** as your email provider 7. Enter your Bird access key, workspace ID, channel ID, project ID/template ID, published version ID, and select your default language 1. Log in to your [Mailjet account](https://app.mailjet.com/) 2. Navigate to **API > API Key Management** and get your **API key** and **Secret key** 3. Navigate to **Content > Email Templates** and get your **Template ID** 4. In the Authsignal Portal, select **Mailjet** as your email provider 5. Enter your Mailjet **API key**, **API secret key**, and **Template ID** 1. Log in to your [Mailgun account](https://app.mailgun.com/) 2. Click on your profile icon in the top-right corner and select **API Keys** 3. Click **Add New API Key** to generate a new API key 4. Note your **Sending domain** from the Mailgun dashboard 5. In the Authsignal Portal, select **Mailgun** as your email provider 6. Enter your Mailgun **API key**, **Sending domain**, **Template name** (optional), and select **API URL** for your region 1. Log in to your [Mailchimp account](https://mailchimp.com/) 2. Go to **Settings** and click on **API Keys** 3. Click **Create API Key** and give it a description. Copy the generated key 4. In the Authsignal Portal, select **Mandrill** as your email provider 5. Enter your Mandrill **API key** and **Template name** 1. Log in to your [SendGrid account](https://login.sendgrid.com/) 2. Go to **Settings** and click on **API Keys** 3. Click **Create New Key** and ensure either **Full Access** or **Custom Access** with the **Mail Send** permission is selected. Copy the generated key 4. Create a new [dynamic template](https://mc.sendgrid.com/dynamic-templates/new) in SendGrid and copy the **Template ID** 5. In the Authsignal Portal, select **SendGrid** as your email provider 6. Enter your SendGrid **API key**, **Template ID**, **From email**, and an optional **From name** For detailed instructions on setting up a webhook for email delivery, see the [webhooks documentation](/advanced-usage/webhooks/introduction). Authsignal supports sending with an SMTP server of your choosing. 1. In the Authsignal Portal, select **SMTP** as your email provider. 2. Enter your **SMTP Server Address**, **Port**, **Username**, **Password**, **From Address**, and an optional **From name** You can use **Authsignal** as an email provider for development, but it's recommended to use an alternative provider in production for more control over emails. ### Email template variables The following template variables are available for use in your email template: The magic link URL. The action that the user is performing. The ID of the user. The user agent associated with the action. The IP address associated with the action. ### Custom template variables You can forward [custom data points](/actions-rules/rules/custom-data-points) to your email provider as additional template variables. This is useful for personalizing emails with data specific to your application, such as a user's account ID or a transaction amount. To configure this, navigate to your [email magic link settings](https://portal.authsignal.com/organisations/tenants/authenticators/magic_links) in the Authsignal Portal and select which custom data points to include under **Custom template variables**. Custom data points are prefixed by model type: * **User data points** are prefixed with `user_` (e.g. `user_accountId`) * **Action data points** are prefixed with `action_` (e.g. `action_transactionAmount`) Custom data points must be [registered in the Authsignal Portal](https://portal.authsignal.com/organisations/tenants/custom_data_points) before they can be selected. Only registered data points with values set on the user or action will be included. ## SDK setup ### Server SDK Initialize the SDK using your secret key from the [API keys page](https://portal.authsignal.com/organisations/tenants/api) and the [API URL](/sdks/server/setup#regions) for your region. ```ts Node.js theme={null} import { Authsignal } from "@authsignal/node"; const authsignal = new Authsignal({ apiSecretKey: "YOUR_SECRET_KEY", apiUrl: "YOUR_API_URL", }); ``` ```csharp C# theme={null} using Authsignal; var authsignal = new AuthsignalClient( apiSecretKey: "YOUR_SECRET_KEY", apiUrl: "YOUR_API_URL" ); ``` ```java Java theme={null} import com.authsignal.AuthsignalClient; AuthsignalClient authsignal = new AuthsignalClient( "YOUR_SECRET_KEY", "YOUR_API_URL" ); ``` ```ruby Ruby theme={null} require 'authsignal' Authsignal.setup do |config| config.api_secret_key = ENV["YOUR_SECRET_KEY"] config.api_url = "YOUR_API_URL" end ``` ```python Python theme={null} from authsignal.client import AuthsignalClient authsignal = AuthsignalClient( api_secret_key="YOUR_SECRET_KEY", api_url="YOUR_API_URL" ) ``` ```php PHP theme={null} Authsignal::setApiSecretKey("YOUR_SECRET_KEY"); Authsignal::setApiUrl("YOUR_API_URL"); ``` ```go Go theme={null} import "github.com/authsignal/authsignalgo/v2/client" client := NewAuthsignalClient( "YOUR_SECRET_KEY", "YOUR_API_URL", ) ``` ### Web SDK Initialize the [Web SDK](/sdks/client/web/setup) using your tenant ID from the [API keys page](https://portal.authsignal.com/organisations/tenants/api) and your [API URL](/sdks/server/setup#regions). ```ts Web theme={null} import { Authsignal } from "@authsignal/browser"; const authsignal = new Authsignal({ tenantId: "YOUR_TENANT_ID", baseUrl: "YOUR_API_URL", }); ``` ```swift iOS theme={null} import Authsignal let authsignal = Authsignal( tenantID: "YOUR_TENANT_ID", baseURL: "YOUR_API_URL" ) ``` ```kotlin Android theme={null} import com.authsignal.Authsignal val authsignal = Authsignal( tenantID = "YOUR_TENANT_ID", baseURL = "YOUR_API_URL", ) ``` ```ts React Native theme={null} import { Authsignal } from "react-native-authsignal"; const authsignal = new Authsignal({ tenantID: "YOUR_TENANT_ID", baseURL: "YOUR_API_URL", }); ``` ```dart Flutter theme={null} import 'package:authsignal_flutter/authsignal_flutter.dart'; final authsignal = Authsignal( "YOUR_TENANT_ID", baseURL: "YOUR_API_URL" ); ``` ## Adaptive MFA The following steps demonstrate how to implement **adaptive MFA** with email magic link - either at sign-in or as step-up authentication when the user performs a sensitive action in your app (e.g. making a payment). ### 1. Track action Use a [Server SDK](/sdks/server) to track an action in your backend. This step can apply [rules](/actions-rules/rules/getting-started) to determine if a challenge is required. ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { email: "jane.smith@authsignal.com", }, }; const response = await authsignal.track(request); if (response.state === "CHALLENGE_REQUIRED") { // Obtain token to present challenge const token = response.token; } ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: new TrackAttributes( Email: "jane.smith@authsignal.com" ) ); var response = await authsignal.Track(request); if (response.State == UserActionState.CHALLENGE_REQUIRED) { // Obtain token to present challenge var token = response.Token; } ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; request.attributes = new TrackAttributes(); request.attributes.email = "jane.smith@authsignal.com"; TrackResponse response = authsignal.track(request).get(); if (response.state.equals(UserActionState.CHALLENGE_REQUIRED)) { // Obtain token to present challenge String token = response.token; } ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', action: 'signIn', attributes: { email: 'jane.smith@authsignal.com' } }) case response[:state] when "CHALLENGE_REQUIRED" # Obtain token to present challenge token = response[:token] end ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn", attributes={ "email": "jane.smith@authsignal.com" } ) if response["state"] == "CHALLENGE_REQUIRED": # Obtain token to present challenge token = response["token"] ``` ```php PHP theme={null} $response = $authsignal->track([ 'userId' => 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', 'action' => 'signIn', 'attributes' => [ 'email' => 'jane.smith@authsignal.com' ] ]); switch ($response['state']) { case 'CHALLENGE_REQUIRED': // Obtain token to present challenge $token = $response['token']; } ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: &TrackAttributes{ Email: "jane.smith@authsignal.com", }, }, ) switch response.State { case "CHALLENGE_REQUIRED": // Obtain token to present challenge token := response.Token } ``` ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { email: "jane.smith@authsignal.com", redirectUrl: "https://yourapp.com/callback", }, }; const response = await authsignal.track(request); if (response.state === "CHALLENGE_REQUIRED") { // Return URL to your frontend to launch pre-built UI const url = response.url; } ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: new TrackAttributes( Email: "jane.smith@authsignal.com", RedirectUrl: "https://yourapp.com/callback", ) ); var response = await authsignal.Track(request); if (response.State == UserActionState.CHALLENGE_REQUIRED) { // Return URL to your frontend to launch pre-built UI var url = response.Url; } ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; request.attributes = new TrackAttributes(); request.attributes.email = "jane.smith@authsignal.com"; request.attributes.redirectUrl = "https://yourapp.com/callback"; TrackResponse response = authsignal.track(trackRequest).get(); if (response.state.equals(UserActionState.CHALLENGE_REQUIRED)) { // Return URL to your frontend to launch pre-built UI String url = response.url; } ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', action: 'signIn', attributes: { email: 'jane.smith@authsignal.com', redirect_url: "https://yourapp.com/callback" } }) case response[:state] when "CHALLENGE_REQUIRED" # Return URL to your frontend to launch pre-built UI url = response[:url] end ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn", attributes={ "email": "jane.smith@authsignal.com", "redirectUrl": "https://yourapp.com/callback" } ) if response["state"] == "CHALLENGE_REQUIRED": # Return URL to your frontend to launch pre-built UI url = response["url"] ``` ```php PHP theme={null} $response = $authsignal->track([ 'userId' => 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', 'action' => 'signIn', 'attributes' => [ 'email' => 'jane.smith@authsignal.com' 'redirectUrl' => 'https://yourapp.com/callback' ] ]); switch ($response['state']) { case 'CHALLENGE_REQUIRED': // Return URL to your frontend to launch pre-built UI $url = $response['url']; } ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: &TrackAttributes{ Email: "jane.smith@authsignal.com", RedirectUrl: "https://yourapp.com/callback", }, }, ) switch response.State { case "CHALLENGE_REQUIRED": // Return URL to your frontend to launch pre-built UI url := response.Url } ``` You can choose a value for the [action](/actions-rules/actions/getting-started) here which best describes what the user is doing in your app (e.g. `signIn` or `createPayment`). Each action can have its own set of rules. To learn more about using rules and handling different action states refer to our documentation on [actions](/actions-rules/actions/getting-started) and [rules](/actions-rules/rules/getting-started). ### 2. Present challenge If the action state is `CHALLENGE_REQUIRED` then you can present an email magic link challenge using the [Web SDK](/sdks/client/web/setup). ```ts Web theme={null} // Set token from the track response authsignal.setToken("eyJhbGciOiJ..."); // Send the user an email magic link // You can call this multiple times via a 'resend' button await authsignal.emailML.challenge(); // Check verification status // This will resolve when user clicks the magic link const verifyResponse = await authsignal.emailML.checkVerificationStatus(); if (verifyResponse.data?.isVerified) { // Obtain a new token const token = verifyResponse.data.token; } ``` ```js theme={null} // Launch the pre-built UI with the URL from the track response const result = await authsignal.launch(url, { mode: "popup" }); // Obtain a token to validate in the next step if (result.token) { const token = result.token; } ``` ### 3. Validate action Use the new token obtained from the client SDK to validate the action on your backend. ```ts Node.js theme={null} const response = await authsignal.validateChallenge({ action: "signIn", token: "eyJhbGciOiJIUzI....", }); if (response.state === "CHALLENGE_SUCCEEDED") { // User completed challenge successfully } ``` ```csharp C# theme={null} var request = new ValidateChallengeRequest( Action: "signIn", Token: "eyJhbGciOiJIUzI...." ); var response = await authsignal.ValidateChallenge(request); if (response.State == UserActionState.CHALLENGE_SUCCEEDED) { // User completed challenge successfully } ``` ```java Java theme={null} ValidateChallengeRequest request = new ValidateChallengeRequest(); request.action = "signIn"; request.token = "eyJhbGciOiJIUzI...."; ValidateChallengeResponse response = authsignal.validateChallenge(request).get(); if (response.state == UserActionState.CHALLENGE_SUCCEEDED) { // User completed challenge successfully } ``` ```ruby Ruby theme={null} response = Authsignal.validate_challenge(action: "signIn", token: "eyJhbGciOiJIUzI....") if response[:state] == "CHALLENGE_SUCCEEDED" # User completed challenge successfully end ``` ```python Python theme={null} response = authsignal.validate_challenge(action="signIn", token="eyJhbGciOiJIUzI....") if response["state"] == "CHALLENGE_SUCCEEDED": # User completed challenge successfully ``` ```php PHP theme={null} $response = Authsignal::validateChallenge(action: "signIn", token: "eyJhbGciOiJIUzI...."); if ($response["state"] === "CHALLENGE_SUCCEEDED") { # User completed challenge successfully } ``` ```go Go theme={null} response, err := client.ValidateChallenge( ValidateChallengeRequest{ Action: "signIn", Token: "eyJhbGciOiJ...", }, ) if response.State == "CHALLENGE_SUCCEEDED" { // User completed challenge successfully } ``` If the action state shows that the email magic link challenge was completed successfully, you can let the user proceed with the action. ## Next steps * [Adaptive MFA](/actions-rules/rules/adaptive-mfa) - Set up smart rules to trigger authentication based on risk * [Passkeys](/authentication-methods/passkey/web-sdk) - Offer the most secure and user-friendly passwordless authentication # Email OTP Source: https://docs.authsignal.com/authentication-methods/email/otp Send one-time verification codes via email for authentication and for verifying users' email addresses. Authsignal SDKs can be used to implement email OTP challenges in two scenarios. 1. [Sign-in](#sign-in). Use our Server SDKs to authenticate users with email OTP as the 1st factor. This integration only requires an email address to initiate. 2. [Adaptive MFA](#adaptive-mfa). Use Server SDKs together with Client SDKs to authenticate users with email OTP as a secondary factor. This integration requires a user ID to initiate and assumes the user has already been authenticated with a primary factor. ## Email provider setup Navigate to [Authenticators](https://portal.authsignal.com/organisations/tenants/authenticators) in the Authsignal Portal, click on **Email OTP**, and choose an email provider. 1. Log in to your [Bird account](https://bird.com/) 2. Get your **Access key** and **Workspace ID** from your Bird settings 3. Create or locate an email channel and note the **Channel ID** 4. Create an email template in Bird and note the **Project ID/Template ID** 5. Publish your template and note the **Published version ID** 6. In the Authsignal Portal, select **Bird** as your email provider 7. Enter your Bird access key, workspace ID, channel ID, project ID/template ID, published version ID, and select your default language 1. Log in to your [Mailjet account](https://app.mailjet.com/) 2. Navigate to **API > API Key Management** and get your **API key** and **Secret key** 3. Navigate to **Content > Email Templates** and get your **Template ID** 4. In the Authsignal Portal, select **Mailjet** as your email provider 5. Enter your Mailjet **API key**, **API secret key**, and **Template ID** 1. Log in to your [Mailgun account](https://app.mailgun.com/) 2. Click on your profile icon in the top-right corner and select **API Keys** 3. Click **Add New API Key** to generate a new API key 4. Note your **Sending domain** from the Mailgun dashboard 5. In the Authsignal Portal, select **Mailgun** as your email provider 6. Enter your Mailgun **API key**, **Sending domain**, **Template name** (optional), and select **API URL** for your region 1. Log in to your [Mailchimp account](https://mailchimp.com/) 2. Go to **Settings** and click on **API Keys** 3. Click **Create API Key** and give it a description. Copy the generated key 4. In the Authsignal Portal, select **Mandrill** as your email provider 5. Enter your Mandrill **API key** and **Template name** 1. Log in to your [SendGrid account](https://login.sendgrid.com/) 2. Go to **Settings** and click on **API Keys** 3. Click **Create New Key** and ensure either **Full Access** or **Custom Access** with the **Mail Send** permission is selected. Copy the generated key 4. Create a new [dynamic template](https://mc.sendgrid.com/dynamic-templates/new) in SendGrid and copy the **Template ID** 5. In the Authsignal Portal, select **SendGrid** as your email provider 6. Enter your SendGrid **API key**, **Template ID**, **From email**, and an optional **From name** For detailed instructions on setting up a webhook for email delivery, see the [webhooks documentation](/advanced-usage/webhooks/introduction). Authsignal supports sending with an SMTP server of your choosing. 1. In the Authsignal Portal, select **SMTP** as your email provider. 2. Enter your **SMTP Server Address**, **Port**, **Username**, **Password**, **From Address**, and an optional **From name** You can use **Authsignal** as an email provider for development, but it's recommended to use an alternative provider in production for more control over emails. ### Email template variables The following template variables are available for use in your email template: The verification code. The action that the user is performing. The ID of the user. The user agent associated with the action. The IP address associated with the action. ### Custom template variables You can forward [custom data points](/actions-rules/rules/custom-data-points) to your email provider as additional template variables. This is useful for personalizing emails with data specific to your application, such as a user's account ID or a transaction amount. To configure this, navigate to your [email OTP settings](https://portal.authsignal.com/organisations/tenants/authenticators/email_otp) in the Authsignal Portal and select which custom data points to include under **Custom template variables**. Custom data points are prefixed by model type: * **User data points** are prefixed with `user_` (e.g. `user_accountId`) * **Action data points** are prefixed with `action_` (e.g. `action_transactionAmount`) Custom data points must be [registered in the Authsignal Portal](https://portal.authsignal.com/organisations/tenants/custom_data_points) before they can be selected. Only registered data points with values set on the user or action will be included. ## SDK setup ### Server SDK Initialize the SDK using your secret key from the [API keys page](https://portal.authsignal.com/organisations/tenants/api) and the [API URL](/sdks/server/setup#regions) for your region. ```ts Node.js theme={null} import { Authsignal } from "@authsignal/node"; const authsignal = new Authsignal({ apiSecretKey: "YOUR_SECRET_KEY", apiUrl: "YOUR_API_URL", }); ``` ```csharp C# theme={null} using Authsignal; var authsignal = new AuthsignalClient( apiSecretKey: "YOUR_SECRET_KEY", apiUrl: "YOUR_API_URL" ); ``` ```java Java theme={null} import com.authsignal.AuthsignalClient; AuthsignalClient authsignal = new AuthsignalClient( "YOUR_SECRET_KEY", "YOUR_API_URL" ); ``` ```ruby Ruby theme={null} require 'authsignal' Authsignal.setup do |config| config.api_secret_key = ENV["YOUR_SECRET_KEY"] config.api_url = "YOUR_API_URL" end ``` ```python Python theme={null} from authsignal.client import AuthsignalClient authsignal = AuthsignalClient( api_secret_key="YOUR_SECRET_KEY", api_url="YOUR_API_URL" ) ``` ```php PHP theme={null} Authsignal::setApiSecretKey("YOUR_SECRET_KEY"); Authsignal::setApiUrl("YOUR_API_URL"); ``` ```go Go theme={null} import "github.com/authsignal/authsignalgo/v2/client" client := NewAuthsignalClient( "YOUR_SECRET_KEY", "YOUR_API_URL", ) ``` ### Client SDK Initialize the [Web SDK](/sdks/client/web/setup) or [Mobile SDK](/sdks/client/mobile/setup) using your tenant ID from the [API keys page](https://portal.authsignal.com/organisations/tenants/api) and your [API URL](/sdks/server/setup#regions). ```ts Web theme={null} import { Authsignal } from "@authsignal/browser"; const authsignal = new Authsignal({ tenantId: "YOUR_TENANT_ID", baseUrl: "YOUR_API_URL", }); ``` ```swift iOS theme={null} import Authsignal let authsignal = Authsignal( tenantID: "YOUR_TENANT_ID", baseURL: "YOUR_API_URL" ) ``` ```kotlin Android theme={null} import com.authsignal.Authsignal val authsignal = Authsignal( tenantID = "YOUR_TENANT_ID", baseURL = "YOUR_API_URL", ) ``` ```ts React Native theme={null} import { Authsignal } from "react-native-authsignal"; const authsignal = new Authsignal({ tenantID: "YOUR_TENANT_ID", baseURL: "YOUR_API_URL", }); ``` ```dart Flutter theme={null} import 'package:authsignal_flutter/authsignal_flutter.dart'; final authsignal = Authsignal( "YOUR_TENANT_ID", baseURL: "YOUR_API_URL" ); ``` ## Sign-in **Scenario** - Let users sign-in with email OTP as the **1st factor**. Our [Server SDKs](/sdks/server) include methods to initiate and verify an OTP challenge for a given email address. These methods are well-suited for passwordless sign-in scenarios where you need to authenticate a user with email OTP as a primary factor. ### 1. Initiate challenge Call [Initiate Challenge](/sdks/server/challenges#initiate-challenge) to send an OTP to an email address. ```ts Node.js theme={null} const request = { verificationMethod: "EMAIL_OTP", action: "signInWithEmail", email: "jane.smith@authsignal.com", }; const response = await authsignal.challenge(request); const challengeId = response.challengeId; ``` ```csharp C# theme={null} var request = new ChallengeRequest( VerificationMethod: VerificationMethod.EMAIL_OTP, Action: "signInWithEmail", Email: "jane.smith@authsignal.com" ); var response = await authsignal.challenge(request); var challengeId = response.ChallengeId; ``` ```java Java theme={null} ChallengeRequest request = new ChallengeRequest(); request.verificationMethod = VerificationMethodType.EMAIL_OTP; request.action = "signInWithEmail"; request.email = "jane.smith@authsignal.com"; ChallengeResponse response = authsignal.challenge(request).get(); String challengeId = response.challengeId; ``` ```ruby Ruby theme={null} response = Authsignal.challenge( verification_method: "EMAIL_OTP", action: "signInWithEmail", email: "jane.smith@authsignal.com", ) challenge_id = response[:challenge_id] ``` ```go Go theme={null} response, err := client.Challenge( ChallengeRequest{ VerificationMethod: "EMAIL_OTP", Action: "signInWithEmail", Email: "jane.smith@authsignal.com", }, ) challengeId := response.challengeId ``` You can choose a value for the [action](/actions-rules/actions/getting-started) here which best describes what the user is doing in your app (e.g. signing in with email). It will be used to track user activity in the Authsignal Portal. ### 2. Verify challenge Once the user inputs the OTP code, call [Verify Challenge](/sdks/server/challenges#verify-challenge) to verify it. ```ts Node.js theme={null} const request = { challengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d", verificationCode: "123456", }; const response = await authsignal.verify(request); const isVerified = response.isVerified; ``` ```csharp C# theme={null} var request = new VerifyRequest( ChallengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d", VerificationCode: "123456" ); var response = await authsignal.Verify(request); var isVerified = response.IsVerified; ``` ```java Java theme={null} VerifyRequest request = new VerifyRequest(); request.challengeId = "3a991a14-690c-492b-a5e5-02b9056a4b7d"; request.verificationCode = "123456"; VerifyResponse response = authsignal.verify(request).get(); boolean isVerified = response.isVerified; ``` ```ruby Ruby theme={null} response = Authsignal.verify( challenge_id: "3a991a14-690c-492b-a5e5-02b9056a4b7d", verification_code: "123456", ) is_verified = response[:is_verified] ``` ```go Go theme={null} response, err := client.Verify( VerifyRequest{ ChallengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d", VerificationCode: "123456", }, ) isVerified := response.IsVerified ``` ### 3. Claim challenge Now that the challenge has been verified, you can lookup the user in your IdP or DB based on their email. For passwordless flows with a combined sign-up and sign-in UX, you may need to create the user at this point if no account exists. Then claim the challenge once you know the primary user ID associated with the email. ```ts Node.js theme={null} const request = { challengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d", userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", }; const response = await authsignal.claimChallenge(request); ``` ```csharp C# theme={null} var request = new ClaimChallengeRequest( ChallengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d", UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833" ); var response = await authsignal.ClaimChallenge(request); ``` ```java Java theme={null} ClaimChallengeRequest request = new ClaimChallengeRequest(); request.challengeId = "3a991a14-690c-492b-a5e5-02b9056a4b7d"; request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; ClaimChallengeResponse response = authsignal.claimChallenge(request).get(); ``` ```ruby Ruby theme={null} response = Authsignal.claim_challenge( challenge_id: "3a991a14-690c-492b-a5e5-02b9056a4b7d", user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", ) ``` ```go Go theme={null} response, err := client.ClaimChallenge( ClaimChallengeRequest{ ChallengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d", UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", }, ) ``` This final step is important because it **attributes activity to the correct user and ensures observability in the Authsignal Portal**. ## Adaptive MFA **Scenario** - Challenge users with email OTP as a **2nd factor** and use rules to decide when and where in your app to trigger the challenge. The following steps demonstrate how to implement adaptive MFA with email OTP - either at sign-in or as step-up authentication when the user performs a sensitive action in your app (e.g. making a payment). In this scenario we assume the user has already been identified by authenticating with another method (e.g. username and password) and is being prompted to complete an email OTP challenge as a 2nd factor. ### 1. Track action Use a [Server SDK](/sdks/server) to track an action in your backend. This step can apply [rules](/actions-rules/rules/getting-started) to determine if a challenge is required. ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { email: "jane.smith@authsignal.com", }, }; const response = await authsignal.track(request); if (response.state === "CHALLENGE_REQUIRED") { // Obtain token to present challenge const token = response.token; } ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: new TrackAttributes( Email: "jane.smith@authsignal.com" ) ); var response = await authsignal.Track(request); if (response.State == UserActionState.CHALLENGE_REQUIRED) { // Obtain token to present challenge var token = response.Token; } ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; request.attributes = new TrackAttributes(); request.attributes.email = "jane.smith@authsignal.com"; TrackResponse response = authsignal.track(request).get(); if (response.state.equals(UserActionState.CHALLENGE_REQUIRED)) { // Obtain token to present challenge String token = response.token; } ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', action: 'signIn', attributes: { email: 'jane.smith@authsignal.com' } }) case response[:state] when "CHALLENGE_REQUIRED" # Obtain token to present challenge token = response[:token] end ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn", attributes={ "email": "jane.smith@authsignal.com" } ) if response["state"] == "CHALLENGE_REQUIRED": # Obtain token to present challenge token = response["token"] ``` ```php PHP theme={null} $response = $authsignal->track([ 'userId' => 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', 'action' => 'signIn', 'attributes' => [ 'email' => 'jane.smith@authsignal.com' ] ]); switch ($response['state']) { case 'CHALLENGE_REQUIRED': // Obtain token to present challenge $token = $response['token']; } ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: &TrackAttributes{ Email: "jane.smith@authsignal.com", }, }, ) switch response.State { case "CHALLENGE_REQUIRED": // Obtain token to present challenge token := response.Token } ``` ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action: "signIn", attributes: { email: "jane.smith@authsignal.com", redirectUrl: "https://yourapp.com/callback", }, }; const response = await authsignal.track(request); if (response.state === "CHALLENGE_REQUIRED") { // Return URL to your frontend to launch pre-built UI const url = response.url; } ``` ```csharp C# theme={null} var request = new TrackRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: new TrackAttributes( Email: "jane.smith@authsignal.com", RedirectUrl: "https://yourapp.com/callback", ) ); var response = await authsignal.Track(request); if (response.State == UserActionState.CHALLENGE_REQUIRED) { // Return URL to your frontend to launch pre-built UI var url = response.Url; } ``` ```java Java theme={null} TrackRequest request = new TrackRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.action = "signIn"; request.attributes = new TrackAttributes(); request.attributes.email = "jane.smith@authsignal.com"; request.attributes.redirectUrl = "https://yourapp.com/callback"; TrackResponse response = authsignal.track(trackRequest).get(); if (response.state.equals(UserActionState.CHALLENGE_REQUIRED)) { // Return URL to your frontend to launch pre-built UI String url = response.url; } ``` ```ruby Ruby theme={null} response = Authsignal.track({ user_id: 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', action: 'signIn', attributes: { email: 'jane.smith@authsignal.com', redirect_url: "https://yourapp.com/callback" } }) case response[:state] when "CHALLENGE_REQUIRED" # Return URL to your frontend to launch pre-built UI url = response[:url] end ``` ```python Python theme={null} response = authsignal.track( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", action="signIn", attributes={ "email": "jane.smith@authsignal.com", "redirectUrl": "https://yourapp.com/callback" } ) if response["state"] == "CHALLENGE_REQUIRED": # Return URL to your frontend to launch pre-built UI url = response["url"] ``` ```php PHP theme={null} $response = $authsignal->track([ 'userId' => 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833', 'action' => 'signIn', 'attributes' => [ 'email' => 'jane.smith@authsignal.com' 'redirectUrl' => 'https://yourapp.com/callback' ] ]); switch ($response['state']) { case 'CHALLENGE_REQUIRED': // Return URL to your frontend to launch pre-built UI $url = $response['url']; } ``` ```go Go theme={null} response, err := client.Track( TrackRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Action: "signIn", Attributes: &TrackAttributes{ Email: "jane.smith@authsignal.com", RedirectUrl: "https://yourapp.com/callback", }, }, ) switch response.State { case "CHALLENGE_REQUIRED": // Return URL to your frontend to launch pre-built UI url := response.Url } ``` You can choose a value for the [action](/actions-rules/actions/getting-started) here which best describes what the user is doing in your app (e.g. `signIn` or `createPayment`). Each action can have its own set of rules. To learn more about using rules and handling different action states refer to our documentation on [actions](/actions-rules/actions/getting-started) and [rules](/actions-rules/rules/getting-started). ### 2. Present challenge If the action state is `CHALLENGE_REQUIRED` then you can present an email OTP challenge using the [Web SDK](/sdks/client/web/setup) or [Mobile SDK](/sdks/client/mobile/setup). ```ts Web theme={null} // Set token from the track response authsignal.setToken("eyJhbGciOiJ..."); // Send the user an email OTP code // You can call this multiple times via a 'resend' button await authsignal.email.challenge(); // Verify the inputted code matches the original code const response = await authsignal.email.verify({ code: "123456" }); // Obtain a new token const token = response.token; ``` ```swift iOS theme={null} // Set token from the track response authsignal.setToken("eyJhbGciOiJ...") // Send the user an email OTP code // You can call this multiple times via a 'resend' button await authsignal.email.challenge() // Verify the inputted code matches the original code let response = await authsignal.email.verify(code: "123456") // Obtain a new token let token = response.token ``` ```kotlin Android theme={null} // Set token from the track response authsignal.setToken("eyJhbGciOiJ...") // Send the user an email OTP code // You can call this multiple times via a 'resend' button authsignal.email.challenge() // Verify the inputted code matches the original code val response = authsignal.email.verify(code = "123456") // Obtain a new token val token = response.token ``` ```ts React Native theme={null} // Set token from the track response await authsignal.setToken("eyJhbGciOiJ..."); // Send the user an email OTP code // You can call this multiple times via a 'resend' button await authsignal.email.challenge(); // Verify the inputted code matches the original code const response = await authsignal.email.verify({ code: "123456" }); // Obtain a new token const token = response.token; ``` ```dart Flutter theme={null} // Set token from the track response await authsignal.setToken("eyJhbGciOiJ..."); // Send the user an email OTP code // You can call this multiple times via a 'resend' button await authsignal.email.challenge(); // Verify the inputted code matches the original code final response = await authsignal.email.verify(code: "123456"); // Obtain a new token final token = response.token; ``` ```js theme={null} // Launch the pre-built UI with the URL from the track response const result = await authsignal.launch(url, { mode: "popup" }); // Obtain a token to validate in the next step if (result.token) { const token = result.token; } ``` ### 3. Validate action Use the new token obtained from the client SDK to validate the action on your backend. ```ts Node.js theme={null} const response = await authsignal.validateChallenge({ action: "signIn", token: "eyJhbGciOiJIUzI....", }); if (response.state === "CHALLENGE_SUCCEEDED") { // User completed challenge successfully } ``` ```csharp C# theme={null} var request = new ValidateChallengeRequest( Action: "signIn", Token: "eyJhbGciOiJIUzI...." ); var response = await authsignal.ValidateChallenge(request); if (response.State == UserActionState.CHALLENGE_SUCCEEDED) { // User completed challenge successfully } ``` ```java Java theme={null} ValidateChallengeRequest request = new ValidateChallengeRequest(); request.action = "signIn"; request.token = "eyJhbGciOiJIUzI...."; ValidateChallengeResponse response = authsignal.validateChallenge(request).get(); if (response.state == UserActionState.CHALLENGE_SUCCEEDED) { // User completed challenge successfully } ``` ```ruby Ruby theme={null} response = Authsignal.validate_challenge(action: "signIn", token: "eyJhbGciOiJIUzI....") if response[:state] == "CHALLENGE_SUCCEEDED" # User completed challenge successfully end ``` ```python Python theme={null} response = authsignal.validate_challenge(action="signIn", token="eyJhbGciOiJIUzI....") if response["state"] == "CHALLENGE_SUCCEEDED": # User completed challenge successfully ``` ```php PHP theme={null} $response = Authsignal::validateChallenge(action: "signIn", token: "eyJhbGciOiJIUzI...."); if ($response["state"] === "CHALLENGE_SUCCEEDED") { # User completed challenge successfully } ``` ```go Go theme={null} response, err := client.ValidateChallenge( ValidateChallengeRequest{ Action: "signIn", Token: "eyJhbGciOiJ...", }, ) if response.State == "CHALLENGE_SUCCEEDED" { // User completed challenge successfully } ``` If the action state shows that the email OTP challenge was completed successfully, you can let the user proceed with the action. ## Enrollment **Scenario** - Enroll users in email OTP while they’re authenticated so it can be used later as a method for adaptive MFA. To use email OTP for adaptive MFA, users must be **enrolled** with email OTP as an authentication method. This means their email address has previously been verified and can be trusted. The following steps demonstrate how to implement an enrollment flow using a [Server SDK](/sdks/server/overview). ### 1. Initiate challenge Call [Initiate Challenge](/sdks/server/challenges#initiate-challenge) to send an OTP to an email address. ```ts Node.js theme={null} const request = { verificationMethod: "EMAIL_OTP", action: "enrollEmail", email: "jane.smith@authsignal.com", userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", scope: "add:authenticators", }; const response = await authsignal.challenge(request); const challengeId = response.challengeId; ``` ```csharp C# theme={null} var request = new ChallengeRequest( VerificationMethod: VerificationMethod.EMAIL_OTP, Action: "enrollEmail", Email: "jane.smith@authsignal.com", UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Scope: "add:authenticators", ); var response = await authsignal.challenge(request); var challengeId = response.ChallengeId; ``` ```java Java theme={null} ChallengeRequest request = new ChallengeRequest(); request.verificationMethod = VerificationMethodType.EMAIL_OTP; request.action = "enrollEmail"; request.email = "jane.smith@authsignal.com"; request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.scope = "add:authenticators"; ChallengeResponse response = authsignal.challenge(request).get(); String challengeId = response.challengeId; ``` ```ruby Ruby theme={null} response = Authsignal.challenge( verification_method: "EMAIL_OTP", action: "enrollEmail", email: "jane.smith@authsignal.com", user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", scope: "add:authenticators", ) challenge_id = response[:challenge_id] ``` ```go Go theme={null} response, err := client.Challenge( ChallengeRequest{ VerificationMethod: "EMAIL_OTP", Action: "enrollEmail", Email: "jane.smith@authsignal.com", UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Scope: "add:authenticators", }, ) challengeId := response.challengeId ``` The **add:authenticators** scope is required to enroll a new email OTP authenticator for an existing user. This scope should only be used **when the user is in an already authenticated state**. For more information on using scopes safely refer to our documentation on [authenticator binding](/advanced-usage/authenticator-binding). ### 2. Verify challenge Once the user inputs the OTP code, call [Verify Challenge](/sdks/server/challenges#verify-challenge) to verify it. ```ts Node.js theme={null} const request = { challengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d", verificationCode: "123456", }; const response = await authsignal.verify(request); const isVerified = response.isVerified; ``` ```csharp C# theme={null} var request = new VerifyRequest( ChallengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d", VerificationCode: "123456" ); var response = await authsignal.Verify(request); var isVerified = response.IsVerified; ``` ```java Java theme={null} VerifyRequest request = new VerifyRequest(); request.challengeId = "3a991a14-690c-492b-a5e5-02b9056a4b7d"; request.verificationCode = "123456"; VerifyResponse response = authsignal.verify(request).get(); boolean isVerified = response.isVerified; ``` ```ruby Ruby theme={null} response = Authsignal.verify( challenge_id: "3a991a14-690c-492b-a5e5-02b9056a4b7d", verification_code: "123456", ) is_verified = response[:is_verified] ``` ```go Go theme={null} response, err := client.Verify( VerifyRequest{ ChallengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d", VerificationCode: "123456", }, ) isVerified := response.IsVerified ``` ## Update email **Scenario** - Let users update their email address while they’re authenticated, completing an OTP challenge to verify the new email. The following steps demonstrate how to implement an update email address flow using a [Server SDK](/sdks/server/overview). ### 1. Initiate challenge Call [Initiate Challenge](/sdks/server/challenges#initiate-challenge) to send an OTP to the user's new email address. ```ts Node.js theme={null} const request = { verificationMethod: "EMAIL_OTP", action: "updateEmail", email: "jane.smith@authsignal.com", userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", scope: "update:authenticators", }; const response = await authsignal.challenge(request); const challengeId = response.challengeId; ``` ```csharp C# theme={null} var request = new ChallengeRequest( VerificationMethod: VerificationMethod.EMAIL_OTP, Action: "updateEmail", Email: "jane.smith@authsignal.com", UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Scope: "update:authenticators", ); var response = await authsignal.challenge(request); var challengeId = response.ChallengeId; ``` ```java Java theme={null} ChallengeRequest request = new ChallengeRequest(); request.verificationMethod = VerificationMethodType.EMAIL_OTP; request.action = "updateEmail"; request.email = "jane.smith@authsignal.com"; request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.scope = "update:authenticators"; ChallengeResponse response = authsignal.challenge(request).get(); String challengeId = response.challengeId; ``` ```ruby Ruby theme={null} response = Authsignal.challenge( verification_method: "EMAIL_OTP", action: "updateEmail", email: "jane.smith@authsignal.com", user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", scope: "update:authenticators", ) challenge_id = response[:challenge_id] ``` ```go Go theme={null} response, err := client.Challenge( ChallengeRequest{ VerificationMethod: "EMAIL_OTP", Action: "updateEmail", Email: "jane.smith@authsignal.com", UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Scope: "update:authenticators", }, ) challengeId := response.challengeId ``` The **update:authenticators** scope is required to update a user's existing email OTP authenticator to change the email address. This scope should only be used **when the user is in an already authenticated state**. For more information on using scopes safely refer to our documentation on [authenticator binding](/advanced-usage/authenticator-binding). ### 2. Verify challenge Once the user inputs the OTP code, call [Verify Challenge](/sdks/server/challenges#verify-challenge) to verify it. ```ts Node.js theme={null} const request = { challengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d", verificationCode: "123456", }; const response = await authsignal.verify(request); const isVerified = response.isVerified; ``` ```csharp C# theme={null} var request = new VerifyRequest( ChallengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d", VerificationCode: "123456" ); var response = await authsignal.Verify(request); var isVerified = response.IsVerified; ``` ```java Java theme={null} VerifyRequest request = new VerifyRequest(); request.challengeId = "3a991a14-690c-492b-a5e5-02b9056a4b7d"; request.verificationCode = "123456"; VerifyResponse response = authsignal.verify(request).get(); boolean isVerified = response.isVerified; ``` ```ruby Ruby theme={null} response = Authsignal.verify( challenge_id: "3a991a14-690c-492b-a5e5-02b9056a4b7d", verification_code: "123456", ) is_verified = response[:is_verified] ``` ```go Go theme={null} response, err := client.Verify( VerifyRequest{ ChallengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d", VerificationCode: "123456", }, ) isVerified := response.IsVerified ``` ## Verified emails **Scenario** - Enroll or update an email OTP authenticator for a user when you've already verified their email address in another system, so it can be used later as a method for adaptive MFA. In some cases you may have already verified a user's email address using another system. This means the user can be enrolled without having to complete an email OTP challenge. ```ts Node.js theme={null} const request = { userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", attributes: { verificationMethod: "EMAIL_OTP", email: "jane.smith@authsignal.com", }, }; const response = authsignal.enrollVerifiedAuthenticator(request); ``` ```csharp C# theme={null} var request = new EnrollVerifiedAuthenticatorRequest( UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Attributes: new EnrollVerifiedAuthenticatorAttributes( VerificationMethod: VerificationMethod.EMAIL_OTP, Email: "jane.smith@authsignal.com" ) ); var response = await authsignal.EnrollVerifiedAuthenticator(request); ``` ```java Java theme={null} EnrollVerifiedAuthenticatorRequest request = new EnrollVerifiedAuthenticatorRequest(); request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"; request.attributes = new EnrollVerifiedAuthenticatorAttributes(); request.attributes.verificationMethod = VerificationMethodType.EMAIL_OTP; request.attributes.email = "jane.smith@authsignal.com"; authsignal.enrollVerifiedAuthenticator(request).get(); ``` ```ruby Ruby theme={null} Authsignal.enroll_verified_authenticator( user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", attributes:{ verification_method: "EMAIL_OTP", email: "jane.smith@authsignal.com" } ) ``` ```python Python theme={null} response = authsignal.enroll_verified_authenticator( user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", attributes={ "verificationMethod": "EMAIL_OTP", "email": "jane.smith@authsignal.com" } ) ``` ```php PHP theme={null} $response = Authsignal::enrollVerifiedAuthenticator([ 'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", 'attributes' => [ "verificationMethod" => "EMAIL_OTP", "email" => "jane.smith@authsignal.com" ] ]); ``` ```go Go theme={null} response, err := client.EnrollVerifiedAuthenticator( EnrollVerifiedAuthenticatorRequest{ UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833", Attributes: &EnrollVerifiedAuthenticatorAttributes{ VerificationMethod: "EMAIL_OTP", Email: "jane.smith@authsignal.com", }, }, ) ``` This same call can also be used to **update** a verified email address to a new value which has also been verified externally. ## Next steps * [Pre-built UI](/implementation-options/prebuilt-ui/overview) - Rapidly deploy email OTP challenges using our pre-built UI * [Web SDK](/sdks/client/web/setup) - Implement email OTP challenges while building your own UI * [Mobile SDK](/sdks/client/mobile/setup) - Implement email OTP challenges in native mobile apps * [Adaptive MFA](/actions-rules/rules/adaptive-mfa) - Set up smart rules to trigger authentication based on risk * [Passkeys](/authentication-methods/passkey/web-sdk) - Offer the most secure and user-friendly passwordless authentication # Passkeys best practice for native mobile apps Source: https://docs.authsignal.com/authentication-methods/passkey/best-practices-mobile Design seamless passkey flows for iOS and Android native applications The following recommendations are designed to help you create the best passkey experience for your users when using Authsignal's [mobile SDKs](/sdks/client/mobile/setup). ## Ensuring a backup option exists when passkeys are unavailable Even if users have previously created a passkey, they may be authenticating on a new device where the passkey cannot be synced - for example, when switching between an iOS and an Android device or vice versa. Additionally, it is possible a user may have deleted their existing passkey credential from Apple iCloud or Google Password Manager. One way to ensure a user always has a backup option available is to prompt the user to create a passkey only after they enroll another authentication method such as [email OTP](/sdks/client/mobile/email-otp) or [SMS OTP](/sdks/client/mobile/sms). Since Authsignal supports binding multiple authentication factors together, this can easily be achieved with the mobile SDK.