# 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.
## 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
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.
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:
**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
**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:
**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)
```
**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)
```
**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`.
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`.
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.
* Finally, click the **Save** button and return to the **Rules** page for your `withdrawFunds` action. You should see your new rule listed.
## 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.
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).
# 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.
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**.
## 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.
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 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.
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)
* Change the conjunction logic from AND to OR so the rule triggers if any condition is met
### 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.
### 4. Configure default action outcome
* Navigate to your action's **Settings** tab
* Change the default outcome to `ALLOW`
* Click **Save**
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.
### 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 .
### 3. Track the action
Click **Track action** and you will see a breakdown of the action response.
## 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.
### 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.
# 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).
### 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.
Once enabled, a link to the JWKS URL for your tenant will be displayed.
### Creating app clients
Next, create an app client in the Authsignal Portal under Settings -> App clients.
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.
## 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'
2. Click 'Export'
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:
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:
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 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.
### 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.
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.
## 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 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 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.
## Optimizing sign-in UX
Now that we can be confident the user always has a backup option, we can design an optimal sign-in UX which puts passkeys front and center while also providing a safe fallback.
For the happy path when a passkey is available, presenting a "Sign in" button which immediately displays the passkey prompt provides a smooth UX.
If no passkey is available on the device, however, we can gracefully fall back to presenting email as a backup option.
This can be implemented by handling error codes returned by the mobile SDK.
```swift iOS theme={null}
let response = await authsignal.passkey.signIn(action: "signInWithPasskey")
if response.errorCode == "user_canceled" {
// Present email sign-in screen
}
```
```kotlin Android theme={null}
val response = await authsignal.passkey.signIn(action = "signInWithPasskey")
if (response.errorCode == "user_canceled" || response.errorCode == "no_credential") {
// Present email sign-in screen
}
```
```ts React Native theme={null}
const response = await authsignal.passkey.signIn({ action: "signInWithPasskey" });
if (
response.errorCode === ErrorCode.user_canceled ||
response.errorCode === ErrorCode.no_credential
) {
// Present email sign-in screen
}
```
```dart Flutter theme={null}
final response = await authsignal.passkey.signIn(action: "signInWithPasskey");
if (response.errorCode == ErrorCode.userCanceled.value ||
response.errorCode == ErrorCode.noCredential.value) {
// Present email sign-in screen
}
```
For a consistent experience between iOS and Android, here we treat the scenario where a user has no passkey available exactly the same as if they dismiss the passkey prompt.
This is required because while Android returns a different error in either case, iOS doesn't differentiate between these two scenarios and returns the same cancellation error in each case.
The above behavior is possible because iOS and Android both expose a
[preferImmediatelyAvailableCredentials
parameter](/sdks/client/mobile/passkeys#param-prefer-immediately-available-credentials) which will
avoid showing the passkey prompt if no credentials are available on the current device. Set this
value to false if you want to allow signing in via QR code with a passkey on another device.
# Passkeys best practice for the web
Source: https://docs.authsignal.com/authentication-methods/passkey/best-practices-web
Optimize passkey user experience and conversion rates for web applications
## Device-initiated authentication
Traditional passwordless sign-in methods such as email OTP are **username-initiated**.
You enter your username (e.g. email address) and the site looks up the associated account and then initiates a challenge to verify it belongs to you.
But passkeys represent a new paradigm of **device-initiated** authentication.
We can display passkeys available on a given device without requiring the user to enter a username.
Passkey credentials are either bound to a single device or synced between multiple devices by a password manager (Apple Passwords, Google Password Manager etc).
The site can present whatever credentials are available on the device and we can look up the account based on the credential selected.
## Availability across devices
Passkeys may not be available on a given device for multiple reasons.
* If the user has created a passkey on one device then switched to a new device where that passkey can't be synced.
* If the user has created a passkey and then deleted it from their password manager.
For this reason **your UI should always clearly present a backup authentication option and avoid leading users into dead ends when a passkey isn't available**.
### Keeping credentials in sync
A related dead end occurs when a passkey is deleted on the server but still appears in the user's password manager. The user selects it expecting it to work, but the server no longer recognizes it.
The Web SDK reduces this by using the [WebAuthn Signal API](/sdks/client/web/passkeys#credential-syncing) to prune stale credentials from the browser after each sign-up and sign-in. This is enabled by default via the `syncCredentials` option, so in supporting browsers the user's passkey list stays aligned with the credentials Authsignal holds without any extra work on your part.
## Passkeys for sign-in
### Autofill
Enabling [passkey autofill](/authentication-methods/passkey/custom-ui#using-autofill-web-and-ios-only) is an unobtrusive way to support passkeys on your sign-in page while also maintaining a familiar experience for users who haven't setup passkeys yet.
Here the user will only be shown a passkey if they have already created one and it's available on the device.
Otherwise the user can manually enter their username and press "Continue" to trigger another kind of challenge (e.g. email OTP).
### Sign-in button
Another approach is to include a separate passkey sign-in button which is clearly presented as an alternative sign-in option.
Clicking this button will launch the browser passkey prompt **regardless of whether or not there are any passkeys available on the device**.
If a passkey is available, then the user will be able to authenticate with it.
If no passkey is available, however, then the browser has to fall back to displaying a QR code or waiting for the user to insert a hardware security key.
This QR code prompt can be useful if the user knows what to do; for example, if they've previously created a passkey on their iPhone and are now on their Windows laptop, then they can scan the QR code with their iPhone in order to sign in.
However, the QR code prompt can be a jarring or confusing experience to users who aren't familiar with the nuances of how passkeys are synced across different platforms.
For this reason we strongly recommend **restricting the QR code flow to a passkey sign-in button or a similar UI interaction where the user has explicitly chosen to use passkeys**.
### Immediate mediation
To avoid the QR code fallback entirely, you can use immediate mediation. The browser only shows the passkey prompt when a credential is immediately available on the device; if none is available, the prompt is skipped rather than falling back to a QR code, so the user is never led into the dead end described above.
This makes immediate mediation well suited to surfacing a passkey prompt automatically on page load, without risking a confusing experience for users who don't have a passkey on the device. To enable it, set `preferImmediatelyAvailableCredentials` to `true` when calling `signIn`. See the [Web SDK reference](/sdks/client/web/passkeys#immediately-available-credentials) for details.
Immediate mediation depends on newer browser APIs and at the time of writing is only supported in
recent versions of Chromium-based browsers. The SDK feature-detects support and returns an
`immediate_mediation_not_supported` error code where it isn't available, so always keep a backup
authentication option for users on unsupported browsers or without an available passkey.
## Passkeys for MFA
When using passkeys as a secondary factor, it's important to only show passkeys for one account.
If a device is shared between family members, or if a person has multiple accounts for the same site, then a device may have passkeys available for different accounts.
The [allowCredentials](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-allowcredentials) parameter is the solution here; it lets us tell the browser to restrict passkeys to a known list of credentials for a given user.
But even if you know all passkeys a user has previously created, you still can't guarantee any will be available on the device the user is currently authenticating on.
If you pass the browser a list of allowed credentials but it doesn't find any on that device, then it will fall back to presenting a QR code.
So even when using `allowCredentials` it's important to clearly present a backup authentication option and avoid leading users into dead ends when their device doesn't have any passkeys available.
Because it is always launched for a specific user, the [Authsignal pre-built
UI](/implementation-options/prebuilt-ui/overview) always restricts passkeys via the
`allowCredentials` parameter. It also provides a clear way to fall back to alternate
authentication methods.
## Security keys for high-assurance flows
Most consumer flows should default to platform passkeys (the device's built-in authenticator), which give the smoothest experience. For workforce or high-assurance scenarios that require a physical hardware authenticator, you can use [security keys](/sdks/client/web/security-key) instead.
To steer the browser towards a specific authenticator type, pass the `hints` parameter (for example `["security-key"]`) when creating a passkey or security key. When you support both platform authenticators and security keys, present them as distinct options (for example "This device" versus "USB security key") so the choice is predictable.
Hints are advisory, not a security guarantee. They are ignored on some platforms (such as Windows
Hello), so always validate the authenticator type on your server if you need to enforce it.
## Creating passkeys
### Increasing adoption
Transitioning your users from traditional sign in methods, like passwords, to passkeys can be a big change.
To help with this transition, you can provide a prompt to encourage users to create a passkey when they sign in.
As this may be the first time your users have encountered passkeys, it's important to provide a summary
of what passkeys are and their benefits.
**Choose the right moment.** Usability research from the FIDO Alliance shows that the highest-converting moments to offer passkey creation are during **account creation**, in **account settings**, and **immediately after account recovery**. Prompting in the middle of the sign-in flow itself tends to convert less well, so prefer these account-management moments and always provide a clear "not now" option along with a path to create a passkey later from settings. Respect a user's dismissal and avoid re-prompting on a device where they have already enrolled a passkey. You can detect this with [`isAvailableOnDevice`](/sdks/client/web/passkeys#checking-passkey-availability), which returns whether the user already has a passkey registered on the current device.
### Automatic passkey upgrades
You can remove the passkey creation prompt entirely for users who already rely on a password manager. After a successful password sign-in, the SDK can ask the user's password manager to silently create a passkey in the background, with no extra step for the user.
To enable this, set `useAutoRegister` to `true` when calling `signUp`. See [Automatic passkey upgrades](/sdks/client/web/passkeys#automatic-passkey-upgrades) in the Web SDK reference for details.
Automatic upgrades only work when the user has a saved password for your app and their browser and
password manager support conditional create. The SDK feature-detects support, and the flow fails
silently when conditions aren't met, so it's safe to attempt without disrupting the user.
### Improving coverage across devices
Whilst many passkeys are available across devices, like ones stored in iCloud Keychain, some passkeys are device-bound.
For example, a passkey created on a desktop browser may not be available on a mobile device.
If a user authenticates on a device without a passkey, you can prompt them to create one in a similar way to the example above. Use [`isAvailableOnDevice`](/sdks/client/web/passkeys#checking-passkey-availability) to gate this prompt so it only appears when the user has no passkey on the current device.
This is especially valuable right after a cross-device (QR code) sign-in. Because that flow used a passkey on another device, the current device still has none. Offering to create a local passkey at that moment turns a one-off QR scan into a faster sign-in next time.
Depending on your needs, you can alter the frequency of these prompts to ensure they're not too intrusive. This could be a timed cooldown or based on the user's behaviour.
## Handling cancellations and errors
When a passkey ceremony fails, the SDK throws a `WebAuthnError`. Most of these are a normal part of the user experience and should not surface a disruptive error.
The most common case is `ERROR_CEREMONY_ABORTED`, thrown when the user dismisses the passkey prompt. The underlying browser API deliberately does not distinguish "the user cancelled" from "no passkey was available", so treat this as an expected outcome: quietly fall back to your backup authentication method rather than showing an error.
This ambiguity is the main reason to prefer [autofill](#autofill) and [immediate mediation](#immediate-mediation) where possible, since both surface passkeys only when one is actually available and avoid the dead end entirely.
See [Passkey error handling](/sdks/client/web/passkeys#passkey-error-handling) in the Web SDK reference for the full list of error codes.
## Displaying passkeys
As users may have multiple passkeys, it's useful to display them in a distinct way. A passkey user authenticator contains
the `webauthnCredential.aaguidMapping` object. The `name` field will contain the name of the credential manager, e.g. iCloud Keychain, where the
passkey is stored. The `svgDark`/`svgLight` fields contain the SVG icons for the credential manager.
In the area of your application where user's manage their authentication options, you can utilize these fields to display the user's passkeys:
Alongside the provider name and icon, showing the created and last used dates (available on the user authenticator) helps users recognise each passkey and gives them the confidence to remove the right one. Encourage users to keep more than one passkey so deleting a single credential never locks them out.
Whilst the `webauthnCredential.aaguidMapping` object is available for most passkeys, it's not
guaranteed to be present. As an alternative, you can use the `webauthnCredential.parsedUserAgent`
object to display details about the device the passkey was created on.
# Passkeys using Mobile SDKs
Source: https://docs.authsignal.com/authentication-methods/passkey/mobile-sdks
Implement passkeys in native mobile apps using Authsignal Mobile SDKs for Swift, Kotlin, React Native, and Flutter
Authsignal's [Mobile SDKs](/sdks/client/mobile/setup) let you rapidly implement passkeys in your mobile apps using fully native iOS and Android UI.
## Portal setup
1. Navigate to the [passkey setup wizard](https://portal.authsignal.com/organisations/tenants/authenticators/passkey) in the Authsignal Portal.
2. Select **I'm building my own UI**.
3. Enter your primary domain. This value should correspond to the web domain associated with your mobile app. For more information refer to our [Mobile SDK documentation](/sdks/client/mobile/passkeys). Click **Continue**.
4. If targeting Android, then go to your **Expected origins** settings, click **Add Android app origin** and enter your app's SHA-256 fingerprint value. For more information on how to obtain this value refer to our [Mobile SDK documentation](/sdks/client/mobile/passkeys).
## SDK setup
Passkeys require integration from your backend server and your mobile app.
1. Get your tenant's credentials from the [API keys page](https://portal.authsignal.com/organisations/tenants/api).
2. Follow the setup instructions for one of our [Server SDKs](/sdks/server/setup) - or integrate via REST with our [Server API](/api-reference/server-api/overview).
3. Follow the setup instructions for our [Mobile SDKs](/sdks/client/mobile/passkeys) for Swift, Kotlin, React Native, or Flutter.
## Creating passkeys
In Authsignal, you define an [action](/actions-rules/actions/getting-started) to let users create passkeys. This does two things:
1. It provides observability for all passkey creation events in the [Authsignal Portal](https://portal.authsignal.com).
2. It ensures that the passkey is securely bound to an existing user.
### 1. Backend - Generate token
In your backend, track an action using a [Server SDK](/sdks/server) to generate a short-lived enrollment token.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "createPasskey",
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: "createPasskey",
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 = "createPasskey";
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: "createPasskey",
attributes: {
scope: "add:authenticators"
}
})
token = response[:token]
```
```python Python theme={null}
response = authsignal.track(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="createPasskey",
attributes={
"scope": "add:authenticators"
}
)
token = response["token"]
```
```php PHP theme={null}
$response = Authsignal::track([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "createPasskey",
'attributes' => [
'scope' => "add:authenticators"
]
]);
$token = $response["token"];
```
```go Go theme={null}
response, err := client.Track(
TrackRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "createPasskey",
Attributes: &TrackAttributes{
Scope: "add:authenticators",
},
},
)
token := response.Token
```
This token includes the **add:authenticators** scope to authorize binding a passkey to an existing user so you should only generate it when the user is [strongly authenticated](/advanced-usage/authenticator-binding).
### 2. App - Create passkey
In your app, call the [signUp](/sdks/client/mobile/passkeys#creating-a-passkey) method using one of our [Mobile SDKs](/sdks/client/mobile/setup), passing the token generated in step 1.
```swift Swift theme={null}
let response = await authsignal.passkey.signUp(
token: "eyJhbGciOiJIUzI....",
username: "jane.smith@authsignal.com",
displayName: "Jane Smith"
)
```
```kotlin Kotlin theme={null}
val response = authsignal.passkey.signUp(
token = "eyJhbGciOiJIUzI....",
username = "jane.smith@authsignal.com",
displayName = "Jane Smith",
)
```
```ts React Native theme={null}
const response = await authsignal.passkey.signUp({
token: "eyJhbGciOiJIUzI....",
username: "jane.smith@authsignal.com",
displayName: "Jane Smith",
});
```
```dart Flutter theme={null}
var response = await authsignal.passkey.signUp(
"eyJhbGciOiJIUzI....",
"jane.smith@authsignal.com",
"Jane Smith",
);
```
You can also use our SDK to help determine **when to display a passkey creation prompt** based on whether or not the user has an existing passkey available on their device.
```swift Swift theme={null}
let showPrompt = await authsignal.passkey.shouldPromptToCreatePasskey()
if showPrompt {
let response = await authsignal.passkey.signUp(
token: "eyJhbGciOiJIUzI....",
username: "jane.smith@authsignal.com",
ignorePasskeyAlreadyExistsError: true,
)
}
```
```kotlin Kotlin theme={null}
val showPrompt = authsignal.passkey.shouldPromptToCreatePasskey()
if (showPrompt) {
val response = authsignal.passkey.signUp(
token = "eyJhbGciOiJIUzI....",
username = "jane.smith@authsignal.com",
ignorePasskeyAlreadyExistsError: true,
)
}
```
```ts React Native theme={null}
const showPrompt = await authsignal.passkey.shouldPromptToCreatePasskey();
if (showPrompt) {
const response = await authsignal.passkey.signUp({
token: "eyJhbGciOiJIUzI....",
username: "jane.smith@authsignal.com",
ignorePasskeyAlreadyExistsError: true,
});
}
```
To learn more about how to conditionally create passkeys refer to the [section below on passkey availability](#passkey-availability).
## Authenticating with passkeys
You can define another [action](/actions-rules/actions/getting-started) to let users authenticate with their passkeys.
This action will be used for observability and to authenticate the user on the server.
### 1. App - Sign in with passkey
In your app, call the [signIn](/sdks/client/mobile/passkeys#using-a-passkey) method using one of our [Mobile SDKs](/sdks/client/mobile/setup).
```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
}
```
```kotlin Kotlin theme={null}
val response = authsignal.passkey.signIn(action = "signInWithPasskey")
if (response.data?.token != null) {
// Send token to your backend for validation
}
```
```ts React Native theme={null}
const response = await authsignal.passkey.signIn({ action: "signInWithPasskey" });
if (response.data?.token) {
// Send token to your backend for validation
}
```
```dart Flutter theme={null}
final response = await authsignal.passkey.signIn(action: "signInWithPasskey");
if (response.data?.token != null) {
// Send token to your backend for validation
}
```
Because some users **may not have a passkey available**, you can conditionally present a fallback authentication method by handling error codes returned by the SDK.
```swift Swift theme={null}
let response = await authsignal.passkey.signIn(action: "signInWithPasskey")
if response.errorCode == "user_canceled" {
// Present fallback authentication method
}
```
```kotlin Kotlin theme={null}
val response = authsignal.passkey.signIn(action = "signInWithPasskey")
if (response.errorCode == "user_canceled" ||
response.errorCode == "no_credential") {
// Present fallback authentication method
}
```
```ts React Native theme={null}
const response = await authsignal.passkey.signIn({ action: "signInWithPasskey" });
if (
response.errorCode === ErrorCode.user_canceled ||
response.errorCode === ErrorCode.no_credential
) {
// Present fallback authentication method
}
```
```dart Flutter theme={null}
final response = await authsignal.passkey.signIn(action: "signInWithPasskey");
if (response.errorCode == ErrorCode.userCanceled.value ||
response.errorCode == ErrorCode.noCredential.value) {
// Present fallback authentication method
}
```
To learn more about how to handle if passkeys are available when authenticating refer to the [section below on passkey availability](#passkey-availability).
### 2. Backend - Validate action
Pass the token returned in the previous step to your backend, validating the result of the authentication server-side.
```ts Node.js theme={null}
const response = await authsignal.validateChallenge({
action: "signInWithPasskey",
token: "eyJhbGciOiJIUzI....",
});
if (response.state === "CHALLENGE_SUCCEEDED") {
// User successfully authenticated with passkey
const userId = response.userId;
} else {
// Authentication failed
}
```
```csharp C# theme={null}
var request = new ValidateChallengeRequest(
Action: "signInWithPasskey",
Token: "eyJhbGciOiJIUzI...."
);
var response = await authsignal.ValidateChallenge(request);
if (response.State == UserActionState.CHALLENGE_SUCCEEDED) {
// User successfully authenticated with passkey
var userId = response.UserId;
} else {
// Authentication failed
}
```
```java Java theme={null}
ValidateChallengeRequest request = new ValidateChallengeRequest();
request.action = "signInWithPasskey";
request.token = "eyJhbGciOiJIUzI....";
ValidateChallengeResponse response = authsignal.validateChallenge(request).get();
if (response.state == UserActionState.CHALLENGE_SUCCEEDED) {
// The user authenticated successfully
String userId = response.userId;
}
```
```ruby Ruby theme={null}
response = Authsignal.validate_challenge(action: "signInWithPasskey", token: "eyJhbGciOiJIUzI....")
if response[:state] == "CHALLENGE_SUCCEEDED"
# The user authenticated successfully
user_id = response[:user_id]
end
```
```python Python theme={null}
response = authsignal.validate_challenge(action="signInWithPasskey", token="eyJhbGciOiJIUzI....")
if response["state"] == "CHALLENGE_SUCCEEDED":
# User successfully authenticated with passkey
user_id = response["user_id"]
else:
# Authentication failed
```
```php PHP theme={null}
$response = Authsignal::validateChallenge(action: "signInWithPasskey", token: "eyJhbGciOiJIUzI....");
if ($response["state"] === "CHALLENGE_SUCCEEDED") {
# The user authenticated successfully
$user_id = $response["user_id"];
}
```
```go Go theme={null}
response, err := client.ValidateChallenge(
ValidateChallengeRequest{
Action: "signInWithPasskey",
Token: "eyJhbGciOiJ...",
},
)
if response.State == "CHALLENGE_SUCCEEDED" {
// The user authenticated successfully
userId := response.UserId
}
```
This server-side validation step can be used within one of our [popular IdP integrations](/getting-started/overview#popular-integrations) or with our [session APIs](/advanced-usage/session-management) to issue access tokens and refresh tokens.
## Passkey availability
Due to security restrictions in the design of passkeys, iOS and Android don't expose an API to query if a passkey is available on a device.
This means we need to handle the UX around passkey availability carefully both when creating and authenticating with passkeys.
### Creating passkeys
Prompting users to create a passkey proactively is a great way of increasing adoption.
The [Mobile SDKs](/sdks/client/mobile/setup) include a helper method to determine whether prompting the user to create a passkey is recommended.
```swift Swift theme={null}
let showPrompt = await authsignal.passkey.shouldPromptToCreatePasskey()
```
```kotlin Kotlin theme={null}
val showPrompt = authsignal.passkey.shouldPromptToCreatePasskey()
```
```ts React Native theme={null}
const showPrompt = await authsignal.passkey.shouldPromptToCreatePasskey();
```
This method will return true if the following conditions are met:
* A passkey has not been created on the current device.
* A passkey has not been used on the current device after it was created on another device.
* A passkey has been removed in the Authsignal Portal after it was created or used on the current device.
The method will also return true as a "false positive" in the following edge case:
* A passkey is available which was created on another device and has not yet been used on the current device.
This case can be handled by ignoring any errors thrown because a valid passkey already exists.
```swift Swift highlight={4} theme={null}
let response = await authsignal.passkey.signUp(
token: "eyJhbGciOiJIUzI....",
username: "jane.smith@authsignal.com",
ignorePasskeyAlreadyExistsError: true,
)
if !response.error {
// Alert the user that the passkey was successfully created
}
```
```kotlin Kotlin highlight={4} theme={null}
val response = authsignal.passkey.signUp(
token = "eyJhbGciOiJIUzI....",
username = "jane.smith@authsignal.com",
ignorePasskeyAlreadyExistsError: true,
)
if (!response.error) {
// Alert the user that the passkey was successfully created
}
```
```ts React Native highlight={4} theme={null}
const response = await authsignal.passkey.signUp({
token: "eyJhbGciOiJIUzI....",
username: "jane.smith@authsignal.com",
ignorePasskeyAlreadyExistsError: true,
});
if (!response.error) {
// Alert the user that the passkey was successfully created
}
```
### Authenticating with passkeys
Since we can't query up front whether a passkey is available or not, the recommended pattern is to attempt passkey sign-in for all users and then provide a fallback option by **handling error codes**.
```swift Swift theme={null}
let response = await authsignal.passkey.signIn(action: "signInWithPasskey")
if response.errorCode == "user_canceled" {
// Present fallback authentication method
}
```
```kotlin Kotlin theme={null}
val response = authsignal.passkey.signIn(action = "signInWithPasskey")
if (response.errorCode == "user_canceled" ||
response.errorCode == "no_credential") {
// Present fallback authentication method
}
```
```ts React Native theme={null}
const response = await authsignal.passkey.signIn({ action: "signInWithPasskey" });
if (
response.errorCode === ErrorCode.user_canceled ||
response.errorCode === ErrorCode.no_credential
) {
// Present fallback authentication method
}
```
```dart Flutter theme={null}
final response = await authsignal.passkey.signIn(action: "signInWithPasskey");
if (response.errorCode == ErrorCode.userCanceled.value ||
response.errorCode == ErrorCode.noCredential.value) {
// Present fallback authentication method
}
```
iOS imposes an additional privacy restriction which prevents apps from determining if the user canceled the passkey flow or if they didn't have a passkey available.
For this reason we recommend presenting an alternative authentication method in both cases.
To learn more about handling error codes refer to the [Mobile SDK documentation](/sdks/client/mobile/error-handling).
## Passkey support
Passkeys are supported on iOS devices running **iOS 15** or higher and on Android devices running **Android 9 (API level 28)** or higher.
You can use the SDK to perform this check.
```swift Swift theme={null}
let isSupported = await authsignal.passkey.isSupported()
```
```kotlin Kotlin theme={null}
val isSupported = authsignal.passkey.isSupported()
```
```ts React Native theme={null}
const isSupported = await authsignal.passkey.isSupported();
```
## Next steps
* [Passkey best practices on web](/authentication-methods/passkey/best-practices-web)
* [Passkey best practices on mobile](/authentication-methods/passkey/best-practices-mobile)
# Passkeys using Authsignal's pre-built UI
Source: https://docs.authsignal.com/authentication-methods/passkey/prebuilt-ui
Rapidly implement passkeys with Authsignal's pre-built UI.
Authsignal's pre-built UI provides an out-of-the-box solution for implementing passkeys. This includes:
* Recovery flows for when a user doesn't have a passkey available.
* Uplift flows to accelerate passkey adoption.
* Advanced controls for customizing the passkey experience.
## Setup
To set up passkeys for your tenant, head to the [passkey setup wizard](https://portal.authsignal.com/organisations/tenants/authenticators/passkey) in the Authsignal Portal.
When asked to choose your implementation approach, select **I'm using Authsignal's pre-built UI**.
### Set up a custom domain
If you do not already have a [custom domain](/implementation-options/prebuilt-ui/custom-domains), you will be prompted to add one.
It is **strongly recommended** to use a custom domain for your passkey implementation.
If you proceed without a custom domain, your users' passkeys will be associated with Authsignal's domain and **will not work outside of the pre-built UI**.
This can be problematic if you want to use passkeys for other applications e.g. in your sign in page as an alternative to username and password.
### Choose recovery methods
To avoid a user being locked out of their account if they don't have a [passkey available](/authentication-methods/passkey/best-practices-web#availability-across-devices),
we **strongly recommend** setting up recovery methods.
If enabled, a user has to have at least **one** recovery method enrolled before they can create a passkey.
## Uplifting users to passkeys
The uplift flow is how a user without a passkey adds one. It's the equivalent of an "add a new passkey" flow, optimized for passkey adoption. To accelerate adoption, Authsignal will automatically prompt users who have enrolled a recovery method to create a passkey, including when they sign in on a new device that doesn't have a passkey available yet.
You can opt out of this behavior by [disabling the passkey uplift prompt](/implementation-options/prebuilt-ui/passkey-uplift-prompt#disabling-the-prompt), or you can [customize when it appears with actions/rules](/implementation-options/prebuilt-ui/passkey-uplift-prompt#advanced-configuration).
## Advanced configuration
The pre-built UI comes with default settings optimized for the best passkey user experience. To customize the passkey experience, you can configure the following settings:
* **Authenticator attachment**: Control which categories of authenticators can be used to create passkeys.
* **Platform**: How the user unlocks their device (e.g. Touch ID, Face ID).
* **Cross-platform**: Security keys or external devices via a QR code.
* **Registration hints**: Communicate hints to the browser to help it determine the best authenticator to use during passkey creation.
* **Client device**: Platform authenticators built into the device e.g. Touch ID, Face ID or passcode.
* **Security key**: External security keys and hardware tokens.
* **Hybrid**: Cross-device authenticators like smartphones.
For example, if you wanted to encourage users to use a physical security key you could set the authenticator attachment to **Cross-platform** and the registration hints to **Security key**.
When creating a passkey, a user would first be prompted to use a security key and would not have the option to use a platform authenticator like Touch ID or Face ID.
Registration hints are currently an experimental feature and may not work in all browsers. [Check browser support](https://passkeys.dev/device-support/#advanced) for more information.
## Using in-app browsers on iOS and Android apps
To deliver the best passkey UX on iOS and Android apps we recommend using our [Mobile SDKs](/sdks/client/mobile/setup).
However, if you need to launch the pre-built UI inside an app-based browser then passkeys are also supported under the following conditions:
* If using [Android Custom Tabs](https://developer.android.com/develop/ui/views/layout/webapps/overview-of-android-custom-tabs) then passkeys will work in the pre-built UI with no additional steps required.
* If using [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) or [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) on iOS then passkeys will work in the pre-built UI with no additional steps required.
* If using an embedded webview on iOS like [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview) then passkeys will work in the pre-built UI provided you set up an [associated domain](https://developer.apple.com/documentation/authenticationservices/supporting-passkeys#Use-passkeys-in-a-web-view). For more information on the steps required to configure your associated domain refer to the [Mobile SDK documentation](/sdks/client/mobile/passkeys).
* If using an embedded webview on Android like [WebView](https://developer.android.com/reference/android/webkit/WebView) then passkeys in the pre-built UI are **not supported**.
# Passkeys using the Web SDK
Source: https://docs.authsignal.com/authentication-methods/passkey/web-sdk
Implement passkeys in a web browser using the Authsignal Web SDK
Authsignal's [Web SDKs](/sdks/client/web/setup) let you rapidly implement passkeys in your browser-based apps.
## Portal setup
1. Navigate to the [passkey setup wizard](https://portal.authsignal.com/organisations/tenants/authenticators/passkey) in the Authsignal Portal.
2. Select **I'm building my own UI**.
3. Enter your primary domain. This needs to match the domain where your app is hosted (e.g. `example.com`). Click **Continue**.
4. If your app includes any subdomains then you can whitelist these by adding expected origins.
## SDK setup
Passkeys require integration from your backend server and your web frontend.
1. Get your tenant's credentials from the [API keys page](https://portal.authsignal.com/organisations/tenants/api).
2. Follow the setup instructions for one of our [Server SDKs](/sdks/server/setup) - or integrate via REST with our [Server API](/api-reference/server-api/overview).
3. Follow the setup instructions for our [Web SDK](/sdks/client/web/setup).
## Creating passkeys
In Authsignal, you define an [action](/actions-rules/actions/getting-started) to let users create passkeys. This does two things:
1. It provides observability for all passkey creation events in the [Authsignal Portal](https://portal.authsignal.com).
2. It ensures that the passkey is securely bound to an existing user.
### 1. Backend - Generate token
In your backend, track an action using a [Server SDK](/sdks/server) to generate a short-lived enrollment token.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "createPasskey",
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: "createPasskey",
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 = "createPasskey";
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: "createPasskey",
attributes: {
scope: "add:authenticators"
}
})
token = response[:token]
```
```python Python theme={null}
response = authsignal.track(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="createPasskey",
attributes={
"scope": "add:authenticators"
}
)
token = response["token"]
```
```php PHP theme={null}
$response = Authsignal::track([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "createPasskey",
'attributes' => [
'scope' => "add:authenticators"
]
]);
$token = $response["token"];
```
```go Go theme={null}
response, err := client.Track(
TrackRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "createPasskey",
Attributes: &TrackAttributes{
Scope: "add:authenticators",
},
},
)
token := response.Token
```
This token includes the **add:authenticators** scope to authorize binding a passkey to an existing user so you should only generate it when the user is [strongly authenticated](/advanced-usage/authenticator-binding).
### 2. Frontend - Create passkey
In your app, call the [signUp](/sdks/client/web/passkeys#creating-a-passkey) method using the [Web SDK](/sdks/client/web/setup), passing the token generated in step 1.
```js JavaScript theme={null}
const response = await authsignal.passkey.signUp({
token: "eyJhbGciOiJIUzI....",
username: "jane.smith@authsignal.com",
displayName: "Jane Smith",
});
```
## Authenticating with passkeys
You can define another [action](/actions-rules/actions/getting-started) to let users authenticate with their passkeys.
This action will be used for observability and to authenticate the user on the server.
### 1. Frontend - Sign in with passkey
In your app, call the [signIn](/sdks/client/web/passkeys#using-a-passkey) method using the [Web SDK](/sdks/client/web/setup).
```ts JavaScript theme={null}
const response = await authsignal.passkey.signIn({
action: "signInWithPasskey",
});
if (response.data?.token) {
// Send token to your backend for validation
}
```
### 2. Backend - Validate action
Pass the token returned in the previous step to your backend, validating the result of the authentication server-side.
```ts Node.js theme={null}
const response = await authsignal.validateChallenge({
action: "signInWithPasskey",
token: "eyJhbGciOiJIUzI....",
});
if (response.state === "CHALLENGE_SUCCEEDED") {
// User successfully authenticated with passkey
const userId = response.userId;
} else {
// Authentication failed
}
```
```csharp C# theme={null}
var request = new ValidateChallengeRequest(
Action: "signInWithPasskey",
Token: "eyJhbGciOiJIUzI...."
);
var response = await authsignal.ValidateChallenge(request);
if (response.State == UserActionState.CHALLENGE_SUCCEEDED) {
// User successfully authenticated with passkey
var userId = response.UserId;
} else {
// Authentication failed
}
```
```java Java theme={null}
ValidateChallengeRequest request = new ValidateChallengeRequest();
request.action = "signInWithPasskey";
request.token = "eyJhbGciOiJIUzI....";
ValidateChallengeResponse response = authsignal.validateChallenge(request).get();
if (response.state == UserActionState.CHALLENGE_SUCCEEDED) {
// The user authenticated successfully
String userId = response.userId;
}
```
```ruby Ruby theme={null}
response = Authsignal.validate_challenge(action: "signInWithPasskey", token: "eyJhbGciOiJIUzI....")
if response[:state] == "CHALLENGE_SUCCEEDED"
# The user authenticated successfully
user_id = response[:user_id]
end
```
```python Python theme={null}
response = authsignal.validate_challenge(action="signInWithPasskey", token="eyJhbGciOiJIUzI....")
if response["state"] == "CHALLENGE_SUCCEEDED":
# User successfully authenticated with passkey
user_id = response["user_id"]
else:
# Authentication failed
```
```php PHP theme={null}
$response = Authsignal::validateChallenge(action: "signInWithPasskey", token: "eyJhbGciOiJIUzI....");
if ($response["state"] === "CHALLENGE_SUCCEEDED") {
# The user authenticated successfully
$user_id = $response["user_id"];
}
```
```go Go theme={null}
response, err := client.ValidateChallenge(
ValidateChallengeRequest{
Action: "signInWithPasskey",
Token: "eyJhbGciOiJ...",
},
)
if response.State == "CHALLENGE_SUCCEEDED" {
// The user authenticated successfully
userId := response.UserId
}
```
This server-side validation step can be used within one of our [popular IdP integrations](/getting-started/overview#popular-integrations) or with our [session APIs](/advanced-usage/session-management) to issue access tokens and refresh tokens.
## Using autofill
Passkey autofill requires you to have an **input field** on your web page for a username (e.g. email address) which can be used to sign in.
When the user focuses the input field, they will be able to select an existing passkey if one is available on their device.
```html HTML theme={null}
```
### 1. Frontend - Enable passkey autofill
In your app's frontend, call the [signIn](/sdks/client/web/passkeys#using-passkey-autofill) method after the page loads using the [Web SDK](/sdks/client/web/setup) and set the **autofill** parameter to true.
```js JavaScript theme={null}
authsignal.passkey.signIn({
action: "signInWithPasskey",
autofill: true
}).then((response) => {
if (response.data?.token) {
// User selected a passkey via autofill
// Send token to your backend for validation
}
});
```
If the user focuses the input field and successfully activates their passkey, the method will resolve with a token.
### 2. Backend - Validate action
Pass the token returned in the previous step to your backend, validating the result of the authentication server-side.
```ts Node.js theme={null}
const response = await authsignal.validateChallenge({
action: "signInWithPasskey",
token: "eyJhbGciOiJIUzI....",
});
if (response.state === "CHALLENGE_SUCCEEDED") {
// User successfully authenticated with passkey
const userId = response.userId;
} else {
// Authentication failed
}
```
```csharp C# theme={null}
var request = new ValidateChallengeRequest(
Action: "signInWithPasskey",
Token: "eyJhbGciOiJIUzI...."
);
var response = await authsignal.ValidateChallenge(request);
if (response.State == UserActionState.CHALLENGE_SUCCEEDED) {
// User successfully authenticated with passkey
var userId = response.UserId;
} else {
// Authentication failed
}
```
```java Java theme={null}
ValidateChallengeRequest request = new ValidateChallengeRequest();
request.action = "signInWithPasskey";
request.token = "eyJhbGciOiJIUzI....";
ValidateChallengeResponse response = authsignal.validateChallenge(request).get();
if (response.state == UserActionState.CHALLENGE_SUCCEEDED) {
// The user authenticated successfully
String userId = response.userId;
}
```
```ruby Ruby theme={null}
response = Authsignal.validate_challenge(action: "signInWithPasskey", token: "eyJhbGciOiJIUzI....")
if response[:state] == "CHALLENGE_SUCCEEDED"
# The user authenticated successfully
user_id = response[:user_id]
end
```
```python Python theme={null}
response = authsignal.validate_challenge(action="signInWithPasskey", token="eyJhbGciOiJIUzI....")
if response["state"] == "CHALLENGE_SUCCEEDED":
# User successfully authenticated with passkey
user_id = response["user_id"]
else:
# Authentication failed
```
```php PHP theme={null}
$response = Authsignal::validateChallenge(action: "signInWithPasskey", token: "eyJhbGciOiJIUzI....");
if ($response["state"] === "CHALLENGE_SUCCEEDED") {
# The user authenticated successfully
$user_id = $response["user_id"];
}
```
```go Go theme={null}
response, err := client.ValidateChallenge(
ValidateChallengeRequest{
Action: "signInWithPasskey",
Token: "eyJhbGciOiJ...",
},
)
if response.State == "CHALLENGE_SUCCEEDED" {
// The user authenticated successfully
userId := response.UserId
}
```
## Local development
For local development we recommend creating a **separate tenant**. Set your **Relying Party ID** to `localhost` for this tenant and add an expected origins entry which includes the port where your development server is running.
This will let you test passkeys locally while keeping your production configuration separate.
## Next steps
* [Passkey best practices on web](/authentication-methods/passkey/best-practices-web)
* [Passkey best practices on mobile](/authentication-methods/passkey/best-practices-mobile)
# SMS OTP
Source: https://docs.authsignal.com/authentication-methods/sms-otp
Send one-time verification codes via SMS for authentication and for verifying users' phone numbers.
Authsignal SDKs can be used to implement SMS OTP challenges in two scenarios.
1. [Sign-in](#sign-in). Use our Server SDKs to authenticate users with SMS as the 1st factor. This integration only requires a phone number to initiate.
2. [Adaptive MFA](#adaptive-mfa). Use Server SDKs together with Client SDKs to authenticate users with SMS as a secondary factor. This integration requires a user ID to initiate and assumes the user has already been authenticated with a primary factor.
## SMS provider setup
Navigate to [Authenticators](https://portal.authsignal.com/organisations/tenants/authenticators) in the Authsignal Portal, click on **SMS OTP**, and choose an SMS provider.
1. Log in to your [Bird account](https://bird.com/)
2. Get your **Access key** from your Bird account settings
3. Note your **Workspace ID** from your workspace settings
4. Create or locate an SMS channel and note the **Channel ID**
5. In the Authsignal Portal, select **Bird** as your SMS provider
6. Enter your Bird access key, workspace ID, and channel ID
1. Log in to your [Twilio Console](https://console.twilio.com/)
2. Ensure you have at least one active phone number or messaging service in your Twilio account
3. From your dashboard, locate and copy your **Account SID** and **Auth Token**
4. In the Authsignal Portal, select **Twilio** as your SMS provider
5. Enter your **Account SID** and **Auth Token**
1. Log in to your [MessageMedia account](https://hub.messagemedia.com/)
2. Navigate to **Settings > API settings**
3. Click **Create API Key** to generate a new key
4. Copy your **API Key** and **API Secret**
5. Optionally, note a **Source number/Sender ID** if you want to specify one
6. In the Authsignal Portal, select **MessageMedia** as your SMS provider
7. Enter your **API Key**, **API Secret**, and optionally your **Source number/Sender ID**
1. Log in to your [TNZ Dashboard](https://www.tnz.co.nz/)
2. Navigate to the **Users** section
3. Create a new user or select an existing user
4. Click on the **API** tab within the user profile
5. Enable API access and copy the **Auth Token** (or **API Key**)
6. In the Authsignal Portal, select **TNZ** as your SMS provider
7. Enter your **API Key**
1. Contact [Modica Group](https://modica.group/) to set up your SMS account
2. Log in to your [Modica Omni dashboard](https://omni.modicagroup.com/)
3. Navigate to **Integrations > API Configuration**
4. Click on the **REST** tab
5. In the **Authentication** section, note your **Application Name** and generate or reset your **Password**
6. In the Authsignal Portal, select **Modica Group** as your SMS provider
7. Enter your **Application Name** and **Password**
For detailed instructions on setting up a webhook for SMS delivery, see the [webhooks documentation](/advanced-usage/webhooks/introduction).
## 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 SMS OTP as the **1st factor**.
Our [Server SDKs](/sdks/server) include methods to initiate and verify an OTP challenge for a given phone number.
These methods are well-suited for passwordless sign-in scenarios where you need to authenticate a user based on their phone number.
### 1. Initiate challenge
Call [Initiate Challenge](/sdks/server/challenges#initiate-challenge) to send an OTP to a phone number.
```ts Node.js theme={null}
const request = {
verificationMethod: "SMS",
action: "signInWithSms",
phoneNumber: "+64270000000",
};
const response = await authsignal.challenge(request);
const challengeId = response.challengeId;
```
```csharp C# theme={null}
var request = new ChallengeRequest(
VerificationMethod: VerificationMethod.SMS,
Action: "signInWithSms",
PhoneNumber: "+64270000000"
);
var response = await authsignal.challenge(request);
var challengeId = response.ChallengeId;
```
```java Java theme={null}
ChallengeRequest request = new ChallengeRequest();
request.verificationMethod = VerificationMethodType.SMS;
request.action = "signInWithSms";
request.phoneNumber = "+64270000000";
ChallengeResponse response = authsignal.challenge(request).get();
String challengeId = response.challengeId;
```
```ruby Ruby theme={null}
response = Authsignal.challenge(
verification_method: "SMS",
action: "signInWithSms",
phone_number: "+64270000000",
)
challenge_id = response[:challenge_id]
```
```go Go theme={null}
response, err := client.Challenge(
ChallengeRequest{
VerificationMethod: "SMS",
Action: "signInWithSms",
PhoneNumber: "+64270000000",
},
)
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 SMS).
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 phone number.
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 phone number.
```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 SMS 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 SMS 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).
### 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: {
phoneNumber: "+64270000000",
},
};
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(
PhoneNumber: "+64270000000"
)
);
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.phoneNumber = "+64270000000";
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: {
phone_number: '+64270000000'
}
})
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={
"phoneNumber": "+64270000000"
}
)
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' => [
'phoneNumber' => '+64270000000'
]
]);
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{
PhoneNumber: "+64270000000",
},
},
)
switch response.State {
case "CHALLENGE_REQUIRED":
// Obtain token to present challenge
token := response.Token
}
```
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 SMS 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 SMS OTP code
// You can call this multiple times via a 'resend' button
await authsignal.sms.challenge();
// Verify the inputted code matches the original code
const response = await authsignal.sms.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 SMS OTP code
// You can call this multiple times via a 'resend' button
await authsignal.sms.challenge()
// Verify the inputted code matches the original code
let response = await authsignal.sms.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 SMS OTP code
// You can call this multiple times via a 'resend' button
authsignal.sms.challenge()
// Verify the inputted code matches the original code
val response = authsignal.sms.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 SMS OTP code
// You can call this multiple times via a 'resend' button
await authsignal.sms.challenge();
// Verify the inputted code matches the original code
const response = await authsignal.sms.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 SMS OTP code
// You can call this multiple times via a 'resend' button
await authsignal.sms.challenge();
// Verify the inputted code matches the original code
final response = await authsignal.sms.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);
// 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 SMS OTP challenge was completed successfully, you can let the user proceed with the action.
## Enrollment
**Scenario** - Enroll users in SMS OTP while they’re authenticated so it can be used later as a
method for adaptive MFA.
To use SMS OTP for adaptive MFA, users must be **enrolled** with SMS OTP as an authentication method.
This means their phone number 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 a phone number.
```ts Node.js theme={null}
const request = {
verificationMethod: "SMS",
action: "enrollSms",
phoneNumber: "+64270000000",
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.SMS,
Action: "enrollSms",
PhoneNumber: "+64270000000",
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.SMS;
request.action = "enrollSms";
request.phoneNumber = "+64270000000";
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: "SMS",
action: "enrollSms",
phone_number: "+64270000000",
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: "SMS",
Action: "enrollSms",
PhoneNumber: "+64270000000",
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Scope: "add:authenticators",
},
)
challengeId := response.challengeId
```
The **add:authenticators** scope is required to enroll a new SMS 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 phone number
**Scenario** - Let users update their phone number while they’re authenticated, completing an OTP
challenge to verify the new number.
The following steps demonstrate how to implement an update phone number 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 phone number.
```ts Node.js theme={null}
const request = {
verificationMethod: "SMS",
action: "updatePhoneNumber",
phoneNumber: "+64270000000",
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.SMS,
Action: "updatePhoneNumber",
PhoneNumber: "+64270000000",
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.SMS;
request.action = "updatePhoneNumber";
request.phoneNumber = "+64270000000";
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: "SMS",
action: "updatePhoneNumber",
phone_number: "+64270000000",
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: "SMS",
Action: "updatePhoneNumber",
PhoneNumber: "+64270000000",
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Scope: "update:authenticators",
},
)
challengeId := response.challengeId
```
The **update:authenticators** scope is required to update a user's existing SMS authenticator to change the phone number.
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 phone numbers
**Scenario** - Enroll or update an SMS authenticator for a user when you've already verified their
phone number 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 phone number using another system.
This means the user can be enrolled without having to complete an SMS OTP challenge.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
attributes: {
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 same call can also be used to **update** a verified phone number to a new value which has also been verified externally.
## Next steps
* [Pre-built UI](/implementation-options/prebuilt-ui/overview) - Rapidly deploy SMS OTP challenges using our pre-built UI
* [Web SDK](/sdks/client/web/setup) - Implement SMS OTP challenges while building your own UI
* [Mobile SDK](/sdks/client/mobile/setup) - Implement SMS OTP challenges in native mobile apps
* [Adaptive MFA](/actions-rules/rules/adaptive-mfa) - Set up smart rules to trigger authentication based on risk
* [Opt-in consent](/implementation-options/prebuilt-ui/opt-in-consent) - Collect user consent before sending SMS messages
* [Passkeys](/authentication-methods/passkey/web-sdk) - Offer the most secure and user-friendly passwordless authentication
# Authenticator app (TOTP)
Source: https://docs.authsignal.com/authentication-methods/totp
Use time-based codes from Google Authenticator and other TOTP apps.
## Portal setup
1. Navigate to [Authenticators](https://portal.authsignal.com/organisations/tenants/authenticators) in the Authsignal Portal and click on **Authenticator app (TOTP)**.
2. Activate the authenticator in the next screen.
## 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"
);
```
## Adaptive MFA
The following steps demonstrate how to implement **adaptive MFA** with authenticator app - 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",
};
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
```
```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
```
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 authenticator app 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...");
// Verify the TOTP code inputted by the user
const response = await authsignal.totp.verify({ code: "123456" });
// Obtain a new token
const token = response.token;
```
```swift iOS theme={null}
// Set token from the track response
authsignal.setToken("eyJhbGciOiJ...")
// Verify the TOTP code inputted by the user
let response = await authsignal.totp.verify(code: "123456")
// Obtain a new token
let token = response.token
```
```kotlin Android theme={null}
// Set token from the track response
authsignal.setToken("eyJhbGciOiJ...")
// Verify the TOTP code inputted by the user
val response = authsignal.totp.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...");
// Verify the TOTP code inputted by the user
const response = await authsignal.totp.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...");
// Verify the TOTP code inputted by the user
final response = await authsignal.totp.verify(code: "123456");
// Obtain a new token
final token = response.token;
```
```js Web theme={null}
// Launch the pre-built UI with the URL from the track response
const result = await authsignal.launch(url);
if (result.token) {
// Obtain a new 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 authenticator app challenge was completed successfully, you can let the user proceed with the action.
## Enrollment
The following steps demonstrate how to let users enroll an authenticator app by scanning a QR code.
### 1. Track action
Use a [Server SDK](/sdks/server) to track an action in your backend.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "enroll",
};
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: "enroll",
);
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 = "enroll";
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: "enroll"
})
token = response[:token]
```
```python Python theme={null}
response = authsignal.track(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="enroll"
)
token = response["token"]
```
```php PHP theme={null}
$response = Authsignal::track([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "enroll"
]);
$token = $response["token"];
```
```go Go theme={null}
response, err := client.Track(
TrackRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "enroll",
},
)
token := response.Token
```
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "enroll",
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: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "enroll",
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 = "enroll";
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: "enroll",
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="enroll",
attributes={
"redirectUrl": "https://yourapp.com/callback",
}
)
url = response["url"]
```
```php PHP theme={null}
$response = Authsignal::track([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "enroll",
'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: "enroll",
Attributes: &TrackAttributes{
RedirectUrl: "https://yourapp.com/callback",
},
},
)
url := response.Url
```
You can choose any value for the [action](/actions-rules/actions/getting-started) here which describes the enrollment flow. This will be used to track the enrollment activity in the Authsignal Portal.
If the user is already enrolled with another authentication method, you will need to pass additional scopes when tracking this action - refer to our documentation on [authenticator binding](/advanced-usage/authenticator-binding) for more information.
### 2. Present QR code
Use the [Web SDK](/sdks/client/web/setup) or [Mobile SDK](/sdks/client/mobile/setup) to present a QR code which the user can scan with their authenticator app.
```ts Web theme={null}
// Set token from the track response
authsignal.setToken("eyJhbGciOiJ...");
const response = await authsignal.totp.enroll();
if (response.data) {
const uri = response.data.uri; // Convert to QR code
const secret = response.data.secret; // Can be entered manually
}
```
```swift iOS theme={null}
// Set token from the track response
authsignal.setToken("eyJhbGciOiJ...")
let response await authsignal.totp.enroll()
if let data = response.data {
let uri = data.uri // Convert to QR code
let secret = data.secret // Can be entered manually
}
```
```kotlin Android theme={null}
// Set token from the track response
authsignal.setToken("eyJhbGciOiJ...")
val response = authsignal.totp.enroll()
response.data.let {
val uri = it.uri // Convert to QR code
val secret = it.secret // Can be entered manually
}
```
```ts React Native theme={null}
// Set token from the track response
await authsignal.setToken("eyJhbGciOiJ...");
const response = await authsignal.totp.enroll();
if (response.data) {
const uri = response.data.uri; // Convert to QR code
const secret = response.data.secret; // Can be entered manually
}
```
```dart Flutter theme={null}
// Set token from the track response
await authsignal.setToken("eyJhbGciOiJ...");
final response = await authsignal.totp.enroll();
if (response.data != null) {
final uri = response.data.uri; // Convert to QR code
final secret = response.data.secret; // Can be entered manually
}
```
```js theme={null}
// Launch the pre-built UI with the URL from the track response
const result = await authsignal.launch(url);
```
### 3. Verify
Use the [Web SDK](/sdks/client/web/setup) or [Mobile SDK](/sdks/client/mobile/setup) to verify the code submitted by the user to complete enrollment.
```ts Web theme={null}
const response = await authsignal.totp.verify({ code: "123456" });
```
```swift iOS theme={null}
let response = await authsignal.totp.verify(code: "123456")
```
```kotlin Android theme={null}
val response = authsignal.totp.verify(code = "123456")
```
```ts React Native theme={null}
const response = await authsignal.totp.verify({ code: "123456" });
```
```dart Flutter theme={null}
final response = await authsignal.totp.verify(code: "123456");
```
## Next steps
* [Pre-built UI](/implementation-options/prebuilt-ui/overview) - Rapidly deploy authenticator app challenges using our pre-built UI
* [Web SDK](/sdks/client/web/setup) - Implement authenticator app challenges while building your own UI
* [Mobile SDK](/sdks/client/mobile/setup) - Implement authenticator app 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
# WhatsApp OTP
Source: https://docs.authsignal.com/authentication-methods/whatsapp-otp
Send one-time verification codes via WhatsApp for authentication and for verifying users' phone numbers.
Authsignal SDKs can be used to implement WhatsApp OTP challenges in two scenarios.
1. [Sign-in](#sign-in). Use our Server SDKs to authenticate users with WhatsApp OTP as the 1st factor. This integration only requires a phone number to initiate.
2. [Adaptive MFA](#adaptive-mfa). Use Server SDKs together with Client SDKs to authenticate users with WhatsApp 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.
## Portal setup
1. Navigate to [Authenticators](https://portal.authsignal.com/organisations/tenants/authenticators) in the Authsignal Portal and click on **WhatsApp**.
2. Choose and set up a WhatsApp Provider you want to use in the next screen. Then click Connect Account.
## 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 WhatsApp OTP as the **1st factor**.
Our [Server SDKs](/sdks/server) include methods to initiate and verify a WhatsApp OTP challenge for a given phone number.
These methods are well-suited for passwordless sign-in scenarios where you need to authenticate a user based on their phone number.
### 1. Initiate challenge
Call [Initiate Challenge](/sdks/server/challenges#initiate-challenge) to send an OTP to a phone number.
```ts Node.js theme={null}
const request = {
verificationMethod: "WHATSAPP",
action: "signInWithWhatsApp",
phoneNumber: "+64270000000",
};
const response = await authsignal.challenge(request);
const challengeId = response.challengeId;
```
```csharp C# theme={null}
var request = new ChallengeRequest(
VerificationMethod: VerificationMethod.WHATSAPP,
Action: "signInWithWhatsApp",
PhoneNumber: "+64270000000"
);
var response = await authsignal.challenge(request);
var challengeId = response.ChallengeId;
```
```java Java theme={null}
ChallengeRequest request = new ChallengeRequest();
request.verificationMethod = VerificationMethodType.WHATSAPP;
request.action = "signInWithWhatsApp";
request.phoneNumber = "+64270000000";
ChallengeResponse response = authsignal.challenge(request).get();
String challengeId = response.challengeId;
```
```ruby Ruby theme={null}
response = Authsignal.challenge(
verification_method: "WHATSAPP",
action: "signInWithWhatsApp",
phone_number: "+64270000000",
)
challenge_id = response[:challenge_id]
```
```go Go theme={null}
response, err := client.Challenge(
ChallengeRequest{
VerificationMethod: "WHATSAPP",
Action: "signInWithWhatsApp",
PhoneNumber: "+64270000000",
},
)
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 WhatsApp).
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 phone number.
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 phone number.
```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 WhatsApp 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 WhatsApp 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).
### 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: {
phoneNumber: "+64270000000",
},
};
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(
PhoneNumber: "+64270000000"
)
);
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.phoneNumber = "+64270000000";
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: {
phone_number: '+64270000000'
}
})
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={
"phoneNumber": "+64270000000"
}
)
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' => [
'phoneNumber' => '+64270000000'
]
]);
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{
PhoneNumber: "+64270000000",
},
},
)
switch response.State {
case "CHALLENGE_REQUIRED":
// Obtain token to present challenge
token := response.Token
}
```
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 a WhatsApp 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 a WhatsApp OTP code
// You can call this multiple times via a 'resend' button
await authsignal.whatsapp.challenge();
// Verify the inputted code matches the original code
const response = await authsignal.whatsapp.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 a WhatsApp OTP code
// You can call this multiple times via a 'resend' button
await authsignal.whatsapp.challenge()
// Verify the inputted code matches the original code
let response = await authsignal.whatsapp.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 a WhatsApp OTP code
// You can call this multiple times via a 'resend' button
authsignal.whatsapp.challenge()
// Verify the inputted code matches the original code
val response = authsignal.whatsapp.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 a WhatsApp OTP code
// You can call this multiple times via a 'resend' button
await authsignal.whatsapp.challenge();
// Verify the inputted code matches the original code
const response = await authsignal.whatsapp.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 a WhatsApp OTP code
// You can call this multiple times via a 'resend' button
await authsignal.whatsapp.challenge();
// Verify the inputted code matches the original code
final response = await authsignal.whatsapp.verify(code: "123456");
// Obtain a new token
final token = response.token;
```
```js Web theme={null}
// Launch the pre-built UI with the URL from the track response
const result = await authsignal.launch(url);
// 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 WhatsApp OTP challenge was completed successfully, you can let the user proceed with the action.
## Enrollment
**Scenario** - Enroll users in WhatsApp OTP while they’re authenticated so it can be used later as
a method for adaptive MFA.
To use WhatsApp OTP for adaptive MFA, users must be **enrolled** with WhatsApp OTP as an authentication method.
This means their phone number 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 a phone number.
```ts Node.js theme={null}
const request = {
verificationMethod: "WHATSAPP",
action: "enrollWhatsApp",
phoneNumber: "+64270000000",
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.WHATSAPP,
Action: "enrollWhatsApp",
PhoneNumber: "+64270000000",
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.WHATSAPP;
request.action = "enrollWhatsApp";
request.phoneNumber = "+64270000000";
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: "WHATSAPP",
action: "enrollWhatsApp",
phone_number: "+64270000000",
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: "WHATSAPP",
Action: "enrollWhatsApp",
PhoneNumber: "+64270000000",
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Scope: "add:authenticators",
},
)
challengeId := response.challengeId
```
The **add:authenticators** scope is required to enroll a new WhatsApp 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 phone number
**Scenario** - Let users update their phone number while they’re authenticated, completing an OTP
challenge to verify the new number.
The following steps demonstrate how to implement an update phone number 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 phone number.
```ts Node.js theme={null}
const request = {
verificationMethod: "WHATSAPP",
action: "updatePhoneNumber",
phoneNumber: "+64270000000",
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.WHATSAPP,
Action: "updatePhoneNumber",
PhoneNumber: "+64270000000",
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.WHATSAPP;
request.action = "updatePhoneNumber";
request.phoneNumber = "+64270000000";
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: "WHATSAPP",
action: "updatePhoneNumber",
phone_number: "+64270000000",
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: "WHATSAPP",
Action: "updatePhoneNumber",
PhoneNumber: "+64270000000",
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Scope: "update:authenticators",
},
)
challengeId := response.challengeId
```
The **update:authenticators** scope is required to update a user's existing WhatsApp authenticator to change the phone number.
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 phone numbers
**Scenario** - Enroll or update a WhatsApp authenticator for a user when you've already verified
their phone number 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 phone number using another system.
This means the user can be enrolled without having to complete a WhatsApp OTP challenge.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
attributes: {
verificationMethod: "WHATSAPP",
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.WHATSAPP,
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.WHATSAPP;
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: "WHATSAPP",
phone_number: "+64270000000"
}
)
```
```python Python theme={null}
response = authsignal.enroll_verified_authenticator(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
attributes={
"verificationMethod": "WHATSAPP",
"phoneNumber": "+64270000000"
}
)
```
```php PHP theme={null}
$response = Authsignal::enrollVerifiedAuthenticator([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'attributes' => [
"verificationMethod" => "WHATSAPP",
"phoneNumber" => "+64270000000"
]
]);
```
```go Go theme={null}
response, err := client.EnrollVerifiedAuthenticator(
EnrollVerifiedAuthenticatorRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Attributes: &EnrollVerifiedAuthenticatorAttributes{
VerificationMethod: "WHATSAPP",
PhoneNumber: "+64270000000",
},
},
)
```
This same call can also be used to **update** a verified phone number to a new value which has also been verified externally.
## Next steps
* [Pre-built UI](/implementation-options/prebuilt-ui/overview) - Rapidly deploy WhatsApp OTP challenges using our pre-built UI
* [Web SDK](/sdks/client/web/setup) - Implement WhatsApp OTP challenges while building your own UI
* [Mobile SDK](/sdks/client/mobile/setup) - Implement WhatsApp 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
# Product updates
Source: https://docs.authsignal.com/changelog/product-updates
New features, updates, and changes to Authsignal.
## Authsignal managed push, push notification templates, and device remembered rules
Let Authsignal deliver your push notifications, customise the push copy per action, and gate rules on whether a device is remembered.
### Authsignal managed push
Provide your APNs and FCM credentials and have [Authsignal manage push delivery](/authentication-methods/app-verification/managed-push) for you, with no webhook to build or host.
***
### Per action push notification templates
Customise the title and body of the [push notification](/authentication-methods/app-verification/push) per action, with [custom data points](/implementation-options/prebuilt-ui/contextual-messaging) such as the action amount or account.
***
### Device remembered rule condition
A new **device is remembered** rule condition branches on whether a device previously completed an authentication and was marked as remembered, set via the [Update device](/api-reference/server-api/update-device) API's `rememberedUntil` timestamp.
## Log events webhook and action activity insights
A new webhook for shipping action and challenge logs to third-party systems, plus breakdowns and CSV export for the action activity graph.
### Log events webhook
You can now configure a [log events webhook URL](/advanced-usage/webhooks/event-types) in the Authsignal Portal to ship action and challenge logs to your own SIEM, data lake, or analytics destination.
***
### Action activity breakdowns and CSV export
The action activity graph in the Authsignal Portal can now be broken down by verification method and action code, making it easier to spot trends across authenticators and actions. You can also export the graph data to CSV for further analysis.
## WSO2 Identity Platform integration, attestation, and more
New identity provider integration, mobile app attestation, improved SMS error visibility, opt-in consent for SMS enrollment, and custom email template variables.
### WSO2 Identity Platform integration
You can now use [WSO2 Identity Platform](/integrations/wso2-identity-platform) as an identity provider with Authsignal, enabling step-up authentication and MFA for users managed in WSO2 Identity Platform.
***
### Attestation
[Attestation](/authentication-methods/app-verification/attestation) ensures that app credentials are enrolled from a legitimate, unmodified version of your app running on a real device. It uses Apple's App Attest on iOS and Google's Play Integrity API on Android, and applies to in-app verification, push verification, and QR code verification methods.
***
### SMS error visibility
SMS send failures from downstream providers are now reported clearly in the Authsignal Portal, making it easier to diagnose delivery issues.
***
### Opt-in consent checkbox for SMS enrollment
You can now require users to check a [consent checkbox](/implementation-options/prebuilt-ui/opt-in-consent) before receiving an SMS during enrollment. This supports compliance with regulations such as 10DLC and TCPA.
***
### Custom template variables for Email OTP
You can now forward [custom data points](/authentication-methods/email/otp#custom-template-variables) to your email provider as additional template variables, enabling personalized emails with user or action-specific data such as account IDs or transaction amounts.
## SMTP server support and custom user fields
You can now use a SMTP server of your choosing for sending OTPs and magic links from Authsignal.
### Custom SMTP server
Configure your own SMTP server for email OTP and email magic link delivery, giving you more control over how Authsignal sends verification emails.
***
### Custom user fields in advance search
You can now use custom user fields in action advance search, making it easier to filter actions by user attributes such as `user.email`,`user.phoneNumber`, etc.
## New Canada region, restricted API keys, and more
New Canada region, restricted API keys, and event timeline improvements.
### Canada region
Authsignal is now available in the Canada region, allowing you to store and process data within Canada to meet data residency requirements.
### Restricted API keys
You can now create restricted API keys with fine-grained permissions, allowing you to scope keys to specific actions and limit access to only what each integration needs.
### Device info on event timeline
App-credential events in the event timeline now display device information, giving you greater visibility into the devices used during authentication.
## New passkey setup flow and multiple custom domains
New passkey setup flow and multiple custom domains for enhanced branding.
### New passkey setup flow
A redesigned setup experience that guides you through passkey implementation with clear options for using Authsignal's pre-built UI or building your own custom UI with the web SDK or mobile SDK.
***
### Multiple custom domains
You can now configure multiple [custom domains](/advanced-usage/custom-api-domains) for Authsignal's pre-built UI flows. Manage multiple branded domains, set a default domain, and activate or deactivate domains as needed.
## Passkey resources website and new messaging providers
New passkey resources website and expanded messaging provider options.
### Passkey resources website
We launched [passkeyresources.com](https://www.passkeyresources.com/), a resource hub for understanding and implementing passkeys. It features:
* Technical overview of how passkeys work
* Interactive demo showcasing passkeys across login, recovery, and step-up authentication workflows
***
### SendGrid email provider
SendGrid is now available as an email provider option for email OTP and email magic link authentication methods.
***
### TNZ SMS provider
TNZ is now available as an SMS provider option for SMS OTP authentication.
***
### Seamless email provider switching
You can now switch between email providers without removing your existing configuration, making it easier to test and migrate between providers.
## Quick action view, enhanced custom data querying, ServiceNow service desk verification and more
Enhanced action inspection with quick view and custom data querying is now available to help you analyze user behavior more effectively.
### Quick action view
Click on any action to open a quick view panel that displays detailed action data. You can add values directly from the action you're viewing to your query for faster filtering.
***
### Custom data querying
Custom data can now be queried using equals and contains filtering operations, giving you the ability to search and analyze your custom data points.
***
### Risk signals
Risk signals are now displayed across the portal on the users list, user detail, and action summary pages, making it easier to identify potentially risky actions at a glance.
***
### ServiceNow service desk verification
Securely verify customer identity during support calls with the new [Authsignal Call Connect app for ServiceNow](https://store.servicenow.com/store/app/aa1018ac33907a9457d93c128d5c7b14). Agents can trigger verification flows using passkeys, OTP, push notifications, or ID checks directly within ServiceNow.
***
### Mandrill email provider
Mandrill is now available as an email provider option for email OTP and email magic link authentication methods.
***
### Enhanced AI explanations
The portal now uses Claude 4.5 Sonnet for improved AI-powered explanations and analysis.
## Advanced action search and Bird message templates
Enhanced search capabilities for actions and the ability to use Bird's message templates for SMS, Email OTP, and Magic Link.
### Advanced search for actions
Find actions quickly with powerful search functionality that supports multiple filter criteria to help you locate exactly what you need.
***
### Bird message templates
Enable the use of Bird's message templates for SMS, Email OTP, and Magic Link authentication.
## WhatsApp OTP authentication
A new authentication method that enables secure one-time passcode delivery via [WhatsApp](/authentication-methods/whatsapp-otp) for enhanced user verification.
## New device credential methods, session management, and passkey registration hints
This update introduces three powerful new authentication methods under device credentials, comprehensive session management capabilities, and enhanced passkey registration controls for better user experience.
### Device credential authentication methods
Three new authentication methods are now available to provide flexible, secure user verification options.
#### Push notifications
Send secure [push notifications](/authentication-methods/push-notification) to users' registered devices for instant authentication approval.
#### QR code authentication
Enable users to authenticate by scanning [QR codes](/authentication-methods/qr-code) with their mobile devices, perfect for cross-device scenarios.
#### Trusted device recognition
Automatically recognize and [trust devices](/authentication-methods/trusted-device) that users have previously verified, reducing friction for repeat access.
***
### Session management
Take full control of user [sessions](/advanced-usage/session-management) with the ability to create, validate, refresh, and revoke access tokens across devices.
***
### Passkey registration hints
Fine-tune the passkey creation experience by providing hints to browsers about the preferred authenticator types.
## AI insights, rule impact analysis, and more
This update brings AI-powered explanations for rule triggers, rule impact analysis for safer deployments, passkey adoption metrics, and enhanced OTP rate limiting controls.
### AI-powered rule insights
Get plain English explanations of why rules were triggered, making it easier to understand and optimize your security logic.
### Rule impact analysis
Preview how rule changes will affect your users before deploying, reducing the risk of unintended consequences.
### Passkey analytics
Track adoption rates, credential manager usage, and uplift success to optimize your passwordless strategy.
### Enhanced OTP rate limiting
Configure submission limits and time windows to prevent brute force attacks and reduce abuse.
# SDK & API updates
Source: https://docs.authsignal.com/changelog/sdk-and-api-updates
Monthly updates to Authsignal SDKs and APIs.
## Mobile SDKs
* Fetching [push challenges](/sdks/client/mobile/push-verification) now returns public custom action and user data points, for building richer push verification screens. [iOS 2.9.0](https://github.com/authsignal/authsignal-ios/releases/tag/v2.9.0), [Android 3.10.0](https://github.com/authsignal/authsignal-android/releases/tag/v3.10.0), [React Native 2.13.0](https://github.com/authsignal/react-native-authsignal/releases/tag/v2.13.0), [Flutter 2.4.2](https://github.com/authsignal/authsignal-flutter/releases/tag/v2.4.2).
* [Passkey sync](/sdks/client/mobile/passkeys) via the WebAuthn Signal API comes to mobile. Passkeys removed server-side are now reflected in the platform's passkey UI. [iOS 2.10.0](https://github.com/authsignal/authsignal-ios/releases/tag/v2.10.0), [Android 3.11.0](https://github.com/authsignal/authsignal-android/releases/tag/v3.11.0), [React Native 2.14.0](https://github.com/authsignal/react-native-authsignal/releases/tag/v2.14.0), [Flutter 2.7.0](https://github.com/authsignal/authsignal-flutter/releases/tag/v2.7.0).
* Flutter's `push.addCredential` now accepts an optional `pushToken` to register the device token at enrolment. [Flutter 2.6.0](https://github.com/authsignal/authsignal-flutter/releases/tag/v2.6.0).
* The Android SDK upgrades to Ktor 3, a major version bump that also rolls through the Flutter and React Native wrappers. [Android 4.0.0](https://github.com/authsignal/authsignal-android/releases/tag/v4.0.0), [React Native 3.0.0](https://github.com/authsignal/react-native-authsignal/releases/tag/v3.0.0), [Flutter 3.0.0](https://github.com/authsignal/authsignal-flutter/releases/tag/v3.0.0).
* New [`push.updateCredential`](/sdks/client/mobile/push-verification) refreshes a device's push token after enrolment, with no user session or track call required. [iOS 2.11.0](https://github.com/authsignal/authsignal-ios/releases/tag/v2.11.0), [Android 4.1.0](https://github.com/authsignal/authsignal-android/releases/tag/v4.1.0), [React Native 3.1.0](https://github.com/authsignal/react-native-authsignal/releases/tag/v3.1.0).
## Web SDK
* Adds a [targeted push authenticator challenge](/sdks/client/web/push-verification) that sends the push to a specific enrolled device. [Browser 1.18.0](https://github.com/authsignal/authsignal-browser/releases/tag/v1.18.0).
* [Passkey](/sdks/client/web/passkeys) authentication now supports immediate mediation. [Browser 1.19.0](https://github.com/authsignal/authsignal-browser/releases/tag/v1.19.0).
## Mobile SDKs
* App Attest now uses a server-issued `nonce` in place of the token's `idempotencyKey`, and Flutter gains App Attest support. [iOS 2.7.0](https://github.com/authsignal/authsignal-ios/releases/tag/v2.7.0), [Android 3.8.0](https://github.com/authsignal/authsignal-android/releases/tag/v3.8.0), [React Native 2.11.0](https://github.com/authsignal/react-native-authsignal/releases/tag/v2.11.0), [Flutter 2.4.0](https://github.com/authsignal/authsignal-flutter/releases/tag/v2.4.0).
* `push.enroll` accepts an optional `pushToken` to register the device token at enrolment. [iOS 2.8.0](https://github.com/authsignal/authsignal-ios/releases/tag/v2.8.0), [Android 3.9.0](https://github.com/authsignal/authsignal-android/releases/tag/v3.9.0), [React Native 2.12.0](https://github.com/authsignal/react-native-authsignal/releases/tag/v2.12.0).
* [Flutter 2.4.0](https://github.com/authsignal/authsignal-flutter/releases/tag/v2.4.0): adds `getDeviceId()`, passkey `isSupported()` / `shouldPromptToCreatePasskey()`, credential options for push / QR / in-app, a full PIN API, and Flutter web support.
## Web SDK
* Passkey sync via the WebAuthn Signal API. Passkeys removed server-side are now reflected in the browser's passkey picker. [Browser 1.17.0](https://github.com/authsignal/authsignal-browser/releases/tag/v1.17.0).
## Server SDKs
* [.NET 4.4.0](https://github.com/authsignal/authsignal-dotnet/releases/tag/v4.4.0): adds `Username` on authenticator enrolment models.
## Server API
* [OIDC redirect flow](/authentication-methods/oidc-providers/redirect-flow) now uses an opaque `authorizationToken` plus a client-generated `codeVerifier` / `codeChallenge`.
* [Update device](/api-reference/server-api/update-device) accepts a `rememberedUntil` timestamp.
* Passkey authenticator responses include the WebAuthn `username`.
# Welcome to Authsignal Docs
Source: https://docs.authsignal.com/getting-started/overview
All the guides and resources you need to get started with Authsignal.
## Authsignal fundamentals
### What you can build
Authsignal enables you to implement various authentication patterns like passwordless login, MFA, adaptive MFA, and step-up authentication that integrate seamlessly with your existing identity stack.
Add strong security with multiple authentication factors for your users.
Apply MFA intelligently based on user behavior and risk signals.
Eliminate passwords while enhancing security and user experience.
Require additional verification for sensitive operations.
### How it works
Authsignal uses two core concepts to control authentication flows:
Security events in your application (like login, payment, or data access) that may require
additional verification.
Conditions and logic that determine when and how users should be challenged based on risk
factors, user behavior, or business requirements.
### Implementation approaches
Choose the approach that best fits your application:
Hosted components with fast setup and customizable styling to match your brand.
Complete control over design using our client SDKs for web and mobile platforms.
## Popular integrations
Works seamlessly with leading identity providers, allowing you to enhance your existing auth infra:
Extend Cognito with passkeys and advanced authentication options.
Integrate with Azure B2C using Technical Profiles and OpenID Connect.
Add Authsignal to IdentityServer for custom authentication workflows.
}
href="/integrations/auth0"
>
Learn how to use our Auth0 integration to implement MFA with just a few lines of code.
## Explore authentication methods
Choose from various authentication factors to secure your applications:
FIDO2 passkeys for phishing-resistant, passwordless authentication.
Secure authentication via push notifications to mobile devices.
Time-based one-time passwords (TOTP) using authenticator apps.
Device authentication by scanning QR codes for cross-device flows.
# Set up your Authsignal account
Source: https://docs.authsignal.com/getting-started/set-up-authsignal
Before you can add Authsignal to your app, you'll need to create an Authsignal account. This guide will walk you through it.
Create your account on [Authsignal](https://portal.authsignal.com/users/sign_up) and set up MFA for added security.
Once signed in, create your first tenant - this is your workspace within Authsignal.
Once your tenant is created, you'll see the Authsignal Portal where you can [configure authenticators](https://portal.authsignal.com/organisations/tenants/authenticators) and select which authentication methods to enable for your users.
Head to [Settings](https://portal.authsignal.com/organisations/tenants/api) to find your Tenant ID, API Host, and API Key - you'll need these to connect Authsignal to your app.
You're all set! Check out our [pre-built UI](/implementation-options/prebuilt-ui/overview) guide for quick implementation with a hosted UI or explore our [Web SDK](/implementation-options/web-sdk/overview) or [Mobile SDK](/implementation-options/mobile-sdks/overview) documentation if you need complete control over your UI.
Check out our [quickstarts](/integrations) to learn how to quickly integrate Authsignal to different identity providers.
# Mobile SDKs
Source: https://docs.authsignal.com/implementation-options/mobile-sdks/overview
Implement authentication flows in native mobile apps using Swift, Kotlin, React Native or Flutter.
You can use Authsignal's [Mobile SDKs](/sdks/client/mobile/setup) to implement authentication flows while crafting your own fully native UI.
## Authentication methods
Our Mobile SDKs can be used to implement a variety of authentication methods.
Add passkeys to your mobile app for phishing-resistant passwordless sign-in and step-up
authentication.
Allow users to sign-in across devices using push notifications and public key cryptography.
Allow users to sign-in across devices using QR codes and public key cryptography.
Send OTPs via email to authenticate users and verify email addresses.
Send OTPs via SMS to authenticate users and verify phone numbers.
Authenticate with OTPs sent via WhatsApp as a cost-saving alternative to traditional SMS.
Authenticate with time-based one-time passcodes generated in an authenticator app such as Google
Authenticator.
Verify users with strong credentials backed by public key cryptography.
## Get started
Follow the [SDK documentation](/sdks/client/mobile/setup) on setting up the Mobile SDK for Swift, Kotlin, React Native or Flutter.
# Contextual messaging
Source: https://docs.authsignal.com/implementation-options/prebuilt-ui/contextual-messaging
Add custom messaging to the pre-built UI to give your users more context.
The contextual messaging feature allows you to provide additional information to users when they're completing a challenge in the pre-built UI.
## Creating a contextual message
To configure messaging for your action:
1. Navigate to the [Authsignal Portal actions page](https://portal.authsignal.com/actions)
2. Select your action
3. Go to the **Messaging** tab
## Dynamic content with custom data points
You can enrich your messages with dynamic data using the syntax: `{{.}}`
### Using action data
To include transaction-specific information (such as withdrawal amount):
```
You will be withdrawing {{action.withdrawalAmount}}
```
Make sure to include these custom data points in your [track action
payload](/actions-rules/rules/custom-data-points#3-send-the-custom-data-in-your-code).
### Using user data
To include user-specific information:
```
Welcome back, {{user.firstName}}. Please confirm this withdrawal of {{action.withdrawalAmount}}.
```
Ensure you have [updated your user profile](/api-reference/server-api/update-user) with these
custom data points before referencing them.
## Pre-built UI
Once you've created a contextual message, it will be displayed in the pre-built UI when the user is completing the challenge.
If any of the data points you're referencing are not available, the message will not be displayed.
# Custom branding
Source: https://docs.authsignal.com/implementation-options/prebuilt-ui/custom-branding
Customize the look and feel of the pre-built UI to match your app's brand.
For a more seamless user experience you can heavily customize the pre-built UI branding by providing your own theme.
Visit the [branding tab in the Authsignal Portal](https://portal.authsignal.com/organisations/tenants/customizations) to get started.
If you require a more bespoke branding experience then try out our [UI customization tool](https://portal.authsignal.com/organisations/tenants/customizations/advanced).
This tool allows you to change a wide range of design tokens (colors, borders, alignment, text) to tweak the look and feel of the entire pre-built UI.
## Custom templates
To override the pre-built UI's layout you can provide a custom template.
Custom templates, at a bare minimum, must contain the following structure.
```html theme={null}
```
The `authsignal-head` and `authsignal-widget` tags are required for the pre-built UI to function
correctly.
To update your tenant's custom template, you can send the following payload to the [Update Theme endpoint](/api-reference/management-api/update-theme).
```json theme={null}
{
"template": ""
}
```
The `template` string must have escaped special characters.
## Adding custom HTML/CSS
Using a custom template allows you to add your own HTML/CSS.
For example, if you wanted to show an image next to the pre-built UI you could use the following template:
```html theme={null}
```
Which would result in:
## Adding custom JavaScript
To add custom JavaScript to your custom template you can use the following structure:
```html theme={null}
```
# Custom domains
Source: https://docs.authsignal.com/implementation-options/prebuilt-ui/custom-domains
Use your own custom domain with Authsignal's pre-built UI.
If you're using Authsignal's pre-built UI for your authentication flows you
can add your own custom domain.
## Add your custom domain to the Authsignal Portal
Decide on the subdomain you want to use.
For example, if your app is hosted on `example.com`, you might want to use `auth.example.com` as
your custom domain.
Now, head to the [custom domains](https://portal.authsignal.com/organisations/tenants/custom_domains)
page in the Authsignal Portal.
Enter your desired subdomain and click **Add**.
You will be prompted to add a CNAME record to your DNS provider. Once Authsignal detects that the CNAME record has been added, you will
see a **Valid configuration** message.
Click **Activate custom domain** to get the pre-built UI to use your custom domain.
You must activate your custom domain before it will take effect.
## Multiple custom domains
Multiple custom domains is an enterprise feature. [Contact us](mailto:support@authsignal.com) to enable this for your account.
If you have multiple applications or brands, you can configure additional custom domains. Each domain can be used independently with Authsignal's pre-built UI.
### Use cases
Multiple custom domains offers flexibility for organizations managing diverse branding, regional requirements, or partner integrations—all from a single Authsignal tenant.
* **Multiple brands, single tenant** — Organizations owning several brands can maintain separate branded authentication experiences (e.g., `auth.brand1.com` and `auth.brand2.com`) while sharing a centralized Authsignal configuration.
* **Regional domains** — Businesses requiring different domains for regional operations can serve users across jurisdictions (e.g., `auth.example.com`, `auth.example.co.uk`, `auth.example.com.au`).
* **White-labeled B2B SaaS** — SaaS providers offering white-labeled solutions can give each customer their own branded authentication domain (e.g., `auth.client-a.com`, `auth.client-b.com`) while maintaining a single tenant.
### Adding additional domains
Once the multiple custom domains feature is enabled for your account, you'll see an **Add another custom domain** option on the custom domains page. Follow the same setup process as your first domain:
1. Enter the subdomain (e.g., `mfa.anotherbrand.com`)
2. Add the CNAME record to your DNS provider
3. Wait for verification
4. Click **Activate custom domain**
### Default domain
One of your custom domains is designated as the **default**. This is the domain used when you don't explicitly specify which domain to use in your integration.
To change the default domain, click **Set as default** on any active domain.
### Using a specific domain
To redirect a user to a specific domain, you can include a `customDomain` attribute in your track action request.
```ts Node.js {6} theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
attributes: {
redirectUrl: "https://yourapp.com/callback",
customDomain: "https://auth.example.com"
},
};
const response = await authsignal.track(request);
const url = response.url;
```
```csharp C# {6} theme={null}
var request = new TrackRequest(
UserId: user.Id,
Action: "signIn",
Attributes: new TrackAttributes(
RedirectUrl: "https://yourapp.com/callback",
CustomDomain: "https://auth.example.com"
)
);
var response = await authsignal.Track(request);
var url = response.Url;
```
```java Java {6} 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";
request.attributes.customDomain = "https://auth.example.com";
TrackResponse response = authsignal.track(request).get();
String url = response.url;
```
```ruby Ruby {6} theme={null}
response = Authsignal.track({
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
attributes: {
redirect_url: "https://yourapp.com/callback",
custom_domain: "https://auth.example.com"
}
})
url = response[:url]
```
```python Python {6} theme={null}
response = authsignal.track(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="signIn",
attributes={
"redirectUrl": "https://yourapp.com/callback",
"customDomain": "https://auth.example.com"
}
)
url = response["url"]
```
```php PHP {6} theme={null}
$response = Authsignal::track([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "signIn",
'attributes' => [
'redirectUrl' => "https://yourapp.com/callback",
'customDomain' => "https://auth.example.com"
]
]);
$url = $response["url"]
```
```go Go {7} theme={null}
response, err := client.Track(
TrackRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
Attributes: &TrackAttributes{
RedirectUrl: "https://yourapp.com/callback",
CustomDomain: "https://auth.example.com",
},
},
)
url := response.Url
```
The `customDomain` attribute must be the full URL of the domain you want to use, including the protocol (e.g. `https://auth.example.com`).
If no `customDomain` is specified or the domain is not active, the default domain will be used.
# Enrolling authenticators
Source: https://docs.authsignal.com/implementation-options/prebuilt-ui/enrolling-authenticators
Learn how your users can enroll new authenticators using Authsignal's pre-built UI.
The Authsignal pre-built UI provides a complete enrollment experience with minimal integration effort.
## First-time enrollment
When users have no existing authenticators, they can enroll their first method directly.
**Backend: Generate enrollment URL**
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "enroll",
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: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "enroll",
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 = "enroll";
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: "enroll",
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="enroll",
attributes={
"redirectUrl": "https://yourapp.com/callback",
}
)
url = response["url"]
```
```php PHP theme={null}
$response = Authsignal::track([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "enroll",
'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: "enroll",
Attributes: &TrackAttributes{
RedirectUrl: "https://yourapp.com/callback",
},
},
)
url := response.Url
```
**Frontend: Launch enrollment flow**
```javascript theme={null}
authsignal.launch(url);
```
Check enrollment status using [Get User](/sdks/server/users#get-user) or [Get
Authenticators](/sdks/server/authenticators#get-authenticators) to customize your UI based on
whether users are enrolled.
## Adding additional authenticators
Users can enroll additional methods through the pre-built UI by first completing a challenge with one of their existing methods.
We can add the `redirectToSettings` attribute to the track request to land the user on a screen after their challenge which will let them enroll more methods.
```ts Node.js {6} theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "manageAuthenticators",
attributes: {
redirectUrl: "https://yourapp.com/callback"
redirectToSettings: true,
},
};
const response = await authsignal.track(request);
const url = response.url;
```
```csharp C# {6} theme={null}
var request = new TrackRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "manageAuthenticators",
Attributes: new TrackAttributes(
RedirectUrl: "https://yourapp.com/callback",
RedirectToSettings: true
)
);
var response = await authsignal.Track(request);
var url = response.Url;
```
```java Java {6} theme={null}
TrackRequest request = new TrackRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
request.action = "manageAuthenticators";
request.attributes = new TrackAttributes();
request.attributes.redirectUrl = https://yourapp.com/callback";
request.attributes.redirectToSettings = true;
TrackResponse response = authsignal.track(request).get();
String url = response.url;
```
```ruby Ruby {6} theme={null}
response = Authsignal.track({
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "manageAuthenticators",
attributes: {
redirect_url: "https://yourapp.com/callback",
redirect_to_settings: true
}
})
url = response[:url]
```
```python Python {6} theme={null}
response = authsignal.track(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="manageAuthenticators",
attributes={
"redirectUrl": "https://yourapp.com/callback",
"redirectToSettings": True
}
)
url = response["url"]
```
```php PHP {6} theme={null}
$response = Authsignal::track([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "manageAuthenticators",
'attributes' => [
'redirectUrl' => "https://yourapp.com/callback",
'redirectToSettings' => true
]
]);
$url = $response["url"]
```
```go Go {1, 8} theme={null}
redirectToSettings := true
response, err := client.Track(
TrackRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "manageAuthenticators",
Attributes: &TrackAttributes{
RedirectUrl: "https://yourapp.com/callback",
RedirectToSettings: &redirectToSettings,
},
},
)
url := response.Url
```
If a user has previously enrolled email OTP, for example, then they can complete an email OTP challenge in order to enroll passkey as another authentication option.
This security requirement ensures [strong binding](/advanced-usage/authenticator-binding) between
authenticators, preventing attackers from adding new methods if they compromise a single factor.
## Enrolling email and SMS authenticators
By default Authsignal's pre-built UI requires users to enter their email address or phone number when enrolling an email or SMS-based authenticator.
If you've already captured the user's email in your own system, however, then you can skip this step by passing the user's email or phone number in the track request.
```ts Node.js {6} theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "enroll",
attributes: {
redirectUrl: "https://yourapp.com/callback",
email: "jane.smith@authsignal.com",
},
};
const response = await authsignal.track(request);
```
```csharp C# {6} theme={null}
var request = new TrackRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "enroll",
Attributes: new TrackAttributes(
RedirectUrl: "https://yourapp.com/callback",
Email: "jane.smith@authsignal.com",
)
);
var response = await authsignal.Track(request);
```
```java Java {6} 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.email = "jane@authsignal.co";
TrackResponse response = authsignal.track(request).get();
```
```ruby Ruby {6} theme={null}
response = Authsignal.track({
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "enroll",
attributes: {
redirect_url: "https://yourapp.com/callback",
email: "jane.smith@authsignal.com",
}
})
```
```python Python {6} theme={null}
response = authsignal.track(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="enroll",
attributes={
"redirectUrl": "https://yourapp.com/callback",
"email": "jane.smith@authsignal.com",
}
)
```
```php PHP {6} theme={null}
$response = Authsignal::track([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "enroll",
'attributes' => [
'redirectUrl' => "https://yourapp.com/callback",
'email' => "jane.smith@authsignal.com",
]
]);
```
```go Go {7} theme={null}
response, err := client.Track(
TrackRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "enroll",
Attributes: &TrackAttributes{
RedirectUrl: "https://yourapp.com/callback",
Email: "jane.smith@authsignal.com",
},
},
)
```
This will skip the email or phone number input step during enrollment, but still allow the user to edit the value later.
If you wish to prevent the user from editing the value, this can be configured in the [Authsignal Portal](https://portal.authsignal.com/organisations/tenants/authenticators) by disabling the **self-service management** setting on the relevant authenticator configuration.
Additionally, if you're already verified the user's email in your own system then you can [programmatically enroll them with an email or SMS authenticator](/advanced-usage/programmatic-authenticator-management) at an appropriate point in your app (e.g. during a registration flow).
# Localization
Source: https://docs.authsignal.com/implementation-options/prebuilt-ui/localization
Configure the language and locale of the pre-built UI for your users.
The pre-built UI supports multiple languages out of the box. By default, the locale is automatically detected from the user's browser language settings, falling back to English if the detected language is not supported.
## Supported languages
| Language | Locale code |
| ---------------------- | ----------- |
| English | `en` |
| French | `fr` |
| French (Canadian) | `fr-ca` |
| German | `de` |
| Spanish | `es` |
| Italian | `it` |
| Dutch | `nl` |
| Polish | `pl` |
| Portuguese (Brazilian) | `pt-br` |
Need a language that isn't listed? [Get in touch](https://www.authsignal.com/contact) and we can work with you to add support for it.
## Overriding the locale
You can override the automatic locale detection by including the `locale` field in the [track action](/api-reference/server-api/track-action) request payload.
The `locale` value will be included in the pre-built UI URL automatically.
This is useful when:
* Your application already knows the user's preferred language
* You want to ensure a consistent language regardless of the user's browser settings
```ts Node.js {6} theme={null}
const response = await authsignal.track({
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
attributes: {
redirectUrl: "https://yourapp.com/callback",
locale: "es",
},
});
const url = response.url;
```
```csharp C# {5} theme={null}
var response = await authsignal.Track(new TrackRequest(
UserId: user.Id,
Action: "signIn",
Attributes: new TrackAttributes(
RedirectUrl: "https://yourapp.com/callback",
Locale: "es"
)
));
var url = response.Url;
```
```ruby Ruby {6} theme={null}
response = Authsignal.track({
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
attributes: {
redirect_url: "https://yourapp.com/callback",
locale: "es"
}
})
url = response[:url]
```
```python Python {5} theme={null}
response = authsignal.track(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="signIn",
attributes={
"redirectUrl": "https://yourapp.com/callback",
"locale": "es"
}
)
url = response["url"]
```
```php PHP {5} theme={null}
$response = Authsignal::track([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "signIn",
'attributes' => [
'redirectUrl' => "https://yourapp.com/callback",
'locale' => "es"
]
]);
$url = $response["url"]
```
```go Go {7} theme={null}
response, err := client.Track(
TrackRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
Attributes: &TrackAttributes{
RedirectUrl: "https://yourapp.com/callback",
Locale: "es",
},
},
)
url := response.Url
```
The `locale` field only accepts values from the supported languages list above.
If an unsupported value is provided, it will be ignored and the pre-built UI will fall back to browser-based detection.
# Opt-in consent checkbox
Source: https://docs.authsignal.com/implementation-options/prebuilt-ui/opt-in-consent
Require users to check a consent checkbox before receiving an SMS during enrollment.
The opt-in consent checkbox allows you to collect explicit user consent before sending an SMS. This is useful for complying with regulations such as 10DLC, TCPA, or other messaging consent requirements.
When enabled, users must check a consent checkbox on the phone number entry screen in the [pre-built UI](/implementation-options/prebuilt-ui/overview) before they can proceed with SMS enrollment.
## Configuration
To enable the opt-in consent checkbox:
1. Navigate to [Authenticators](https://portal.authsignal.com/organisations/tenants/authenticators) in the Authsignal Portal
2. Select **SMS OTP**
3. Scroll to **Pre-built UI settings**
4. Enable **Show opt-in checkbox**
Once enabled, a rich text editor will appear where you can configure the consent message displayed alongside the checkbox.
## Localization
The consent message can be configured per language. Select a language tab in the editor to provide a translated version of your consent message.
If no message is configured for the user's locale, the first available configured language will be used as a fallback.
# Pre-built UI
Source: https://docs.authsignal.com/implementation-options/prebuilt-ui/overview
Rapidly implement web-based authentication flows using Authsignal's pre-built UI.
## How it works
Your app redirects to a web page hosted by Authsignal (or displays it modally in an iframe).
This is often the quickest way to integrate.
You can configure your own [custom domain](/implementation-options/prebuilt-ui/custom-domains) and you can [heavily customize](/implementation-options/prebuilt-ui/custom-branding) the look and feel.
## Integration steps
```mermaid theme={null}
sequenceDiagram
participant F as Your frontend
participant B as Your backend
participant A as Authsignal
B->>A: (1) Track action
A->>B: Pre-built UI URL
B->>F: Pre-built UI URL
Note left of F: (2) Present challenge via pre-built UI
F->>B: Check result
B->>A: (3) Validate challenge
A->>B: Challenge result
```
### 1. Backend - Track action
In your app's backend, track an action which represents what your user is doing, e.g. `signIn`.
You can do this using one of the [Authsignal Server SDKs](/sdks/server) or you can call the [Authsignal Server API](/api-reference/server-api) for a REST integration.
```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. Frontend - Launch the pre-built UI
In your app's 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.
```javascript theme={null}
authsignal.launch(url);
```
### 3. Backend - Validate challenge
Once the user has completed the challenge, Authsignal will send them back to the redirect URL you provided in step 1, appending a `token` as a query param which you can use to determine the result of the challenge server-side.
```ts Node.js theme={null}
const request = {
action: "signIn",
token: "eyJhbGciOiJ...",
};
const response = await authsignal.validateChallenge(request);
if (response.state === "CHALLENGE_SUCCEEDED") {
// The user completed the challenge successfully
// Proceed with authenticated action
} else {
// The user did not complete the challenge successfully
}
```
```csharp C# theme={null}
var request = new ValidateChallengeRequest(
Action: "signIn,
Token: "eyJhbGciOiJ..."
);
var response = await authsignal.ValidateChallenge(request);
if (response.State == UserActionState.CHALLENGE_SUCCEEDED) {
// The user completed the challenge successfully
// Proceed with authenticated action
} else {
// The user did not complete the challenge successfully
}
```
```java Java theme={null}
ValidateChallengeRequest request = new ValidateChallengeRequest();
request.action = "signIn";
request.token = "eyJhbGciOiJ...";
ValidateChallengeResponse response = authsignal.validateChallenge(request).get();
if (response.state == UserActionState.CHALLENGE_SUCCEEDED) {
// The user completed the challenge successfully
// Proceed with authenticated action
} else {
// The user did not complete the challenge successfully
}
```
```ruby Ruby theme={null}
response = Authsignal.validate_challenge(
action: "signIn",
token: "eyJhbGciOiJ..."
)
if response[:state] == "CHALLENGE_SUCCEEDED"
# The user completed the challenge successfully
# Proceed with authenticated action
else
# The user did not complete the challenge successfully
end
```
```python Python theme={null}
response = authsignal.validate_challenge(
attributes={
"action": "signIn",
"token": "eyJhbGciOiJ..."
}
)
if response["state"] == "CHALLENGE_SUCCEEDED":
# The user completed the challenge successfully
# Proceed with authenticated action
else:
# The user did not complete the challenge successfully
```
```php PHP theme={null}
$response = Authsignal::validateChallenge([
'action' => "signIn",
'token' => "eyJhbGciOiJ...",
]);
if ($response["state"] === "CHALLENGE_SUCCEEDED") {
# The user completed the challenge successfully
# Proceed with authenticated action
} else {
# The user did not complete the challenge successfully
}
```
```go Go theme={null}
response, err := client.ValidateChallenge(
ValidateChallengeRequest{
Action: "signIn",
Token: "eyJhbGciOiJ...",
},
)
if response.State == "CHALLENGE_SUCCEEDED" {
// The user completed the challenge successfully
// Proceed with authenticated action
} else {
// The user did not complete the challenge successfully
}
```
## Next steps
* [Launching the pre-built UI](/implementation-options/prebuilt-ui/presentation-modes)
# Passkey uplift prompt
Source: https://docs.authsignal.com/implementation-options/prebuilt-ui/passkey-uplift-prompt
Accelerate passkey adoption by prompting users to create passkeys.
The passkey uplift prompt is how a user who doesn't yet have a passkey gets one. It's the equivalent of an "add a new passkey" flow, optimized for passkey adoption: instead of asking users to find a setting and manually enroll, Authsignal automatically prompts them to create a passkey at the right moment.
To accelerate adoption, Authsignal will automatically prompt users who have enrolled a [recovery method](/authentication-methods/passkey/prebuilt-ui#choose-recovery-methods) to create a passkey. This includes users signing in on a new device that doesn't have a passkey available yet, so the same flow covers improving passkey coverage across a user's devices.
## Controlling the prompt frequency
By default, the passkey uplift prompt will be shown after every successful authentication. You can change the frequency
of the prompt in the [pre-built UI passkey settings](https://portal.authsignal.com/organisations/tenants/authenticators/passkey).
## Disabling the prompt
To opt out of the passkey uplift prompt, you can disable it in the [pre-built UI passkey settings](https://portal.authsignal.com/organisations/tenants/authenticators/passkey).
## Advanced configuration
If you want finer control over when the passkey uplift prompt is shown, you can configure it on a per-action or per-rule basis.
First, make sure to opt out of the default behavior by disabling the passkey uplift prompt in the pre-built UI passkey settings.
To configure the prompt for an action:
* Navigate to your action's configuration settings in the [Authsignal Portal](https://portal.authsignal.com/actions).
* Go to the **Settings** tab.
* Toggle the **Prompt users to add a passkey** switch to **on**.
To configure the prompt for a rule:
* Navigate to your action's configuration settings in the [Authsignal Portal](https://portal.authsignal.com/actions).
* Go to the **Rules** tab.
* View your rule and go to the **Settings** tab.
* Toggle the **Prompt users to add a passkey** switch to **on**.
## Troubleshooting visibility
There are a few reasons a user may not see the uplift prompt:
* **Device doesn't support passkeys** — the prompt is only shown on devices that support passkeys.
* **Passkey already enrolled on the device** — if the user has already set up a passkey on their current device, the prompt will not be shown.
* **Prompt frequency hasn't elapsed** — if **Prompt frequency** is set to anything other than "Every Challenge", the prompt will only reappear once the configured interval has passed since the user last dismissed it.
# Presentation modes
Source: https://docs.authsignal.com/implementation-options/prebuilt-ui/presentation-modes
Learn about the Authsignal pre-built UI's different presentation modes.
The pre-built UI can be launched using [Authsignal's Web SDK](/sdks/client/web/prebuilt-ui) in either **redirect** or **popup** mode.
Alternatively, you can handle displaying the pre-built UI (via redirect or modal) yourself for this step - using the Authsignal Web SDK is optional.
## Redirect mode
* Works with both client-side and server-side redirects.
* Typically looks better on mobile.
* Requires additional implementation to handle the redirect back to your app after the user completes the challenge, e.g. via a callback page.
* Passkey registration works seamlessly across browsers, e.g. when using the [passkey uplift prompt](/implementation-options/prebuilt-ui/passkey-uplift-prompt).
## Popup mode
* Allows users to complete authentication without leaving the context of your application.
* Simpler to implement if you need to maintain in-memory state in the browser during the authentication process, e.g. for a shopping cart or checkout flow.
* Support for passkey registration within an iframe is inconsistent across different browsers.
Both redirect mode and popup mode can be customized with your branding using our [UI customization tool](/implementation-options/prebuilt-ui/custom-branding).
## Integration steps
### Step 1 - Backend
Obtain a URL by [tracking an action](/implementation-options/prebuilt-ui/overview#1-backend-track-action) on your backend.
### Step 2 - Frontend
#### Redirect mode
Launch an enrollment or re-authentication flow by redirecting to the URL either server-side or client-side.
The following code snippet demonstrates how to launch the pre-built UI client-side using the [Authsignal Web SDK](/sdks/client/web/prebuilt-ui).
```javascript theme={null}
authsignal.launch(url);
```
The pre-built UI will be launched on your [custom
domain](/implementation-options/prebuilt-ui/custom-domains) if you have set one up in the
[Authsignal Portal](https://portal.authsignal.com/organisations/tenants/custom_domains). If you
don't have a domain setup, the domain will be mfa.authsignal.com.
After the user completes the challenge, Authsignal will redirect them to the URL provided in your track action call, appending a token as a query parameter. You can use this token to [determine the result of the challenge server-side](/implementation-options/prebuilt-ui/overview#3-backend-validate-challenge).
#### Popup mode
Launch an enrollment or re-authentication flow. The following code snippet demonstrates how to do this with the [Authsignal Web SDK](/sdks/client/web/prebuilt-ui).
```javascript theme={null}
const { token } = await authsignal.launch(url, { mode: "popup" });
```
Once the user has completed the challenge, the token returned from Authsignal's `launch` function can be used to [determine the result of the challenge server-side](/implementation-options/prebuilt-ui/overview#3-backend-validate-challenge).
# Removing authenticators
Source: https://docs.authsignal.com/implementation-options/prebuilt-ui/removing-authenticators
Let users remove their authenticators via Authsignal's pre-built UI
Users may want to remove authentication methods for various reasons:
* Switching to a new device or authenticator app
* No longer having access to an email or phone number
* Simplifying their authentication setup
The Authsignal pre-built UI provides a secure, user-friendly way for users to remove authenticators.
## Implementation steps
**1. Backend: Track action with settings redirect**
Passing `redirectToSettings: true` in the track request will mean that after completing a challenge with an existing authentication method, users will be redirected to a settings menu where they can remove authentication methods.
```ts Node.js {6} theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "manageAuthenticators",
attributes: {
redirectUrl: "https://yourapp.com/callback"
redirectToSettings: true,
},
};
const response = await authsignal.track(request);
const url = response.url;
```
```csharp C# {6} theme={null}
var request = new TrackRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "manageAuthenticators",
Attributes: new TrackAttributes(
RedirectUrl: "https://yourapp.com/callback",
RedirectToSettings: true
)
);
var response = await authsignal.Track(request);
var url = response.Url;
```
```java Java {6} theme={null}
TrackRequest request = new TrackRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
request.action = "manageAuthenticators";
request.attributes = new TrackAttributes();
request.attributes.redirectUrl = https://yourapp.com/callback";
request.attributes.redirectToSettings = true;
TrackResponse response = authsignal.track(request).get();
String url = response.url;
```
```ruby Ruby {6} theme={null}
response = Authsignal.track({
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "manageAuthenticators",
attributes: {
redirect_url: "https://yourapp.com/callback",
redirect_to_settings: true
}
})
url = response[:url]
```
```python Python {6} theme={null}
response = authsignal.track(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="manageAuthenticators",
attributes={
"redirectUrl": "https://yourapp.com/callback",
"redirectToSettings": True
}
)
url = response["url"]
```
```php PHP {6} theme={null}
$response = Authsignal::track([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "manageAuthenticators",
'attributes' => [
'redirectUrl' => "https://yourapp.com/callback",
'redirectToSettings' => true
]
]);
$url = $response["url"]
```
```go Go {1, 8} theme={null}
redirectToSettings := true
response, err := client.Track(
TrackRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "manageAuthenticators",
Attributes: &TrackAttributes{
RedirectUrl: "https://yourapp.com/callback",
RedirectToSettings: &redirectToSettings,
},
},
)
url := response.Url
```
**2. Frontend: Launch settings flow**
```javascript theme={null}
// Launch the pre-built UI for authenticator management
authsignal.launch(url);
```
**3. User experience**
Users will:
1. Complete a challenge with one of their existing authenticators
2. Access the settings menu where they can view all their enrolled methods
3. Remove unwanted authenticators
The pre-built UI automatically enforces security by requiring authentication before allowing
removal. Users cannot remove their last remaining authenticator to prevent account lockout.
## Administrative removal
Admins can remove authenticators for users through the [Authsignal Portal](https://portal.authsignal.com/users):
1. Navigate to the Users section
2. Search for and select the user
3. Scroll down to see the enrolled authenticators
4. Remove specific methods as needed
## Next steps
* [Programmatic authenticator removal](/advanced-usage/programmatic-authenticator-management)
# Web SDK
Source: https://docs.authsignal.com/implementation-options/web-sdk/overview
Implement authentication flows in web browsers while maintaining full control over your own UI.
You can use Authsignal's [Web SDK](/sdks/client/web/setup) to implement authentication flows in web browsers while crafting your own UI.
## Authentication methods
The Web SDK can be used to implement a variety of authentication methods.
Add passkeys to your web app for phishing-resistant passwordless sign-in and step-up
authentication.
Authenticate with time-based one-time passcodes generated in an authenticator app such as Google
Authenticator.
Send OTPs via email to authenticate users and verify email addresses.
Send magic links via email to authenticate users and verify email addresses.
Send OTPs via SMS to authenticate users and verify phone numbers.
Authenticate with OTPs sent via WhatsApp as a cost-saving alternative to traditional SMS.
Allow users to sign-in across devices using push notifications and public key cryptography.
Allow users to sign-in across devices using QR codes and public key cryptography.
## Get started
Follow the [SDK documentation](/sdks/client/web/setup) on setting up the Web SDK.
# Using Authsignal with Auth0
Source: https://docs.authsignal.com/integrations/auth0
Learn how to integrate Authsignal with Auth0 to add MFA to your login flow, using methods such as Authenticator App, SMS OTP, Email OTP, and Passkeys.
This guide will demonstrate how to integrate Authsignal with Auth0 by creating a [Custom Action for the Login / Post-Login trigger](https://auth0.com/docs/customize/actions/flows-and-triggers/login-flow#login-post-login).
The end result will be to allow users to complete an MFA challenge after they login with their username and password, redirecting them from the Auth0 Universal Login Page to Authsignal's [pre-built UI](/implementation-options/prebuilt-ui/overview).
The integration follows Auth0’s official guidelines for redirecting users after login to do custom MFA.
You can learn more here about how to [Redirect with Actions in Auth0](https://auth0.com/docs/customize/actions/flows-and-triggers/login-flow/redirect-with-actions).
## User flow
## Integration steps
In the Auth0 Dashboard, go to Actions → Library and select “Build Custom”. Then select the “Login / Post Login” trigger and give the action an appropriate name (for example “post-login-mfa”).
Now your action has been created, create a new secret called `AUTHSIGNAL_SECRET` and provide the value of your secret from the Api Keys section in the Authsignal Portal.
Add the `@authsignal/node` dependency.
Add the following code snippet to the action.
```javascript theme={null}
const { handleAuth0ExecutePostLogin, handleAuth0ContinuePostLogin } = require("@authsignal/node");
exports.onExecutePostLogin = handleAuth0ExecutePostLogin;
exports.onContinuePostLogin = handleAuth0ContinuePostLogin;
```
If using a non-US region (e.g. AU or EU) you will need to override the API URL as follows:
```javascript theme={null}
const { handleAuth0ExecutePostLogin, handleAuth0ContinuePostLogin } = require("@authsignal/node");
const apiUrl = "https://eu.api.authsignal.com/v1";
exports.onExecutePostLogin = async (event, api) => {
await handleAuth0ExecutePostLogin(event, api, { apiUrl });
};
exports.onContinuePostLogin = async (event, api) => {
await handleAuth0ContinuePostLogin(event, api, { apiUrl });
};
```
Now connect your action in the Flows section of the Auth0 Dashboard by dragging it into the Login flow.
Once the previous steps have been completed, the next time the Auth0 action is run you will see an action appear in the Authsignal Portal called **“Auth0 Login”.** You will need to set the default outcome for this action to `Challenge` and save it.
That’s it! You’ve done everything required to add MFA to your Auth0 login flow using Authsignal.
## Next steps
* [Refining the MFA experience](/integrations/auth0-refine-mfa)
* [Using an internal user ID](/integrations/auth0-internal-user-id)
# Using an internal user ID
Source: https://docs.authsignal.com/integrations/auth0-internal-user-id
Learn how to use your internal user ID when integrating Authsignal with Auth0 instead of the default Auth0 user ID.
You may want to configure your Authsignal integration with Auth0 to use an internal user ID instead of defaulting to use the Auth0 user ID.
If you are storing your own internal user ID in Auth0 - for example as [user metadata](https://auth0.com/docs/manage-users/user-accounts/metadata) - then you can pass this in your custom action code snippet.
```javascript theme={null}
const {
handleAuth0ExecutePostLogin,
handleAuth0ContinuePostLogin,
} = require("@authsignal/node");
exports.onExecutePostLogin = async (event, api) => {
const userId = event.user.user_metadata["your_internal_user_id"];
await handleAuth0ExecutePostLogin(event, api, { userId });
};
exports.onContinuePostLogin = async (event, api) => {
const userId = event.user.user_metadata["your_internal_user_id"];
await handleAuth0ContinuePostLogin(event, api, { userId });
};
```
Passing an internal user ID here is recommended if you're also [tracking
actions](/api-reference/server-api/track-action) in other parts of your app
not related to Auth0 login - you should always use a consistent identifier.
# Auth0 Native Bridge
Source: https://docs.authsignal.com/integrations/auth0-native-bridge
Learn how to use issue an Auth0 access token during authsignal flows like passkey auto-fill
In this guide we will show you how to complete authentication with Auth0 in a native mobile app, when using Authsignal's passkey implementation.
The integration requires configuring a new custom database connection, configuring your Application in Auth0, and finally implementing a back-end API to validate the Authsignal challenge and issue an Auth0 access token.
## Integration steps
### Create a new custom database connection
Click on the **Create DB Connection** button.
Scroll down to **Database Action Scripts**, copy and paste the following database action script for "Login"
```javascript theme={null}
function login(identifierValue, password, context, callback) {
const request = require("request");
// Ensure that the Authsignal API host name is pointing to the correct region
// For example, if you're using the US region, the host name should be https://us.connect.authsignal.com
// If you're using the EU region, the host name should be https://eu.connect.authsignal.com
request.post(
{
url: "https://us.connect.authsignal.com/get-user",
auth: {
user: configuration.AUTHSIGNAL_SECRET,
sendImmedaiately: true,
},
json: {
token: password,
},
},
function (err, response, body) {
if (err) return callback(err);
if (response.statusCode >= 400) return callback();
const user = body;
if (user.userId) {
const profile = {
email: user.email,
email_verified: true,
user_id: user.userId.split("|")[1],
};
return callback(null, profile);
}
}
);
}
```
Scroll down to the **Database Settings** section and add your Authsignal Server API secret key to database settings.
That's it, we've created a new custom database connection, which will allow us to use Authsignal as an external authentication source.
### Configure your Auth0 Application
In this guide we are using the "Default App" as an example, in order for your integration work
ensure that the application you're using is created as a **Regular Web Application**.
Navigate to the Auth0 Application section and click menu for on the application you want to configure, and select **Connections**
Ensure that the **"authsignal"** (the name of the custom database connection we created in the
previous step) database connection is turned on.
Click to check the **Password** grant type.
### Implement a back-end API for validation
Once the above steps are completed, you'll be able to complete the final step of the integration, the back-end API implementation takes care of validating the Authsignal challenge token (for example when a user completes a passkey [sign-in](/sdks/client/mobile/passkeys#using-a-passkey)) and issues an Auth0 access token.
The following steps will guide you through the two critical steps that you need to implement in your back-end API. In this guide we will use a Node.js implementation, but you can use any language you prefer.
Initiate Passkey on your mobile client, see [documentation](/sdks/client/mobile/passkeys#using-a-passkey).
Pass client side token to the implemented back-end API. In your implementation you are doing the following:
* Authsignal [Validate Challenge](/sdks/server/challenges#validate-challenge)
* Auth0 [Resource Owner Password Grant API](https://auth0.com/docs/get-started/authentication-and-authorization-flow/resource-owner-password-flow/call-your-api-using-resource-owner-password-flow).
In this guide we've used the `scope` values of `openid profile email`, and had a placeholder for the `audience` value, but you'll have to configure the appropriate values for your application.
```ts Node.js theme={null}
const request = {
token: "eyJhbGciOiJ...",
};
// Step 1. Validate the Authsignal challenge token
const response = await authsignal.validateChallenge(request);
if (response.state === "CHALLENGE_SUCCEEDED") {
// Step 2. Call Auth0 Resource Owner Password Grant API to issue an access token
const user = await authsignal.getUser({userId: response.userId});
const response = await fetch(`https://{yourAuth0Domain}/oauth/token`, {
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'http://auth0.com/oauth/grant-type/password-realm',
username: user.email,
password: request.token,
scope: 'openid profile email',
audience: '{yourApiIdentifier}',
client_id: '{yourClientId}',
client_secret: '{yourClientSecret}',
realm: 'authsignal'
})
});
try {
const data = await response.json();
const accessToken = data.access_token;
const idToken = data.id_token;
} catch (error) {
// The Auth0 custom database login script failed to validate
}
} else {
// The user did not complete the challenge successfully
}
```
```curl cURL theme={null}
curl -X POST \
'https://{yourAuth0Domain}/oauth/token' \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'grant_type=http://auth0.com/oauth/grant-type/password-realm' \
-d 'username={yourUsersEmailAddress}' \
-d 'password={authsignalToken}' \
-d 'scope=openid profile email' \
-d 'audience={yourApiIdentifier}' \
-d 'client_id={yourClientId}' \
-d 'client_secret={yourClientSecret}' \
-d 'realm=authsignal'
```
We've now completed the integration, please do reach out to us if you have any questions or need any assistance.
# Adding adaptive MFA to Auth0
Source: https://docs.authsignal.com/integrations/auth0-refine-mfa
Learn how to add adaptive MFA to your Auth0 login flow using Authsignal.
## Controlling the frequency of MFA challenges based on device
By default a user will be required to complete an MFA challenge every time they sign in.
This behavior can be fine-tuned by configuring a rule for your Auth0 login action - for example, you could let users skip MFA if you know they have previously authenticated within the last 24 hours on the same device.
To enable this rule you must send a **device ID** to Authsignal.
This can be achieved by adding `device_id` as an [authorization param](https://auth0.github.io/auth0-spa-js/interfaces/AuthorizationParams.html) in your Auth0 integration code.
If you are already tracking device ID in your own app you can pass this value - or use the [Authsignal Web SDK](/sdks/client/web/device-tracking) to pass a persistent device ID which is stored in a cookie.
For example if using the [auth0-spa-js](https://auth0.github.io/auth0-spa-js/interfaces/AuthorizationParams.html) library:
```javascript theme={null}
await loginWithRedirect({
authorizationParams: { device_id: authsignal.anonymousId },
});
```
Or if using the [auth0-react](https://auth0.github.io/auth0-react/interfaces/Auth0ProviderOptions.html) library:
```jsx theme={null}
```
Or if using the [nextjs-auth0](https://auth0.github.io/nextjs-auth0/interfaces/types.AuthorizationParameters.html) library:
```javascript theme={null}
await handleLogin(req, res, {
authorizationParams: { device_id: authsignal.anonymousId },
});
```
## Opting out for social or federated logins
You may want to let users opt out of MFA if they are using an external social login provider, for example Google or Facebook.
This can easily be achieved by checking the connection on the `event` object.
```javascript theme={null}
const { handleAuth0ExecutePostLogin, handleAuth0ContinuePostLogin } = require("@authsignal/node");
/**
* @param {Event} event
* @param {PostLoginAPI} api
*/
exports.onExecutePostLogin = async (event, api) => {
// Only redirect for auth0 database connections, not social or federated
if (event.connection.strategy === "auth0") {
await handleAuth0ExecutePostLogin(event, api);
}
};
exports.onContinuePostLogin = handleAuth0ContinuePostLogin;
```
## Using the Server SDK directly for more customization and control
To allow for more customization and control you can also use the [Authsignal Server SDK](/sdks/server/overview) directly from within your Auth0 custom action.
```javascript theme={null}
const { Authsignal, UserActionState } = require("@authsignal/node");
/**
* @param {Event} event
* @param {PostLoginAPI} api
*/
exports.onExecutePostLogin = async (event, api) => {
// Redirects are not possible for refresh token exchange
// https://auth0.com/docs/customize/actions/flows-and-triggers/login-flow/redirect-with-actions#refresh-tokens
if (event.transaction?.protocol === "oauth2-refresh-token") {
return;
}
const secret = event.secrets.AUTHSIGNAL_SECRET;
const userId = event.user.user_id;
const redirectUrl = `https://${event.request.hostname}/continue`;
const authsignal = new Authsignal({ secret });
const { state, url, isEnrolled } = await authsignal.track({
action: "auth0-login",
userId,
redirectUrl,
// optional
email: event.user.email,
ipAddress: event.request.ip,
userAgent: event.request.user_agent,
deviceId: event.request.query?.["device_id"],
});
// Redirect to the Challenge UI if a challenge is required
// Also redirect users not yet enrolled so they can enroll
if (state === UserActionState.CHALLENGE_REQUIRED || !isEnrolled) {
api.redirect.sendUserTo(url);
}
};
/**
* @param {Event} event
* @param {PostLoginAPI} api
*/
exports.onContinuePostLogin = async (event, api) => {
const secret = event.secrets.AUTHSIGNAL_SECRET;
const userId = event.user.user_id;
const authsignal = new Authsignal({ secret });
const result = await authsignal.validateChallenge({
token: event.request.query?.["token"],
userId,
});
if (result && result.state !== "CHALLENGE_SUCCEEDED") {
api.access.deny("Access denied");
}
};
```
## Next steps
* [Using an internal user ID](/integrations/auth0-internal-user-id)
* [Using rules to implement risk-based authentication](/actions-rules/rules/getting-started#creating-a-rule-to-challenge-high-risk-users)
* [Using business-specific data points in rules](/actions-rules/rules/custom-data-points)
* [Restricting authenticators with actions](/actions-rules/actions/restricting-authenticators)
# Managing authenticators when integrating Authsignal with Amazon Cognito
Source: https://docs.authsignal.com/integrations/aws-cognito/authenticators
Learn how to let users enroll and manage different authenticators when integrating Authsignal with Amazon Cognito.
In the [previous guide](/integrations/aws-cognito/getting-started) we demonstrated how to enroll the user's first authenticator as part of a combined sign-up/sign-in flow.
This guide will cover the implementation steps for letting users enroll and manage multiple different authentication methods when integrating Authsignal with Amazon Cognito.
## Github example code
## Enrolling more authenticators
Once the user has enrolled their first authentication method and has signed in, we want to give them the option to enroll more authentication methods.
As an example, we'll demonstrate how to let users enroll **email OTP** as a second authentication method - but the implementation approach will be similar for all authentications methods.
While authentication methods can be enrolled in any sequence, it's recommended to only let users
enroll [passkeys](/integrations/aws-cognito/passkeys) after they've first enrolled a method like
email or SMS OTP, so users always have a fallback option in cases where their passkey \[isn't
available.
### Lambda integration
To authorize binding the new authentication method to the user, we will create an **authenticated API endpoint** which our app can call once they've signed in.
This endpoint will use a [JWT Authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html) to authenticate using the Cognito access token.
It will return a short-lived **Authsignal token** which we'll use in the following step.
```ts theme={null}
export const handler = async (event: APIGatewayProxyEventV2WithJWTAuthorizer) => {
const { token: authsignalToken } = await authsignal.track({
userId: event.requestContext.authorizer.jwt.claims.sub,
action: "addAuthenticator",
attributes: {
scope: "add:authenticators",
},
});
return {
authsignalToken,
};
};
```
To authorize binding the new authentication method to the user, we will create an **authenticated API endpoint** which our app can call once they've signed in.
This endpoint will use a [JWT Authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html) to authenticate using the Cognito access token.
It will return a short-lived **Authsignal pre-built UI URL** which we'll use in the following step.
```ts theme={null}
export const handler = async (event: APIGatewayProxyEventV2WithJWTAuthorizer) => {
const { url: prebuiltUiUrl } = await authsignal.track({
action: "manageAuthenticators",
userId: event.requestContext.authorizer.jwt.claims.sub,
attributes: {
// Only required for redirect mode
redirectUrl: "https://yourapp.com/callback",
},
});
return {
prebuiltUiUrl,
};
};
```
### App integration
1. Call our endpoint to obtain the Authsignal token.
```ts theme={null}
// Replace with your API endpoint URL
const apiEndpointUrl = "https://abcd1234.execute-api.us-west-1.amazonaws.com/authenticators";
const authsignalToken = await fetch(apiEndpointUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${cognitoAccessToken}`,
},
})
.then((res) => res.json())
.then((json) => json.authsignalToken);
```
2. Enroll the email OTP authenticator using a Client SDK.
```ts Web theme={null}
// 1. Set the token obtained from your API endpoint
authsignal.setToken(token);
// 2. Send the user an OTP code via email
await authsignal.email.enroll({ email: "user@example.com" });
// 3. Verify the code inputted by the user to complete the enrollment
const response = await authsignal.email.verify({ code: "123456" });
```
```swift iOS theme={null}
// 1. Set the token obtained from your API endpoint
authsignal.setToken(token)
// 2. Send the user an OTP code via email
await authsignal.email.enroll(email: "user@example.com")
// 3. Verify the code inputted by the user to complete the enrollment
let response = await authsignal.email.verify(code: "123456")
```
```kotlin Android theme={null}
// 1. Set the token obtained from your API endpoint
authsignal.setToken(token)
// 2. Send the user an OTP code via email
authsignal.email.enroll(email = "user@example.com").get()
// 3. Verify the code inputted by the user to complete the enrollment
val response = authsignal.email.verify(code = "123456").get()
```
```ts React Native theme={null}
// 1. Set the token obtained from your API endpoint
await authsignal.setToken(token);
// 2. Send the user an OTP code via email
await authsignal.email.enroll({ email: "user@example.com" });
// 3. Verify the code inputted by the user to complete the enrollment
const response = await authsignal.email.verify({ code: "123456" });
```
```dart Flutter theme={null}
// 1. Set the token obtained from your API endpoint
await authsignal.setToken(token);
// 2. Send the user an OTP code via email
await authsignal.email.enroll(email: "user@example.com");
// 3. Verify the code inputted by the user to complete the enrollment
const response = await authsignal.email.verify(code: "123456");
```
For more information on how to use other authentication methods refer to our [Web SDK](/sdks/client/web/setup) and [Mobile SDK](/sdks/client/mobile/setup) documentation.
1. Call our endpoint to obtain the Authsignal pre-built UI URL.
```ts theme={null}
// Replace with your API endpoint URL
const apiEndpointUrl = "https://abcd1234.execute-api.us-west-1.amazonaws.com/authenticators";
const prebuiltUiUrl = await fetch(apiEndpointUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${cognitoAccessToken}`,
},
})
.then((res) => res.json())
.then((json) => json.prebuiltUiUrl);
```
2. Launch the pre-built UI URL (optionally using the Authsignal Web SDK).
```ts theme={null}
authsignal.launch(prebuiltUiUrl, { mode: "redirect" });
```
For more information refer to our [documentation on launching the pre-built UI](/implementation-options/prebuilt-ui/presentation-modes).
## Removing authenticators
The Authsignal Server SDK can be used within your authenticated API endpoint to get the user's currently enrolled authenticators and to remove an authenticator by its ID.
The example code below demonstrates how to remove the user's email OTP authenticator by using the Authsignal Server SDK inside an API endpoint which is authenticated using a JWT authorizer.
```ts theme={null}
export const handler = async (event: APIGatewayProxyEventV2WithJWTAuthorizer) => {
const userId = event.requestContext.authorizer.jwt.claims.sub;
// Get the user's currently enrolled authenticators
const authenticators = await authsignal.getAuthenticators({ userId });
// Find the email OTP authenticator ID
const emailOtpAuthenticator = authenticators.find((a) => a.verificationMethod === "EMAIL_OTP");
// Remove the email OTP authenticator
await authsignal.deleteAuthenticator({
userId,
userAuthenticatorId: emailOtpAuthenticator.userAuthenticatorId,
});
};
```
For more information refer to our [Server SDK documentation](/sdks/server/overview).
The Authsignal pre-built UI also supports removing authenticators when launched in settings mode.
For more information refer to our [pre-built UI documentation](/implementation-options/prebuilt-ui/overview).
## Next steps
* [Passkeys](/integrations/aws-cognito/passkeys)
* [Adaptive MFA](/integrations/aws-cognito/rules)
# Uber app example
Source: https://docs.authsignal.com/integrations/aws-cognito/example-app
Learn how to implement a sign-in UX inspired by the Uber app using SMS and email OTP, passkeys, and social login with Apple or Google.
This guide demonstrates how to use Authsignal's SDKs together with Cognito to achieve a sign-in UX similar to the Uber mobile app.
The example uses the Authsignal React Native SDK but a similar example could also be built using our [SDKs for iOS, Android, or Flutter](/sdks/client/mobile/setup).
## Github example code
## Authentication methods
Our example app uses Authsignal SDKs together with Cognito to rapidly implement five different authentication methods:
* SMS OTP (or WhatsApp OTP)
* Email OTP
* Passkey
* Sign in with Apple
* Sign in with Google
All of these authentication methods are validated in our [verify auth challenge response lambda](https://github.com/authsignal/cognito-lambdas/blob/main/triggers/verify-auth-challenge-response.ts) with a minimal amount of code:
```ts theme={null}
export const handler = async (event) => {
// For SMS, email OTP, and passkey this will be an Authsignal token
// For Apple and Google sign-in it will be an Apple or Google ID token
const token = event.request.challengeAnswer;
const { isValid } = await authsignal.validateChallenge({
action: "cognitoAuth",
userId: event.userName,
token,
});
event.response.answerCorrect = isValid;
return event;
};
```
### SMS
SMS OTP is the first authentication method which we present on the sign-in screen.
If the user opts to use SMS and it's their first time signing in, we also prompt them to:
* Input their email address after signing in
* Verify their email address via another OTP challenge
* Input their first and last name
We verify the email using Authsignal's SDKs, sharing the same UI implementation for [signing in with email OTP](https://github.com/authsignal/cognito-react-native-example/blob/main/src/screens/VerifyEmailScreen.tsx).
### Email OTP
Email OTP is an additional authentication method which the user can choose to sign in with.
If the user chooses this option and it's their first time signing in, we also prompt them to:
* Input their phone number after signing in
* Verify their phone number via another OTP challenge
* Input their first and last name
We verify the phone number using Authsignal's SDKs, sharing the same UI implementation for [signing in with SMS OTP](https://github.com/authsignal/cognito-react-native-example/blob/main/src/screens/VerifySmsScreen.tsx).
### Passkey
In our example app, a passkey can only be created after both phone number and email have been verified.
Once a passkey has been created the user can authenticate with it directly from the sign-in screen.
Following the approach taken by the Uber app, we present the passkey prompt automatically [when the sign-in screen appears](https://github.com/authsignal/cognito-react-native-example/blob/main/src/screens/SignInScreen.tsx#L38) if a passkey is available on the device.
In addition, we add a [small passkey icon inside the phone number input](https://github.com/authsignal/cognito-react-native-example/blob/main/src/screens/SignInScreen.tsx#L134) which the user can press at any time to make the passkey prompt reappear.
### Apple sign-in
Signing in with Apple is another authentication option which the user can choose.
To implement this we use the [react-native-apple-authentication](https://github.com/invertase/react-native-apple-authentication) library to obtain an Apple ID token.
```ts theme={null}
const appleAuthResponse = await appleAuth.performRequest({
requestedOperation: appleAuth.Operation.LOGIN,
requestedScopes: [appleAuth.Scope.FULL_NAME, appleAuth.Scope.EMAIL],
});
const idToken = appleAuthResponse.identityToken;
```
Then we send this ID token to Cognito as the challenge answer and use the Authsignal Server SDK to validate it within our [verify auth challenge response lambda](https://github.com/authsignal/cognito-lambdas/blob/main/triggers/verify-auth-challenge-response.ts).
### Google sign-in
Signing in with Google is the final authentication method which the user can select on our sign-in screen.
To implement this we use the [react-native-app-auth](https://github.com/FormidableLabs/react-native-app-auth) library to launch a web-based OIDC flow and obtain a Google ID token.
```ts theme={null}
const config = {
issuer: "https://accounts.google.com",
clientId: `${GOOGLE_OAUTH_APP_GUID}.apps.googleusercontent.com`,
redirectUrl: `com.googleusercontent.apps.${GOOGLE_OAUTH_APP_GUID}:/oauth2redirect/google`,
scopes: ["openid", "profile", "email"],
};
const authorizeResult = await authorize(config);
const idToken = authorizeResult.idToken;
```
Then we follow the same approach as for Apple sign-in, sending this ID token to Cognito as the challenge answer and using the Authsignal Server SDK to validate it within our [verify auth challenge response lambda](https://github.com/authsignal/cognito-lambdas/blob/main/triggers/verify-auth-challenge-response.ts).
# Integrating Authsignal with Amazon Cognito
Source: https://docs.authsignal.com/integrations/aws-cognito/getting-started
Learn how to integrate Authsignal with Amazon Cognito to rapidly implement passwordless authentication.
Amazon Cognito offers two approaches for authentication: a [managed login](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-managed-login.html) with a hosted UI, or integration via [Custom authentication challenge Lambda triggers](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html) when you need more control over the authentication UI and user experience.
To integrate Authsignal with Cognito, you'll use the second approach. Authsignal offers two ways of integrating with Cognito's custom authentication challenge Lambda triggers:
1. **Custom UI** - Build your own UI using our Client SDKs for [web](/sdks/client/web/setup) and [mobile](/sdks/client/mobile/setup) when you need complete control over the user experience.
2. **Pre-built UI** - Drop in our ready-to-use hosted UI that supports passkeys, SMS and WhatsApp OTP, and more. You can customize the design to match your brand.
## Github example code
## Lambda integration steps
### 1. Create auth challenge
The first lambda trigger which we will use is [Create auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-create-auth-challenge.html).
We will use the Authsignal Server SDK in this lambda to return a short-lived **challenge token**.
We'll also return whether the user is enrolled or not, so we know which Client SDK method to use in a subsequent step.
```ts theme={null}
export const handler: CreateAuthChallengeTriggerHandler = async (event) => {
const userId = event.request.userAttributes.sub;
// Only required when authenticating with email
const email = event.request.userAttributes.email;
// Only required when authenticating with SMS or WhatsApp
const phoneNumber = event.request.userAttributes.phone_number;
const { token } = await authsignal.track({
action: "cognitoAuth",
userId,
attributes: {
email,
phoneNumber,
},
});
event.response.publicChallengeParameters = { token };
return event;
};
```
We will use the Authsignal Server SDK in this lambda to return a short-lived **pre-built UI URL**.
```ts theme={null}
export const handler: CreateAuthChallengeTriggerHandler = async (event) => {
const userId = event.request.userAttributes.sub;
// Only required when authenticating with email
const email = event.request.userAttributes.email;
// Only required when authenticating with SMS or WhatsApp
const phoneNumber = event.request.userAttributes.phone_number;
// Required if launching the pre-built UI in redirect mode
// Authsignal will redirect back here after the user completes the challenge
const redirectUrl = "https://www.yourapp.com/callback";
const { url } = await authsignal.track({
action: "cognitoAuth",
userId,
attributes: {
email,
phoneNumber,
redirectUrl,
},
});
event.response.publicChallengeParameters = {
url,
};
return event;
};
```
### 2. Verify auth challenge response
The second lambda trigger which we will use is [Verify auth challenge response](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-verify-auth-challenge-response.html).
In this lambda we will take the **validation token** obtained from the Authsignal Client SDK and pass it to the Authsignal Server SDK to verify the challenge.
```ts theme={null}
export const handler: VerifyAuthChallengeResponseTriggerHandler = async (event) => {
const userId = event.request.userAttributes.sub;
const token = event.request.challengeAnswer;
const { isValid } = await authsignal.validateChallenge({
action: "cognitoAuth",
userId,
token,
});
event.response.answerCorrect = isValid;
return event;
};
```
In this lambda we will take the **validation token** obtained when redirecting back from the Authsignal pre-built UI and pass it to the Authsignal Server SDK to verify the challenge.
```ts theme={null}
export const handler: VerifyAuthChallengeResponseTriggerHandler = async (event) => {
const userId = event.request.userAttributes.sub;
const token = event.request.challengeAnswer;
const { isValid } = await authsignal.validateChallenge({
action: "cognitoAuth",
userId,
token,
});
event.response.answerCorrect = isValid;
return event;
};
```
## App integration steps
### 1. Obtain a username
Once the user has inputted their email address or phone number you can either:
* Use this value as the Cognito username, or
* Use this value to find the user record in your database and obtain their username
The approach will depend on your user pool configuration.
For more detail on Cognito usernames refer to the [AWS documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-usernames).
Passkeys represent a new paradigm of \[device-initiated authentication where a username is not
required as the first step. For this reason the integration steps are slightly different - see our
guide on [implementing passkeys with Cognito](/integrations/aws-cognito/passkeys).
### 2. Call SignUp (if required)
You can call [SignUp](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_SignUp.html) either as part of a separate account registration flow, or "just-in-time" before every sign-in attempt (ignoring if the user already exists).
```ts AWS SDK theme={null}
import {
CognitoIdentityProviderClient,
SignUpCommand,
} from "@aws-sdk/client-cognito-identity-provider";
const client = new CognitoIdentityProviderClient({
region: "YOUR_AWS_REGION",
});
// If a password is required, generate a dummy value
// It will never be used for passwordless authentication
const password = Math.random().toString(36).slice(-16) + "X";
const command = new SignUpCommand({
ClientId: "YOUR_USER_POOL_CLIENT_ID",
Username: username,
Password: password,
UserAttributes: [
// Include email attribute if obtained in step 1
{
Name: "email",
Value: email,
},
// Include phone_number attribute if obtained in step 1
{
Name: "phone_number",
Value: phoneNumber,
},
],
});
await client.send(command);
```
```ts Amplify theme={null}
import { signUp, SignUpInput } from "aws-amplify/auth";
// If a password is required, generate a dummy value
// It will never be used for passwordless authentication
const password = Math.random().toString(36).slice(-16) + "X";
const signUpInput: SignUpInput = {
username: username,
password: password,
options: {
userAttributes: {
// Include email attribute if obtained in step 1
email: email,
// Include phone_number attribute if obtained in step 1
phoneNumber: phoneNumber,
},
},
};
await signUp(signUpInput);
```
Alternately you can handle the sign-up logic in your backend using Cognito's AdminCreateUserCommand as in [this example](https://github.com/authsignal/cognito-lambdas/blob/main/api/start-sign-in.ts).
### 3. Call InitiateAuth
The next step in authenticating with Cognito from your web or mobile app is to call [InitiateAuth](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html).
This will invoke the Create auth challenge lambda which we have [implemented above](#1-create-auth-challenge) to return an Authsignal challenge token.
Here we obtain a challenge token to pass to an Authsignal Client SDK.
```ts AWS SDK theme={null}
import {
CognitoIdentityProviderClient,
InitiateAuthCommand,
} from "@aws-sdk/client-cognito-identity-provider";
const client = new CognitoIdentityProviderClient({
region: "YOUR_AWS_REGION",
});
const command = new InitiateAuthCommand({
ClientId: "YOUR_USER_POOL_CLIENT_ID",
AuthFlow: AuthFlowType.CUSTOM_AUTH,
AuthParameters: {
USERNAME: username,
},
});
const output = await client.send(command);
// We will pass this challenge token to an Authsignal Client SDK
const token = output.ChallengeParameters?.token;
```
```ts Amplify theme={null}
import { signIn, SignInInput } from "aws-amplify/auth";
const signInInput: SignInInput = {
username: username,
options: {
authFlowType: "CUSTOM_WITHOUT_SRP",
},
};
const { nextStep } = await signIn(signInInput);
// We will pass this challenge token to an Authsignal Client SDK
const token = nextStep.additionalInfo?.token;
```
Here we obtain a short-lived URL for the Authsignal pre-built UI.
```ts AWS SDK theme={null}
import {
CognitoIdentityProviderClient,
InitiateAuthCommand,
} from "@aws-sdk/client-cognito-identity-provider";
const client = new CognitoIdentityProviderClient({
region: "YOUR_AWS_REGION",
});
const command = new InitiateAuthCommand({
ClientId: "YOUR_USER_POOL_CLIENT_ID",
AuthFlow: AuthFlowType.CUSTOM_AUTH,
AuthParameters: {
USERNAME: username,
},
});
const output = await client.send(command);
// We will launch the Authsignal pre-built UI using this url
const url = output.ChallengeParameters?.url;
// Keep a reference to the session
// We will pass this back when calling RespondToAuthChallenge
const session = output.Session;
```
```ts Amplify theme={null}
import { signIn, SignInInput } from "aws-amplify/auth";
const signInInput: SignInInput = {
username: username,
options: {
authFlowType: "CUSTOM_WITHOUT_SRP",
},
};
const { nextStep } = await signIn(signInInput);
// We will launch the Authsignal pre-built UI using this url
const url = nextStep.additionalInfo?.url;
```
### 4. Use Authsignal
The next step is to use Authsignal to handle presenting the user with a challenge.
Use an [Authsignal Client SDK](/sdks/client) to present the user with a challenge.
Here we show **email OTP** but our SDKs support a variety of methods including SMS or WhatsApp OTP, email magic link, authenticator app, and more.
```ts Web theme={null}
// 1. Set the challenge token obtained from the InitiateAuth call
authsignal.setToken(token);
// 2. Send the user an OTP code via email
await authsignal.email.challenge();
// 3. Verify the code inputted by the user matches the original code
const response = await authsignal.email.verify({ code: "123456" });
// 4. Obtain a validation token for the next step
const validationToken = response.data?.token;
```
```swift iOS theme={null}
// 1. Set the challenge token obtained from the InitiateAuth call
authsignal.setToken(token)
// 2. Send the user an OTP code via email
await authsignal.email.challenge()
// 3. Verify the code inputted by the user matches the original code
let response = await authsignal.email.verify(code: "123456")
// 4. Obtain a validation token for the next step
let validationToken = response.data?.token
```
```kotlin Android theme={null}
// 1. Set the challenge token obtained from the InitiateAuth call
authsignal.setToken(token)
// 2. Send the user an OTP code via email
authsignal.email.challenge().get()
// 3. Verify the code inputted by the user matches the original code
val response = authsignal.email.verify(code = "123456").get()
// 4. Obtain a validation token for the next step
val validationToken = response.data.token
```
```ts React Native theme={null}
// 1. Set the challenge token obtained from the InitiateAuth call
await authsignal.setToken(token);
// 2. Send the user an OTP code via email
await authsignal.email.challenge();
// 3. Verify the code inputted by the user matches the original code
const response = await authsignal.email.verify({ code: "123456" });
// 4. Obtain a validation token for the next step
const validationToken = response.data?.token;
```
```dart Flutter theme={null}
// 1. Set the challenge token obtained from the InitiateAuth call
await authsignal.setToken(token);
// 2. Send the user an OTP code via email
await authsignal.email.challenge();
// 3. Verify the code inputted by the user matches the original code
final response = await authsignal.email.verify(code: "123456");
// 4. Obtain a validation token for the next step
final validationToken = response.data.token;
```
Use the **Authsignal pre-built UI** to present the user with a challenge.
The [Authsignal Web SDK](/sdks/client/web/prebuilt-ui) can be used to launch the pre-built UI in [redirect or popup mode](/implementation-options/prebuilt-ui/presentation-modes).
```ts Redirect theme={null}
// Redirect user to the pre-built UI
// They will be redirected back to your app's redirect URL
// A validation token will be appended as a URL query param
authsignal.launch(url);
```
```ts Popup theme={null}
// This promise will resolve with a new token when the popup closes
const { token } = await authsignal.launch(url, { mode: "popup" });
```
### 5. Call RespondToAuthChallenge
The final step of your app integration is to call [RespondToAuthChallenge](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_RespondToAuthChallenge.html).
This will invoke the Verify auth challenge response lambda which we have [implemented above](#1-verify-auth-challenge-response) to complete authentication using our Authsignal validation token.
Pass the validation token obtained from the Authsignal Client SDK to Cognito as the challenge answer.
```ts AWS SDK theme={null}
import {
CognitoIdentityProviderClient,
RespondToAuthChallengeCommand,
} from "@aws-sdk/client-cognito-identity-provider";
const client = new CognitoIdentityProviderClient({
region: "YOUR_AWS_REGION",
});
const command = new RespondToAuthChallengeCommand({
ClientId: "YOUR_USER_POOL_CLIENT_ID",
ChallengeName: ChallengeNameType.CUSTOM_CHALLENGE,
Session: session,
ChallengeResponses: {
USERNAME: username,
ANSWER: token, // The Authsignal validation token
},
});
const output = await client.send(command);
const accessToken = output.AuthenticationResult?.AccessToken;
```
```ts Amplify theme={null}
import { confirmSignIn } from "aws-amplify/auth";
const { isSignedIn } = await confirmSignIn({
challengeResponse: token, // The Authsignal validation token
});
```
Pass the validation token obtained from the Authsignal pre-built UI to Cognito as the challenge answer.
```ts AWS SDK theme={null}
import {
CognitoIdentityProviderClient,
RespondToAuthChallengeCommand,
} from "@aws-sdk/client-cognito-identity-provider";
const client = new CognitoIdentityProviderClient({
region: "YOUR_AWS_REGION",
});
const command = new RespondToAuthChallengeCommand({
ClientId: "YOUR_USER_POOL_CLIENT_ID",
ChallengeName: ChallengeNameType.CUSTOM_CHALLENGE,
Session: session,
ChallengeResponses: {
USERNAME: username,
ANSWER: token, // The Authsignal validation token
},
});
const output = await client.send(command);
const accessToken = output.AuthenticationResult?.AccessToken;
```
```ts Amplify theme={null}
import { confirmSignIn } from "aws-amplify/auth";
const { isSignedIn } = await confirmSignIn({
challengeResponse: token, // The Authsignal validation token
});
```
## Next steps
* [Managing authenticators](/integrations/aws-cognito/authenticators)
* [Adding passkeys](/integrations/aws-cognito/passkeys)
* [Adaptive MFA](/integrations/aws-cognito/rules)
* [Uber app example](/integrations/aws-cognito/example-app)
# Using passkeys when integrating Authsignal with Amazon Cognito
Source: https://docs.authsignal.com/integrations/aws-cognito/passkeys
Learn how to use passkeys when integrating Authsignal with Amazon Cognito.
In the [previous guide](/integrations/aws-cognito/authenticators) we covered the integration steps required to let users enroll additional authentication methods - either using an **Authsignal Client SDK** or the **Authsignal pre-built UI**.
This guide will demonstrate how to use an **Authsignal Client SDK** to let the user create a passkey as an alternative authentication option which they can use for future sign-ins.
This guide shows how to use passkeys with our React Native SDK, but you can just as easily use any
of our [Mobile SDKs](/sdks/client/mobile/setup) for Swift, Kotlin, or Flutter, or our [Web
SDK](/sdks/client/web/setup) for browser-based apps.
## Github example code
## Creating a passkey
Once a user has signed in using their email address or phone number, we can prompt them to create a passkey to use for next time.
### Lambda integration
To authorize binding the passkey to the user, we will create an authenticated endpoint which our app can call to obtain an Authsignal token.
This endpoint will use a [JWT Authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html) to authenticate using the Cognito access token.
```ts theme={null}
export const handler = async (event: APIGatewayProxyEventV2WithJWTAuthorizer) => {
const claims = event.requestContext.authorizer.jwt.claims;
const userId = claims.sub;
const { token: authsignalToken } = await authsignal.track({
userId,
action: "addAuthenticator",
attributes: {
scope: "add:authenticators",
},
});
return {
authsignalToken,
};
};
```
The value we provide for `action` here will be used to keep track of these events in the
Authsignal Portal.
### App integration
1. Call our endpoint to obtain a short-lived Authsignal token.
```ts theme={null}
// Replace with your API endpoint URL
const url = "https://abcd1234.execute-api.us-west-1.amazonaws.com/authenticators";
const authsignalToken = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${cognitoAccessToken}`,
},
})
.then((res) => res.json())
.then((json) => json.authsignalToken);
```
2. Set the Authsignal token using the React Native SDK.
```ts theme={null}
await authsignal.setToken(authsignalToken);
```
3. Create the passkey using the React Native SDK.
```ts theme={null}
// This can be the Cognito username (e.g email or phone number)
const username = "jane.smith@authsignal.com";
// This is an optional display name for the passkey
const displayName = "Jane Smith";
await authsignal.passkey.signUp({ username, displayName });
```
## Signing in with a passkey
Now that the user has created a passkey, the next time they sign in we can present them with the option to use their passkey.
### Lambda integration
No changes to our lambda code are required to handle passkeys as a sign-in method - our [verify auth challenge response lambda](/integrations/aws-cognito/getting-started#2-verify-auth-challenge-response) will validate the token which we pass as the challenge answer.
### App integration
In our React Native app we'll add some code to our sign-in screen to automatically show the passkey sign-in prompt if the user has one available on their device.
```ts AWS SDK theme={null}
async function signInWithPasskey() {
// Show the passkey sign-in prompt
const { data, errorCode } = await authsignal.passkey.signIn({
action: "cognitoAuth",
});
// Exit if the user has no passkey available or if they dismissed the prompt
if (errorCode === ErrorCode.user_canceled || errorCode === ErrorCode.no_credential) {
return;
}
// Get the Cognito username associated with the passkey
const username = data.username;
// Get the Authsignal validation token
const token = data.token;
// Call InitiateAuth to obtain a session
const initiateAuthCommand = new InitiateAuthCommand({
ClientId: "YOUR_USER_POOL_CLIENT_ID",
AuthFlow: AuthFlowType.CUSTOM_AUTH,
AuthParameters: {
USERNAME: username,
},
});
const initiateAuthOutput = await client.send(initiateAuthCommand);
const session = initiateAuthOutput.Session;
// Call RespondToAuthChallenge and pass the Authsignal validation token
const respondToAuthChallengeCommand = new RespondToAuthChallengeCommand({
ClientId: "YOUR_USER_POOL_CLIENT_ID",
ChallengeName: ChallengeNameType.CUSTOM_CHALLENGE,
Session: session,
ChallengeResponses: {
USERNAME: username,
ANSWER: token,
},
});
const respondToAuthChallengeOutput = await cognito.send(initiateAuthCommand);
// Obtain the Cognito access token and complete sign-in
const accessToken = respondToAuthChallengeOutput.AuthenticationResult?.AccessToken;
}
```
```ts Amplify theme={null}
async function signInWithPasskey() {
// Show the passkey sign-in prompt
const { data, errorCode } = await authsignal.passkey.signIn({
action: "cognitoAuth",
});
// Exit if the user has no passkey available or if they dismissed the prompt
if (errorCode === ErrorCode.user_canceled || errorCode === ErrorCode.no_credential) {
return;
}
// Get the Cognito username associated with the passkey
const username = data.username;
// Get the Authsignal validation token
const token = data.token;
// Call InitiateAuth
await signIn({
username: username,
options: {
authFlowType: "CUSTOM_WITHOUT_SRP",
},
});
// Call RespondToAuthChallenge and pass the Authsignal validation token
await confirmSignIn({
challengeResponse: token,
});
}
```
Then finally we'll run this function in a `useEffect` hook when our sign-in screen first appears.
```ts theme={null}
useEffect(() => {
signInWithPasskey();
}, []);
```
## Next steps
* [Uber app example](/integrations/aws-cognito/example-app)
* [Adaptive MFA](/integrations/aws-cognito/rules)
# Adding adaptive MFA to your Cognito login flow
Source: https://docs.authsignal.com/integrations/aws-cognito/rules
Learn how to add adaptive MFA to your Cognito login flow using Authsignal.
Adaptive MFA can be achieved by utilizing Authsignal's rules engine.
For example, you can create [risk-based authentication flows](/actions-rules/rules/getting-started#creating-a-rule-to-challenge-high-risk-users) so that MFA is only required when certain conditions are met e.g. if a user is authenticating on a new device.
For more bespoke scenarios, you can also integrate with your [business-specific data points](/actions-rules/rules/custom-data-points).
This guide will cover how to modify your Authsignal integration with Cognito to utilize rules.
## Adaptive MFA on login
A common use case for adaptive MFA is on login.
For example, we might want to reduce user friction by not requiring MFA for users who are authenticating from a known device.
### Authsignal rule set up
1. Go to the [Actions page](https://portal.authsignal.com/actions) and find the **Cognito Auth** action.
2. Click on the **Cognito Auth** action go to the **Rules** tab.
3. Click on the **Create Rule** button and create a new rule. For example, a [new device rule](/actions-rules/rules/adaptive-mfa#new-device-detection)
The **Cognito Auth** action will only appear if you have tested your existing Authsignal + Cognito
integration. If you don't see it, you can use the **Configure a new action** button to create it.
Make sure to name it `cognitoAuth` so it matches the value in your lambda code.
### Define Auth Challenge lambda
In this case we assume the [Define Auth Challenge lambda](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-define-auth-challenge.html) is implemented to require multiple authentication steps.
```ts theme={null}
import { DefineAuthChallengeTriggerHandler } from "aws-lambda";
export const handler: DefineAuthChallengeTriggerHandler = async (event) => {
const { session } = event.request;
if (session.length === 1 && session[0].challengeName === "SRP_A") {
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = "PASSWORD_VERIFIER";
} else if (session.length === 2 && session[1].challengeName === "PASSWORD_VERIFIER") {
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = "CUSTOM_CHALLENGE";
} else if (
session.length === 3 &&
session[2].challengeName === "CUSTOM_CHALLENGE" &&
session[2].challengeResult === true
) {
event.response.issueTokens = true;
event.response.failAuthentication = false;
} else {
event.response.issueTokens = false;
event.response.failAuthentication = true;
}
return event;
};
```
The `"CUSTOM_CHALLENGE"` step is delegated to Authsignal.
### Create Auth Challenge lambda
When tracking our action in the [Create Auth Challenge lambda](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-create-auth-challenge.html) we can check the `state` field in the response to see whether our rule has determined that a challenge is required for the action.
In addition to the `url` for the pre-built UI, we will also pass this `state` value along with a `token` back to our app as public challenge parameters.
```ts theme={null}
import { Authsignal } from "@authsignal/node";
import { CreateAuthChallengeTriggerHandler } from "aws-lambda";
const authsignal = new Authsignal({
apiSecretKey: process.env.AUTHSIGNAL_SECRET,
apiUrl: process.env.AUTHSIGNAL_URL,
});
export const handler: CreateAuthChallengeTriggerHandler = async (event) => {
const userId = event.request.userAttributes.sub;
const email = event.request.userAttributes.email;
const { state, token, url } = await authsignal.track({
action: "cognitoAuth",
userId,
attributes: {
email,
},
});
event.response.publicChallengeParameters = { state, token, url };
return event;
};
```
Make sure to include any additional data points required by the rule you set up on your **Cognito
Auth** action.
### The app code
Now we can adapt our app code to use the `state` param to determine whether MFA is required.
```ts theme={null}
// Sign in with username and password using Amplify
const { nextStep } = await signIn({
username: email,
password,
options: {
authFlowType: "CUSTOM_WITH_SRP",
},
});
const { state, token, url } = nextStep.additionalInfo;
if (state === "ALLOW") {
// No MFA is required
// Pass the initial token to confirm sign-in
await confirmSignIn({ challengeResponse: token });
} else {
// MFA is required
// Launch the pre-built UI to obtain a validation token for an Authsignal challenge
const response = await authsignal.launch(url, { mode: "popup" });
await confirmSignIn({ challengeResponse: response.token });
}
```
### Verify Auth Challenge Response lambda
Finally, we need to update our [Verify Auth Challenge Response lambda](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-verify-auth-challenge-response.html) to handle if the user is allowed to bypass MFA.
The only change here is to set `event.response.answerCorrect` to true if the `state` of the action is either `"CHALLENGE_SUCCEEDED"` (because the user successfully completed an MFA step) or `"ALLOW"` (because the user wasn't required to complete MFA).
```ts theme={null}
import { Authsignal, UserActionState } from "@authsignal/node";
import { VerifyAuthChallengeResponseTriggerHandler } from "aws-lambda";
const authsignal = new Authsignal({
apiSecretKey: process.env.AUTHSIGNAL_SECRET,
apiUrl: process.env.AUTHSIGNAL_URL,
});
export const handler: VerifyAuthChallengeResponseTriggerHandler = async (event) => {
const userId = event.request.userAttributes.sub;
const token = event.request.challengeAnswer;
const { state } = await authsignal.validateChallenge({
action: "cognitoAuth",
userId,
token,
});
event.response.answerCorrect =
state === UserActionState.CHALLENGE_SUCCEEDED || state === UserActionState.ALLOW;
return event;
};
```
## Next steps
* [Adaptive MFA with rules](/actions-rules/rules/getting-started#creating-a-rule-to-challenge-high-risk-users)
* [Using business-specific data points in rules](/actions-rules/rules/custom-data-points)
# Using Authsignal for MFA with Microsoft Azure AD B2C
Source: https://docs.authsignal.com/integrations/azure-ad-b2c/azure-ad-b2c-mfa
Learn how to integrate Authsignal for MFA when using Microsoft Azure AD B2C.
Authsignal provides a simple integration with Azure AD B2C via our [Open ID Connect (OIDC)](/integrations/oidc) endpoints.
With this integration you can use Azure AD B2C's [custom policies](https://learn.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview#custom-policies) and [technical profiles](https://learn.microsoft.com/en-us/azure/active-directory-b2c/technicalprofiles) to orchestrate passwordless login and adaptive MFA through Authsignal's pre-built UI.
The guide below outlines how to use Authsignal as a MFA provider for Azure AD B2C. Authsignal provides other utility endpoints to achieve further customization, see [Authsignal's custom policy code snippets](/integrations/azure-ad-b2c/azure-ad-b2c-snippets).
Authsignal integrates with Azure AD B2C by acting as an [OIDC provider](/integrations/oidc) - this
integration model can also be used for other platforms which support OIDC.
## Prerequisites
This guide assumes that you have already set up an Azure AD B2C tenant and are using custom policies. If not, it is recommended to familiarize yourself with [Azure AD B2C custom policies](https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-overview) and to follow Microsoft's official [getting started with custom policies guide](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy) before continuing.
We also assume that you have an Authsignal tenant created, with at least one Authenticator enabled. [Enable an Authenticator on your Authsignal tenant here.](https://portal.authsignal.com/organisations/tenants/authenticators)
## Sequence
The following sequence diagram demonstrates the necessary orchestration steps and corresponding requests when using Authsignal as an MFA provider for Azure AD B2C.
```mermaid theme={null}
sequenceDiagram
participant F as Your frontend
participant AAD as Azure AD B2C
participant AS as Authsignal
F->>AAD: User clicks the "Sign in" button
Note over AAD: User enters their login details
Note over AAD: User identifier is stored in the objectId claim
critical User Journey Step A
AAD->>AS: POST /init-auth
activate AS
AS->>AAD: token
deactivate AS
end
critical User Journey Step B
AAD->>AS: GET /oidc/auth with token
activate AS
Note over AS: Redirect to Authsignal's pre-built UI
Note over AS: User completes authentication challenge
AS->>AAD: Return auth code
deactivate AS
AAD->>AS: POST /oidc/token with auth code
activate AS
AS->>AAD: Return id_token
deactivate AS
end
critical User Journey Step C, D, E
Note over AAD: Validate result of challenge
end
AAD->>F: Return session token
```
## Code example
You can find a [full code example and starter template](https://github.com/authsignal/azure-ad-b2c-example) referenced in this guide on Github.
This guide builds on top of the [SignUpOrSignIn policy from the Azure AD B2C starter pack](https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/main/LocalAccounts/TrustFrameworkBase.xml#L875-L910).
## Step by step guide
### Step 1: Add orchestration steps to your user journey
Add the following five steps to your user journey. These steps should be performed after the user has been identified, but before the user is authenticated and issued a token.
```xml theme={null}
isMatchingUserIdTrueSkipThisOrchestrationStepisChallengeSuccessfultrueSkipThisOrchestrationStepauthsignalEnrolledtrueSkipThisOrchestrationStep
```
### Step 2: Add the base technical profile
This is the base technical profile that is used to connect to the Authsignal Connect (OIDC) API by setting the authorization header, request body and other necessary configuration for the [Azure AD B2C's RestfulProvider](https://learn.microsoft.com/en-us/azure/active-directory-b2c/restful-technical-profile). It is referenced by other technical profiles.
```xml theme={null}
Authsignal Connect API BaseBodyBasicfalsetruerequestBodyCannot process your request right now, please try again later.
```
### Step 3: Add the init auth technical profile and corresponding input claims transformation
Replace the `AUTHSIGNAL_CONNECT_HOSTNAME` and `INSERT_AUTHSIGNAL_ACTION` placeholders with the appropriate values.
A list of available hostnames can be found in the [OIDC documentation](/integrations/oidc#regions). You should use the hostname where your Authsignal tenant is located.
**Technical profile:**
```xml theme={null}
Authsignal OIDC Init Auth APIhttps://AUTHSIGNAL_CONNECT_HOSTNAME/init-auth
```
**Input Claims Transformation:**
Replace the `INSERT_AUTHSIGNAL_ACTION` placeholder with the desired Authsignal action.
```xml theme={null}
```
### Step 4: Add the OIDC authorize technical profile and corresponding output claims transformations
Replace the `AUTHSIGNAL_CONNECT_HOSTNAME` and `INSERT_AUTHSIGNAL_TENANT_ID` placeholders with the appropriate values.
**Technical profile:**
```xml theme={null}
Authsignal OIDC Authorize APIhttps://AUTHSIGNAL_CONNECT_HOSTNAME/oidc/.well-known/openid-configurationINSERT_AUTHSIGNAL_TENANT_IDhttps://AUTHSIGNAL_CONNECT_HOSTNAME/oidc/authhttps://AUTHSIGNAL_CONNECT_HOSTNAME/oidc/tokencodeopenidPOSTform_postclient_secret_postfalsetruetrue
```
**Output Claims Transformations:**
```xml theme={null}
```
### Step 5: Add technical profiles for the error pages
These are basic error pages shown when the authentication challenge is failed. It is using the default `api.selfasserted` content definition. You may want to customize this page to suit your branding.
```xml theme={null}
Not enrolled errorapi.selfassertedfalsefalseAuthentication failedapi.selfassertedfalsefalse
```
### Step 6: Declare the claims used by the technical profiles
```xml theme={null}
requestBodystringbearerTokenstringactionCodestringactionStatestringbooleanstringstringbooleanstringParagraph
```
### Step 7: Store your Authsignal Server API secret key on Azure AD B2C
Your Authsignal Server API secret key is stored as a policy key on Azure AD B2C's Identity Experience Framework and referenced by our technical profile with the Id `B2C_1A_AuthsignalSecret`.
You can find the Server API secret key for your tenant in the [API keys page](https://portal.authsignal.com/organisations/tenants/api) and add it to your Azure AD B2C tenant as a policy key via the [Azure Portal](https://portal.azure.com).
### Step 8: Upload your custom policy and test your integration
You can now test your integration by navigating to your custom policy in the Azure AD B2C portal and clicking "Run now". You should see the Authsignal UI for MFA after enrolling your user and for subsequent login attempts.
## Congratulations!
You have successfully integrated Authsignal with Azure AD B2C for MFA.
## Next steps
* [How to use Authsignal for passwordless login with Azure AD B2C](/integrations/azure-ad-b2c/azure-ad-b2c-passwordless)
* [How to add passkey autofill to the Azure AD B2C login page](/integrations/azure-ad-b2c/azure-ad-b2c-passkey)
# Using Passkeys with Microsoft Azure AD B2C
Source: https://docs.authsignal.com/integrations/azure-ad-b2c/azure-ad-b2c-passkey
Learn how to add passkey autofill to your Microsoft Azure AD B2C login page with Authsignal.
This guide extends our previous guides where we showed you how to integrate Authsignal into Microsoft Azure AD B2C for [multi-factor authentication (MFA)](/integrations/azure-ad-b2c/azure-ad-b2c-mfa) and [passwordless login](/integrations/azure-ad-b2c/azure-ad-b2c-passwordless)
In this guide, we will show you how to add passkey autofill into your Microsoft Azure AD B2C login page.
You can use this guide to add passkey autofill if you are using Authsignal for MFA or passwordless
login.
Note that the live demo is using the same underlying Azure B2C Tenant as our previous guides.
You can use the same email address to sign in.
## Prerequisites
Before continuing with this guide, you should first complete the following:
* Replace your login screen with [custom html templates](https://learn.microsoft.com/en-us/azure/active-directory-b2c/customize-ui-with-html?pivots=b2c-custom-policy). In our example, we have made a copy of the [sample templates](https://learn.microsoft.com/en-us/azure/active-directory-b2c/customize-ui-with-html?pivots=b2c-custom-policy#sample-templates) provided by Microsoft. This is required as we will need to add custom javascript to the login page.
* Set
up custom domains on your Azure AD B2C tenant. You can follow the [official documentation](https://docs.microsoft.com/en-us/azure/active-directory-b2c/custom-domain?pivots=b2c-custom-policy)
to set up custom domains. This is required as we will need to replace the default
`b2clogin.com` domain with the domain where we will register our passkeys, so that
the browser is able to detect available passkeys.
We recommend setting your b2c domain to `identity.yourdomain.com`, and Authsignal [custom
domain](/implementation-options/prebuilt-ui/custom-domains) to `auth.yourdomain.com`
## Code example
A [full code example and starter template](https://github.com/authsignal/azure-ad-b2c-passkey-example) referenced in this guide can be found on Github.
You can also refer to the [code diff between the previous guide](https://github.com/authsignal/azure-ad-b2c-passwordless-example/compare/main...authsignal:azure-ad-b2c-passkey-example:main?expand=1) if you only want to see the changes.
## Step by step guide
### Step 1: Configure the custom domain of your Authsignal tenant
Navigate to [Authsignal portal > Settings > Custom domains](https://portal.authsignal.com/organisations/tenants/custom_domains) to configure the custom domain of your Authsignal tenant. We recommend this be a distinct subdomain from your Azure AD B2C tenant domain.
### Step 2: Enable and configure the passkey authentication method on your Authsignal tenant
Enable passkeys on your tenant by following the [passkey setup steps](/authentication-methods/passkey/web-sdk#portal-setup).
After passkeys have been enabled, add your Azure AD B2C domain and Authsignal custom domain to the **Expected origins** settings e.g. `https://identity.yourdomain.com` and `https://auth.yourdomain.com`.
### Step 3: Add JavaScript to the login page html template
In order to enable passkey autofill in our B2C login page, we need add custom JavaScript into our html template.
In the `` tag, add the following script which will:
* Wait for Azure AD B2C to dynamically populate the `api` element with the login from
* Load the Authsignal Browser SDK
* Set the `autocomplete` attribute of the login input to `username webauthn`
* Set the `autocomplete` attribute of the password input to `current-password webauthn`
```js theme={null}
```
### Step 5 (Optional): Hide the password input
This step is optional, and only required if you have a passwordless login flow. If you still allow users to login with their password, skip this step.
Add the following `
```
### Step 6: Enable javascript in your html templates
Add the following `` element to our Relying Party policy under the `` element
```xml theme={null}
Allow
```
[See the official Microsoft guide](https://learn.microsoft.com/en-us/azure/active-directory-b2c/javascript-and-page-layout?pivots=b2c-custom-policy#enable-javascript)
### Step 7: Add the required claim types
```xml theme={null}
booleanbooleanstring
```
### Step 8: Add the claims transformations
```xml theme={null}
```
### Step 9: Add new technical profiles to check and validate passkey autofill challenges
```xml theme={null}
Check is passkey autofillValidate passkey challenge{Settings:AuthsignalServerUrl}/validate
```
### Step 10: Adjust the technical profile used in our CombinedSignInAndSignUp orchestration step
Add the following claims to the `OutputClaims` element:
```xml theme={null}
```
Add the following validation technical profiles:
```xml theme={null}
isPasskeyAutofillSkipThisValidationTechnicalProfileisPasskeyAutofillTrueSkipThisValidationTechnicalProfilepasskeyTokenSkipThisValidationTechnicalProfileisPasskeyAutofillTrueSkipThisValidationTechnicalProfile
```
### Step 11: Add a precondition to the orchestration step which shows an error page if the passkey autofill challenge is not successful
```xml theme={null}
isPasskeyChallengeSuccessfultrueSkipThisOrchestrationStep
```
### Step 12: Add a precondition to orchestration steps that should be skipped if passkey autofill is successful
```xml theme={null}
isPasskeyAutofillTrueSkipThisOrchestrationStep
```
### Step 13: Upload the updated html template and custom policies, and test the changes
You can now test your integration by navigating to your custom policy in the Azure AD B2C portal and clicking “Run now”, ensuring that you change the API URL to your custom domain.
Once you sign up a new user and register a passkey, when the user next lands on the login screen they will be prompted to sign in with their passkey.
## Congratulations!
You have successfully added passkey autofill to your Microsoft Azure AD B2C login page.
# Passwordless login with Microsoft Azure AD B2C
Source: https://docs.authsignal.com/integrations/azure-ad-b2c/azure-ad-b2c-passwordless
Learn how to use Authsignal for passwordless login with Microsoft Azure AD B2C.
This guide extends our previous guide where we showed you how to [integrate Authsignal with Microsoft Azure AD B2C](/integrations/azure-ad-b2c/azure-ad-b2c-mfa) for multi-factor authentication.
In this guide, we will show you how to use Authsignal for passwordless login with Microsoft Azure AD B2C.
Note that the live demo is using the same underlying Azure B2C Tenant as the previous guide. You
can use the same email address to sign in.
## Code example
A [full code example and starter template](https://github.com/authsignal/azure-ad-b2c-passwordless-example) referenced in this guide can be found on Github.
You can also refer to the [code diff between the previous guide](https://github.com/authsignal/azure-ad-b2c-example/compare/main...authsignal:azure-ad-b2c-passwordless-example:main?expand=1) if you only want to see the changes.
## Step by step guide
### Step 1: Create sign up technical profiles without passwords
The following technical profiles are a copy of the [LocalAccountSignUpWithLogonEmail](https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/fc39b7dbf7648dcfbcd5d511b49da6583671128c/LocalAccounts/TrustFrameworkBase.xml#L663-L694) and [AAD-UserWriteUsingLogonEmail](https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/fc39b7dbf7648dcfbcd5d511b49da6583671128c/LocalAccounts/TrustFrameworkBase.xml#L501-L530) technical profiles from the `TrustFrameworkBase.xml` file provided by Microsoft with the password claims removed.
This removes the need for the user to provide a password on sign up, but still collects some data from the user such as display name, first name and last name.
```xml theme={null}
Email signupIpAddressapi.localaccountsignupfalseWritetruefalse
```
### Step 2: Update the sign up orchestration step in your user journey to use the new technical profile
```xml theme={null}
```
### Step 3: Create sign in technical profiles to look up the user from email address without passwords
The following technical profiles are a copy of the [SelfAsserted-LocalAccountSignin-Email](https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/fc39b7dbf7648dcfbcd5d511b49da6583671128c/LocalAccounts/TrustFrameworkBase.xml#L697-L720) and [AAD-UserReadUsingEmailAddress](https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/fc39b7dbf7648dcfbcd5d511b49da6583671128c/LocalAccounts/TrustFrameworkBase.xml#L532-L557) technical profiles from the `TrustFrameworkBase.xml` file provided by Microsoft adjusted to look up the user without utilising passwords.
```xml theme={null}
ReadtruefalseLocal Account SigninSignUpWithLogonEmailExchangeEmailapi.localaccountsignintruefalse
```
### Step 4: Update the login orchestration step in your user journey to use the new technical profile
```xml theme={null}
```
### Step 5: Upload your custom policy and test your integration
You can now test your integration by navigating to your custom policy in the Azure AD B2C portal and clicking "Run now".
You should see both the Azure AD B2C login and sign up screens without password inputs with the user being redirected to Authsignal to complete their authentication.
## Congratulations!
You have successfully integrated Authsignal with Azure AD B2C for passwordless authentication.
## Next steps
* [How to add passkey autofill to the Azure AD B2C login page](/integrations/azure-ad-b2c/azure-ad-b2c-passkey)
# Microsoft Azure AD B2C Custom Policy Snippets
Source: https://docs.authsignal.com/integrations/azure-ad-b2c/azure-ad-b2c-snippets
Use Authsignal custom policy snippets to customize Microsoft Azure AD B2C flows.
## Base Technical Profile
This is the base technical profile that is used to connect to the Authsignal Connect (OIDC) API by setting the authorization header, request body and other necessary configuration for the [Azure AD B2C's RestfulProvider](https://learn.microsoft.com/en-us/azure/active-directory-b2c/restful-technical-profile). It is referenced by other technical profiles.
```xml theme={null}
Authsignal Connect API BaseBodyBasicfalsetruerequestBodyCannot process your request right now, please try again later.
```
## OpenID Connect (OIDC) Endpoints
The following technical profiles are used to interact with the Authsignal Connect API using the OpenID Connect (OIDC) protocol.
An explanation of the integration model can be found in [Authsignal's Open ID Connect (OIDC)](/integrations/oidc) documentation.
### Init Auth API
The [POST /init-auth](/integrations/oidc#init-endpoint) endpoint must be called before federating the flows to Authsignal via the OIDC Authorize technical profile.
Through the input claims, we pass in the user identifier, the Authsignal action being performed, and any other customizations such as `redirectToSettings`.
```xml theme={null}
```
The init-auth endpoint only requires an `userId` and an `action`.
Other inputs are optional but can be used to customize the behavior of Authsignal's pre-built UI, such as redirecting the users to manage their authenticator settings post authentication.
Refer to the [POST /init-auth](/integrations/oidc#init-endpoint) documentation for the full list of available inputs.
```xml theme={null}
Authsignal OIDC Init Auth APIhttps://AUTHSIGNAL_CONNECT_HOSTNAME/init-auth
```
```xml theme={null}
```
```xml theme={null}
requestBodystringbearerTokenstring
```
Note that the Authsignal OIDC endpoints should only be called when you have
identified the user, but before the user is authenticated and is issued a
token. These snippets are dependent on `objectId` which is an output claim of
the default Azure AD B2C self-asserted technical profiles.
### OIDC Authorize API
The [GET /oidc/auth](/integrations/oidc#oidc-authorize-endpoint) endpoint is used to begin the authentication flow with Authsignal. The token returned by the previous init-auth endpoint will be used in this endpoint.
```xml theme={null}
isMatchingUserIdTrueSkipThisOrchestrationStepisChallengeSuccessfultrueSkipThisOrchestrationStep
```
```xml theme={null}
Authsignal OIDC Authorize APIhttps://AUTHSIGNAL_CONNECT_HOSTNAME/oidc/.well-known/openid-configurationINSERT_AUTHSIGNAL_TENANT_IDhttps://AUTHSIGNAL_CONNECT_HOSTNAME/oidc/authhttps://AUTHSIGNAL_CONNECT_HOSTNAME/oidc/tokencodeopenidPOSTform_postclient_secret_postfalsetruetrue
```
```xml theme={null}
```
```xml theme={null}
actionCodestringactionStatestringbooleanstringstring
```
This step must always be performed after the `AuthsignalOidcInitAuth`
orchestration step
## Utility Endpoints
These API calls extend on the above [Base Technical Profile](#base-technical-profile) which sets the required authorization headers.
Authsignal has a core [server API](/api-reference/server-api/overview) which can be invoked using the [Azure AD B2C RESTful technical profile](https://learn.microsoft.com/en-us/azure/active-directory-b2c/restful-technical-profile).
Due to Azure AD B2C technical profiles being restricted to setting input claims in either the url or body and not both, we've created convenience proxy endpoints to map to our key APIs.
### Get user
The following code snippets map to our [Get User](/api-reference/server-api/get-user) API call.
The policies only use the `isEnrolled` property from the response, but you can extend this to include other claims as needed.
`isEnrolled` is `true` if the user is enrolled with at least one verification method and can be challenged.
```xml theme={null}
```
```xml theme={null}
Authsignal Get Userhttps://AUTHSIGNAL_CONNECT_HOSTNAME/get-user
```
```xml theme={null}
```
```xml theme={null}
getUserRequestBodystringisEnrolledboolean
```
# Adding MFA to Duende IdentityServer
Source: https://docs.authsignal.com/integrations/identityserver/getting-started
Learn how to add MFA to Duende IdentityServer using Authsignal.
## Overview
[Duende IdentityServer](https://duendesoftware.com/products/identityserver) is an ASP.NET Core framework for building your own login server in compliance with OpenID Connect and OAuth 2.0 standards.
This guide shows how to integrate IdentityServer with Authsignal in order to add MFA after a traditional username & password flow.
The solution in this example consists of 2 projects.
* [/src/IdentityServer](https://github.com/authsignal/identity-server-example/tree/main/src/IdentityServer)
\-- the login server running on `https://localhost:5001`
* [/src/WebClient](https://github.com/authsignal/identity-server-example/tree/main/src/WebClient)
\-- the application server running on `https://localhost:5002`
Duende IdentityServer + Authsignal example repository.
## Configuration
### Enabling authenticators
For the purposes of this example, we have enabled authenticator app on our tenant in the Authsignal Portal.
### Tenant credentials
Get your tenant's credentials from the [API keys page](https://portal.authsignal.com/organisations/tenants/api).
In your [appsettings.json](https://github.com/authsignal/identity-server-example/blob/main/src/IdentityServer/appsettings.json) file, set the `AuthsignalUrl` to your **API host** and `AuthsignalSecret` to your **Server API secret key**.
```json appsettings.json theme={null}
{
"AuthsignalUrl": "YOUR_API_HOST",
"AuthsignalSecret": "YOUR_SERVER_API_SECRET_KEY"
}
```
In your [client-side JS snippet](https://github.com/authsignal/identity-server-example/blob/main/src/IdentityServer/wwwroot/js/login.js) used for passkeys, set the `tenantId` to your **Tenant ID** and `baseUrl` to your **API host**.
```js login.js theme={null}
var client = new window.authsignal.Authsignal({
tenantId: "YOUR_TENANT_ID",
baseUrl: "YOUR_API_HOST",
});
```
## Adding MFA on login
The quickest way to add MFA to IdentityServer is to use [Authsignal's pre-built UI](/implementation-options/prebuilt-ui/overview).
We will redirect the user here after validating their username and password.
Authsignal's pre-built UI can be [highly
customized](/implementation-options/prebuilt-ui/custom-branding) to align with your login server's
existing branding.
### Initiating the MFA challenge
To initiate an MFA challenge using the pre-built UI, we can [track an action](/implementation-options/prebuilt-ui/overview) and use the URL that is returned.
[/src/IdentityServer/Pages/Account/Login/Index.cshtml.cs](https://github.com/authsignal/identity-server-example/blob/main/src/IdentityServer/Pages/Account/Login/Index.cshtml.cs)
```csharp theme={null}
public async Task OnPost()
{
if (_users.ValidateCredentials(Input.Username, Input.Password))
{
var user = _users.FindByUsername(Input.Username);
var trackRequest = new TrackRequest(
UserId: user.SubjectId,
Action: "identity-server-login",
Attributes: new TrackAttributes(
Username: user.Username,
RedirectUrl: "https://localhost:5001/Account/Login/Callback?returnUrl=" + returnUrl
)
);
var trackResponse = await _authsignal.Track(trackRequest);
if (!trackResponse.IsEnrolled || trackResponse.State == UserActionState.CHALLENGE_REQUIRED)
{
return Redirect(trackResponse.Url);
}
}
}
```
For convenience we are prompting the user to enroll for MFA on login if they are not yet enrolled - but you can enroll users at a different point in your user journey as required.
The `RedirectUrl` we pass to the track request here will be a callback endpoint that we will add to IdentityServer to validate the result of the MFA challenge.
### Validating the MFA challenge
Once the user has been redirected back to IdentityServer, we need to validate the result.
We do this by implementing a callback page which uses the token that Authsignal's pre-built UI appends as a URL query param when redirecting the user back to IdentityServer.
This token is used to lookup the result of the challenge server-side.
[/src/IdentityServer/Pages/Account/Login/Callback.cshtml.cs](https://github.com/authsignal/identity-server-example/blob/main/src/IdentityServer/Pages/Account/Login/Callback.cshtml.cs)
```csharp theme={null}
public async Task OnGet(string returnUrl, string token)
{
var validateChallengeRequest = new ValidateChallengeRequest(Token: token);
var validateChallengeResponse = await _authsignal.ValidateChallenge(validateChallengeRequest);
var userId = validateChallengeResponse.UserId;
var user = _users.FindBySubjectId(userId);
if (validateChallengeResponse.State != UserActionState.CHALLENGE_SUCCEEDED)
{
// The user did not complete the MFA challenge successfully
// Redirect them back to the login page
return Redirect("https://localhost:5001/Account/Login?ReturnUrl=" + returnUrl);
}
// Proceed with authentication and issue session cookie
}
```
### Restricting MFA to authenticator apps
By default all authenticators which have been configured will be available to use on the pre-built UI, including passkeys.
We only want to allow users to use authenticator apps for MFA. To achieve this we can go to the [Settings](https://portal.authsignal.com/actions/signIn/settings) tab
of our `identity-server-login` action and update the permitted authenticators.
## MFA with email
What if you're using email and password as your primary authentication step and you want to use an email-based Authsignal method (OTP or magic link) as the secondary MFA step?
In this scenario you're already capturing the user's email in the first step, so you want to avoid prompting the user to input their email again in the second step.
This can be achieved by passing the user's email with the track request.
```csharp {5} theme={null}
var trackRequest = new TrackRequest(
UserId: user.SubjectId,
Action: "identity-server-login",
Attributes: new TrackAttributes(
Email: user.Email,
RedirectUrl: "https://localhost:5001/Account/Login/Callback?returnUrl=" + returnUrl
)
);
```
Alternatively, you can use our Server SDK to [programmatically enroll the user with an email-based authenticator](/advanced-usage/programmatic-authenticator-management).
In either case, you'll likely also want to disable the **"Self-service management"** setting for your email authenticator in the Authsignal Portal.
This means the user will not be able to input or edit their email in the Authsignal UI themselves.
## Next steps
* [Adding passkeys to Duende IdentityServer](/integrations/identityserver/passkeys)
# Adding passkeys to Duende IdentityServer
Source: https://docs.authsignal.com/integrations/identityserver/passkeys
Learn how to add passkeys to Duende IdentityServer using Authsignal.
The [previous guide](/integrations/identityserver/getting-started) showed how to integrate IdentityServer with Authsignal in order to add MFA after a traditional username & password sign-in.
In this guide, we'll demonstrate how to let the user create a passkey and use it instead of their password the next time they sign in.
## Configuring passkeys
In the Authsignal Portal we have configured `localhost` as the Relying Party for the purposes of [local development](/authentication-methods/passkey/web-sdk#local-development).
We also whitelist both the login server and the application server as **expected origins**.
This means the user can enroll a passkey directly from our web app running on `https://localhost:5002`, then reauthenticate with the same passkey when signing in to IdentityServer running on `https://localhost:5001`.
## Enrolling a passkey
Once the user has logged in with MFA, we can use the [Authsignal Web SDK](/sdks/client/web/setup) to allow the user to add a passkey.
This can be done by adding a button to the application server (i.e. the [WebClient](https://github.com/authsignal/identity-server-example/tree/main/src/WebClient)).
To implement this we add a new page [/src/WebClient/Pages/Passkeys.cshtml](https://github.com/authsignal/identity-server-example/blob/main/src/WebClient/Pages/Passkeys.cshtml).
```csharp theme={null}
@page
@model PasskeysModel
@Html.HiddenFor(m => m.enrollmentToken)
```
The `enrollmentToken` here is fetched server-side in [/src/WebClient/Pages/Passkeys.cshtml.cs](https://github.com/authsignal/identity-server-example/blob/main/src/WebClient/Pages/Passkeys.cshtml.cs) when the page loads.
```csharp theme={null}
public async Task OnGet()
{
var trackRequest = new TrackRequest(
UserId: User.Claims.First(x => x.Type == "sub").Value!,
Action: "add-passkey",
Attributes: new TrackAttributes(
Scope: "add:authenticators"
)
);
var trackResponse = await _authsignal.Track(trackRequest);
this.enrollmentToken = trackResponse.Token;
return Page();
}
```
Finally, we need to add the client-side JS implementation for the [addPasskey](https://github.com/authsignal/identity-server-example/blob/main/src/WebClient/wwwroot/js/passkeys.js) function which uses the [Authsignal Web SDK](/sdks/client/web/setup).
```csharp theme={null}
function addPasskey() {
var client = new window.authsignal.Authsignal({
tenantId: "YOUR_TENANT_ID",
baseUrl: "https://api.authsignal.com/v1", // Update for your region
});
var token = document.getElementById("enrollmentToken").value;
client.passkey.signUp({ token }).then((resultToken) => {
if (resultToken) {
alert("Passkey added");
}
});
}
```
## Signing in with a passkey
Now that the user has enrolled a passkey, there are two ways to offer passkey sign-in on the IdentityServer login page.
### Passkey autofill
Passkey autofill is an unobtrusive approach where the user's available passkeys are suggested automatically when they focus the username field.
First we need to ensure that the input field has the value `username webauthn` in the [autocomplete attribute](https://github.com/authsignal/identity-server-example/blob/main/src/IdentityServer/Pages/Account/Login/Index.cshtml#L28C33-L28C175).
```html theme={null}
```
Then we add some [JavaScript](https://github.com/authsignal/identity-server-example/blob/main/src/IdentityServer/wwwroot/js/login.js) to run when the page loads.
```csharp theme={null}
function initPasskeyAutofill() {
var client = new window.authsignal.Authsignal({
tenantId: "YOUR_TENANT_ID",
baseUrl: "https://api.authsignal.com/v1", // Update for your region
});
client.passkey.signIn({ autofill: true }).then((token) => {
if (token) {
var returnUrl = document.getElementById("Input_ReturnUrl").value;
window.location = `https://localhost:5001/Account/Login/Callback?returnUrl=${returnUrl}&token=${token}`;
}
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initPasskeyAutofill);
} else {
initPasskeyAutofill();
}
```
When a user focuses the input field and selects their passkey, the method resolves with a token.
### Sign-in button
A dedicated sign-in button launches the passkey prompt immediately when clicked, making passkeys a clearly visible option on your login page.
Add a button to your IdentityServer login page:
```html theme={null}
```
Then add the click handler, calling `signIn()` without the `autofill` flag:
```csharp theme={null}
function signInWithPasskey() {
var client = new window.authsignal.Authsignal({
tenantId: "YOUR_TENANT_ID",
baseUrl: "https://api.authsignal.com/v1", // Update for your region
});
client.passkey.signIn().then((token) => {
if (token) {
var returnUrl = document.getElementById("Input_ReturnUrl").value;
window.location = `https://localhost:5001/Account/Login/Callback?returnUrl=${returnUrl}&token=${token}`;
}
});
}
```
In both cases, the validation process is the same as for signing in with MFA in the [previous guide](/integrations/identityserver/getting-started#validating-the-mfa-challenge). The token returned from the [Authsignal Web SDK](/sdks/client/web/setup) is passed to the [callback page](https://github.com/authsignal/identity-server-example/blob/main/src/IdentityServer/Pages/Account/Login/Callback.cshtml.cs), which validates the result of the challenge server-side and logs the user in.
# Adding MFA to your Keycloak login flow
Source: https://docs.authsignal.com/integrations/keycloak/getting-started
Learn how to add MFA to your Keycloak login flow with Authsignal.
## Overview
In this guide, we will demonstrate how to leverage a Keycloak
provider to seamlessly integrate MFA into a traditional username and password login flow using Authsignal's [pre-built UI](/implementation-options/prebuilt-ui/overview),
enhancing security with minimal disruption to the user experience.
The above example can be extended to meet your specific requirements.
## Prerequisites
This guide assumes you have a basic understanding of Keycloak and Authsignal.
If you are new to Keycloak, we recommend you follow the [Keycloak Quickstart](https://www.keycloak.org/getting-started/getting-started-zip)
guide to get up and running.
## Authsignal configuration
Head to the [Authenticators section in the Authsignal Portal](https://portal.authsignal.com/organisations/tenants/authenticators) to configure authenticators.
For this example, we have enabled Authenticator App.
Head to the [API Keys section in the Authsignal Portal](https://portal.authsignal.com/organisations/tenants/api) to get your API keys.
## Keycloak configuration
### Creating a new provider
Download the pre-built
[authsignal-keycloak-\*.jar](https://github.com/authsignal/authsignal-keycloak-providers/packages/2329169)
JAR file. Alternatively, you can build the JAR file yourself using the [GitHub
repository](https://github.com/authsignal/authsignal-keycloak-providers).
Download the [Authsignal (version 2.0+) Java SDK (dependency) JAR
file](https://mvnrepository.com/artifact/com.authsignal/authsignal-java) from Maven.
Copy the JAR files to the /providers/ directory
>
}
/>
### Configuring the Authsignal Authentication flow
If you have not already created a Keycloak realm, do this by clicking the **Create realm** button
in the Keycloak admin UI (within the Keycloak drop-down menu top left).
After installing the provider JAR files, you'll need to configure Keycloak to use Authsignal for MFA. This section walks through setting up a custom authentication flow that incorporates the Authsignal Authenticator.
To configure the authentication flow:
Inside the subflow which already contains the ‘Username Password Form’, we need to add the Authsignal provider as a step.
If you successfully added the Authsignal .JAR files to the `/providers/` folder in the previous steps, you will see the Authsignal Authenticator listed in the menu - select it to add it to your flow.
Add your Server API secret key and API host.
When **Enroll by default** is toggled on, users will be prompted to enroll an authenticator when they first log in.
If toggled off, the user will not be prompted to enroll an authenticator, and you will need to handle [enrollment
programmatically](/advanced-usage/programmatic-authenticator-management).
Finally, click the Action -> Bind flow button. Select the `browser` flow to enable the new Authsignal flow.
## Conclusion
That's it! You've successfully added MFA to your Keycloak login flow using Authsignal.
To test the flow, log in and you will be prompted to enroll an authenticator.
The next time you log in, you will be prompted to complete an MFA challenge.
Want to use Keycloak groups and roles in your authentication rules? See the [Groups and Roles
guide](./keycloak-groups-roles) for instructions on setting this up.
# Federated Login with MFA
Source: https://docs.authsignal.com/integrations/keycloak/keycloak-federated-sign-in
Learn how to set up federated login with SSO and Authsignal in Keycloak.
## Overview
This guide demonstrates how to set up federated login (SSO) in Keycloak, using the Authsignal provider for post-login multi-factor authentication (MFA).
## Step 1: Create Your Authsignal Flow
First, ensure you have created a custom authentication flow that uses the Authsignal provider. The screen shot below shows an example of the flow you need to create.
You can follow the steps in the [Keycloak MFA guide](./keycloak-mfa) to configure the authsignal provider (click on the cog button to open the settings).
## Step 2: Configure the Identity Provider
Navigate to **Identity providers** in the Keycloak admin panel.
Select your SSO provider (e.g., OpenID Connect v1.0, SAML v2.0, etc.).
Configure the identity provider with your federated login provider configuration details.
Set the **Post login flow** to the custom authentication flow you configured in step 1.
## Step 3: Save and Test
Click **Save** at the bottom of the page.
Now, when users login via your federated SSO provider, they will be routed through the Authsignal post-login flow for MFA.
If you need to create a new authentication flow, or want to customize the steps, see the [Keycloak MFA guide](./keycloak-mfa).
## Summary
You have now enabled federated login with SSO and Authsignal MFA in Keycloak. Users authenticating via your identity provider will be prompted for MFA according to your Authsignal flow configuration.
# Using Keycloak groups and roles in authentication rules
Source: https://docs.authsignal.com/integrations/keycloak/keycloak-groups-roles
Learn how to use Keycloak groups and roles in Authsignal authentication rules.
## Overview
Starting with version 2.2.0 of the Authsignal Keycloak provider, Keycloak user groups and roles are automatically sent to Authsignal as [custom data points](/actions-rules/rules/custom-data-points). This enables you to create authentication rules based on user permissions and group memberships without requiring any code changes.
### Feature overview
The authenticator automatically passes three custom data points to Authsignal:
* **`keycloakGroups`** - Contains all groups the user belongs to
* **`keycloakRoles`** - Contains all realm-level roles assigned to the user
* **`keycloakClientRoles`** - Contains all client-level roles assigned to the user
This feature requires version 2.2.0 or later of the Authsignal Keycloak provider. The data is sent automatically with no additional configuration needed in Keycloak.
## Setup instructions
### 1. Configure custom data points in Authsignal
To use Keycloak groups and roles in your authentication rules, create the corresponding [custom data points](/actions-rules/rules/custom-data-points) in the Authsignal portal. You only need to create the data points you plan to use in your rules—you can create one, two, or all three depending on your needs.
Go to [Settings > Rules > Custom data points](https://portal.authsignal.com/organisations/tenants/custom_data_points) in the Authsignal Portal.
For each data point you want to use, click **Create data point** and configure one or more of the following:
* **keycloakGroups**
* **Type:** Multiselect
* **Description:** Keycloak groups the user belongs to
* **keycloakRoles**
* **Type:** Multiselect
* **Description:** Realm-level roles assigned to the user
* **keycloakClientRoles**
* **Type:** Multiselect
* **Description:** Client-level roles assigned to the user
You only need to create the [custom data points](/actions-rules/rules/custom-data-points) you plan to reference in your rules. If you're only using groups, for example, you only need to create `keycloakGroups`.
### 2. Create rules using the custom data
Once the custom data points are configured, you can use them in your authentication rules:
Go to the **Rules** section in the Authsignal Portal for your action.
Create a new rule or edit an existing one.
In the **Conditions** section, click **Add feature**, then select one of your Keycloak custom data points from the **Custom** tab.
Set the operation (e.g., "contains") and specify the group or role name to match.
Click **Save** to apply your rule.
## Use case examples
Here are practical examples of how you can use Keycloak groups and roles in your authentication rules:
**Example 1: Require MFA for admin group**
* **Condition:** If `keycloakGroups` contains "Admin"
* **Action:** Always challenge with MFA
* **Use case:** Ensure administrators always complete multi-factor authentication for enhanced security
**Example 2: Step-up authentication for privileged roles**
* **Condition:** If `keycloakRoles` contains "Finance" OR "Executive"
* **Action:** Require passkey authentication
* **Use case:** Require stronger authentication methods for users with access to sensitive financial data
# Adding passkey autofill to your Keycloak login flow
Source: https://docs.authsignal.com/integrations/keycloak/keycloak-passkey-autofill
Learn how to add passkey autofill to your Keycloak login flow with Authsignal.
## Overview
Passkey autofill provides an incredibly seamless and secure way for users to sign-in using their passkey.
In this guide, we will show you how to add passkey autofill to your Keycloak sign-in flow using Authsignal.
When clicking on the username or password
input fields, users will be prompted to authenticate with their passkey that they added through Authsignal's pre-built UI.
The above example can be extended to meet your specific requirements.
## Prerequisites
* You have completed the previous guide on [adding passkey MFA to your Keycloak login flow](/integrations/keycloak/keycloak-passkey-signup-with-prebuilt-UI).
## Authsignal configuration
### Local development
To make things easy, we will make a few simple changes to your local development setup so that you can simulate a production environment, locally.
We will use `mkcert` to create a local SSL certificate and `local-ssl-proxy` to proxy requests to your local Keycloak server.
Replace `keycloak-demo.authsignallabs.com` with your domain. If using the pre-built UI, this will
differ from your Authsignal custom domain. i.e. `keycloak-demo.authsignallabs.com` which our demo
app uses vs `keycloak-demo-auth.authsignallabs.com` where the pre-built UI is running - in this
demonstration.
```bash theme={null}
brew install mkcert
mkcert -install
mkcert your_domain # e.g. mkcert keycloak-demo.authsignallabs.com
```
Take note of the path to the `.pem` files that are created as you'll need them in the next steps.
Map your custom domain to your local IP address by adding the following line to your `hosts` file:
```bash theme={null}
sudo vim /etc/hosts
```
Add this line:
```
127.0.0.1 your_domain # e.g. 127.0.0.1 keycloak-demo.authsignallabs.com
```
```bash theme={null}
npx local-ssl-proxy --key keycloak-demo.authsignallabs.com-key.pem --cert keycloak-demo.authsignallabs.com.pem --source 8444 --target 8443
```
Learn more about the [Keycloak server configuration options](https://www.keycloak.org/server/enabletls).
```bash theme={null}
bin/kc.sh start-dev \
--hostname=keycloak-demo.authsignallabs.com \
--https-certificate-file=keycloak-demo.authsignallabs.com.pem \
--https-certificate-key-file=keycloak-demo.authsignallabs.com-key.pem \
--http-host=127.0.0.1
```
```powershell theme={null}
# Install mkcert
winget install mkcert
mkcert -install
mkcert your_domain # e.g. mkcert keycloak-demo.authsignallabs.com
```
Take note of the path to the `.pem` files that are created as you'll need them in the next steps.
Open Notepad as Administrator and open the hosts file located at:
`C:\Windows\System32\drivers\etc\hosts`
Add this line:
```
127.0.0.1 your_custom_domain # e.g. 127.0.0.1 keycloak-demo.authsignallabs.com
```
```powershell theme={null}
npx local-ssl-proxy --key keycloak-demo.authsignallabs.com-key.pem --cert keycloak-demo.authsignallabs.com.pem --source 8444 --target 8443
```
Learn more about the [Keycloak server configuration options](https://www.keycloak.org/server/enabletls).
```powershell theme={null}
.\bin\kc.bat start-dev ^
--hostname=keycloak-demo.authsignallabs.com ^
--https-certificate-file=keycloak-demo.authsignallabs.com.pem ^
--https-certificate-key-file=keycloak-demo.authsignallabs.com-key.pem ^
--http-host=127.0.0.1
```
Open your browser and navigate to `https://your_domain:8443` (e.g. `https://keycloak-demo.authsignallabs.com:8443`) which will redirect you to the admin sign-in page on your proxied https Keycloak server.
### Changes to your Keycloak configuration
In your Keycloak admin UI, navigate to the **Authentication** section and click on your custom flow. Delete the default username and password flow.
Click on the Authsignal Authenticator settings and toggle on **Enable Passkey Autofill**.
### Sign-in UI configuration
We need to add some custom code to the Keycloak sign-in page so that it can use [Authsignal's Web SDK](/sdks/client/web/setup) to handle passkey autofill.
Start by creating a new theme for your Keycloak instance, at the following path: `themes/mytheme/login/login.ftl` on your keycloak server.
You can call the theme whatever you want, but for this example, we will call it `mytheme`.
Add the following code to the `login.ftl` file. This code will allow autofill to work when the user clicks the username or password input fields.
```html themes/mytheme/login/login.ftl theme={null}
Please enter your credentials to continue
```
`webauthn` has to be the last autocomplete attribute value in the list otherwise it will not work.
Create a Javascript file at `themes/mytheme/login/resources/js/script.js` and add the following code. This code is required to allow autofill to work when the user clicks the input.
```js themes/mytheme/login/resources/js/script.js theme={null}
function setWebauthnAttribute() {
var usernameInput = document.getElementById("username");
var passwordInput = document.getElementById("password");
const formElement = document.querySelector("form");
if (usernameInput && passwordInput) {
usernameInput.setAttribute("autocomplete", "username webauthn");
// NOTE: Replace the following values with your Authsignal tenant ID and server URL
var client = new window.authsignal.Authsignal({
tenantId: "YOUR_TENANT_ID",
baseUrl: "https://api.authsignal.com/v1",
});
client.passkey
.signIn({ autofill: true })
.then((response) => {
if (response) {
const hiddenTokenInput = document.createElement("input");
hiddenTokenInput.type = "hidden";
hiddenTokenInput.name = "token";
hiddenTokenInput.value = response.token;
formElement.appendChild(hiddenTokenInput);
formElement.submit();
}
})
.catch((error) => {
console.log("error", error);
});
}
}
function loadAuthsignalSdk() {
var script = document.createElement("script");
script.onload = setWebauthnAttribute;
script.src = "https://unpkg.com/@authsignal/browser@0.5.2/dist/index.min.js";
document.head.appendChild(script);
}
document.addEventListener("DOMContentLoaded", loadAuthsignalSdk);
```
Restart your Keycloak server, and navigate to the **Realm settings** section in the Keycloak admin UI. Click **Themes**.
On the **Login theme** line, select `mytheme` and click **Save**.
You can style your theme by adding your own CSS to the
`themes/mytheme/login/resources/css/styles.css` file.
Navigate to your realm's sign-in page and you should see the new theme.
You can find your realm's sign in page URL by selecting your realm and then navigating to the
**Clients** section in the Keycloak admin UI and clicking **Home URL**.
### Passkey configuration updates
Before passkeys can be used on your Keycloak login page, you need to ensure that you have added your
Keycloak login page domain as an [expected origin](/authentication-methods/passkey/prebuilt-ui#configure-passkeys-in-the-authsignal-portal).
As an example, in this demo the passkey configuration is as follows:
* Relying Party ID: `authsignallabs.com`
* Expected Origins: `keycloak-demo.authsignallabs.com` (Keycloak login page) and `keycloak-demo-auth.authsignallabs.com` (pre-built UI custom domain)
## Conclusion
That's it! You've successfully added passkey autofill to your Keycloak sign-in flow using Authsignal.
# Adding passkey MFA to your Keycloak login flow
Source: https://docs.authsignal.com/integrations/keycloak/keycloak-passkey-signup-with-prebuilt-UI
Learn how to add passkey MFA to your Keycloak login flow with Authsignal.
## Overview
In the [previous guide](/integrations/keycloak/keycloak-mfa), we demonstrated how to add an MFA step to your Keycloak login flow using Authsignal.
In this guide, we will add passkey MFA to your Keycloak login flow using Authsignal's pre-built UI.
## Configuration
Set up passkeys by following the steps in [Authsignal's pre-built UI + passkeys configuration guide](/authentication-methods/passkey/prebuilt-ui).
## Testing the flow
The next time users log in and complete an MFA challenge, they will then be prompted to add a passkey.
Once users have added a passkey, they will be prompted to use passkeys for MFA by default.
### Passkey upgrade flow demo
### Passkey being used for MFA demo
## Next steps
In the next guide, we will demonstrate how to set up passkey autofill.
This will allow users to use their passkeys by clicking on the username or password input fields.
# NextAuth.js (now known as Auth.js)
Source: https://docs.authsignal.com/integrations/nextauth-js
Learn how to integrate Authsignal with your Next.js app when using NextAuth.
This guide shows how to add a passkey sign-in option to [NextAuth](https://next-auth.js.org/) using Authsignal. We have used email magic link for demo purposes, but the same flow can be used for other authentication methods.
Authsignal Passkeys with NextAuth in a Next.js Pages Router app.
## Create a new Next.js application
```bash npm theme={null}
npm create next-app@latest
```
```bash pnpm theme={null}
pnpm create next-app
```
```bash yarn theme={null}
yarn create next-app
```
```bash bun theme={null}
bun create next-app
```
## Setup NextAuth with Email Magic Link Sign In
Users will need to create an account and sign in before they can create a passkey. To set up NextAuth with email magic link sign in, follow the NextAuth [Email Provider Docs](https://next-auth.js.org/configuration/providers/email).
## Install Authsignal SDKs
Install the Authsignal [web](/sdks/client/web/setup) and [node](/sdks/server/overview) SDKs:
```bash npm theme={null}
npm install @authsignal/browser @authsignal/node
```
```bash yarn theme={null}
yarn add @authsignal/browser @authsignal/node
```
```bash pnpm theme={null}
pnpm add @authsignal/browser @authsignal/node
```
```bash bun theme={null}
bun add @authsignal/browser @authsignal/node
```
We recommend disabling 1Password browser extensions during development as they can intercept error
messages from Authsignal's SDKs.
## Add Authsignal secret key, tenant ID, and region API host
Get the Server API secret key, tenant ID, and region API host to your from [Authsignal Portal](https://portal.authsignal.com/organisations/tenants/api) and add them to your `.env.local` file.
```bash .env.local theme={null}
NEXT_PUBLIC_AUTHSIGNAL_TENANT_ID=YOUR_TENANT_ID
AUTHSIGNAL_SERVER_API_SECRET=YOUR_TENANT_SERVER_API_SECRET
NEXT_PUBLIC_AUTHSIGNAL_API_HOST=YOUR_REGION_API_HOST
```
## Creating a passkey
Create a protected endpoint that tracks an action using Authsignal's [Node Server SDK](/sdks/server/actions#track-action). In our example, we've created
an endpoint called `enroll-passkey` that checks the session token before tracking an action. Pass the token returned by the `track` call to your frontend.
```tsx pages/api/auth/enroll-passkey.ts theme={null}
import { Authsignal } from "@authsignal/node";
import { NextApiRequest, NextApiResponse } from "next";
import { getToken } from "next-auth/jwt";
const authsignal = new Authsignal({
apiSecretKey: process.env.AUTHSIGNAL_SERVER_API_SECRET!,
apiUrl: process.env.NEXT_PUBLIC_AUTHSIGNAL_API_HOST!,
});
export default async function enrollPasskey(
req: NextApiRequest,
res: NextApiResponse
) {
const sessionToken = await getToken({ req });
if (!sessionToken || !sessionToken.sub) {
return res.status(401).json("Unauthenticated");
}
const { token } = await authsignal.track({
userId: sessionToken.sub,
action: "enroll-passkey",
attributes: {
scope: "add:authenticators",
},
});
res.status(200).json(token);
}
```
In your app's frontend, call the `signUp` function using Authsignal's [Web SDK](/sdks/client/web/setup), passing the token returned in step 1.
```tsx pages/index.tsx theme={null}
const authsignal = useAuthsignal();
// Get a short lived token by tracking an action
const enrollPasskey = async () => {
// Get a short lived token by tracking an action
const enrollPasskeyResponse = await fetch(
"/api/auth/enroll-passkey"
);
const token = await enrollPasskeyResponse.json();
// Initiate the passkey enrollment flow
const username = session.user.email;
const resultToken = await authsignal.passkey.signUp({
token,
username,
});
};
```
Create a protected endpoint that validates the result token from step 2.
Pass the result token returned from Authsignal's [passkey.signUp](/sdks/client/web/passkeys#creating-a-passkey) in step 2 to your backend, validating the result of the enrollment server-side. In our example, we've
created an API route called `callback` that checks the NextAuth session token, then validates the result token passed in from step 2.
```tsx pages/api/auth/callback.ts theme={null}
import { Authsignal } from "@authsignal/node";
import { NextApiRequest, NextApiResponse } from "next";
import { getToken } from "next-auth/jwt";
const authsignal = new Authsignal({
apiSecretKey: process.env.AUTHSIGNAL_SERVER_API_SECRET!,
apiUrl: process.env.NEXT_PUBLIC_AUTHSIGNAL_API_HOST!,
});
export default async function callback(req: NextApiRequest, res: NextApiResponse) {
const sessionToken = await getToken({ req })
if (!sessionToken) {
return res.status(401).json('Unauthenticated');
}
const { token } = req.query;
if (!token || Array.isArray(token)) {
res.status(400).json("Invalid token");
return;
}
const data = await authsignal.validateChallenge({ token });
res.status(200).json(data);
};
```
Notify the user of the success or failure of the passkey addition.
```tsx pages/index.tsx theme={null}
const { success } = await callbackResponse.json();
if (success) {
alert("Successfully added passkey");
} else {
alert("Failed to add passkey");
}
```
The full frontend enrollment logic is shown below:
```tsx pages/index.tsx theme={null}
import { useRouter } from "next/router";
import { useSession, signOut } from "next-auth/react";
import { useEffect } from "react";
import { useAuthsignal } from "../utils/authsignal";
export default function Index() {
const { data: session, status } = useSession();
const router = useRouter();
const authsignal = useAuthsignal();
useEffect(() => {
if (status === "unauthenticated") {
router.push("/signin");
}
}, [status, router]);
const enrollPasskey = async () => {
if (!session?.user?.email || !session.user) {
throw new Error("No user in session");
}
// Get a short lived token by tracking an action
const enrollPasskeyResponse = await fetch("/api/auth/enroll-passkey");
const token = await enrollPasskeyResponse.json();
// Initiate the passkey enroll flow
const username = session.user.email;
const resultToken = await authsignal.passkey.signUp({
token,
username,
});
// Check that the enrollment was successful
const callbackResponse = await fetch(`/api/auth/callback/?token=${resultToken}`);
const { success } = await callbackResponse.json();
if (success) {
alert("Successfully added passkey");
} else {
alert("Failed to add passkey");
}
};
if (status === "loading") {
return
Loading...
;
}
return (
Welcome, {session?.user?.email}
You now have a NextAuth session.
);
}
```
You can remove passkeys along with other authenticators in the [Authsignal
Portal](https://portal.authsignal.com/users). You will also need to remove it from your device,
e.g. in chrome://settings/passkeys.
## Signing in with a passkey
Now that the user has enrolled a passkey, the next time they sign in we would like to give them the option to use their passkey instead of email magic link.
First, we need to ensure that the input field has the value `username webauthn` in the [autocomplete attribute](https://github.com/authsignal/next-auth-passkeys-example/blob/db24102a2a6be898afca1879626317fd9da0b4e7/pages/signin.tsx#L69).
The `webauthn` value in the `autocomplete` attribute is required for autofill to work.
`webauthn` can be combined with other typical `autocomplete` values, including `username` and `current-password`, but must appear at the end to consistently trigger conditional UI across browsers.
```js pages/signin.tsx theme={null}
setEmail(input.target.value)}
autoComplete="username webauthn"
/>
```
Then we call [authsignal.passkey.signIn](/sdks/client/web/passkeys#using-a-passkey) when the page loads. This will initialize the input field for passkey autofill, which means if a user has enrolled a passkey then they should be able to select it when focusing the text field.
On success, this will return a token that we will validate on the backend.
```tsx pages/signin.tsx theme={null}
const signInToken = await authsignal.passkey.signIn({
autofill: true,
});
```
We pass this token to NextAuth's `signIn` method, specifying that we want to use the `credentials` provider. This uses the token to validate the result of the challenge server-side and logs in the user.
We've set `redirect: false` so that we can handle errors manually, on the current page.
```tsx pages/signin.tsx theme={null}
import { signIn } from "next-auth/react";
const result = await signIn("credentials", {
signInToken,
redirect: false,
});
```
The full sign in logic code is shown below:
```tsx pages/signin.tsx theme={null}
useEffect(() => {
const handlePasskeySignIn = async () => {
try {
// Initialize the input for passkey autofill
const response = await authsignal.passkey.signIn({
autofill: true,
});
// Extract token from response.data.token
let signInToken = null;
if (response && response.data && response.data.token) {
signInToken = response.data.token;
}
// Run NextAuth's sign in flow. This will run if the user selects one of their passkeys from the Webauthn dropdown.
if (signInToken && typeof signInToken === "string") {
const result = await signIn("credentials", {
signInToken,
redirect: false,
});
if (result?.error) {
alert("Failed to sign in with passkey");
} else {
router.push("/");
}
} else {
alert("Failed to sign in with passkey: Invalid token format");
}
} catch (err: any) {
if (err.name === "AbortError") {
// Ignore
} else {
console.error("Passkey sign-in error:", err);
throw err;
}
}
};
if (status === "unauthenticated") {
handlePasskeySignIn();
}
}, [status, authsignal.passkey, router]);
```
We need to add a [CredentialsProvider](https://next-auth.js.org/providers/credentials) to the providers array in `authOptions` within the `[...nextauth]` API route.
This will validate the signInToken from step 1 using Authsignal's `validateChallenge` method.
On successful validation, NextAuth will create a session containing user information that you specify. Returning `null` will throw an error.
```ts pages/api/auth/[...nextauth].ts theme={null}
const authOptions = {
adapter: PrismaAdapter(prisma),
session: {
strategy: "jwt" as SessionStrategy,
},
providers: [
EmailProvider({
server: {
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASSWORD,
},
},
from: process.env.SMTP_FROM,
}),
CredentialsProvider({
name: "webauthn",
credentials: {},
async authorize(cred) {
const { signInToken } = cred as { signInToken: string };
if (!signInToken) {
return null;
}
try {
const result = await authsignal.validateChallenge({
token: signInToken,
});
const userId = result.userId;
if (!userId) {
return null;
}
const user = await prisma.user.findUnique({
where: { id: userId },
});
if (!user) {
return null;
}
const state = result.state;
if (state === "CHALLENGE_SUCCEEDED") {
return { id: user.id, email: user.email };
}
} catch {
return null;
}
return null;
},
}),
],
secret: process.env.NEXTAUTH_SECRET,
};
```
You'll notice that we've also added a JWT strategy to the session option. We need to enable JWT session tokens for NextAuth's CredentialsProvider to work,
according to [NextAuth's documentation](https://next-auth.js.org/providers/credentials).
# Ruby on Rails
Source: https://docs.authsignal.com/integrations/ruby-on-rails
Learn how to use Authsignal with Ruby on Rails to implement Multi-factor Authentication and Passkeys
This guide will demonstrate how to integrate Authsignal in a Ruby on Rails app in two scenarios, Multi-factor Authentication flows on “Sign in” and on a example user action e.g. “Withdrawing Money”.
It uses the most widely used Authentication gem Devise/Warden as an example, the Stimulus.JS as the client side library to handle challenge flows, and assumes that you have these libraries already configured.
## Installation
Add the Authsignal Ruby gem into your Gemfile:
* Github: [https://github.com/authsignal/authsignal-ruby](https://github.com/authsignal/authsignal-ruby)
* Rubygems: [https://rubygems.org/gems/authsignal-ruby](https://rubygems.org/gems/authsignal-ruby)
```bash theme={null}
gem 'authsignal-ruby'
```
Add the [@authsignal/browser](/sdks/client/web/setup) JavaScript client:
```bash npm theme={null}
npm install @authsignal/browser
```
```bash yarn theme={null}
yarn add @authsignal/browser
```
```bash pnpm theme={null}
pnpm add @authsignal/browser
```
```bash bun theme={null}
bun add @authsignal/browser
```
Add the Authsignal initialization code block into `config/initializers/authsignal.rb`:
```ruby theme={null}
require 'authsignal'
Authsignal.setup do |config|
config.api_secret_key = ENV["AUTHSIGNAL_SECRET_KEY"]
config.api_url = "YOUR_REGION_API_URL" # Ensure this is set to the appropriate region for your tenant. See https://docs.authsignal.com/sdks/server/overview#initialization.
config.retry = true
# config.debug = ENV['RAILS_ENV'] == 'development'
end
```
Initialize the [@authsignal/browser](/sdks/client/web/setup) client anywhere your Javascript gets loaded. This could be in `app/javascript/application.js`. Doing this initializes the Authsignal cookie.
```js theme={null}
window.authsignal = new Authsignal({ tenantId: "YOUR_TENANT_ID", baseUrl: "YOUR_REGION_BASE_URL" });
```
## Allowing your users to enroll
The first step is allow your user to enroll authenticators. This step assumes you have already setup at least one Authenticator for your tenant in the admin portal.
Authsignal's ruby SDK allows you to check a user's enrollment status and provides the URL for your user to manage their authenticators.
The following is an example of a controller action that redirects the user to the Authsignal enrollment and management flow and sets a redirect url when the user completes the self-service flows.
The most important thing to note is that in order to trigger a flow which allows the self service enrollment and management screens you need to add the following attribute to the `track_action` input `redirect_to_settings: true`.
```ruby theme={null}
class MfaController < ApplicationController
def index
result = Authsignal.track(
user_id: user.id,
action: "enrollment",
attributes: {
redirect_url: root_url,
email: user.email,
device_id: authsignal_cookie,
user_agent: auth.request.user_agent,
ip_address: auth.request.ip,
redirect_to_settings: true })
redirect_to result[:url], allow_other_host: true
end
end
```
## Devise/Warden - (Sign In Scenario)
This step in the guide implements MFA challenge flows in a typical Devise Sign in scenario and uses the `authsignal-ruby` SDK. If Authsignal returns a challenge and the user is enrolled with authentication factors, we will redirect the user to a challenge flow and on completion of the challenge, complete the login process.
Insert the following `after_authentication` hook into `config/initializers/warden.rb`. This block fires after a successful login and makes the `track` call.
```ruby theme={null}
Warden::Manager.after_authentication do |user,auth,opts|
# Using this cookie will help with rules that require device tracking
# This cookie uses the authsignal-browser SDK to instantiate
authsignal_cookie = auth.request.cookies["__as_aid"]
begin
result = Authsignal.track(
user_id: user.id,
action: "signIn",
attributes: {
redirect_url: Rails.application.routes.url_helpers.complete_mfa_url,
email: user.email,
device_id: authsignal_cookie,
user_agent: auth.request.user_agent,
ip_address: auth.request.ip
}
)
rescue => e
auth.logout
end
case result[:state]
when "BLOCK"
# If Authsignal rules give back a BLOCK decision, then do
# not log the user in and log out
auth.logout
throw(:warden, :message => "Your account is blocked")
when "CHALLENGE_REQUIRED"
auth.env["authsignal_devise.response"] = result
end
end
```
Override the Devise Sessions controller by creating a new controller file in `app/controllers/users/sessions_controller.rb`:
```ruby theme={null}
class Users::SessionsController < Devise::SessionsController
def create
super do |resource|
if request.env["authsignal_devise.response"]
@authsignal_challenge_initiated = true
session[:authsignal_user_id] = resource.id
sign_out(resource)
redirect_to request.env["authsignal_devise.response"][:url], allow_other_host: true
return
end
end
end
def complete_mfa
token = params[:token]
user_id = session[:authsignal_user_id]
user = User.find(user_id)
session[:authsignal_user_id] = nil
validate_challenge(token, user)
end
private
def validate_challenge(token, user)
result = Authsignal.validate_challenge(
token: token,
user_id: user.id,
action: "signIn"
)
if result[:is_valid]
sign_in user
redirect_to after_sign_in_path_for(user)
return
end
flash[:alert] = "Failed Step Up Authentication"
redirect_to root_path
end
def authsignal_challenge_initiated?
@authsignal_challenge_initiated == true
end
end
```
Register the newly created Sessions controller and the new `complete_mfa` action into your `routes.rb` file:
```ruby theme={null}
devise_for :users, :controllers => {:registrations => "users/registrations", :sessions => "users/sessions"}
devise_scope :user do
get 'users/complete_mfa', to: 'users/sessions#complete_mfa', as: :complete_mfa
end
```
You now have your sign-in flows protected with Authsignal.
## User Action Scenario
Authsignal is designed to be dropped into any part of your user journey, not just sign-in. The next part of the guide will show how to use the Challenge flow pop-up via the [@authsignal/browser](/sdks/client/web/setup) JavaScript client, in conjunction with the server-side track action call.
It assumes that you are using Stimulus as the client-side library for handling browser-based Javascript, but this approach could be used with any client-side library or framework (React, Vue). The flow follows the convention described in the [How Authsignal Works](/#how-it-works) section.
### Server-side
```ruby theme={null}
class WithdrawalController < ApplicationController
before_action :authenticate_user!
skip_before_action :verify_authenticity_token, only:[:create, :complete]
def create
# Using this cookie will help with rules that require device tracking
authsignal_cookie = auth.request.cookies["__as_aid"]
result = Authsignal.track(
user_id: current_user.id,
action: "withdrawal",
attributes: {
email: current_user.email,
device_id: authsignal_cookie,
user_agent: request.user_agent,
ip_address: request.ip
custom: {
withdrawal_amount: params[:amount]
}
}
)
case result[:state]
when "BLOCK"
render :json => { error: "Withdrawal has been blocked" }, :status => :forbidden
return
when "CHALLENGE_REQUIRED"
render :json => { url: result[:url],
idempotency_key: result[:idempotency_key],
error: "Challenge Required" }, :status => :unprocessable_entity
return
when "ALLOW"
complete_withdrawal
render :json => { success: true }
return
end
end
def complete
token = params[:token]
# This call fetches the action that was previously created
# The response specifies whether the user has completed the challenge successfully
result = Authsignal.validate_challenge(
token: token,
user_id: current_user.id,
action: "withdrawal"
)
# Complete your action if the challenge was successful
if ["CHALLENGE_SUCCEEDED", "ALLOW"].include?(result[:state])
complete_withdrawal
render :json => { success: true }
return
else
render :json => { error: "Challenge has not been completed" }, :status => :forbidden
return
end
end
private
def complete_withdrawal
# Business logic to complete your withdrawal step
end
end
```
Here is an example of a server-side action that simulates a “Withdraw” money flow, which is a typical use case where you might want to protect your user with a step-up challenge. There are two actions in this controller: `create` which calls `track` and `complete` which is called after the user finishes a challenge flow. These are all called via a JSON request from the stimulus client-side.
### Client side
Rails View
```html theme={null}
```
Stimulus Controller
```js theme={null}
// withdrawal_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["amount"];
async withdraw() {
const amount = { amount: this.amountTarget.value };
const response = await fetch("/withdrawal", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ amount }),
});
const result = await response.json();
// If the url is returned then we need to trigger
// the authsignal challenge flow
if (result.url) {
const { idempotency_key } = result;
// This step brings up the challenge pop up
// When the challenge flow completes the pop up will close and the promise
// will resolve
const challengeFlow = await window.authsignal.launch(result.url, {
mode: "popup",
});
// Return the idempotency key from the initial result,
// as part of the complete request
if (challengeFlow) {
const complete_step_response = await fetch("/withdrawal/complete", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ amount, idempotency_key }),
});
const complete_step_result = await complete_step_response.json();
const { success } = complete_step_result;
success
? alert("Great your user has successfully completed the action")
: alert("Great your user has not successfully completed the action");
}
}
}
}
```
# Getting started with Salesforce
Source: https://docs.authsignal.com/integrations/salesforce/getting-started
Learn how to configure Authsignal Call Connect with Salesforce for secure caller verification.
This guide shows how to configure Authsignal Call Connect with Salesforce. This integration enables IT help-desks and service desks to securely verify the identity of customers before taking action.
Please [get in touch with the Authsignal team](https://www.authsignal.com/contact) to enquire
about using our Salesforce integration
## Package installation
### Prerequisites
* Salesforce Organization (Enterprise Edition and up)
* System Administrator access
* Lightning Experience enabled
* Sites feature enabled
### Step 1: Install the package
The Authsignal team will provide you with a URL to use to install the managed package to your Salesforce instance.
Navigate to this URL to begin.
Be sure to use the correct URL for your environment type `Production`/`Developer` vs `Sandbox`
Select **Install for Admins Only**, additional permissions settings will be set later.
When prompted about remote sites, click **Yes, grant access to these third-party websites**.
This allows Authsignal Call Connect to communicate with Authsignal APIs.
Typically installation takes 2-5 minutes.
Once complete navigate to **Setup -> Apps -> Packaging -> Installed Packages** to verify the presence of **Call Connect**
### Step 2: Assign permission sets
You must assign permission sets **immediately** after installation to access Call Connect
functionality.
1. Navigate to **Setup → Users → Permission Sets**
2. Click **Call Connect Administrator**
3. Click **Manage Assignments**
4. Select your admin user(s) to assign them the Call Connect Administrator role
5. Click **Save**
1. Navigate to **Setup → Users → Permission Sets**
2. Click **Call Connect Standard User**
3. Click **Manage Assignments**
4. Select all users who need to create authentication challenges and add them to assign the Standard User role
5. Click **Save**
## Salesforce Sites configuration
Authsignal Call Connect uses a Site in order to receive a webhook from Authsignal. This informs Salesforce of the outcome of the Authsignal Call Connect authentication session.
### Step 3: Enable and configure Site
Authsignal Call Connect makes use of Salesforce Sites. Other site types, such as Experience Sites,
will not work with this webhook configuration.
Navigate to **Setup → Sites and Domains -> Sites**
If sites is not enabled for your Salesforce instance: click **Enable Sites**, accept the terms and conditions, and then you will be prompted to register for a domain.
If Sites is already enabled but no domain exists, you will be asked to register for a domain name (e.g., `yourcompany-callconnect`)
* Click **New Site**
* Enter the following details to configure your site:
| Parameter | Value |
| --------------------- | ---------------------------------------------------------------------------------- |
| Site Label | Call Connect Webhook Site |
| Site Name | CallConnectWebhookSite |
| Active Site Home Page | Select any available page e.g. **AnswersHome**, **FileNotFound**, or **SiteLogin** |
* Click **Save**
* Note the site domain name configuration, for later configuration in the Authsignal portal.
You must select an active home page for the site to function properly
### Step 4: Assign webhook permissions to guest user
In the Site details, click **Public Access Settings**.
Click **View Users**, and find the **Site Guest User**.
Click on their name **Site guest user** to open their profile.
Click **Edit Assignments** under Permission Set Assignments
Add **Call Connect Webhook User** permission set to the list.
Click **Save**
Verify the webhook is accessible by calling the health check endpoint.
This varies based on organisation type:
| Organisation Type | Example Webhook URL |
| ----------------- | ------------------------------------------------------------------------------------------------------------- |
| Production | `https://[your-site-domain].my.salesforce-sites.com/services/apexrest/authsignal/callconnect/webhook` |
| Developer Edition | `https://[your-site-domain].develop.my.salesforce-sites.com/services/apexrest/authsignal/callconnect/webhook` |
| Sandbox | `https://[your-site-domain].sandbox.my.salesforce-sites.com/services/apexrest/authsignal/callconnect/webhook` |
Substitute `your-site-domain` for the domain you noted earlier.
For a healthy webhook, you should see a health check response like this:
```xml theme={null}
/services/apexrest/authsignal/callconnect/webhookhealthy2025-11-17T10:30:15.719Z1.0.0
```
If you see a `FORBIDDEN` error here this means the permission set what not assigned correctly to
the guest user.
## Lightning page configuration
### Step 5: Configure case record page
The included Case record page is a template and cannot be used directly. You must clone it first.
Navigate to **Setup -> Objects and Fields -> Object Manager**, and select **Case** from the list.
On the navigation bar on the left select **Lightning Record Pages**, and select **Call Connect Case Record Page Template**.
Click **Clone**.
Be sure to rename the **Label** of the template.
Modify the template as you wish.
Note the **Authenticate** button on the top right of the page, this is the **Call Connect Challenge** component:
Additionally, there is a **Call Connect Transaction Record** component for keeping a log of all Call Connect Activities:
Click **Save** to save your progress.
Once saved, click **Activation** on the top right of the page.
Here you can choose how far to scope this template, for example you can activate org-wide.
Click **Assign as Org Default**, select **Desktop**, click **Next** and then **Save**.
## Final configuration
### Step 6: Configure Call Connect custom metadata
You cannot edit managed package custom metadata directly. You must clone the record.
Navigate to **Setup -> Custom Code -> Custom Metadata Types**.
Next to **Call Connect Settings** click **Manage Records**.
Click **Case Settings Template (please clone)**.
Click **Clone**.
Configure as below:
| Variable | Explanation | Example |
| ------------------------------ | ------------------------------------------------------------------- | ---------------------------------- |
| Label | Give it a descriptive name | My Case Settings |
| Call Connect Settings Name | Will auto populate | My Case Settings |
| Is Active | | `true` |
| Challenge Status Field | Field API name where challenge status is stored | `authsignal__CallConnectStatus__c` |
| Challenge Time Allowed | Time in seconds for challenge | 60 |
| Email | Section of the case to be mapped to the Email Field in Call Connect | `Contact.Email` |
| Phone | Section of the case to be mapped to the Phone Field in Call Connect | `Contact.Phone` |
| Transaction Relationship Field | How to map the Call Connect Transaction to the parent record | `CaseId` |
**Save** the record.
The original managed package metadata is already deactivated, so your new cloned record with "Is Active" = `true` becomes the active configuration
### Step 7: Authsignal Call Connect portal configuration
In the Authsignal Portal, navigate to **Settings -> Call Connect**. Select **Salesforce**.
Enter the following information, which can be found from the Authsignal Salesforce Site configuration page:
* Salesforce Organization ID
* Organization Type
* Site ID
* Site Domain Name
Select **Activate Call Connect**
You are presented with the Call Connect Settings Page. Scroll to the **Call Connect API Keys** section at the bottom of the page.
Select to copy the **API secret key**. Keep this handy for the next step.
### Step 8: Configure API key in Salesforce
In Salesforce navigate to **Setup -> Security -> Named Credentials**. Click on the **External
Credentials** tab, and select the **CallConnectExtCredentials** named credential.
Under **Principal**, select the dropdown on the right and select **Edit**.
Enter the following as a Parameter:
| Parameter Name | Value |
| -------------- | ------------------------------------------------------------- |
| `API_KEY` | Authsignal Call Connect API Key copied from the previous step |
Select **Save**
# ServiceNow
Source: https://docs.authsignal.com/integrations/service-now
Learn how to configure Authsignal Call Connect with ServiceNow for secure caller verification.
This guide shows how to configure Authsignal Call Connect with ServiceNow. This integration enables IT help-desks and service desks to securely verify the identity of customers before taking action.
View the ServiceNow Store listing for Authsignal Call Connect
## Prerequisites
* ServiceNow instance with [API Key and HMAC Authentication](https://www.servicenow.com/docs/r/platform-security/authentication/api-key-and-hmac-rest-apis.html) (`com.glide.tokenbased_auth`) plugin installed and configured.
* ServiceNow instance with the [Authsignal Call Connect app](https://store.servicenow.com/store/app/aa1018ac33907a9457d93c128d5c7b14) installed from the ServiceNow Store
* Twilio or Bird account with SMS enabled and configured in Authsignal
* ServiceNow ITSM module with a contact that has at least a mobile phone number
## Configuration
### 1. Create a ServiceNow API key record
Create a ServiceNow API key record with a generated key. This key will be used for the callback from Authsignal to ServiceNow.
In your ServiceNow instance, navigate to **All -> Organization -> Users** and select **New**.
Enter user details:
| Field | Value |
| ------------- | ------------------- |
| User ID | svc-authsignal |
| First name | Authsignal |
| Last name | Service Account |
| Identity Type | Machine |
| Email | Enter Email Address |
Click **Submit**
Navigate to your newly created User.
Select **Roles** and then **Edit**
Add the following roles:
* `itil`
* `snc_internal`
* `x_auths_cc.admin`
* `x_auths_cc.agent`
* `x_auths_cc.user`
Click **Save**
In your ServiceNow instance, navigate to **All -> System Web Services -> API Access Policies -> REST API Key**.
Select **New**, fill in the name "Authsignal Call Connect Webhook".
Select the service account you made earlier, set the Auth Scope to `useraccount`, and set an expiry date for the API key.
Click **Submit**.
Click on the newly created record and reveal the token value. Copy this value - you'll need it for the Authsignal configuration in the next step.
Additionally, right click on the API key name and click **Copy sys\_id** and save this for later.
### 2. Configure Call Connect in Authsignal portal
Navigate to the Call Connect section in the Authsignal portal ([Settings > Call Connect](https://portal.authsignal.com/organisations/tenants/call_connect)) and select the ServiceNow option.
Fill in the ServiceNow instance name field. The instance name is usually the subdomain of your ServiceNow instance URL.
Paste the API key token from the previous step into the **ServiceNow webhook API key** field.
Click the **Activate Call Connect** button to activate the integration.
### 3. Complete ServiceNow configuration
In your ServiceNow instance, navigate to **All > Authsignal Call Connect > Call Connect Tracking > Properties** and configure the following mandatory properties:
Based on the region of your Authsignal tenant, copy the following URL and paste it into the **Authsignal Call Connect URL with path** field:
| Region | Call Connect URL |
| ------------- | ---------------------------------------------- |
| US (Oregon) | `https://us-connect.authsignal.com/call/start` |
| AU (Sydney) | `https://au-connect.authsignal.com/call/start` |
| EU (Ireland) | `https://eu-connect.authsignal.com/call/start` |
| CA (Montreal) | `https://ca-connect.authsignal.com/call/start` |
Paste the API key from the [Authsignal portal for the Call Connect API](https://portal.authsignal.com/organisations/tenants/call_connect) into the **Authsignal API key** field.
Paste the Sys ID of the webhook API key record created in step 1 into the **Sys ID of the webhook API key record** field.
Click **Save** to complete the configuration.
### 4. Final configuration
Complete the setup by ensuring:
* All callers in the `sys_user` table have phone numbers in E.164 format (e.g., `+61417888999`)
* All engaged agents have the agent role assigned (`x_auths_cc.agent`) to appropriate groups and/or users
# Using Authsignal with WSO2 Identity Platform
Source: https://docs.authsignal.com/integrations/wso2-identity-platform
Learn how to add MFA to your WSO2 Identity Platform login flow with Authsignal.
This guide shows how to add MFA to a [WSO2 Identity Platform](https://wso2.com/asgardeo/) login flow using Authsignal. The integration is configured as a **Custom Authentication** connection in WSO2 Identity Platform that points to an Authsignal-hosted adapter endpoint.
## Overview
WSO2 Identity Platform's Custom Authentication lets you plug in an external authentication service as a step in any login flow. The Authsignal adapter implements that contract and delegates challenge orchestration to Authsignal, so users are redirected to the Authsignal [pre-built UI](/implementation-options/prebuilt-ui/overview) to complete an MFA challenge before being returned to WSO2 Identity Platform to finish signing in.
## Prerequisites
* A WSO2 Identity Platform organization with an application configured for login.
* An Authsignal tenant with at least one authenticator enabled.
## Authsignal configuration
In the [Authenticators section of the Authsignal Portal](https://portal.authsignal.com/organisations/tenants/authenticators), enable the authentication methods you want to offer as MFA factors (for example, SMS OTP, Email OTP, or Passkeys).
In the [API Keys section of the Authsignal Portal](https://portal.authsignal.com/organisations/tenants/api), copy your tenant's **Server API key**. You will paste this into the WSO2 Identity Platform connection in the next section.
## WSO2 Identity Platform configuration
### 1. Create the Authsignal connection
In the WSO2 Identity Platform Console, go to **Connections**, click **New Connection**, and choose **Custom Authenticator (Service-based)**. This opens the **Custom Authentication** wizard, which has three steps.
Select **2FA Authentication** and click **Next**.
Provide an **Identifier** and a **Display Name**.
Set the **Endpoint** to the Authsignal adapter URL for your tenant's region:
```text US (Oregon) theme={null}
https://us-connect.authsignal.com/integrations/wso2-asgardeo/authenticate
```
```text EU (Ireland) theme={null}
https://eu-connect.authsignal.com/integrations/wso2-asgardeo/authenticate
```
```text AU (Sydney) theme={null}
https://au-connect.authsignal.com/integrations/wso2-asgardeo/authenticate
```
```text CA (Montreal) theme={null}
https://ca-connect.authsignal.com/integrations/wso2-asgardeo/authenticate
```
Under **Endpoint Authentication**, select **Basic** as the scheme. Paste your Authsignal tenant's **Server API key** into the **Username** field. The **Password** field is required by WSO2 Identity Platform but is not used by the adapter - enter any non-empty placeholder value. Click **Finish**.
### 2. Add the connection to your login flow
In the WSO2 Identity Platform Console, go to **Applications** and select the application you want to add MFA to.
On the **Login Flow** tab, keep your existing first step (for example, username and password) and add a new step.
In the new step, add the `Authsignal MFA` connection you created. This step will run after the user completes the first factor. Then click **Update** to save the flow.
# Resetting a user's authenticators
Source: https://docs.authsignal.com/knowledge-base/administration/resetting-user-authenticators
Learn how a customer support agent can use the Authsignal Portal to reset a user's multi-factor authenticators.
The Authsignal Portal is useful for many customer support use cases, such as resetting a user's authenticators.
This can be useful for helping customers re-gain access to an account in the scenario where they have lost a device, passkey, etc.
It is expected the agent performing this administrator action has independently verified the user's identity before doing so.
## Prerequisites
* Access to the [Authsignal Portal](https://portal.authsignal.com).
* Correct permissions to allow managing authenticators (`Support analyst` or `Admin`).
## Steps
### Find user
In the Authsignal Portal, click on the **Users** tab.
Using the search box, enter the user's **Email address** or **User ID** and click **Search**.
The user list will be filtered to show all users with the address or ID you entered.
Click on the relevant user.
You will be presented with an overview of key details of the user, including their **Authenticators** and **Latest activity**.
### Delete relevant authenticator
Under **Authenticators**, find the relevant Authenticator for the method you want to remove.
Click the **trash can** button. Alternatively, you can use the **Remove all** to reset the user back to a fresh state.
In the dialog that pops up, select **Continue**.
The authenticator will now be deleted. Check that the authentication method has been removed from the list.
## Conclusion
The user's authenticator has now been removed.
If the user has no more authentication methods left, and configured for your tenant, the user may now enroll themselves with a new authentication method.
# SSO (Single Sign-On)
Source: https://docs.authsignal.com/knowledge-base/administration/single-sign-on
Learn how to configure SAML 2.0 single sign-on for your Authsignal admin portal.
Authsignal supports SAML 2.0 single sign-on (SSO) for the Admin Portal, allowing you to federate authentication to your organization's identity provider.
SSO does not provision new users. Team members must first be invited to your organization, and are matched to your identity provider by email address. Users who have been invited but haven't yet accepted can sign in directly via SSO, and their invitation is accepted automatically.
## Configuration
From the top right menu, navigate to **Organization settings > SSO** to access your SAML 2.0 configuration.
### Import from metadata
If your identity provider provides a metadata XML file, you can automatically populate your SSO configuration.
1. Click **Import from METADATA.xml**
2. Enter your identity provider's metadata URL
3. Click **Import Metadata**
### Manual configuration
Alternatively, you can manually configure SSO by following these steps:
You'll need the following values to configure SSO in your identity provider:
**Service provider (SP) entity ID**
This is the unique identifier for Authsignal as a service provider. This value is unique to your tenant and can be found in the SSO settings page. Use this value when setting up SSO in your IdP.
**ACS URL**
The Assertion Consumer Service URL is where your identity provider will send SAML responses. This value can be found in the SSO settings page. Use this value for the Assertion Consumer Service URL in your IdP.
Configure SAML 2.0 in your identity provider using the service provider details above.
Key configuration requirements:
* **Entity ID / Audience**: Use the SP Entity ID
* **ACS URL / Reply URL**: Use the ACS URL
* **Name ID format**: Email address
* **Name ID value**: User's email address
The specific configuration steps will vary depending on your identity provider. See your IdP's documentation for detailed instructions on adding a SAML application.
Once you've configured your identity provider, provide the following information in the Authsignal admin portal:
**Identity provider (IdP) entity ID**
Your identity provider's generated entity ID. This is typically a URL that uniquely identifies your IdP.
**Identity provider (IdP) SSO target URL**
Your identity provider's SSO target URL that will receive SAML requests. This is sometimes called the "Single Sign-On URL" or "SAML 2.0 Endpoint".
**Identity provider (IdP) public x509 certificate**
Your identity provider's public x509 certificate used to verify SAML responses.
### User attributes (optional)
In addition to the Name ID, Authsignal can populate team members' full names from SAML attributes. Configure your identity provider to send:
| Attribute | Value |
| ----------- | --------------------- |
| `firstName` | The user's first name |
| `lastName` | The user's last name |
Or, if your identity provider has a single full-name attribute, send `displayName` instead. If both are sent, `displayName` takes precedence.
Attribute names are matched exactly and are case-sensitive. The standard claim formats are also accepted, including the LDAP attribute OIDs (`urn:oid:2.5.4.42`, `urn:oid:2.5.4.4`, `urn:oid:2.16.840.1.113730.3.1.241`) and the default Microsoft Entra ID claims (`.../claims/givenname`, `.../claims/surname`), so Entra ID applications typically need no additional configuration.
Names are updated from your identity provider on each sign-in. Name attributes are optional. If none are sent, sign-in works normally and existing names are unchanged.
In your SAML app's settings, add two attribute statements: `firstName` mapped to `user.firstName`, and `lastName` mapped to `user.lastName`.
No additional configuration is needed. Entra ID sends the user's given name and surname claims by default.
Full names not appearing? Check that the attribute names in your identity provider's SAML response exactly match the names above, including capitalization.
### Enable single sign-on
Once you've configured your SSO settings (either via metadata import or manual configuration), enable the **Enable Single Sign On (SAML 2.0)** toggle to activate SSO.
When enabled, all admin portal sign-ins will be redirected to your identity provider and team members will no longer be able to sign in using their Authsignal password. When disabled, team members can sign in using their Authsignal credentials.
# Viewing user activity
Source: https://docs.authsignal.com/knowledge-base/administration/view-user-activity
Learn how a customer support agent can use the Authsignal Portal to understand user activity
## Prerequisites
* Access to the [Authsignal Portal](https://portal.authsignal.com).
* Correct permissions to allow managing authenticators (`Support analyst` or `Admin`).
## Finding user activity data
There are two ways to find user activity data, either:
* searching for the user first
* searching within the activity for a given Action
### Finding user activity data by email address or User ID
In the Authsignal Portal, click on the **Users** tab.
Using the search box, enter the user's **Email address** or **User ID** and click **Search**.
The user list will be filtered to show all users with the address or ID you entered.
Click on the relevant user.
You will be presented with an overview of key details of the user, including their **Authenticators** and **Latest activity**.
After you have navigated to the user's latest activity, select an action from the list to see more details.
### Finding activity for a given action
In the Authsignal Portal, click on the **Actions** tab.
From the list, select an Action.
From the **Latest activity** tab you are presented a list of recent actions.
Use the **Group by** selector to alter the graph's grouping.
Select the time period you want to search.
The Authsignal Portal will surface the previous 3 months of action data.
Use the **search box** to filter the list.
For example, filter the list to only include actions which have not been completed:
Click the **Search** button.
Select the action from the result list.
## User action details
### Overview
The overview provides a quick snapshot of key details about the action, including device information (if available), authentication methods, and timestamp.
Click the **User ID** to see other events for this same user.
### Triggered rules
This will show any rules that were triggered, helping provide understanding on why a user was challenged or not.
### Event timeline
Use the event timeline to understand the steps the user took to complete any challenges.
**Explaining action decisions**
Next to the action response click the **Explain** button to get an AI-generated response explaining why the action state is set to where it is.
AI-generated content for guidance only
**Failure cases**
The event timeline is useful for understanding where a user may have had an issue.
Common failure reasons include:
* entering an incorrect code
* rate limits being hit
* the session expiring
* third party SMS/Email provider rejecting the request
### Action response
This provides a behind-the-scenes view at the context the Rules engine uses to make a determination.
Expand sections to identify more information about the user's device, IP Address, etc.
# FAQs
Source: https://docs.authsignal.com/knowledge-base/faqs
Frequently asked questions.
## Getting started and concepts
**MFA (Multi-Factor Authentication):**
* Requires additional authentication after primary credentials (username/password)
* Applied consistently to all users or specific groups
* Example: Always require SMS OTP after password login
**Adaptive MFA:**
* Uses rules to intelligently determine when to challenge users based on risk factors
* Challenges users only when necessary (new device, suspicious location, etc.)
* Balances security with user experience
**Step-up Authentication:**
* Requires additional verification for sensitive operations, even if already logged in
* Applied to specific high-risk actions (payments, account changes)
* Example: Require passkey verification before processing a large transaction
Learn more about [implementing MFA](/actions-rules/actions/implementing-mfa) and [adaptive MFA](/actions-rules/rules/adaptive-mfa).
**Actions** represent security events in your application that may require additional verification:
* Login attempts, payments, account changes, data access
* Each action returns one of four outcomes: `ALLOW`, `CHALLENGE`, `REVIEW`, or `BLOCK`
* Have configurable default outcomes
**Rules** are conditional logic that determine when and how to challenge users:
* Evaluate risk factors like device characteristics, IP data, user behavior
* Can override action default outcomes based on context
* Enable adaptive authentication policies
Together, Actions define *what* to protect, while Rules define *when* to challenge users. See our guides for [Actions](/actions-rules/actions/getting-started) and [Rules](/actions-rules/rules/getting-started).
When you track an action, Authsignal returns one of four possible states:
* **`ALLOW`** - Action is permitted without additional authentication. Proceed with the user's request.
* **`CHALLENGE_REQUIRED`** - User must complete an authentication challenge before proceeding.
* **`BLOCK`** - Action is blocked for security reasons. Deny the user's request.
* **`REVIEW`** - Action requires manual review before proceeding.
Your application logic should handle each outcome appropriately. Most commonly, you'll redirect users to complete challenges when `CHALLENGE_REQUIRED` is returned.
## Configuration and setup
[Custom domains](/implementation-options/prebuilt-ui/custom-domains) are a pre-requisite when using passkeys. Outside of this scenario, custom domains are optional but highly recommended as they help to create a more branded and trusted user experience.
**As a standalone authentication method:**
* Users receive OTP codes directly via WhatsApp as their primary phone-based authentication
* Configured independently in the Authenticators section
* Follow our [WhatsApp OTP guide](/authentication-methods/whatsapp-otp) for implementation
**As a fallback for SMS OTP:**
* WhatsApp automatically delivers OTP codes when SMS delivery fails or is unavailable
* Helps reduce messaging costs while maintaining reliable delivery
* Configured within the SMS OTP settings as a backup channel
* See the [SMS OTP guide](/authentication-methods/sms-otp#use-whatsapp-as-a-fallback) for setup instructions
## Passkeys
The approach differs between web and mobile platforms:
**For web applications:**
Use the WebAuthn API's conditional UI feature (passkey autofill) which only shows passkeys when they're available on the device. This provides a seamless experience without requiring explicit detection.
**For mobile applications:**
Handle error codes when passkey authentication fails to provide graceful fallback experiences. Present a "Sign in" button that immediately displays the passkey prompt for the optimal experience when passkeys are available, then gracefully fall back to email authentication when they're not.
For detailed implementation guidance and code examples, see our [web best practices](/authentication-methods/passkey/best-practices-web#autofill) and [mobile best practices](/authentication-methods/passkey/best-practices-mobile#optimizing-sign-in-ux) guides.
Unlike traditional methods where you type your username first, passkeys can show you available accounts automatically without any typing.
**Traditional sign-in (username-initiated):**
* You enter your username/email first
* The site looks up your account
* Then sends you a code or prompts for a password
**Passkey sign-in (device-initiated):**
* Your device shows available passkeys automatically
* You select which account to use
* Authentication happens instantly with biometrics or device PIN
This shift from **username-initiated** to **device-initiated** authentication allows for more seamless flows, especially with [passkey autofill](/authentication-methods/passkey/best-practices-web#autofill) that can populate sign-in forms automatically.
Passkey uplift is the process of prompting users to create a passkey after they've successfully signed in using a traditional method like email OTP or password. It's the equivalent of an "add a new passkey" flow: it's how a user who doesn't yet have a passkey (or doesn't have one on their current device) gets one, optimized for adoption so users don't have to find a setting and enroll manually. This helps transition users gradually from legacy authentication to passkeys.
With the [pre-built UI](/implementation-options/prebuilt-ui/passkey-uplift-prompt), this prompt is shown automatically. You can also control its frequency, disable it, or trigger it per action or rule.
**When to show passkey uplift:**
* After a user completes email/SMS OTP authentication
* When a user signs in on a new device without a passkey
* During onboarding flows for new users
* After password-based sign-ins
**Best practices for uplift:**
* Explain the benefits clearly (faster, more secure, no passwords to remember)
* Make it optional, not mandatory
* Use timed cooldowns to avoid being intrusive
* Show the prompt at natural transition points in your app
This approach helps increase passkey adoption while maintaining a smooth user experience for those not ready to switch immediately.
Passkeys may not be available on a device for several reasons:
* User created a passkey on one device but switched to a new device where it can't be synced
* User deleted the passkey from their password manager
* Switching between iOS and Android devices (cross-platform sync limitations)
**Always provide backup authentication options** and avoid leading users into dead ends. Your UI should clearly present alternatives like email OTP or SMS when passkeys aren't available.
For detailed implementation guidance, see our [web best practices](/authentication-methods/passkey/best-practices-web) and [mobile best practices](/authentication-methods/passkey/best-practices-mobile).
Yes. If a user has a passkey on one device (for example their phone) but is signing in on another device that doesn't have one (for example a laptop), the browser can use the cross-device flow: it displays a QR code that the user scans with the device that holds the passkey to complete sign-in.
Because the QR code prompt can be confusing for users who aren't familiar with how passkeys sync, we recommend restricting it to interactions where the user has explicitly chosen to use passkeys, such as a dedicated "Sign in with a passkey" button. See [web best practices](/authentication-methods/passkey/best-practices-web#sign-in-button) for the recommended UX.
To get the user a passkey on the device they're currently using, prompt them with the [uplift flow](/implementation-options/prebuilt-ui/passkey-uplift-prompt).
Both approaches have their benefits:
**Passkey Autofill (Recommended for Web)**
* Unobtrusive way to support passkeys while maintaining familiar UX
* Only shows passkeys if already created and available on device
* Users can manually enter username if no passkey is available
* Particularly good option when you already have a username input on your existing username and password login page
**Dedicated Sign-in Button**
* Clear alternative authentication option
* Launches passkey prompt regardless of availability
* May show QR code if no passkey available (can be confusing for some users)
We recommend **restricting QR code flows** to explicit user interactions where they've chosen to use passkeys, as the QR code prompt can be jarring for unfamiliar users.
**For MFA (Secondary Factor):**
* Use `allowCredentials` parameter to restrict passkeys to the specific user's credentials
* Only show passkeys for one account to avoid confusion on shared devices
* Still provide backup options as the device may not have the user's passkeys
**For Primary Authentication:**
* Can show all available passkeys on device
* Device-initiated flow allows users to select from available credentials
* Focus on smooth fallback experience when no passkeys are present
The [Authsignal pre-built UI](/authentication-methods/passkey/prebuilt-ui) automatically handles MFA scenarios by restricting credentials and providing clear fallback options.
**Gradual Introduction:**
* Prompt users to create passkeys after they sign in with existing methods
* Provide clear explanations of passkey benefits (faster, more secure, no passwords)
* Use gentle prompts rather than forcing immediate adoption
**Improve Device Coverage:**
* Prompt users to create passkeys when they authenticate on new devices
* Consider enrollment flows that create backup authentication methods first
* Use timed cooldowns or behavior-based prompts to avoid being intrusive
**Better Passkey Management:**
* Display passkeys using credential manager names (iCloud Keychain, Google Password Manager)
* Show appropriate icons using `webauthnCredential.aaguidMapping` data
* Help users understand which devices their passkeys are available on
See our complete guides for [web](/authentication-methods/passkey/best-practices-web#creating-passkeys) and [mobile](/authentication-methods/passkey/best-practices-mobile) implementation strategies.
**Best practice: Create backup authentication first, then passkeys.**
Unlike web where users can easily switch between devices, mobile users often switch between iOS and Android where passkeys don't sync. Always ensure users have a backup option:
**Recommended flow:**
1. User signs up and completes email OTP or SMS OTP enrollment
2. After successful enrollment, prompt to create a passkey
3. User now has both a backup method and a convenient passkey
This approach prevents users from getting locked out when switching between platforms or devices where their passkey isn't available.
This parameter controls whether to show the passkey prompt when no credentials are available on the device.
**When set to `true` (recommended):**
* Only shows passkey prompt if credentials exist on the current device
* Avoids confusing QR code prompts for users without passkeys
* Enables smooth fallback to email/SMS authentication
**When set to `false`:**
* Always shows passkey prompt, even with no local credentials
* May display QR code for cross-device authentication
* Can be confusing for users unfamiliar with passkey cross-device flows
**Best practice:** Keep this `true` for most mobile apps to ensure users aren't presented with QR codes when they don't have passkeys available locally.
Passkeys are designed to work seamlessly across your devices and are typically synced through password managers like iCloud Keychain or Google Password Manager.
Security keys are physical hardware devices (like YubiKeys) that require manual insertion or proximity to authenticate. While both use similar underlying technology, passkeys provide a more convenient user experience for most applications.
## Push notifications
It's a common misconception that Push Authentication relies on push notifications (like FCM or APNs) to function. In fact, Authsignal's Push Authentication is built to work even without them.
Push authentication uses device credentials and public key cryptography - the push notification is only used to prompt users to open the app. If the notification doesn't arrive, users can still open the app manually and complete authentication.
Yes, Authsignal allows you to plug in your own push infrastructure. Through our [webhooks](/advanced-usage/webhooks/introduction) or [SDKs](/sdks/server/overview), you can trigger push events via your existing provider while still using Authsignal, device enrollment, and risk-based logic.
Yes, you have full control over push notifications' content, language, and appearance. Since Authsignal integrates with your existing push infrastructure via webhooks, you control all aspects of the notification including the message text, images, sounds, and branding.
When a user has multiple devices registered for push authentication, Authsignal works similar to how services like Gmail handle multi-device authentication.
**How it works:**
* **Single option in UI:** The pre-built UI shows "Push" as a single authentication option if the user has at least one registered push device, regardless of how many devices they have.
* **Any device can respond:** A push authentication challenge can be completed (accepted or denied) by any device that has been registered for that user. The mobile app calls `getChallenge()` when opened to check for pending authentication requests.
* **One response per challenge:** Once a device has responded to a push challenge, other devices are unable to respond to that same challenge.
* **Send to all devices:** You should send push notifications to all registered devices for a user. This allows the user to complete authentication by opening your app on any of their devices, providing maximum flexibility and convenience.
**Device management:**
Each registered push device appears as a separate authenticator in the Authsignal Portal and APIs. This allows you to view all devices registered for a user, remove specific devices if needed, and track which device was used for each authentication.
Yes, you can use the [Get Authenticators](/api-reference/server-api/get-authenticators) API to retrieve the list of enrolled devices for a user, and implement logic to enforce device limits. This includes de-registering existing devices when the limit is reached (for example, removing the oldest device when a user tries to add a new one beyond your limit).
## App verification
When you ship an app update that introduces app verification (push, QR code, or in-app), users who are already signed in won't go through a new sign-in flow, so you need a way to enroll them silently the next time they open the app.
The recommended pattern is a **post-launch handler** that runs after app launch:
1. If the user is authenticated (their session is still valid), call `authsignal.push.getCredential()` (or `authsignal.qr.getCredential()` / `authsignal.inapp.getCredential()` depending on the flow you're implementing).
2. If no credential exists on the device, fetch an enrollment token from your backend and call `addCredential()` on the same namespace to silently enroll the user.
3. If a credential already exists, do nothing. `getCredential` short-circuits, so the handler is safe to run on every launch.
This means users who simply update the app get enrolled invisibly, with no extra prompts or re-authentication required.
See the [enrollment lifecycle guide](/authentication-methods/app-verification/enrollment-lifecycle#handling-app-updates) for full code examples across iOS, Android, React Native, and Flutter.
## Authenticators and user management
In order to deter and protect challenge flows from abuse and high volume attacks, Authsignal has built-in rate limits for different authenticator types.
These limits are in place to deter and stop bad actors and typically will not be noticed by legitimate users on your platform.
**Rate limits for sending**
The following limits apply when sending emails or SMS to initiate a challenge.
| Authenticator type | Rate limit |
| ------------------ | -------------- |
| Email magic link | 12 per 10 mins |
| Email OTP | 12 per 10 mins |
| SMS OTP | 6 per 10 mins |
**Rate limits for verification**
The following limits apply when submitting OTP codes to complete a challenge.
| Authenticator type | Rate limit |
| --------------------- | ------------- |
| Email OTP | 10 per 5 mins |
| SMS OTP | 10 per 5 mins |
| Time-based OTP (TOTP) | 10 per 5 mins |
Yes, when you request a new OTP code for email or SMS, the previous codes remain valid for a short time window. This helps prevent user frustration when messages are delayed or multiple requests are triggered accidentally. All codes expire quickly and rate limits still apply to prevent abuse.
Authsignal marks up the OTP input with `autocomplete="one-time-code"` and a numeric `inputmode`, which are the standard signals browsers and operating systems look for to offer a detected code (for example as a predictive text suggestion above the keyboard). Whether a code is actually offered, and how, is then up to the OS, browser, and password manager, since Authsignal has no access to the user's inbox and can't type the code in for them. Users almost always still need to tap the suggestion, and it requires the user to open the OTP screen and receive the code on the same device.
Yes, you can remove authenticators for users in two ways:
**Through the Authsignal Portal:**
1. Navigate to the user details page
2. Click the "Remove authenticators" button
3. Select which authenticators you want to remove and submit
See our [administrative removal guide](/advanced-usage/programmatic-authenticator-management) for detailed steps.
**Programmatically using APIs:**
You can also remove authenticators programmatically using our APIs. See our [programmatic authenticator management guide](/advanced-usage/programmatic-authenticator-management) for implementation details.
## API and integration
Please check your Server API secret key and API host are correct. You can find the values for your tenant in the Authsignal Portal under [Settings -> API keys](https://portal.authsignal.com/organisations/tenants/api). Ensure that the API host corresponds to your tenant's region.
This error is returned when no authenticators have been configured for your tenant.
If you're using the [Authsignal Web SDK](/sdks/client/web#extras), a cookie named `__as_aid` is set on the user's browser.
When tracking an action on your server, you can extract the `deviceId` from this cookie:
```ts Server {1} theme={null}
const deviceId = req.cookies["__as_aid"]; // Or however you access cookies in your framework of choice
const { url } = await authsignal.track({
action: "signIn",
userId,
attributes: {
deviceId,
},
});
```
If reading the request cookie is not an option, you can use retrieve the `deviceId` on the client via `authsignal.anonymousId`
and pass it to your server in the request body:
```ts Client {7} theme={null}
async function handleSignIn(username: string, password: string) {
const deviceId = authsignal.anonymousId;
const signInResponse = await fetch("/signIn", {
method: "POST",
body: JSON.stringify({
deviceId,
username,
password,
}),
});
}
```
## Webhooks
The most common cause is framework manipulation of the request body. The Authsignal SDK requires the **raw body** to verify signatures.
**Common issues:**
* Framework parsing JSON and converting back to string
* Body compression/decompression
* Character encoding changes
* Middleware modifying the request
**Solution:** Ensure your webhook endpoint receives the raw, unmodified request body. Many frameworks provide ways to access the raw body before parsing.
For implementation examples, see our [Server SDK webhook documentation](/sdks/server/webhooks).
## 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 |
Make sure to whitelist the IP addresses for your tenant's region.
## Error handling and troubleshooting
There are currently only two scenarios where this can occur:
1. In push notification auth when the user presses "Deny" instead of "Accept" in the in-app notification
2. In SMS or email OTP auth when the number of code submission attempts exceeds rate limit thresholds
In other cases when an action is incomplete or abandoned it will remain in a `CHALLENGE_FAILED` state.
**`invalid_configuration`:** Your tenant configuration is invalid. Check that authenticators are properly configured in the Authsignal Portal.
**`invalid_credential`:** The credential (e.g., passkey) is invalid for the user. This may happen if the credential was deleted or is being used on the wrong device.
**`invalid_request`:** Request failed due to invalid parameters. Check your request payload and ensure all required fields are provided.
**`too_many_requests`:** Rate limit exceeded. Implement back-off and retry logic.
**`unauthorized`:** Invalid tenant credentials or wrong region. Verify your API secret key and API URL match your tenant's region.
See our [error handling documentation](/sdks/server/error-handling) for implementation examples.
You can find your region information in the Authsignal Portal under [Settings -> API Keys](https://portal.authsignal.com/organisations/tenants/api). The API URL will indicate your region:
* **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`
Make sure your Server SDK and API calls use the correct URL for your region to avoid `unauthorized` errors.
Authsignal's [Server API](/api-reference/server-api/overview) / [Server SDKs](/sdks/server/overview) are designed to be called from your backend, not directly from frontend JavaScript in browsers.
Alternatively the Authsignal [Client API](/api-reference/client-api/overview) / [Client SDKs](/implementation-options/web-sdk/overview) are designed to be called in frontend or mobile authentication flows, as an alternative to the [pre-built UI](/implementation-options/prebuilt-ui/overview) for those who want full UI customisation.
**Correct flow:**
1. Frontend sends request to your backend
2. Your backend calls the Authsignal [Server API](/api-reference/server-api/overview)
3. Your backend returns response to frontend
**For frontend authentication challenges:**
* Use the [pre-built UI](/implementation-options/prebuilt-ui/overview) (hosted by Authsignal, no CORS issues)
* Use our [Client SDKs](/implementation-options/web-sdk/overview) for custom UI implementations
Direct browser calls to Authsignal APIs will fail due to CORS restrictions.
# E2E testing with passkeys
Source: https://docs.authsignal.com/knowledge-base/testing/e2e-passkeys
Automate end-to-end browser tests that enroll and authenticate with passkeys using Playwright's virtual authenticator.
This guide shows how to write browser-based end-to-end tests that cover the full passkey lifecycle: enrolling a passkey, then using it to complete a challenge.
The test drives your app exactly like a user would, letting it redirect into your identity provider and on to Authsignal. This exercises your policy and user-flow configuration alongside the passkey ceremony.
## The challenge with passkey E2E tests
Passkeys are backed by hardware-bound or password-manager-synced credentials. A regular Playwright or Selenium test can't unlock Touch ID, summon a security key, or open Apple Passwords.
Chromium ships with the [WebAuthn DevTools Protocol domain](https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/), which lets you install a **virtual authenticator**. Once installed, every `navigator.credentials.create` and `navigator.credentials.get` call resolves against that virtual authenticator instead of real platform hardware. Playwright exposes this via [CDP sessions](https://playwright.dev/docs/api/class-cdpsession).
## Prerequisites
* An Authsignal tenant. We recommend a **dedicated tenant for testing** so test traffic doesn't pollute production analytics.
* The passkey authenticator enabled for the tenant.
* An app integrated with either the pre-built UI, the Web SDK, or an identity provider integration.
* Playwright installed in your project.
## Setting up the virtual authenticator
The helper below enables the WebAuthn CDP domain and adds a platform-style virtual authenticator that supports resident keys and user verification. This is the closest analogue to a real platform authenticator like Touch ID.
```ts utils/page-helpers.ts theme={null}
import { Page } from "@playwright/test";
export async function setupWebAuthn(page: Page) {
const client = await page.context().newCDPSession(page);
await client.send("WebAuthn.enable", { enableUI: true });
const result = await client.send("WebAuthn.addVirtualAuthenticator", {
options: {
protocol: "ctap2",
transport: "internal",
hasResidentKey: true,
hasUserVerification: true,
isUserVerified: true,
automaticPresenceSimulation: true,
},
});
return { client, authenticatorId: result.authenticatorId };
}
```
Key options:
* `transport: "internal"` makes the credential behave like a platform authenticator. Use `"usb"` or `"nfc"` if you want to simulate a roaming security key.
* `hasResidentKey: true` enables discoverable credentials so [passkey autofill](/authentication-methods/passkey/web-sdk#using-autofill) works.
* `isUserVerified: true` and `automaticPresenceSimulation: true` let the test bypass biometric/touch prompts.
## Provision a test user
Each test needs a known user in your identity provider. Stand this up as a fixture so it runs once per test suite:
* **Azure AD B2C**: create users via the [Microsoft Graph API](https://learn.microsoft.com/en-us/graph/api/user-post-users).
* **Auth0**: use the [Management API `POST /api/v2/users`](https://auth0.com/docs/api/management/v2/users/post-users).
* **Cognito**: use [`AdminCreateUser`](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminCreateUser.html) with a known password.
* **Keycloak**: use the [Admin REST API](https://www.keycloak.org/docs-api/latest/rest-api/index.html#_users).
* Other or custom identity providers will have their own admin APIs to programmatically create users.
## Drive the journey end-to-end
Navigate to your app's normal login URL and complete the user login via primary factor first. If your application is passwordless, this will just be providing the username.
A realistic test covers two passes: an **enrollment pass** that creates the passkey, and a **re-authentication pass** that signs the same user back in using the credential just enrolled. The virtual authenticator persists for the life of the `BrowserContext`, so both passes share the same credential.
Depending on your integration, this may either redirect to Authsignal's pre-built UI or your custom UI which supports passkey enrollment and authentication.
```ts tests/passkey.spec.ts theme={null}
import { test, expect } from "@playwright/test";
import { setupWebAuthn } from "./utils/page-helpers";
import { provisionTestUser } from "./utils/idp-helpers";
test("user can enroll a passkey then sign back in with it", async ({ page }) => {
// 1. Set up webAuthn and provision a test user
const { client } = await setupWebAuthn(page);
const testUser = await provisionTestUser();
// 2. Navigate to your application's login page
await page.goto("https://yourapp.com/login");
// 3. Complete the login form and click the sign-in button
await page.getByRole("textbox", { name: "Email Address" }).fill(testUser.email);
await page.getByRole("textbox", { name: "Password" }).fill(testUser.password);
await page.getByRole("button", { name: "Sign in" }).click();
// 4. Wait for the virtual authenticator to create the passkey
const credentialAdded = new Promise((resolve) => {
client.once("WebAuthn.credentialAdded", () => resolve());
});
// Depending on your integration and action configuration, you may need to customize this step to initiate the passkey creation prompt.
// This example assumes the use of the Authsignal pre-built UI with an action configuration that has multiple authentication methods enabled.
await page.getByText("Passkey", { exact: true }).click();
await credentialAdded;
// 5. The pre-built UI redirects back to your app and assert that the user is authenticated.
await page.waitForURL(/yourapp\.com\/dashboard/);
await expect(page.getByText(`Welcome, ${testUser.email}`)).toBeVisible();
// 6. Sign out from your application to start a fresh journey on the next request
await page.goto("https://yourapp.com/logout");
// 7. Sign back in to begin the re-authentication process.
await page.goto("https://yourapp.com/login");
await page.getByLabel("Email Address").fill(testUser.email);
await page.getByLabel("Password").fill(testUser.password);
// 8. After clicking sign in, the user will be redirected to the Authsignal pre-built UI where they passkey prompt will be displayed.
const credentialAsserted = new Promise((resolve) => {
client.once("WebAuthn.credentialAsserted", () => resolve());
});
await page.getByRole("button", { name: "Sign in" }).click();
// Present the virtual authenticator credential to the browser
await credentialAsserted;
// 9. Wait for the pre-built UI to redirect back to your application and assert that the user is authenticated.
await page.waitForURL(/yourapp\.com\/dashboard/);
await expect(page.getByText(`Welcome, ${testUser.email}`)).toBeVisible();
});
```
The `WebAuthn.credentialAdded` and `WebAuthn.credentialAsserted` events fire when the virtual authenticator actually completes a ceremony, so they're a reliable signal that the WebAuthn call resolved successfully before you make further DOM assertions.
## Other test frameworks
This guide is written for Playwright, but the same virtual-authenticator approach works in any framework that speaks either the Chrome DevTools Protocol or the [W3C WebAuthn Automation extension](https://www.w3.org/TR/webauthn-2/#sctn-automation):
* **Selenium 4**: first-class support via the W3C WebDriver extension. See [Virtual Authenticator](https://www.selenium.dev/documentation/webdriver/interactions/virtual_authenticator/) in the Selenium docs. Works across Chrome, Edge, and Firefox.
* **WebdriverIO**: exposes the same WebDriver extension. See [`addVirtualAuthenticator`](https://webdriver.io/docs/api/webdriver#addvirtualauthenticator).
* **Puppeteer**: uses raw CDP, the same protocol Playwright uses under the hood. Open a [`CDPSession`](https://pptr.dev/api/puppeteer.cdpsession) and call the [`WebAuthn` domain](https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/) directly.
## Next steps
* Review [passkey best practices on web](/authentication-methods/passkey/best-practices-web) before deciding which actions to cover with E2E tests.
# API testing using Postman
Source: https://docs.authsignal.com/knowledge-base/testing/postman
Learn how to test Authsignal APIs using Postman.
This guide shows how to use Postman to test Authsignal APIs. In this tutorial we will track an action with the [Server API](/api-reference/server-api/overview), and then use the [Client API](/api-reference/client-api/overview) to send an [Email OTP](/authentication-methods/email/otp).
## Prerequisites
* An Authsignal tenant
* An authenticator configured (in this example we use [Email OTP](/authentication-methods/email/otp))
## Configuration
Navigate to your Postman workspace and click **Import**
Enter the relevant OpenAPI specification URL in the import dialog.
Links to OpenAPI specifications can be found in the relevant API reference overviews.
For now, we will enter the URL for the Server API `https://docs.authsignal.com/server-api.json` and the Client API `https://docs.authsignal.com/client-api.json`.
Choose **Postman Collection** to add to your existing Workspace.
Click **Import**
## Server API
Double-click on the **Server API** collection.
In the **Authorization** tab, enter your Server API secret from the Authsignal settings page .
In the **Variables** tab, ensure the `baseUrl` matches the correct regional URL for your tenant's configuration.
In the **Server API** workspace, select `users` -> `actions` -> `{action}` -> `Track Action`.
Be sure to update the variables to match a relevant user ID and action code.
For the purposes of this tutorial, use a random GUID as the `userId`, and `signIn` as your action code. The relevant values here may differ depending on your Authsignal Tenant's configuration.
Select **Send**.
In the response body, make note of the `token` value in the response. We will use this in a moment to test the Client API.
## Client API
Now that we have a token generated by our track action call, next we will set up the Client API to simulate a front end application.
Double click on **Client API** in the workspace menu.
On the **Authorization** tab, enter the token obtained from the previous step as the `bearerToken` value.
On the **Variables** tab, ensure the `baseUrl` matches the correct regional URL for your tenant's configuration.
In this example, we will enroll the user with an Email OTP authenticator.
In the **Client API** workspace select `user-authenticators` -> `email-otp` -> `Start Email OTP enrollment`.
Head to the `body` of the request, and enter your email address.
Click **Send** to start the enrollment.
You should now have received an email with the OTP code.
In the Client API workspace select `verify` -> `email-otp` -> `Verify Email OTP Challenge`.
Head to the `body` of the request, and enter the OTP code from the email.
Click **Send** to verify the enrollment.
The response should include recovery codes, and an `accessToken` that can be used to call the [Session API](/api-reference/server-api/create-session).
## Summing up
In this tutorial we used the Server API to track an action, and then used the token provided to call the Client API.
This provides a good introduction to how to interact with the Authsignal APIs using Postman.
# Adaptive MFA video walk-through
Source: https://docs.authsignal.com/knowledge-base/video-walkthroughs/adaptive-mfa
Video walk-through and Postman companion for adaptive MFA with Authsignal.
Video walk-through of adaptive MFA with Authsignal rules, plus how to reproduce the flow using Postman and the Server API.
To follow along with **Postman**, see [API testing using Postman](/knowledge-base/testing/postman) for importing the Server API from OpenAPI and setting `baseUrl`, `secretKey`, and `userId`.
This walk-through uses these **Server API** endpoints:
* [Enroll verified authenticator](/api-reference/server-api/enroll-verified-authenticator) — `POST /users/{userId}/authenticators`
* [Track action](/api-reference/server-api/track-action) — `POST /users/{userId}/actions/{action}`
* [Validate action](/api-reference/server-api/validate-action) — `POST /validate`
* [Get action](/api-reference/server-api/get-action) — `GET /users/{userId}/actions/{action}/{idempotencyKey}`
See also the [Adaptive MFA](/actions-rules/rules/adaptive-mfa) guide.
# Call Connect SDK
Source: https://docs.authsignal.com/sdks/call-connect/overview
Use Authsignal's Call Connect SDK for Node.js.
The Call Connect SDK makes it easier to interact with our [REST API](/api-reference/call-connect-api/overview) from your server-side code.
## Installation
```bash npm theme={null}
npm install @authsignal/node
```
```bash yarn theme={null}
yarn add @authsignal/node
```
```bash pnpm theme={null}
pnpm add @authsignal/node
```
```bash bun theme={null}
bun add @authsignal/node
```
## Initialization
Initialize the Authsignal Call Connect client by providing your Call Connect API secret key and the Call Connect API URL for your region.
These values can be found in the Authsignal Portal under [Settings -> Call Connect](https://portal.authsignal.com/organisations/tenants/call_connect).
```ts Node.js theme={null}
import { CallConnect } from "@authsignal/node";
const callConnect = new CallConnect({
apiSecretKey: "YOUR_CALL_CONNECT_API_SECRET_KEY",
apiUrl: "YOUR_CALL_CONNECT_API_URL",
});
```
## Regions
The API URLs for each region are defined below.
| Region | API URL |
| ------------- | ----------------------------------- |
| US (Oregon) | `https://us-connect.authsignal.com` |
| AU (Sydney) | `https://au-connect.authsignal.com` |
| EU (Ireland) | `https://eu-connect.authsignal.com` |
| CA (Montreal) | `https://ca-connect.authsignal.com` |
## Usage
### Start call
Start a Call Connect authentication session, triggering a user verification challenge based on the configured channels.
```ts Node.js theme={null}
const request = {
referenceId: "12aec191-69cf-419c-92d9-81e7a3b017ff",
phoneNumber: "+64271234567",
};
const response = await callConnect.startCall(request);
```
### Finish call
Finish an existing Call Connect authentication session.
```ts Node.js theme={null}
const request = {
referenceId: "12aec191-69cf-419c-92d9-81e7a3b017ff",
state: CallState.CHALLENGE_SUCCEEDED,
};
const response = await callConnect.finishCall(request);
```
# Actions
Source: https://docs.authsignal.com/sdks/client/mobile/actions
Initiate actions using a Server SDK before using Mobile SDK methods for email OTP, SMS OTP, WhatsApp OTP, or authenticator app.
When using Mobile SDK methods for email OTP, SMS OTP, WhatsApp OTP, or authenticator app, you must first track an action using a [Server SDK](/sdks/server/overview) to generate a short-lived token.
This token is valid for 10 minutes by default and authorizes a window in which Mobile SDK methods can be used to authenticate the user associated with the token.
```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
```
For enrollment flows, you must also specify the scope `add:authenticators` when tracking the
action if the user already has an existing authenticator. For more detail refer to our guide on
[how to ensure a strong binding when adding
authenticators](/advanced-usage/authenticator-binding).
Then you can use the SDK's `setToken` method.
You must use the **same token** for the initial enroll/challenge call and the subsequent verify call.
```swift iOS theme={null}
authsignal.setToken("eyJhbGciOiJ...")
// Send the user a WhatsApp OTP code
// You can call this multiple times via a 'resend' button
await authsignal.whatsapp.challenge()
// Verify the inputted code matches the original code
let response = await authsignal.whatsapp.verify(code: "123456")
```
```kotlin Android theme={null}
authsignal.setToken("eyJhbGciOiJ...")
// Send the user a WhatsApp OTP code
// You can call this multiple times via a 'resend' button
authsignal.whatsapp.challenge()
// Verify the inputted code matches the original code
val response = authsignal.whatsapp.verify(code = "123456")
```
```ts React Native theme={null}
await authsignal.setToken("eyJhbGciOiJ...");
// Send the user a WhatsApp OTP code
// You can call this multiple times via a 'resend' button
await authsignal.whatsapp.challenge();
// Verify the inputted code matches the original code
const response = await authsignal.whatsapp.verify({ code: "123456" });
```
```dart Flutter theme={null}
await authsignal.setToken("eyJhbGciOiJ...");
// Send the user a WhatsApp OTP code
// You can call this multiple times via a 'resend' button
await authsignal.whatsapp.challenge();
// Verify the inputted code matches the original code
final response = await authsignal.whatsapp.verify(code: "123456");
```
# Email OTP
Source: https://docs.authsignal.com/sdks/client/mobile/email-otp
Implement email OTP in your mobile apps using the Authsignal SDKs for iOS, Android, React Native, and Flutter.
All Mobile SDK methods for email OTP require you to [initiate an action
first](/sdks/client/mobile/actions). Check out our [guide on email OTP
authentication](/authentication-methods/email/otp) for more details.
## Enroll email
Start enrollment for a new email OTP authenticator by sending the user an email containing an OTP code.
This method is typically used when you've **not yet verified** the user's email address.
```swift iOS theme={null}
await authsignal.email.enroll(email: "jane.smith@authsignal.com")
```
```kotlin Android theme={null}
authsignal.email.enroll(email = "jane.smith@authsignal.com")
```
```ts React Native theme={null}
await authsignal.email.enroll({ email: "jane.smith@authsignal.com" });
```
```dart Flutter theme={null}
await authsignal.email.enroll("jane.smith@authsignal.com");
```
If you've already verified a user's email address independently of Authsignal, you can
alternatively use a Server SDK to [enroll the user
programmatically](/advanced-usage/programmatic-authenticator-management).
### Parameters
The user's email address.
### Response
An unstructured error description present if the SDK call encountered an error.
## Challenge email
Start an email OTP challenge by sending the user an email containing an OTP code.
```swift iOS theme={null}
await authsignal.email.challenge()
```
```kotlin Android theme={null}
authsignal.email.challenge()
```
```ts React Native theme={null}
await authsignal.email.challenge();
```
```dart Flutter theme={null}
await authsignal.email.challenge();
```
#### Response
An unstructured error description present if the SDK call encountered an error.
## Verify email
Finish enrolling or re-authenticating a new or existing email OTP authenticator by verifying the code submitted by the user.
```swift iOS theme={null}
await authsignal.email.verify(code: "123456")
```
```kotlin Android theme={null}
authsignal.email.verify(code = "123456")
```
```ts React Native theme={null}
await authsignal.email.verify({ code: "123456" });
```
```dart Flutter theme={null}
await authsignal.email.verify("123456");
```
### Parameters
The OTP code inputted by the user.
### Response
An unstructured error description present if the SDK call encountered an error.
True if the verification was successful.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
A structured code present if the verification failed due to user error. Possible values are:
`CODE_INVALID_OR_EXPIRED` or `MAX_ATTEMPTS_EXCEEDED`.
# Error handling
Source: https://docs.authsignal.com/sdks/client/mobile/error-handling
Handle errors using the Authsignal SDKs for iOS, Android, React Native, and Flutter.
Mobile SDK methods can return the following fields when encountering an error.
A description of the error. This value should only be used for logging or troubleshooting
purposes.
A code which identifies a specific error state. This value can be used to drive application logic
such as handling when [a passkey is not available on the
device](/authentication-methods/passkey/best-practices-mobile#optimal-sign-in-experience).
## Error codes
Indicates that an error occurred when making a network request to the Authsignal API. This will
occur if the device has no network connection.
Indicates that the Authsignal token has expired. This will occur if more than 10 minutes has
elapsed since it was [generated by tracking an
action](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token) or since a
[previous challenge was
completed](/advanced-usage/authenticator-binding#presenting-a-challenge-with-an-existing-method).
Indicates that no Authsignal has been set to authorize the operation. Learn more about [setting a
token](#setting-a-token).
Indicates that the user dismissed the passkey sign-in prompt. On iOS this may also indicate that
the user has no passkey credential available on the device.
Android only. Indicates that the user has no passkey credential available on the device.
Indicates that an error occurred when creating a passkey because Authsignal has a record of an
existing passkey credential and the platform has determined that it is available on the user's
device. This error will be ignored if `ignorePasskeyAlreadyExistsError` is set to true when
creating a passkey.
# In-app verification
Source: https://docs.authsignal.com/sdks/client/mobile/in-app-verification
Implement in-app verification using the Authsignal SDKs for iOS, Android, React Native, and Flutter.
Check out our end-to-end guide on [how to implement in-app
verification](/authentication-methods/app-verification/in-app).
## Adding a credential
Adding an in-app credential generates a private/public key pair, where the private key is secured on the user's mobile device and the public key is held by Authsignal.
This operation must be authorized with a short-lived token, which can be obtained by [tracking an action from your backend](/authentication-methods/app-verification/push#1-generate-enrollment-token) in an authenticated context.
```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..." });
```
### Parameters
A short-lived token obtained by [tracking an
action](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token).
When set to `true`, the SDK will generate a platform-specific attestation during enrollment:
* **iOS**: Uses [App Attest](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity) (DCAppAttestService) to generate an attestation bound to the device.
* **Android**: Uses [Play Integrity](https://developer.android.com/google/play/integrity) to generate an integrity token.
The attestation is generated internally by the SDK and sent to the Authsignal server for verification.
This provides an additional layer of assurance that the credential is being enrolled from a legitimate app on a real device.
### Response
An unstructured error description present if the SDK call encountered an error.
A formatted error code which may additionally be present if the SDK call encountered an error.
Possible values are: `token_not_set`, `expired_token` or `network_error`.
The ID of the app credential.
The date and time the app credential was created.
The user ID of the user that the app credential belongs to.
## Getting a credential
Get information about the in-app credential stored on the device, if one exists.
```swift iOS theme={null}
let response = await authsignal.inapp.getCredential()
```
```kotlin Android theme={null}
val response = authsignal.inapp.getCredential()
```
```ts React Native theme={null}
const response = await authsignal.inapp.getCredential();
```
```dart Flutter theme={null}
final response = await authsignal.inapp.getCredential();
```
### Response
An unstructured error description present if the SDK call encountered an error.
A formatted error code which may additionally be present if the SDK call encountered an error.
Possible values are: `invalid_credential` when the credential exists on the device but has
been removed from the server.
The ID of the app credential.
The date and time the app credential was created.
The date and time the app credential was last used for authentication. Will be null if the
credential has never been used.
The user ID of the user that the app credential belongs to.
## Removing a credential
```swift iOS theme={null}
await authsignal.inapp.removeCredential()
```
```kotlin Android theme={null}
authsignal.inapp.removeCredential()
```
```ts React Native theme={null}
await authsignal.inapp.removeCredential();
```
```dart Flutter theme={null}
await authsignal.inapp.removeCredential();
```
### Response
An unstructured error description present if the SDK call encountered an error. This could
occur if the credential on the device is no longer valid because the corresponding user
authenticator has been deleted in the Authsignal Portal.
True if the app credential was successfully removed.
## Verifying an action
Verify an action in your app using the credential stored securely on the device.
```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;
```
### Response
An unstructured error description present if the SDK call encountered an error. This could
occur if the credential on the device is no longer valid because the corresponding user
authenticator has been deleted in the Authsignal Portal.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
The user ID of the user that the device credential belongs to.
The user authenticator ID that the device credential belongs to.
The username of the user that the device credential belongs to.
## Creating a PIN
Create a custom PIN backed by a cryptographic in-app credential stored securely on the user's device.
```swift iOS theme={null}
let response = await authsignal.inapp.createPin(
token: "eyJhbGciOiJ...",
username: "jane.smith@authsignal.com",
pin: "123456"
)
```
```kotlin Android theme={null}
val response = authsignal.inapp.createPin(
token = "eyJhbGciOiJ...",
username = "jane.smith@authsignal.com",
pin = "123456",
)
```
```ts React Native theme={null}
const response = await authsignal.inapp.createPin({
token: "eyJhbGciOiJ...",
username: "jane.smith@authsignal.com",
pin: "123456",
});
```
### Parameters
A short-lived token obtained by [tracking an
action](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token).
The username associated with the PIN. A device can have multiple PINs stored for different users.
The PIN value. Must be a valid 6-digit numeric value.
### Response
An unstructured error description present if the SDK call encountered an error.
A formatted error code which may additionally be present if the SDK call encountered an error.
Possible values are: `token_not_set`, `expired_token` or `network_error`.
The ID of the app credential.
The date and time the app credential was created.
The user ID of the user that the app credential belongs to.
## Verifying a PIN
Check a submitted PIN value against the value stored on the device and use the associated in-app credential to verify the user.
```swift iOS theme={null}
let response = await authsignal.inapp.verifyPin(
username: "jane.smith@authsignal.com",
pin: "123456"
)
let isVerified = response.data?.isVerified
let token = response.data?.token
```
```kotlin Android theme={null}
val response = authsignal.inapp.verifyPin(
username = "jane.smith@authsignal.com",
pin = "123456",
)
val isVerified = response.data?.isVerified
val token = response.data?.token
```
```ts React Native theme={null}
const response = await authsignal.inapp.verifyPin({
username: "jane.smith@authsignal.com",
pin: "123456",
});
const isVerified = response.data?.isVerified;
const token = response.data?.token;
```
### Response
An unstructured error description present if the SDK call encountered an error. This could
occur if the credential on the device is no longer valid because the corresponding user
authenticator has been deleted in the Authsignal Portal.
True if PIN verification is successful.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
The user ID of the user that the PIN and in-app credential belong to.
## Get PIN usernames on device
Get all the usernames for PINs currently stored on the device. This method can be used if allowing multiple users to create PINs on the same device.
```swift iOS theme={null}
let response = await authsignal.inapp.getAllPinUsernames()
let usernames = response.data
```
```kotlin Android theme={null}
val response = authsignal.inapp.getAllPinUsernames()
val usernames = response.data
```
```ts React Native theme={null}
const response = await authsignal.inapp.getAllPinUsernames();
const usernames = response.data;
```
### Response
An unstructured error description present if the SDK call encountered an error.
An array of usernames for all the PINs currently stored on the device
# Passkeys
Source: https://docs.authsignal.com/sdks/client/mobile/passkeys
Implement passkeys in your mobile apps using the Authsignal SDKs for iOS, Android, React Native, and Flutter.
In addition to the reference documentation below, check out our end-to-end guide on [how to
implement passkeys in mobile apps using Authsignal](/authentication-methods/passkey/mobile-sdks).
## Prerequisites
After you have [configured your Relying Party](/authentication-methods/passkey/custom-ui#configuration) you should follow the steps below.
To use passkeys you must first [setup an associated domain](https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys#overview) with the `webcredentials` service type.
Host an `apple-app-site-association` file on the domain that matches your [relying party](/authentication-methods/passkey/custom-ui#configuration):
```
GET https:///.well-known/apple-app-site-association
```
The response JSON should look something like this:
```json theme={null}
{
"applinks": {},
"webcredentials": {
"apps": ["ABCDE12345.com.example.app"]
},
"appclips": {}
}
```
where `ABCDE12345` is your team id and `com.example.app` is your bundle identifier.
In XCode under "Signing & Capabilities" add a `webcredentials` entry for your domain / relying party e.g. `example.com`:
To use passkeys on Android you must [setup a Digital Asset Links JSON](https://developer.android.com/identity/sign-in/credential-manager#add-support-dal) file on your web domain.
Host an `assetlinks.json` file on the domain that matches your [relying party](/authentication-methods/passkey/custom-ui#configuration):
```
GET https:///.well-known/assetlinks.json
```
The response JSON should look something like this:
```json theme={null}
[
{
"relation": [
"delegate_permission/common.handle_all_urls",
"delegate_permission/common.get_login_creds"
],
"target": {
"namespace": "android_app",
"package_name": "com.example",
"sha256_cert_fingerprints": [
"FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C"
]
}
}
]
```
but with the package name and **SHA-256 fingerprint** updated to match your own app.
You can obtain your app's SHA-256 fingerprint by running a [signing report](https://developer.android.com/studio/publish/app-signing#signing_report) in Android Studio.
```bash highlight={8} theme={null}
Task :app:signingReport
Variant: debug
Config: debug
Alias: androiddebugkey
MD5: 20:F4:61:48:B7:2D:8E:5E:5C:A2:3D:37:A4:F4:14:90
SHA1: 5E:8F:16:06:2E:A3:CD:2C:4A:0D:54:78:76:BA:A6:F3:8C:AB:F6:25
SHA-256: FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C
```
For more information on this step refer to the [Credential Manager documentation](https://developer.android.com/identity/sign-in/credential-manager#add-support-dal).
Finally, you will need to add an expected origin value for your Android app when configuring passkeys in the [Authsignal Portal](https://portal.authsignal.com/organisations/tenants/authenticators/passkey).
Enter your app's SHA-256 value which you obtained in the previous step.
This value will automatically be converted into a **base64URL encoded string with no padding**.
## Creating a passkey
Creating a passkey must be authorized by [presenting a challenge with an existing method](/advanced-usage/authenticator-binding#option-1%3A-challenge-then-enroll) or [tracking an action to obtain a short-lived token](/advanced-usage/authenticator-binding#option-2%3A-token-based-binding).
```swift Swift theme={null}
let response = await authsignal.passkey.signUp(
token: "eyJhbGciOiJIUzI....",
username: "jane.smith@authsignal.com",
displayName: "Jane Smith"
)
```
```kotlin Kotlin theme={null}
val response = authsignal.passkey.signUp(
token = "eyJhbGciOiJIUzI....",
username = "jane.smith@authsignal.com",
displayName = "Jane Smith",
)
```
```ts React Native theme={null}
const response = await authsignal.passkey.signUp({
token: "eyJhbGciOiJIUzI....",
username: "jane.smith@authsignal.com",
displayName: "Jane Smith",
});
```
```dart Flutter theme={null}
var response = await authsignal.passkey.signUp(
"eyJhbGciOiJIUzI....",
"jane.smith@authsignal.com",
"Jane Smith",
);
```
You can also use our SDK to help determine **when to display a passkey creation prompt** based on whether or not the user has an existing passkey available on their device.
```swift Swift theme={null}
let showPrompt = await authsignal.passkey.shouldPromptToCreatePasskey()
if showPrompt {
let response = await authsignal.passkey.signUp(
token: "eyJhbGciOiJIUzI....",
username: "jane.smith@authsignal.com",
ignorePasskeyAlreadyExistsError: true,
)
}
```
```kotlin Kotlin theme={null}
val showPrompt = authsignal.passkey.shouldPromptToCreatePasskey()
if (showPrompt) {
val response = authsignal.passkey.signUp(
token = "eyJhbGciOiJIUzI....",
username = "jane.smith@authsignal.com",
ignorePasskeyAlreadyExistsError: true,
)
}
```
```ts React Native theme={null}
const showPrompt = await authsignal.passkey.shouldPromptToCreatePasskey();
if (showPrompt) {
const response = await authsignal.passkey.signUp({
token: "eyJhbGciOiJIUzI....",
username: "jane.smith@authsignal.com",
ignorePasskeyAlreadyExistsError: true,
});
}
```
To learn more about how to conditionally create passkeys refer to our guide on [handling passkey availability](/authentication-methods/passkey/mobile-sdks#passkey-availability).
### Parameters
A short-lived token obtained by [tracking an
action](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token).
The primary user identifier associated with the passkey, e.g. the user's email address.
An optional secondary user identifier which the OS may display in place of or alongside the
username, e.g. the user's full name.
### Response
An unstructured error description present if the SDK call encountered an error.
A formatted error code which may additionally be present if the SDK call encountered an error.
Possible values are: `token_not_set`, `expired_token`, `network_error` or `user_canceled`.
A new token which can optionally be used to [bind another
authenticator](#authenticator-binding) for the user.
## Using a passkey
Calling `signIn` will present the passkey sign-in prompt if a credential is available on the device.
If the user successfully authenticates with their passkey, send the result token to your server to [validate the challenge](/sdks/server/challenges#validate-challenge).
```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
}
```
```kotlin Kotlin theme={null}
val response = authsignal.passkey.signIn(action = "signInWithPasskey")
if (response.data?.token != null) {
// Send token to your backend for validation
}
```
```ts React Native theme={null}
const response = await authsignal.passkey.signIn({ action: "signInWithPasskey" });
if (response.data?.token) {
// Send token to your backend for validation
}
```
```dart Flutter theme={null}
final response = await authsignal.passkey.signIn(action: "signInWithPasskey");
if (response.data?.token != null) {
// Send token to your backend for validation
}
```
Because some users **may not have a passkey available**, you can conditionally present a fallback authentication method by handling error codes returned by the SDK.
```swift Swift theme={null}
let response = await authsignal.passkey.signIn(action: "signInWithPasskey")
if response.errorCode == "user_canceled" {
// Present fallback authentication method
}
```
```kotlin Kotlin theme={null}
val response = authsignal.passkey.signIn(action = "signInWithPasskey")
if (response.errorCode == "user_canceled" ||
response.errorCode == "no_credential") {
// Present fallback authentication method
}
```
```ts React Native theme={null}
const response = await authsignal.passkey.signIn({ action: "signInWithPasskey" });
if (
response.errorCode === ErrorCode.user_canceled ||
response.errorCode === ErrorCode.no_credential
) {
// Present fallback authentication method
}
```
```dart Flutter theme={null}
final response = await authsignal.passkey.signIn(action: "signInWithPasskey");
if (response.errorCode == ErrorCode.userCanceled.value ||
response.errorCode == ErrorCode.noCredential.value) {
// Present fallback authentication method
}
```
To learn more about how to handle if passkeys are available when authenticating refer our guide on [handling passkey availability](/authentication-methods/passkey/mobile-sdks#passkey-availability).
### Parameters
A string which determines how the action associated with the passkey sign-in attempt will be named
in the [Authsignal Portal](https://portal.authsignal.com). Values are validated with the following
regex: `^[a-zA-Z0-9_-]{(1, 64)}$`.
If set to true, the passkey prompt will not be shown when no credentials are available on the
device and an [error code will be
returned](/authentication-methods/passkey/best-practices-mobile#optimal-sign-in-experience).
Defaults to true. Set this to false if you want to support signing in with credentials on another
device via QR code.
### Response
An unstructured error description present if the SDK call encountered an error.
A formatted error code which may additionally be present if the SDK call encountered an error.
Possible values are: `user_canceled` or `no_credential` (Android only). These values can be
used to [handle if a passkey is not available on the
device](/authentication-methods/passkey/best-practices-mobile#optimal-sign-in-experience).
True if the passkey sign-in attempt was successful.
A new token which can be used to [validate the
challenge](/sdks/server/challenges#validate-challenge) on your server or optionally to
[bind another authenticator](#authenticator-binding) for the user.
The ID of the Authsignal user if the sign-in attempt was successful.
The ID of the Authsignal user authenticator if the sign-in attempt was successful.
The username associated with the passkey if the sign-in attempt was successful.
The display name associated with the passkey if the sign-in attempt was successful.
## Device support
Passkeys are supported from iOS 15 and Android API level 28 or higher.
You can detect whether or not the current device supports passkeys by using the `isSupported` helper method.
```swift iOS theme={null}
let isSupported = authsignal.passkey.isSupported
```
```kotlin Android theme={null}
val isSupported = authsignal.passkey.isSupported
```
```ts React Native theme={null}
const isSupported = authsignal.passkey.isSupported;
```
### Passkey autofill (iOS)
This feature requires rendering a text field with the `textContentType` property.
```swift iOS theme={null}
userTextField.textContentType = .username
```
```tsx React Native theme={null}
```
Then when the screen loads, you should initialize the text field for passkey autofill by running the following code.
```swift iOS theme={null}
let result = await authsignal.passkey.signIn(
action: "signInWithPasskeyAutofill",
autofill: true
)
if let token = result.data?.token {
// The user has focused your text input and authenticated with an existing passkey
// Send the response token to your server to validate the result of the challenge
}
```
```ts React Native theme={null}
authsignal.passkey
.signIn({ action: "signInWithPasskeyAutofill", autofill: true })
.then(({ data, error }) => {
if (data?.token) {
// The user has focused your text input and authenticated with an existing passkey
// Send the response token to your server to validate the result of the challenge
}
});
```
## Cancelling a request (iOS)
If you call `signIn` to present the passkey sign-in prompt, you will need to cancel a request that is already in progress (e.g. an autofill request).
```swift iOS theme={null}
authsignal.passkey.cancel()
```
```ts React Native theme={null}
await authsignal.passkey.cancel();
```
```dart Flutter theme={null}
await authsignal.passkey.cancel();
```
This is typically required if your UI presents an username input with autofill as well as a separate "Sign in with passkey" button.
# Push verification
Source: https://docs.authsignal.com/sdks/client/mobile/push-verification
Implement app verification with push notifications using the Authsignal SDKs for iOS, Android, React Native, and Flutter.
Check out our end-to-end guide on [how to implement app verification with push
notifications](/authentication-methods/app-verification/push).
## Adding a credential
Adding a push credential generates a private/public key pair, where the private key is secured on the user's mobile device and the public key is held by Authsignal.
This operation must be authorized with a short-lived token, which can be obtained by [tracking an action from your backend](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token) in an authenticated context.
```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...");
```
### Parameters
A short-lived token obtained by [tracking an
action](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token).
### Response
An unstructured error description present if the SDK call encountered an error.
A formatted error code which may additionally be present if the SDK call encountered an error.
Possible values are: `token_not_set`, `expired_token` or `network_error`.
The ID of the app credential.
The date and time the app credential was created.
The user ID of the user that the app credential belongs to.
## Getting a credential
Get information about the push credential stored on the device, if one exists.
```swift iOS theme={null}
let response = await authsignal.push.getCredential()
```
```kotlin Android theme={null}
val response = authsignal.push.getCredential()
```
```ts React Native theme={null}
const response = await authsignal.push.getCredential();
```
```dart Flutter theme={null}
final response = await authsignal.push.getCredential();
```
### Response
An unstructured error description present if the SDK call encountered an error.
A formatted error code which may additionally be present if the SDK call encountered an error.
Possible values are: `invalid_credential` when the credential exists on the device but has
been removed from the server.
The ID of the app credential.
The date and time the app credential was created.
The date and time the app credential was last used for authentication. Will be null if the
credential has never been used.
The user ID of the user that the app credential belongs to.
## Updating a credential
Push tokens are not permanent. The OS rotates them - on app reinstall or restore to a new device, after clearing app data, when a token expires, or whenever the push service decides to issue a new one. The token you registered with [`addCredential`](#adding-a-credential) eventually goes stale, and a notification sent to a stale token is silently dropped.
Use `updateCredential` to point the existing credential at a fresh device token. Unlike `addCredential`, it requires **no token from your backend and no user session** - the device proves possession of its existing credential, so you can call it from a background handler without any user interaction. It also leaves the credential's keys in place, so the user stays enrolled.
```swift iOS theme={null}
await authsignal.push.updateCredential(pushToken: "")
```
```kotlin Android theme={null}
authsignal.push.updateCredential("")
```
```ts React Native theme={null}
await authsignal.push.updateCredential("");
```
### When to call it
Both Apple and Google recommend the same pattern: subscribe to the OS token callback and [send the new token to your server as soon as it changes](https://firebase.google.com/docs/cloud-messaging/manage-tokens#detect-invalid-token-responses-from-the-fcm-backend). With Authsignal, "send to your server" means calling `updateCredential`. Register the latest token whenever it changes so Authsignal always has a deliverable one:
* **On token rotation** - the most important trigger. Subscribe to the OS push token callback and update the credential whenever a new token is issued.
* **iOS, Default provider** - the raw APNs token, delivered to [`application(_:didRegisterForRemoteNotificationsWithDeviceToken:)`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application\(_:didregisterforremotenotificationswithdevicetoken:\)).
* **Android, and iOS with the Firebase provider** - the FCM registration token, delivered to [`onNewToken`](https://firebase.google.com/docs/cloud-messaging/android/client#monitor-token-generation) (Android) or [`messaging(_:didReceiveRegistrationToken:)`](https://firebase.google.com/docs/cloud-messaging/ios/client#token-swizzle-disabled) (iOS).
* **React Native** - [`messaging().onTokenRefresh`](https://rnfirebase.io/messaging/usage#getting-the-device-token) covers the FCM token (Android, and iOS in Firebase mode). For the raw APNs token in Default mode, use Expo Notifications' [`addPushTokenListener`](https://docs.expo.dev/versions/latest/sdk/notifications/#addpushtokenlistenerlistener), which surfaces the same `didRegisterForRemoteNotificationsWithDeviceToken` callback to JavaScript. Pass the token the listener receives straight to `updateCredential` rather than re-fetching it, as Expo warns that calling `getDevicePushTokenAsync` inside the listener can re-trigger it.
* **On app foreground or launch** - retrieve the current token, [compare it against the one you last registered](https://firebase.google.com/docs/cloud-messaging/manage-tokens), and update the credential if it changed. This is a backstop for tokens that rotated while the app was killed, when no rotation callback fires.
Skip the call if no push credential exists yet - the first token is registered by [`addCredential`](#adding-a-credential) at enrolment. The token type must match your tenant's [configured provider](/authentication-methods/app-verification/managed-push#which-token-to-register), the same as for `addCredential`.
### Keeping the credential alive
`updateCredential` has a second use. If you configure a [credential lifetime](/authentication-methods/app-verification/enrollment-lifecycle#keeping-credentials-alive) on the push authenticator, credentials expire unless kept alive. Pass `resetExpiry` to reset the credential's lease and keep the device enrolled. Both parameters are optional, so you can rotate the token, reset the expiry, or do both in one call.
```swift iOS theme={null}
// Reset the lease without changing the token
await authsignal.push.updateCredential(resetExpiry: true)
// Rotate the token and reset the lease together
await authsignal.push.updateCredential(pushToken: "", resetExpiry: true)
```
```kotlin Android theme={null}
// Reset the lease without changing the token
authsignal.push.updateCredential(resetExpiry = true)
// Rotate the token and reset the lease together
authsignal.push.updateCredential(pushToken = "", resetExpiry = true)
```
```ts React Native theme={null}
// Reset the lease without changing the token
await authsignal.push.updateCredential({ resetExpiry: true });
// Rotate the token and reset the lease together
await authsignal.push.updateCredential({ pushToken: "", resetExpiry: true });
```
When no lifetime is configured, credentials never expire and you don't need `resetExpiry`. See [Keeping credentials alive](/authentication-methods/app-verification/enrollment-lifecycle#keeping-credentials-alive) for where to call this in your app's lifecycle.
`updateCredential` is supported on the iOS SDK from `v2.11.0`, the Android SDK from `v4.1.0`, and the React Native SDK from `v3.1.0`.
### Response
An unstructured error description present if the SDK call encountered an error. This could
occur if no credential exists on the device, or if the credential is no longer valid because
the corresponding user authenticator has been deleted in the Authsignal Portal.
The ID of the user authenticator backing the credential.
The user ID of the user that the app credential belongs to.
The date and time the credential was last verified, updated as part of this call.
The push token now registered against the credential.
## Removing a credential
```swift iOS theme={null}
await authsignal.push.removeCredential()
```
```kotlin Android theme={null}
authsignal.push.removeCredential()
```
```ts React Native theme={null}
await authsignal.push.removeCredential();
```
```dart Flutter theme={null}
await authsignal.push.removeCredential();
```
### Response
An unstructured error description present if the SDK call encountered an error. This could
occur if the credential on the device is no longer valid because the corresponding user
authenticator has been deleted in the Authsignal Portal.
True if the app credential was successfully removed.
## Getting a challenge
```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
}
```
### Response
An unstructured error description present if the SDK call encountered an error. This could
occur if the credential on the device is no longer valid because the corresponding user
authenticator has been deleted in the Authsignal Portal.
The ID of the challenge corresponding to a particular device credential authentication
request.
The action code identifying the action associated with the challenge.
The idempotency key identifying the action associated with the challenge.
The user agent of the device which initiated the challenge. Present if passed as input to
the original track request.
The device ID of the device which initiated the challenge. Present if passed as input to
the original track request.
The IP address of the device which initiated the challenge. Present if passed as input to
the original track request.
Action-scope custom data associated with the challenge. Only [custom data points marked as
public](/actions-rules/rules/custom-data-points#public-custom-data-points) are returned.
User-scope custom data for the user being challenged. Only [custom data points marked
as public](/actions-rules/rules/custom-data-points#public-custom-data-points) are
returned.
## Updating a challenge
After presenting the user with a prompt to approve or reject the request, you should update the challenge with their response.
```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,
);
```
### Response
An unstructured error description present if the SDK call encountered an error. This could
occur if the credential on the device is no longer valid because the corresponding user
authenticator has been deleted in the Authsignal Portal.
True if the device challenge was successfully updated.
## Requiring user authentication
When adding a credential for push verification, it is possible to require that the user authenticate via their OS biometrics or PIN whenever they access the credential (e.g. when approving or rejecting a challenge).
### iOS
To require user authentication on iOS, set the `userPresenceRequired` flag to true when adding the credential.
```swift theme={null}
await authsignal.push.addCredential(
token: token,
userPresenceRequired: true
)
```
This is all that's needed - iOS will automatically handle displaying the biometrics or PIN prompt when [updating a challenge](#updating-a-challenge) or [verifying a device](#verifying-a-device).
### Android
To require user authentication on Android, set the `userAuthenticationRequired` flag to true when adding the credential.
```kotlin theme={null}
authsignal.push.addCredential(
token = token,
userAuthenticationRequired = true
)
```
It's also possible to specify additional [user authentication parameters](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationParameters\(int,%20int\)).
```kotlin theme={null}
authsignal.push.addCredential(
token = token,
userAuthenticationRequired = true,
timeout = 60,
authorizationType = 0
)
```
The `timeout` param determines the time (in seconds) that the credential can be accessed after authenticating - or `0` if authentication must occur for every credential use.
The `authorizationType` param determines if authentication is required via biometrics and/or device credential (e.g. pin).
If user authentication is required for a credential, you **must** call `updateChallenge` in a [biometric prompt authentication callback](https://developer.android.com/identity/sign-in/biometric-auth).
1. Initialize a signature before displaying the biometric prompt
```kotlin theme={null}
val signature = authsignal.push.startSigning()
val cryptoObject = CryptoObject(signature)
// Initialize biometric prompt and prompt info
val biometricPrompt = ...
val promptInfo = ...
biometricPrompt.authenticate(promptInfo, cryptoObject);
```
2. Retrieve the signature from the crypto object in your prompt's authentication callback
```kotlin theme={null}
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
super.onAuthenticationSucceeded(result)
val cryptoObject = result.cryptoObject
val signer = cryptoObject.signature
authsignal.push.updateChallenge(
challengeId = challengeId,
approved = true,
signer = signer
)
}
```
# QR code verification
Source: https://docs.authsignal.com/sdks/client/mobile/qr-code-verification
Implement app verification with QR codes using the Authsignal SDKs for iOS, Android, React Native, and Flutter.
Check out our end-to-end guide on [how to implement app verification with QR
codes](/authentication-methods/app-verification/qr-code).
## Adding a credential
Adding a QR code credential generates a private/public key pair, where the private key is secured on the user's mobile device and the public key is held by Authsignal.
This operation must be authorized with a short-lived token, which can be obtained by [tracking an action from your backend](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token) in an authenticated context.
```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...");
```
### Parameters
A short-lived token obtained by [tracking an
action](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token).
### Response
An unstructured error description present if the SDK call encountered an error.
A formatted error code which may additionally be present if the SDK call encountered an error.
Possible values are: `token_not_set`, `expired_token` or `network_error`.
The ID of the app credential.
The date and time the app credential was created.
The user ID of the user that the app credential belongs to.
## Getting a credential
Get information about the QR code credential stored on the device, if one exists.
```swift iOS theme={null}
let response = await authsignal.qr.getCredential()
```
```kotlin Android theme={null}
val response = authsignal.qr.getCredential()
```
```ts React Native theme={null}
const response = await authsignal.qr.getCredential();
```
```dart Flutter theme={null}
final response = await authsignal.qr.getCredential();
```
### Response
An unstructured error description present if the SDK call encountered an error.
A formatted error code which may additionally be present if the SDK call encountered an error.
Possible values are: `invalid_credential` when the credential exists on the device but has
been removed from the server.
The ID of the app credential.
The date and time the app credential was created.
The date and time the app credential was last used for authentication. Will be null if the
credential has never been used.
The user ID of the user that the app credential belongs to.
## Removing a credential
```swift iOS theme={null}
await authsignal.qr.removeCredential()
```
```kotlin Android theme={null}
authsignal.qr.removeCredential()
```
```ts React Native theme={null}
await authsignal.qr.removeCredential();
```
```dart Flutter theme={null}
await authsignal.qr.removeCredential();
```
### Response
An unstructured error description present if the SDK call encountered an error. This could
occur if the credential on the device is no longer valid because the corresponding user
authenticator has been deleted in the Authsignal Portal.
True if the app credential was successfully removed.
## Claiming a challenge
When the user scans the QR code, you should call `claimChallenge` to set the user attempting to complete the challenge.
This will return some context about the desktop or kiosk device 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.
```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,
);
```
### Response
An unstructured error description present if the SDK call encountered an error. This could
occur if the credential on the device is no longer valid because the corresponding user
authenticator has been deleted in the Authsignal Portal.
The IP address of the device which initiated the challenge.
The user agent of the device which initiated the challenge.
Action-scope custom data passed to the challenge. Only [custom data points marked as
public](/actions-rules/rules/custom-data-points#public-custom-data-points) are returned.
User-scope custom data for the user being challenged. Only [custom data points marked
as public](/actions-rules/rules/custom-data-points#public-custom-data-points) are
returned.
## Updating a challenge
After presenting the user with a prompt to approve or reject the request, you should update the challenge with their response.
```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,
);
```
### Response
An unstructured error description present if the SDK call encountered an error. This could
occur if the credential on the device is no longer valid because the corresponding user
authenticator has been deleted in the Authsignal Portal.
True if the device challenge was successfully updated.
## Requiring user authentication
When adding a credential for QR code verification, it is possible to require that the user authenticate via their OS biometrics or PIN whenever they access the credential (e.g. when approving or rejecting a challenge).
### iOS
To require user authentication on iOS, set the `userPresenceRequired` flag to true when adding the credential.
```swift theme={null}
await authsignal.push.addCredential(
token: token,
userPresenceRequired: true
)
```
This is all that's needed - iOS will automatically handle displaying the biometrics or PIN prompt when [updating a challenge](#updating-a-challenge) or [verifying a device](#verifying-a-device).
### Android
To require user authentication on Android, set the `userAuthenticationRequired` flag to true when adding the credential.
```kotlin theme={null}
authsignal.push.addCredential(
token = token,
userAuthenticationRequired = true
)
```
It's also possible to specify additional [user authentication parameters](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationParameters\(int,%20int\)).
```kotlin theme={null}
authsignal.push.addCredential(
token = token,
userAuthenticationRequired = true,
timeout = 60,
authorizationType = 0
)
```
The `timeout` param determines the time (in seconds) that the credential can be accessed after authenticating - or `0` if authentication must occur for every credential use.
The `authorizationType` param determines if authentication is required via biometrics and/or device credential (e.g. pin).
If user authentication is required for a credential, you **must** call `updateChallenge` in a [biometric prompt authentication callback](https://developer.android.com/identity/sign-in/biometric-auth).
1. Initialize a signature before displaying the biometric prompt
```kotlin theme={null}
val signature = authsignal.push.startSigning()
val cryptoObject = CryptoObject(signature)
// Initialize biometric prompt and prompt info
val biometricPrompt = ...
val promptInfo = ...
biometricPrompt.authenticate(promptInfo, cryptoObject);
```
2. Retrieve the signature from the crypto object in your prompt's authentication callback
```kotlin theme={null}
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
super.onAuthenticationSucceeded(result)
val cryptoObject = result.cryptoObject
val signer = cryptoObject.signature
authsignal.push.updateChallenge(
challengeId = challengeId,
approved = true,
signer = signer
)
}
```
# Mobile SDKs
Source: https://docs.authsignal.com/sdks/client/mobile/setup
Learn how to use the Authsignal SDKs for iOS, Android, React Native, and Flutter.
## Requirements
Passkeys are supported only on **iOS 15** and above.
Passkey autofill requires **iOS 16** and above.
The minimum SDK version required is **Android 6 (API level 23)** or higher.
The minimum Java version required is **Java 17**.
We recommend using React Native version `0.73` or higher because this version includes [Android 14 support](https://reactnative.dev/blog/2023/12/06/0.73-debugging-improvements-stable-symlinks#android-14-support).
This requires upgrading to **Java 17** and **AGP 8** or higher.
The minimum SDK version required is `3.2.2` and the minimum Flutter version is `3.3.0`.
## Installation
**Cocoapods**
Add Authsignal to your Podfile.
```rb theme={null}
pod 'Authsignal', '~> 2.3.0'
```
**Swift Package Manager**
Add the following to the `dependencies` value of your `Package.swift` file.
```swift theme={null}
dependencies: [
.package(
url: "https://github.com/authsignal/authsignal-ios.git",
from: "2.3.0"
)
]
```
Ensure that you have `mavenCentral` listed in your project's buildscript repositories section.
```groovy theme={null}
buildscript {
repositories {
mavenCentral()
...
}
}
```
Add the following to your app's `build.gradle` file.
```groovy theme={null}
implementation 'com.authsignal:authsignal-android:3.3.0'
```
Install using yarn.
```bash theme={null}
yarn add react-native-authsignal
```
Then link the native iOS dependencies.
```bash theme={null}
npx pod-install ios
```
Add the package dependency to your app.
```bash theme={null}
flutter pub add authsignal_flutter
```
## Initialization
Initialize the client with your tenant ID and the API URL for your region.
| 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` |
You can find your tenant ID in the [Authsignal Portal](https://portal.authsignal.com/organisations/tenants/api).
```swift iOS theme={null}
import Authsignal
let authsignal = Authsignal(
tenantID: "YOUR_TENANT_ID",
baseURL: "YOUR_REGION_BASE_URL"
)
```
```kotlin Android theme={null}
import com.authsignal.Authsignal
val authsignal = Authsignal(
tenantID = "YOUR_TENANT_ID",
baseURL = "YOUR_REGION_BASE_URL",
activity = this, // An activity-based context param is required only when using passkeys
)
```
```ts React Native theme={null}
import { Authsignal } from "react-native-authsignal";
const authsignal = new Authsignal({
tenantID: "YOUR_TENANT_ID",
baseURL: "YOUR_REGION_BASE_URL",
});
```
```dart Flutter theme={null}
import 'package:authsignal_flutter/authsignal_flutter.dart';
final authsignal = Authsignal("YOUR_TENANT_ID", baseURL: "YOUR_REGION_BASE_URL");
```
## Usage
## Repositories
# SMS OTP
Source: https://docs.authsignal.com/sdks/client/mobile/sms
Implement SMS OTP in your mobile apps using the Authsignal SDKs for iOS, Android, React Native, and Flutter.
All SMS OTP SDK methods require you to [initiate an action first](/sdks/client/mobile/actions).
Check out our [guide on SMS authentication](/authentication-methods/sms-otp) for more details.
## Enroll SMS
Start enrollment for a new SMS OTP authenticator by sending the user an SMS containing an OTP code.
This method is typically used when you've **not yet verified** the user's phone number.
```swift iOS theme={null}
await authsignal.sms.enroll(phoneNumber: "+64270000000")
```
```kotlin Android theme={null}
authsignal.sms.enroll(phoneNumber = "+64270000000")
```
```ts React Native theme={null}
await authsignal.sms.enroll({ phoneNumber: "+64270000000" });
```
```dart Flutter theme={null}
await authsignal.sms.enroll("+64270000000");
```
If you've already verified a user's phone number independently of Authsignal, you can
alternatively use a Server SDK to [enroll the user
programmatically](/advanced-usage/programmatic-authenticator-management).
### Parameters
The user's phone number in [E.164 format](https://en.wikipedia.org/wiki/E.164) e.g. +14155552671.
### Response
An unstructured error description present if the SDK call encountered an error.
## Challenge SMS
Start an SMS challenge by sending the user an SMS containing an OTP code.
```swift iOS theme={null}
await authsignal.sms.challenge()
```
```kotlin Android theme={null}
authsignal.sms.challenge()
```
```ts React Native theme={null}
await authsignal.sms.challenge();
```
```dart Flutter theme={null}
await authsignal.sms.challenge();
```
### Response
An unstructured error description present if the SDK call encountered an error.
## Verify SMS
Finish enrolling or re-authenticating a new or existing SMS OTP authenticator by verifying the code submitted by the user.
```swift iOS theme={null}
await authsignal.sms.verify(code: "123456")
```
```kotlin Android theme={null}
authsignal.sms.verify(code = "123456")
```
```ts React Native theme={null}
await authsignal.sms.verify({ code: "123456" });
```
```dart Flutter theme={null}
await authsignal.sms.verify("123456");
```
### Parameters
The OTP code inputted by the user.
### Response
An unstructured error description present if the SDK call encountered an error.
True if the verification was successful.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
A structured code present if the verification failed due to user error. Possible values are:
`CODE_INVALID_OR_EXPIRED` or `MAX_ATTEMPTS_EXCEEDED`.
# Authenticator app (TOTP)
Source: https://docs.authsignal.com/sdks/client/mobile/totp
Implement authenticator app verification in your mobile apps using the Authsignal SDKs for iOS, Android, React Native, and Flutter.
All authenticator app SDK methods require you to [initiate an action
first](/sdks/client/mobile/actions). Check out our [guide on TOTP
authentication](/authentication-methods/totp) for more details.
### Enroll TOTP
Start enrollment for a new TOTP authenticator by generating a QR code to display to the user.
```swift iOS theme={null}
let response await authsignal.totp.enroll()
if let data = response.data {
let uri = data.uri // Convert to QR code
let secret = data.secret // Can be entered manually
}
```
```kotlin Android theme={null}
val response = authsignal.totp.enroll()
response.data.let {
val uri = it.uri // Convert to QR code
val secret = it.secret // Can be entered manually
}
```
```ts React Native theme={null}
const response = await authsignal.totp.enroll();
if (response.data) {
const uri = response.data.uri; // Convert to QR code
const secret = response.data.secret; // Can be entered manually
}
```
```dart Flutter theme={null}
final response = await authsignal.totp.enroll();
if (response.data != null) {
final uri = response.data.uri; // Convert to QR code
final secret = response.data.secret; // Can be entered manually
}
```
#### Response
An unstructured error description present if the SDK call encountered an error.
A TOTP URI which can be converted into a QR code and presented to the user to scan with
their authenticator app.
The raw TOTP secret which can be presented to the user as a fallback option if they wish to
enter a code manually instead of scanning a QR code.
### Verify TOTP
Finish enrolling or re-authenticating a TOTP authenticator by verifying the code submitted by the user.
```swift iOS theme={null}
await authsignal.totp.verify(code: "123456")
```
```kotlin Android theme={null}
authsignal.totp.verify(code = "123456")
```
```ts React Native theme={null}
await authsignal.totp.verify({ code: "123456" });
```
```dart Flutter theme={null}
await authsignal.totp.verify("123456");
```
#### Parameters
The OTP code inputted by the user.
#### Response
An unstructured error description present if the SDK call encountered an error.
True if the verification was successful.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
A structured code present if the verification failed due to user error. Possible values are:
`CODE_INVALID_OR_EXPIRED` or `MAX_ATTEMPTS_EXCEEDED`.
# WhatsApp OTP
Source: https://docs.authsignal.com/sdks/client/mobile/whatsapp
Implement WhatsApp OTP in your mobile apps using the Authsignal SDKs for iOS, Android, React Native, and Flutter.
All WhatsApp OTP SDK methods require you to [initiate an action
first](/sdks/client/mobile/actions). Check out our [guide on WhatsApp
authentication](/authentication-methods/whatsapp-otp) for more details.
## Challenge
Start a WhatsApp OTP challenge by sending the user a WhatsApp message containing an OTP code.
```swift iOS theme={null}
await authsignal.whatsapp.challenge()
```
```kotlin Android theme={null}
authsignal.whatsapp.challenge()
```
```ts React Native theme={null}
await authsignal.whatsapp.challenge();
```
```dart Flutter theme={null}
await authsignal.whatsapp.challenge();
```
### Response
An unstructured error description present if the SDK call encountered an error.
## Verify
Finish re-authenticating a WhatsApp OTP authenticator by verifying the code submitted by the user.
```swift iOS theme={null}
let response = await authsignal.whatsapp.verify(code: "123456")
let isVerified = response.isVerified
```
```kotlin Android theme={null}
val response = authsignal.whatsapp.verify(code = "123456")
val isVerified = response.isVerified
```
```ts React Native theme={null}
const response = await authsignal.whatsapp.verify({ code: "123456" });
const isVerified = response.isVerified;
```
```dart Flutter theme={null}
final response = await authsignal.whatsapp.verify("123456");
final isVerified = response.isVerified;
```
### Parameters
The OTP code inputted by the user.
#### Response
An unstructured error description present if the SDK call encountered an error.
True if the verification was successful.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
A structured code present if the verification failed due to user error. Possible values are:
`CODE_INVALID_OR_EXPIRED` or `MAX_ATTEMPTS_EXCEEDED`.
# React
Source: https://docs.authsignal.com/sdks/client/react
Learn how to use the Authsignal React SDK.
The Authsignal React SDK builds on top of the [Web SDK](/sdks/client/web/setup) by providing UI components which can be added to your React app.
The Authsignal React SDK currently only supports re-authentication flows.
## Installation
```bash npm theme={null}
npm install @authsignal/react
```
```bash yarn theme={null}
yarn add @authsignal/react
```
```bash pnpm theme={null}
pnpm add @authsignal/react
```
```bash bun theme={null}
bun add @authsignal/react
```
## AuthsignalProvider component
The `AuthsignalProvider` component allows you to use the [useAuthsignal hook](/sdks/client/react#useauthsignal-hook). Render the `AuthsignalProvider` component
at the root of your application so that it is available everywhere you need it.
```tsx theme={null}
import { AuthsignalProvider } from "@authsignal/react";
import { Checkout } from "./Checkout";
export function App() {
return (
);
}
```
The `AuthsignalProvider` component accepts the following props:
Your tenant ID. It can be found in the [API
keys](https://portal.authsignal.com/organisations/tenants/api) section.
Your tenant's API URL. It can be found in the [API
keys](https://portal.authsignal.com/organisations/tenants/api) section.
Customize the design of the UI components.
Set variables to customize the appearance of the UI components. Each variable accepts a valid
CSS value.
Your brand's primary color. Used for buttons.
The foreground color for the primary color. Used for text on buttons.
The color used for the background.
The foreground color for the background. Used for text.
The color used to indicate errors.
The color used for the focus ring.
The color used for input borders.
The base unit of spacing.
The border radius used for buttons and inputs.
## useAuthsignal hook
The `useAuthsignal` hook returns two functions, `startChallenge` and `startChallengeAsync`, that you can use to
trigger the authentication flow.
Both functions require a `token` to be passed as an argument. The `token` should be
returned by your server after [tracking an action](/api-reference/server-api/track-action).
### `startChallenge`
The `startChallenge` function triggers the authentication flow.
```tsx theme={null}
import { useAuthsignal } from "@authsignal/react";
export function Checkout() {
const { startChallenge } = useAuthsignal();
const handlePayment = async () => {
const response = await fetch("/api/payment", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
if (data.token) {
startChallenge({
token: data.token,
onChallengeSuccess: ({ token }) => {
// Challenge was successful
// Send the token to your server to validate the challenge
},
onCancel: () => {
// User cancelled the challenge
},
onTokenExpired: () => {
// Token expired
},
});
}
};
return (
);
}
```
As well as a `token`, the `startChallenge` function accepts the following callbacks:
A callback function that is called when the challenge is successful. It receives an object with a
`token` property. This `token` should be sent to your server to [validate the
challenge](/sdks/server/challenges#validate-challenge).
A callback function that is called when the user cancels the challenge.
A callback function that is called when the token expires.
### `startChallengeAsync`
Alternatively, you can use the `startChallengeAsync` function, which returns a `Promise` that resolves with a `token` when the challenge is successful.
It will throw a `ChallengeError` if the user cancels the challenge or the token expires.
```tsx theme={null}
import { useAuthsignal, ChallengeError } from "@authsignal/react";
export function Checkout() {
const { startChallengeAsync } = useAuthsignal();
const handlePayment = async () => {
const response = await fetch("/api/payment", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
if (data.token) {
try {
const { token } = await startChallengeAsync({
token: data.token,
});
// Challenge was successful
// Send the token to your server to validate the challenge
} catch (e) {
if (e instanceof ChallengeError) {
if (e.code === "USER_CANCELED") {
// User cancelled the challenge
} else if (e.code === "TOKEN_EXPIRED") {
// Token expired
}
}
}
}
};
return (
);
}
```
## Customizing the appearance
You can customize the appearance of the UI components by passing an `appearance` prop to the [AuthsignalProvider component](/sdks/client/react#authsignalprovider-component).
```tsx theme={null}
import { AuthsignalProvider } from "@authsignal/react";
import { Checkout } from "./Checkout";
export function App() {
return (
);
}
```
# Actions
Source: https://docs.authsignal.com/sdks/client/web/actions
Initiate actions using a Server SDK before using Web SDK methods for email OTP, SMS OTP, WhatsApp OTP, or authenticator app.
When using Web SDK methods for email OTP, SMS OTP, WhatsApp OTP, or authenticator app, you must first track an action using a [Server SDK](/sdks/server/overview) to generate a short-lived token.
This token is valid for 10 minutes by default and authorizes a window in which Web SDK methods can be used to authenticate the user associated with the token.
```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
```
For enrollment flows, you must also specify the scope `add:authenticators` when tracking the
action if the user already has an existing authenticator. For more detail refer to our guide on
[how to ensure a strong binding when adding
authenticators](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token).
Then you can use the SDK's `setToken` method.
You must use the **same token** for the initial enroll/challenge call and the subsequent verify call.
```js JS theme={null}
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" });
```
# Device tracking
Source: https://docs.authsignal.com/sdks/client/web/device-tracking
Use the Web SDK to remember users' devices and create rules for adaptive MFA.
The Web SDK client exposes an `anonymousId` property which can be used as the `deviceId` value when tracking an action.
```js Web theme={null}
const deviceId = authsignal.anonymousId;
```
This value is persisted as a cookie on the domain where you have initialized the Web SDK client. The name of the cookie is **\_\_as\_aid**.
You can pass this value to your server and into a track request using a Server SDK in order to use [rules based on device](/actions-rules/rules/getting-started#using-rules-with-actions).
# Email magic link
Source: https://docs.authsignal.com/sdks/client/web/email-magic-link
Use the Web SDK to implement email magic link authentication.
All Web SDK methods for email magic link require you to [initiate an action
first](/sdks/client/web/actions). Check out our [guide on email magic link
authentication](/authentication-methods/email/magic-link) for more details.
## Enroll
Start enrollment for a new email magic link authenticator by sending the user an email containing a verification link.
This method is typically used when you've **not yet verified** the user's email address.
```ts theme={null}
const response = await authsignal.emailML.enroll({ email: "jane.smith@authsignal.com" });
```
If you've already verified a user's email address independently of Authsignal, you can
alternatively use a Server SDK to [enroll the user
programmatically](/advanced-usage/programmatic-authenticator-management).
### Parameters
The user's email address.
### Response
The ID of the Authsignal user.
The ID of the Authsignal user authenticator.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Challenge
Start re-authentication for an existing email magic link authenticator by sending the user an email containing a verification link.
This method is typically used when you've **already verified** the user's email address.
```ts theme={null}
const response = await authsignal.emailML.challenge();
```
#### Response
The ID of the Authsignal challenge.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Check email magic link verification status
Check the verification status of an email magic link authenticator. When the user clicks a valid
magic link, the promise will resolve.
```ts theme={null}
const response = await authsignal.emailML.checkVerificationStatus();
```
#### Response
True if the verification was successful.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
Only present on first time verification e.g. enrollment.
The ID of the Authsignal user authenticator.
The date and time the authenticator was created in ISO 8601 format.
The date and time the authenticator was first verified in ISO 8601 format.
The date and time the authenticator was last verified in ISO 8601 format.
Whether the authenticator is the default authenticator for the user.
The email address of the authenticator. Only present for email authenticators e.g. email OTP and email magic link.
The phone number of the authenticator. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
The previous SMS channel used to send the OTP code. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
Only present for passkey authenticators.
The ID of the passkey credential.
The device ID of the passkey credential.
The name of the passkey credential.
The AAGUID of the passkey credential.
Whether the passkey credential is backed up.
The device type of the passkey credential.
The parsed user agent of the passkey credential.
The user agent of the passkey credential.
The browser of the passkey credential.
The name of the browser.
The version of the browser.
The major version of the browser.
The device of the passkey credential.
The model of the device.
The type of the device.
The vendor of the device.
The engine of the passkey credential.
The name of the engine.
The version of the engine.
The OS of the passkey credential.
The name of the OS.
The version of the OS.
The CPU of the passkey credential.
The architecture of the CPU.
The date and time the passkey credential was verified in ISO 8601 format.
The authenticator attachment of the passkey credential.
The AAGUID mapping of the passkey credential.
The name of the AAGUID mapping.
The SVG icon of the AAGUID mapping in light mode.
The SVG icon of the AAGUID mapping in dark mode.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
# Email OTP
Source: https://docs.authsignal.com/sdks/client/web/email-otp
Use the Web SDK to implement email OTP authentication.
All Web SDK methods for email OTP require you to [initiate an action
first](/sdks/client/web/actions). Check out our [guide on email OTP
authentication](/authentication-methods/email/otp) for more details.
## Enroll
Start enrollment for a new email OTP authenticator by sending the user an email containing an OTP code.
This method is typically used when you've **not yet verified** the user's email address.
```ts theme={null}
const response = await authsignal.email.enroll({ email: "jane.smith@authsignal.com" });
```
If you've already verified a user's email address independently of Authsignal, you can
alternatively use a Server SDK to \[enroll the user
programmatically]/advanced-usage/programmatic-authenticator-management).
### Parameters
The user's email address.
### Response
The ID of the Authsignal user.
The ID of the Authsignal user authenticator.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Challenge
Start re-authentication for an existing email OTP authenticator by sending the user an email containing an OTP code.
This method is typically used when you've **already verified** the user's email address.
```ts theme={null}
const response = await authsignal.email.challenge();
```
### Response
The ID of the Authsignal challenge.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Verify
Finish enrolling or re-authenticating a new or existing email OTP authenticator by verifying the code submitted by the user.
```ts theme={null}
const response = await authsignal.email.verify({ code: "123456" });
```
### Parameters
The OTP code inputted by the user.
### Response
True if the verification was successful.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
Only present on first time verification e.g. enrollment.
The ID of the Authsignal user authenticator.
The date and time the authenticator was created in ISO 8601 format.
The date and time the authenticator was first verified in ISO 8601 format.
The date and time the authenticator was last verified in ISO 8601 format.
Whether the authenticator is the default authenticator for the user.
The email address of the authenticator. Only present for email authenticators e.g. email OTP and email magic link.
The phone number of the authenticator. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
The previous SMS channel used to send the OTP code. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
Only present for passkey authenticators.
The ID of the passkey credential.
The device ID of the passkey credential.
The name of the passkey credential.
The AAGUID of the passkey credential.
Whether the passkey credential is backed up.
The device type of the passkey credential.
The parsed user agent of the passkey credential.
The user agent of the passkey credential.
The browser of the passkey credential.
The name of the browser.
The version of the browser.
The major version of the browser.
The device of the passkey credential.
The model of the device.
The type of the device.
The vendor of the device.
The engine of the passkey credential.
The name of the engine.
The version of the engine.
The OS of the passkey credential.
The name of the OS.
The version of the OS.
The CPU of the passkey credential.
The architecture of the CPU.
The date and time the passkey credential was verified in ISO 8601 format.
The authenticator attachment of the passkey credential.
The AAGUID mapping of the passkey credential.
The name of the AAGUID mapping.
The SVG icon of the AAGUID mapping in light mode.
The SVG icon of the AAGUID mapping in dark mode.
A structured code present if the verification failed due to user error. Possible values are:
`CODE_INVALID_OR_EXPIRED` or `MAX_ATTEMPTS_EXCEEDED`.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
# Error handling
Source: https://docs.authsignal.com/sdks/client/web/error-handling
How to handle errors using the Authsignal Web SDK.
The Web SDK methods can return the following fields when encountering an error.
A code which identifies a specific error state. This value can be used to drive application logic
such as when a user encounters a rate limit error.
A description of the error. This value should only be used for logging or troubleshooting
purposes.
## Error codes
Indicates that an error occurred when making a network request to the Authsignal API. This will
occur if the device has no network connection.
Indicates that the Authsignal token has expired. This will occur if more than 10 minutes has
elapsed since it was [generated by tracking an
action](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token) or since a
[previous challenge was
completed](/advanced-usage/authenticator-binding#presenting-a-challenge-with-an-existing-method).
Indicates that the credential (e.g. passkey) being used for verification is invalid. This may occur
if the credential has been removed from the server but still exists on the user's credential manager.
Indicates that the passkey credential presented during sign-in is not recognized by the server. This
can occur if the credential was deleted from the server but still exists in the user's credential
manager. When credential syncing is enabled, the SDK signals the browser to remove the stale
credential so it is no longer offered.
Indicates that no immediately available passkey credential was found when signing in with
`preferImmediatelyAvailableCredentials` set to `true`. Fall back to a standard passkey sign-in or an
alternative authentication method.
Indicates that the current browser does not support immediate mediation UI mode, which is required
when signing in with `preferImmediatelyAvailableCredentials` set to `true`.
Indicates that no token has been set before calling a method that requires one. Track an action to
obtain a token, then call `setToken` before retrying.
Indicates that the server cannot respond as requests have been rate-limited due to exceeding a
threshold. For example, a user has requested too many OTP codes in a short period of time.
# Passkeys
Source: https://docs.authsignal.com/sdks/client/web/passkeys
Use the Web SDK to implement passkeys.
In addition to the reference documentation below, check out our end-to-end guide on [how to
implement passkeys in web apps using Authsignal](/authentication-methods/passkey/web-sdk).
## Creating a passkey
Creating a passkey must be authorized by [presenting a challenge with an existing method](/advanced-usage/authenticator-binding#presenting-a-challenge-with-an-existing-method) or [tracking an action to obtain a short-lived token](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token).
```ts theme={null}
const response = await authsignal.passkey.signUp({
token: "eyJhbGciOiJ...",
username: "jane.smith@authsignal.com",
displayName: "Jane Smith",
});
```
### Parameters
A short-lived token obtained by [tracking an
action](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token).
The primary user identifier associated with the passkey, e.g. the user's email address.
An optional secondary user identifier which the browser may display in place of or alongside the
username, e.g. the user's full name.
The preferred authenticator type. Defaults to `platform`, which targets the device's built-in
authenticator (e.g. Touch ID or Windows Hello). Set to `cross-platform` to target a roaming
authenticator such as a security key, or `null` to let the user choose.
An array of [WebAuthn credential hints](https://www.w3.org/TR/webauthn-3/#enum-hints) which guide
the browser's UI, e.g. `["client-device"]`, `["security-key"]`, or `["hybrid"]`.
Whether to use the automatic passkey upgrade flow. If true, the user's password manager will be
prompted to automatically create a passkey after successful authentication via password autofill.
Whether to use cookies to bind the session to the browser. Defaults to `false`. Requires a [custom
API domain](/advanced-usage/custom-api-domains) on the same parent domain as your site.
Whether to keep the browser's credential manager in sync with the credentials stored by Authsignal.
Defaults to `true`. See [Credential syncing](#credential-syncing) for more details.
### Response
A new token which can optionally be used to [bind another
authenticator](#authenticator-binding) for the user.
Only present on first time verification e.g. enrollment.
The ID of the Authsignal user authenticator.
The date and time the authenticator was created in ISO 8601 format.
The date and time the authenticator was first verified in ISO 8601 format.
The date and time the authenticator was last verified in ISO 8601 format.
Whether the authenticator is the default authenticator for the user.
The email address of the authenticator. Only present for email authenticators e.g. email OTP and email magic link.
The phone number of the authenticator. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
The previous SMS channel used to send the OTP code. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
Only present for passkey authenticators.
The ID of the passkey credential.
The device ID of the passkey credential.
The name of the passkey credential.
The AAGUID of the passkey credential.
Whether the passkey credential is backed up.
The device type of the passkey credential.
The parsed user agent of the passkey credential.
The user agent of the passkey credential.
The browser of the passkey credential.
The name of the browser.
The version of the browser.
The major version of the browser.
The device of the passkey credential.
The model of the device.
The type of the device.
The vendor of the device.
The engine of the passkey credential.
The name of the engine.
The version of the engine.
The OS of the passkey credential.
The name of the OS.
The version of the OS.
The CPU of the passkey credential.
The architecture of the CPU.
The date and time the passkey credential was verified in ISO 8601 format.
The authenticator attachment of the passkey credential.
The AAGUID mapping of the passkey credential.
The name of the AAGUID mapping.
The SVG icon of the AAGUID mapping in light mode.
The SVG icon of the AAGUID mapping in dark mode.
The raw WebAuthn registration response returned by the browser. Most apps can ignore this.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Automatic passkey upgrades
Automatic passkey upgrades are supported in Safari 18+ on macOS, all browsers on iOS 18+, Chrome
136+ on desktop, and Chrome 142+ on Android.
With passkey upgrades, your app can prompt your user's password manager to automatically create a passkey. This works provided the user
has a password saved for your app in their password manager and has recently authenticated with it.
To enable this, you must set the `useAutoRegister` parameter to `true` when calling `signUp`.
```ts {5} theme={null}
const response = await authsignal.passkey.signUp({
token: "eyJhbGciOiJ...",
username: "jane.smith@authsignal.com",
displayName: "Jane Smith",
useAutoRegister: true,
});
```
### Example usage
```ts {10} theme={null}
async function onSubmit(formData) {
// Validate user on BE and return a token from `track`
// If valid user, attempt to automatically create a passkey
try {
const response = await authsignal.passkey.signUp({
token: "eyJhbGciOiJ...", // Token from `track`
username: "jane.smith@authsignal.com",
displayName: "Jane Smith",
useAutoRegister: true,
});
} catch (error) {
// Failed to automatically create a passkey
console.error(error);
}
// Continue with post-sign-in flow
window.location.href = "/dashboard";
}
```
## Using a passkey
Calling `signIn` will present the passkey sign-in prompt.
If the user successfully authenticates with their passkey, send the result token to your server to [validate the challenge](/sdks/server/challenges#validate-challenge).
```ts theme={null}
const response = await authsignal.passkey.signIn({ action: "signInWithPasskey" });
if (response.data?.token) {
// Send the response token to your server to validate the result of the challenge
}
```
Check out our [best practice guide for passkeys on web
browsers](/authentication-methods/passkey/best-practices-web) for tips on how to implement an
optimal passkey UX and avoid leading users into dead ends.
#### Parameters
A string which determines how the action associated with the passkey sign-in attempt will be named
in the [Authsignal Portal](https://portal.authsignal.com). Values are validated with the following
regex: `^[a-zA-Z0-9_-]{(1, 64)}$`. Cannot be combined with `token`.
A short-lived token obtained by [tracking an
action](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token). Use this to
authorize the sign-in attempt instead of `action`. Cannot be combined with `action` or `autofill`.
Whether to use [passkey autofill](#using-passkey-autofill) (browser conditional UI). Defaults to
`false`. Cannot be combined with `token` or `preferImmediatelyAvailableCredentials`.
A callback invoked after the user selects a passkey and the browser ceremony completes, just before
the result is verified with the Authsignal API. Useful for showing a loading state.
Whether to use immediate mediation UI mode, which only prompts the user if a passkey is immediately
available on the device. Defaults to `false`. See [Immediately available
credentials](#immediately-available-credentials) for more details.
Whether to use cookies to bind the session to the browser. Defaults to `false`. Requires a [custom
API domain](/advanced-usage/custom-api-domains) on the same parent domain as your site.
Whether to keep the browser's credential manager in sync with the credentials stored by Authsignal.
Defaults to `true`. See [Credential syncing](#credential-syncing) for more details.
#### Response
True if the passkey sign-in attempt was successful.
A new token which can be used to [validate the
challenge](/sdks/server/challenges#validate-challenge) on your server or optionally to
[bind another authenticator](#authenticator-binding) for the user.
The ID of the Authsignal user if the sign-in attempt was successful.
The ID of the Authsignal user authenticator if the sign-in attempt was successful.
The username associated with the passkey if the sign-in attempt was successful.
The display name associated with the passkey if the sign-in attempt was successful.
The raw WebAuthn authentication response returned by the browser. Most apps can ignore this.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Using passkey autofill
This feature requires rendering a text input with the attribute `autocomplete="username webauthn"`:
```html theme={null}
```
It can also be added to a password input:
```html theme={null}
```
The `webauthn` value must be the last value in the `autocomplete` attribute, otherwise the browser
will not autofill the passkey.
Then when the page loads you should initialize the input for passkey autofill by running the following code.
```ts theme={null}
authsignal.passkey
.signIn({
action: "signInWithPasskeyAutofill",
autofill: true,
})
.then((response) => {
if (response.data?.token) {
// The user has focused your text input and authenticated with an existing passkey
// Send the response token to your server to validate the result of the challenge
}
});
```
## Immediately available credentials
Setting `preferImmediatelyAvailableCredentials` to `true` uses the browser's immediate mediation UI mode. The passkey prompt is only shown if a credential is immediately available on the device, otherwise the call returns without interrupting the user. This is useful for surfacing a passkey prompt automatically on page load without showing an error to users who don't have a passkey.
```ts theme={null}
const response = await authsignal.passkey.signIn({
action: "signInWithPasskey",
preferImmediatelyAvailableCredentials: true,
});
if (response.errorCode === "credential_not_found") {
// No passkey was immediately available, fall back to another method
} else if (response.data?.token) {
// Send the response token to your server to validate the result of the challenge
}
```
Immediate mediation UI mode is only supported in browsers that implement the WebAuthn
`getClientCapabilities` and immediate `get` APIs. When unsupported, the call returns the
`immediate_mediation_not_supported` error code. It also cannot be combined with `autofill`.
## Credential syncing
A common source of passkey friction is stale credentials: a passkey is deleted on the server but still appears in the user's browser or password manager, so they select it and hit a dead end.
To prevent this, the SDK uses the [WebAuthn Signal API](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential#static_methods) to keep the browser's credential manager in sync with the passkeys stored by Authsignal. This happens automatically while `syncCredentials` is `true` (the default), so there's no separate method to call:
* After a successful `signUp` or `signIn`, the SDK calls `signalAllAcceptedCredentials` with the full set of the user's valid credentials. The browser removes any passkeys for your app that are no longer on this list.
* When `signIn` fails because the presented passkey is no longer recognized by the server (the `unknown_credential` error code), the SDK calls `signalUnknownCredential` so the browser removes that specific passkey.
Set `syncCredentials` to `false` on `signUp` or `signIn` to opt out.
The Signal API is only available in [supporting
browsers](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/signalAllAcceptedCredentials_static#browser_compatibility).
Where it isn't supported, syncing is silently skipped and sign-in is unaffected.
## Checking passkey availability
Use `isAvailableOnDevice` to check whether a passkey for a given user has previously been registered on the current device. This is useful for deciding whether to show a passkey sign-in button.
```ts theme={null}
const isAvailable = await authsignal.passkey.isAvailableOnDevice({
userId: "b9e2c1a4-...",
});
```
### Parameters
The ID of the Authsignal user to check for a locally registered passkey.
### Response
Returns a `boolean` which is `true` if a passkey for the user is registered on the current device and still recognized by the server.
## Passkey error handling
When any of the underlying native browser WebAuthn APIs fail, the SDK will throw a `WebAuthnError`.
Most of these errors are benign and can be safely ignored. However, if you wish to handle them, you can do so by catching the error in a `try/catch` block.
Common errors you will encounter during passkey authentication are:
* `ERROR_CEREMONY_ABORTED` - The user cancelled the passkey ceremony. We recommend ignoring this error as it's a normal part of the user experience.
* `ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED` - The user already has a passkey registered for this authenticator (exclusive to `signUp`). For example,
the user is trying to create an iCloud Keychain passkey but already has one registered in iCloud Keychain.
During development, you may also encounter `ERROR_INVALID_RP_ID` which occurs when the [relying party ID](/authentication-methods/passkey/custom-ui#web) is invalid for the domain.
```ts theme={null}
import { WebAuthnError } from "@authsignal/browser";
try {
const response = await authsignal.passkey.signUp({
token: "eyJhbGciOiJ...",
username: "jane.smith@authsignal.com",
displayName: "Jane Smith",
});
} catch (error) {
if (error instanceof WebAuthnError) {
if (error.code === "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED") {
// The user already has a passkey registered for this device
// You can choose to handle this however you see fit
}
}
}
```
# Launching the pre-built UI
Source: https://docs.authsignal.com/sdks/client/web/prebuilt-ui
Use the Authsignal Web SDK to launch the pre-built UI.
The Web SDK can be used to [launch the pre-built UI](/implementation-options/prebuilt-ui/presentation-modes).
```js Popup theme={null}
const result = await authsignal.launch(url, {
mode: "popup",
});
```
```js Redirect theme={null}
authsignal.launch(url, {
mode: "redirect",
});
```
```js Window theme={null}
const result = await authsignal.launch(url, {
mode: "window",
});
```
### Parameters
The URL obtained on your server by [tracking an
action](/implementation-options/prebuilt-ui/overview#1-backend-track-action).
The options used when launching the pre-built UI.
How the pre-built UI should launch: `popup` opens it in an overlay, `window` opens it in a
separate browser window, and `redirect` triggers a full page redirect. If no value is supplied,
mode defaults to `redirect`.
Only available when `mode` is `popup`.
Any valid CSS value for the `width` property.
Any valid CSS value for the `height` property. If not set, the popup will auto-adjust to
the height of the content.
Whether the popup is closable with the escape key and by clicking the backdrop.
Only available when `mode` is `window`.
The width of the window in pixels.
The height of the window in pixels.
Called when an unexpected API error occurs in the pre-built UI. Available when `mode` is
`popup` or `window`.
The error code returned by the API.
The HTTP status code of the failed request.
### Response
When `mode` is set as `popup` or `window` it returns a `Promise` that resolves when the popup or window closes.
The token that can be used to verify the outcome of the user action.
For more information on launching the pre-built UI see our [detailed guide](/implementation-options/prebuilt-ui/presentation-modes).
# Push verification
Source: https://docs.authsignal.com/sdks/client/web/push-verification
Use the Web SDK to initiate push verification challenges.
Check out our end-to-end guide on [how to implement push verification using
Authsignal](/authentication-methods/app-verification/push).
## Start a push challenge
Start a push challenge by sending a notification to the user's mobile device.
```ts theme={null}
const response = await authsignal.push.challenge({ action: "login" });
```
To target a specific enrolled push authenticator when the user has more than one, pass its `userAuthenticatorId`. The challenge is then sent only to that authenticator and can only be approved from that device.
```ts theme={null}
const response = await authsignal.push.challenge({
action: "login",
userAuthenticatorId: "9b89d063-72c8-4a3b-9c2b-1e2f3a4b5c6d",
});
```
### Parameters
The action being performed.
The ID of a specific enrolled push authenticator to target. When omitted, the challenge is sent to all of the user's eligible push authenticators.
### Response
The ID of the Authsignal challenge. Use this ID to poll for the challenge result.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Check push challenge verification status
```ts theme={null}
const response = await authsignal.push.verify({
challengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d",
});
```
### Parameters
The ID of the Authsignal challenge returned when starting the push challenge.
### Response
True if the challenge has been either approved or denied by a user.
True if the challenge has been approved by a user.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge). Only present if the challenge
succeeded.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
# QR code verification
Source: https://docs.authsignal.com/sdks/client/web/qr-code-verification
Use the Web SDK to generate QR code challenges which can be scanned and authenticated using a mobile app.
Check out our end-to-end guide on [how to implement QR code verification using
Authsignal](/authentication-methods/app-verification/qr-code).
## Start a QR code challenge
```js theme={null}
const { data } = await authsignal.qrCode.challenge({
action: "kiosk-login",
onStateChange: (state: ChallengeState, token?: string) => {
if (state === "approved" && token) {
// Validate the challenge on your backend
}
},
});
```
### Parameters
The action being performed.
A function that is called when the challenge state changes.
The current state of the challenge.
Possible values are: `unclaimed`, `claimed`, `approved`, `rejected`.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
A function that is called when the challenge should be refreshed.
The ID of the Authsignal challenge. The value of this should be rendered as a QR code.
The timestamp in ISO 8601 format at which the challenge will expire.
A JSON object which can include any key/value pairs. Can be used to provide additional context to the challenge which can be reviewed on the user's mobile device.
The interval in milliseconds at which the challenge should be refreshed. Defaults to `540000` (9 minutes). This should be set to a value lower than your token's expiry time to allow a tolerance for the challenge to be completed.
Whether to poll for the challenge result using RESTful API endpoints. Should only be used if you are unable to use WebSocket connections.
Defaults to `false` and uses WebSockets.
The interval in milliseconds at which the challenge should be polled. Defaults to `5000` (5 seconds).
Only relevant when `polling` is set to `true`.
### Response
The ID of the Authsignal challenge. The value of this should be rendered as a QR code.
The timestamp in ISO 8601 format at which the challenge will expire.
A short code which can be entered manually on the user's mobile device as a fallback to scanning the QR code. Only available when `polling` is set to `true`.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Refresh a QR code challenge
```js theme={null}
await authsignal.qrCode.refresh();
```
This can only be called after a QR code challenge has been initiated. The callback functions passed to the `challenge` method will be re-used.
### Parameters
A JSON object which can include any key/value pairs. Can be used to update context to the
challenge which can be reviewed on the user's mobile device.
## Disconnect a QR code challenge
Call `disconnect` to tear down the active challenge. This closes the WebSocket connection (or stops polling) and clears any refresh timers. Call it when the QR code is no longer displayed, for example when the user navigates away.
```js theme={null}
authsignal.qrCode.disconnect();
```
# Security key
Source: https://docs.authsignal.com/sdks/client/web/security-key
Use the Web SDK to enroll and verify hardware security keys.
All Web SDK methods for security keys require you to [initiate an action
first](/sdks/client/web/actions).
Security keys are roaming hardware authenticators (such as YubiKeys) that authenticate using the WebAuthn standard. Unlike passkeys, they are not synced across devices.
## Enroll
Start enrollment for a new security key by prompting the user to register their hardware authenticator.
```ts theme={null}
const response = await authsignal.securityKey.enroll();
```
### Parameters
An array of [WebAuthn credential hints](https://www.w3.org/TR/webauthn-3/#enum-hints) which guide
the browser's UI, e.g. `["security-key"]`.
### Response
A new token which can be used to [validate the
challenge](/sdks/server/challenges#validate-challenge) on your server.
The raw WebAuthn registration response returned by the browser. Most apps can ignore this.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Verify
Re-authenticate an existing security key by prompting the user to present their hardware authenticator.
```ts theme={null}
const response = await authsignal.securityKey.verify();
if (response.data?.token) {
// Send the response token to your server to validate the result of the challenge
}
```
### Response
True if the verification was successful.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
The raw WebAuthn authentication response returned by the browser. Most apps can ignore this.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Security key error handling
When any of the underlying native browser WebAuthn APIs fail, the SDK will throw a `WebAuthnError`. Most of these errors are benign and can be safely ignored, for example when the user cancels the ceremony. If you wish to handle them, catch the error in a `try/catch` block. See [Passkey error handling](/sdks/client/web/passkeys#passkey-error-handling) for the common error codes.
# Web SDK
Source: https://docs.authsignal.com/sdks/client/web/setup
Learn how to use the Authsignal Web SDK.
The Authsignal Web SDK can be used to:
* Launch the pre-built UI to let users set up MFA and complete challenges
* Sign up and sign in users using passkeys or security keys
* Build custom UIs for authenticating with email OTP, SMS OTP, WhatsApp OTP, authenticator app, push, or QR code
## Installation
```bash npm theme={null}
npm install @authsignal/browser
```
```bash yarn theme={null}
yarn add @authsignal/browser
```
```bash pnpm theme={null}
pnpm add @authsignal/browser
```
```bash bun theme={null}
bun add @authsignal/browser
```
This will add our module to your `package.json`.
## Initialization
Initialize the client with your tenant ID and the API URL for your region.
```ts theme={null}
import { Authsignal } from "@authsignal/browser";
const authsignal = new Authsignal({
tenantId: "YOUR_TENANT_ID",
baseUrl: "YOUR_REGION_BASE_URL",
});
```
You can find your `tenantId` in the [Authsignal Portal](https://portal.authsignal.com/organisations/tenants/api).
You must specify the correct `baseUrl` for your tenant's region.
| 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` |
### Parameters
Your Authsignal tenant ID, found in the [Authsignal Portal](https://portal.authsignal.com/organisations/tenants/api).
The API URL for your tenant's region. See the [region table](#initialization) above.
A callback invoked when the current token expires. Use it to prompt the user to re-authenticate or to
fetch a fresh token.
The name of the cookie used to persist the anonymous device ID. Defaults to `__as_aid`.
The domain used when setting the anonymous device ID cookie. Defaults to the current
`location.hostname`.
Whether to log SDK warnings and errors to the console. Defaults to `false`.
## Usage
# SMS OTP
Source: https://docs.authsignal.com/sdks/client/web/sms
Use the Web SDK to implement SMS authentication.
All Web SDK methods for SMS require you to [initiate an action first](/sdks/client/web/actions).
Check out our [guide on SMS authentication](/authentication-methods/sms-otp) for more details.
## Enroll
Start enrollment for a new SMS OTP authenticator by sending the user an SMS containing an OTP code.
This method is typically used when you've **not yet verified** the user's phone number.
```ts theme={null}
const response = await authsignal.sms.enroll({ phoneNumber: "+64270000000" });
```
If you've already verified a user's phone number independently of Authsignal, you can
alternatively use a Server SDK to [enroll the user
programmatically](/advanced-usage/programmatic-authenticator-management).
### Parameters
The user's phone number in [E.164 format](https://en.wikipedia.org/wiki/E.164) e.g. +14155552671.
### Response
The ID of the Authsignal user.
The ID of the Authsignal user authenticator.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Challenge
Start re-authentication for an existing SMS authenticator by sending the user an SMS containing an OTP code.
This method is typically used when you've **already verified** the user's phone number.
```ts theme={null}
const response = await authsignal.sms.challenge();
```
### Response
The ID of the Authsignal challenge.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Verify
Finish enrolling or re-authenticating a new or existing SMS OTP authenticator by verifying the code submitted by the user.
```ts theme={null}
const response = await authsignal.sms.verify({ code: "123456" });
```
### Parameters
The OTP code inputted by the user.
### Response
True if the verification was successful.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
Only present on first time verification e.g. enrollment.
The ID of the Authsignal user authenticator.
The date and time the authenticator was created in ISO 8601 format.
The date and time the authenticator was first verified in ISO 8601 format.
The date and time the authenticator was last verified in ISO 8601 format.
Whether the authenticator is the default authenticator for the user.
The email address of the authenticator. Only present for email authenticators e.g. email OTP and email magic link.
The phone number of the authenticator. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
The previous SMS channel used to send the OTP code. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
Only present for passkey authenticators.
The ID of the passkey credential.
The device ID of the passkey credential.
The name of the passkey credential.
The AAGUID of the passkey credential.
Whether the passkey credential is backed up.
The device type of the passkey credential.
The parsed user agent of the passkey credential.
The user agent of the passkey credential.
The browser of the passkey credential.
The name of the browser.
The version of the browser.
The major version of the browser.
The device of the passkey credential.
The model of the device.
The type of the device.
The vendor of the device.
The engine of the passkey credential.
The name of the engine.
The version of the engine.
The OS of the passkey credential.
The name of the OS.
The version of the OS.
The CPU of the passkey credential.
The architecture of the CPU.
The date and time the passkey credential was verified in ISO 8601 format.
The authenticator attachment of the passkey credential.
The AAGUID mapping of the passkey credential.
The name of the AAGUID mapping.
The SVG icon of the AAGUID mapping in light mode.
The SVG icon of the AAGUID mapping in dark mode.
A structured code present if the verification failed due to user error. Possible values are:
`CODE_INVALID_OR_EXPIRED` or `MAX_ATTEMPTS_EXCEEDED`.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
# Authenticator app (TOTP)
Source: https://docs.authsignal.com/sdks/client/web/totp
Use the Web SDK to implement authenticator app (TOTP) authentication.
All TOTP SDK methods require you to [initiate an action first](/sdks/client/web/actions).
## Enroll
Start enrollment for a new TOTP authenticator by generating a QR code to display to the user.
```ts theme={null}
const response = await authsignal.totp.enroll();
```
### Response
A TOTP URI which can be converted into a QR code and presented to the user to scan with
their authenticator app.
The raw TOTP secret which can be presented to the user as a fallback option if they wish to
enter a code manually instead of scanning a QR code.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Verify
Finish enrolling or re-authenticating a TOTP authenticator by verifying the code submitted by the user.
```ts theme={null}
const response = await authsignal.totp.verify({ code: "123456" });
```
### Parameters
The OTP code inputted by the user.
### Response
True if the verification was successful.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
Only present on first time verification e.g. enrollment.
The ID of the Authsignal user authenticator.
The date and time the authenticator was created in ISO 8601 format.
The date and time the authenticator was first verified in ISO 8601 format.
The date and time the authenticator was last verified in ISO 8601 format.
Whether the authenticator is the default authenticator for the user.
The email address of the authenticator. Only present for email authenticators e.g. email OTP and email magic link.
The phone number of the authenticator. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
The previous SMS channel used to send the OTP code. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
Only present for passkey authenticators.
The ID of the passkey credential.
The device ID of the passkey credential.
The name of the passkey credential.
The AAGUID of the passkey credential.
Whether the passkey credential is backed up.
The device type of the passkey credential.
The parsed user agent of the passkey credential.
The user agent of the passkey credential.
The browser of the passkey credential.
The name of the browser.
The version of the browser.
The major version of the browser.
The device of the passkey credential.
The model of the device.
The type of the device.
The vendor of the device.
The engine of the passkey credential.
The name of the engine.
The version of the engine.
The OS of the passkey credential.
The name of the OS.
The version of the OS.
The CPU of the passkey credential.
The architecture of the CPU.
The date and time the passkey credential was verified in ISO 8601 format.
The authenticator attachment of the passkey credential.
The AAGUID mapping of the passkey credential.
The name of the AAGUID mapping.
The SVG icon of the AAGUID mapping in light mode.
The SVG icon of the AAGUID mapping in dark mode.
A structured code present if the verification failed due to user error. Possible values are:
`CODE_INVALID_OR_EXPIRED` or `MAX_ATTEMPTS_EXCEEDED`.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
# WhatsApp OTP
Source: https://docs.authsignal.com/sdks/client/web/whatsapp
Use the Web SDK to implement WhatsApp authentication.
All Web SDK methods for WhatsApp require you to [initiate an action
first](/sdks/client/web/actions). Check out our [guide on WhatsApp
authentication](/authentication-methods/whatsapp-otp) for more details.
## Challenge
Start enrollment re-authentication for an existing WhatsApp authenticator by sending the user a WhatsApp message containing an OTP code.
```ts theme={null}
const response = await authsignal.whatsapp.challenge();
```
### Response
The ID of the Authsignal challenge.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
## Verify
Finish enrolling or re-authenticating a WhatsApp OTP authenticator by verifying the code submitted by the user.
```ts theme={null}
const response = await authsignal.whatsapp.verify({ code: "123456" });
```
### Parameters
The OTP code inputted by the user.
### Response
True if the verification was successful.
A token which can be used for [server-side
validation](/sdks/server/challenges#validate-challenge).
Only present on first time verification e.g. enrollment.
The ID of the Authsignal user authenticator.
The date and time the authenticator was created in ISO 8601 format.
The date and time the authenticator was first verified in ISO 8601 format.
The date and time the authenticator was last verified in ISO 8601 format.
Whether the authenticator is the default authenticator for the user.
The email address of the authenticator. Only present for email authenticators e.g. email OTP and email magic link.
The phone number of the authenticator. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
The previous SMS channel used to send the OTP code. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
Only present for passkey authenticators.
The ID of the passkey credential.
The device ID of the passkey credential.
The name of the passkey credential.
The AAGUID of the passkey credential.
Whether the passkey credential is backed up.
The device type of the passkey credential.
The parsed user agent of the passkey credential.
The user agent of the passkey credential.
The browser of the passkey credential.
The name of the browser.
The version of the browser.
The major version of the browser.
The device of the passkey credential.
The model of the device.
The type of the device.
The vendor of the device.
The engine of the passkey credential.
The name of the engine.
The version of the engine.
The OS of the passkey credential.
The name of the OS.
The version of the OS.
The CPU of the passkey credential.
The architecture of the CPU.
The date and time the passkey credential was verified in ISO 8601 format.
The authenticator attachment of the passkey credential.
The AAGUID mapping of the passkey credential.
The name of the AAGUID mapping.
The SVG icon of the AAGUID mapping in light mode.
The SVG icon of the AAGUID mapping in dark mode.
A structured code present if the verification failed due to user error. Possible values are:
`CODE_INVALID_OR_EXPIRED` or `MAX_ATTEMPTS_EXCEEDED`.
See the [Error handling](/sdks/client/web/error-handling) section for more details.
# Server SDKs - Actions
Source: https://docs.authsignal.com/sdks/server/actions
Use Authsignal's server-side SDK methods for actions.
## Track Action
This method lets you track authentication actions performed by users and initiate challenges via the [Authsignal pre-built UI](/implementation-options/prebuilt-ui/overview), [Web SDK](/sdks/client/web/setup) or [Mobile SDK](/sdks/client/mobile/setup).
```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") {
// Present challenge via Authsignal pre-built UI URL
const url = response.url;
} else if (response.state === "ALLOW") {
// Allow the user to sign in
} else if (response.state === "BLOCK") {
// Block the user from signing in
}
```
```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) {
// Present challenge via Authsignal pre-built UI URL
var url = response.Url;
} else if (response.State == UserActionState.ALLOW) {
// Allow the user to sign in
} else if (response.State == UserActionState.BLOCK) {
// Block the user from signing in
}
```
```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();
if (response.state == UserActionState.CHALLENGE_REQUIRED) {
// Present challenge via Authsignal pre-built UI URL
String url = response.url;
} else if (response.state == UserActionState.ALLOW) {
// Allow the user to sign in
} else if (response.state == UserActionState.BLOCK) {
// Block the user from signing in
}
```
```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"
# Present challenge via Authsignal pre-built UI URL
url = response[:url]
when "ALLOW"
# Allow the user to sign in
when "BLOCK"
# Block the user from signing in
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":
# Present challenge via Authsignal pre-built UI URL
url = response["url"]
elif response["state"] == "ALLOW":
# Allow the user to sign in
pass
elif response["state"] == "BLOCK":
# Block the user from signing in
pass
```
```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":
// Present challenge via Authsignal pre-built UI URL
$url = $response["url"]
break;
case "ALLOW":
// Allow the user to sign in
break;
case "BLOCK":
// Block the user from signing in
break;
}
```
```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":
// Present challenge via Authsignal pre-built UI URL
url := response.Url
// Use url for redirect
case "ALLOW":
// Allow the user to sign in
case "BLOCK":
// Block the user from signing in
default:
// Handle unexpected state
}
```
Learn more about how to track actions to implement [MFA](/actions-rules/actions/implementing-mfa) or [passwordless login flows](/actions-rules/actions/passwordless-login).
## Get Action
This method lets you retrieve information about an action which was previously tracked.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
};
const response = await authsignal.getAction(request);
```
```csharp C# theme={null}
var request = new GetActionRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72"
);
var response = await authsignal.GetAction(request);
```
```java Java theme={null}
GetActionRequest request = new GetActionRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
request.action = "signIn";
request.idempotencyKey = "83d07321-9bba-4f08-a871-02e2af813b72";
GetActionResponse response = authsignal.getAction(request).get();
```
```ruby Ruby theme={null}
response = Authsignal.get_action(
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotency_key: "83d07321-9bba-4f08-a871-02e2af813b72")
```
```python Python theme={null}
response = authsignal.get_action(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="signIn",
idempotency_key="83d07321-9bba-4f08-a871-02e2af813b72"
)
```
```PHP PHP theme={null}
$response = Authsignal::getAction([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "signIn",
'idempotencyKey' => "83d07321-9bba-4f08-a871-02e2af813b72"
]);
```
```go Go theme={null}
response, err := client.GetAction(
GetActionRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
},
)
```
## Query Actions
This method lets you retrieve a list of actions for a user, with optional filters for action codes and date range.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
// Optional filters
codes: "signIn,withdrawFunds", // Comma-separated list of action codes
fromDate: "2024-01-01T00:00:00Z", // ISO 8601 format
};
const actions = await authsignal.queryActions(request);
```
```csharp C# theme={null}
var request = new QueryActionsRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Codes: "signIn,withdrawFunds", // Comma-separated list of action codes
FromDate: "2024-01-01T00:00:00Z" // ISO 8601 format
);
var actions = await authsignal.QueryActions(request);
```
```java Java theme={null}
QueryActionsRequest request = new QueryActionsRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
request.codes = "signIn,withdrawFunds"; // Comma-separated list of action codes
request.fromDate = "2024-01-01T00:00:00Z"; // ISO 8601 format
QueryActionsResponse actions = authsignal.queryActions(request).get();
```
```ruby Ruby theme={null}
actions = Authsignal.query_actions(
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
codes: "signIn,withdrawFunds", # Comma-separated list of action codes
from_date: "2024-01-01T00:00:00Z" # ISO 8601 format
)
```
```python Python theme={null}
actions = authsignal.query_actions(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
codes="signIn,withdrawFunds", # Comma-separated list of action codes
from_date="2024-01-01T00:00:00Z" # ISO 8601 format
)
```
```php PHP theme={null}
$actions = Authsignal::queryActions([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'codes' => "signIn,withdrawFunds", // Comma-separated list of action codes
'fromDate' => "2024-01-01T00:00:00Z" // ISO 8601 format
]);
```
```go Go theme={null}
actions, err := client.QueryActions(
QueryActionsRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Codes: "signIn,withdrawFunds", // Comma-separated list of action codes
FromDate: "2024-01-01T00:00:00Z", // ISO 8601 format
},
)
```
## Update Action
This method lets you manually update the state of an action that was previously tracked.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
attributes: {
state: "REVIEW_REQUIRED",
},
};
const updatedAttributes = await authsignal.updateAction(request);
```
```csharp C# theme={null}
var request = new UpdateActionRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
Attributes: new ActionAttributes(
state: UserActionState.REVIEW_REQUIRED
)
);
var updatedAttributes = await authsignal.UpdateAction(request);
```
```java Java theme={null}
UpdateActionRequest request = new UpdateActionRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
request.action = "signIn";
request.idempotencyKey = "83d07321-9bba-4f08-a871-02e2af813b72";
request.attributes = new ActionAttributes();
request.attributes.state = UserActionState.REVIEW_REQUIRED;
ActionAttributes updatedAttributes = authsignal.updateAction(request).get();
```
```ruby Ruby theme={null}
updatedAttributes = Authsignal.update_action(
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotency_key: "83d07321-9bba-4f08-a871-02e2af813b72",
attributes: {
state: "REVIEW_REQUIRED"
})
```
```python Python theme={null}
updatedAttributes = authsignal.update_action(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="signIn",
idempotency_key="83d07321-9bba-4f08-a871-02e2af813b72",
attributes={
"state": "REVIEW_REQUIRED"
}
)
```
```PHP PHP theme={null}
$attributes = array(
"state" => "REVIEW_REQUIRED",
);
$updatedAttributes = Authsignal::updateAction(
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
attributes: $attributes);
```
```go Go theme={null}
updatedAttributes, err := client.UpdateAction(
UpdateActionRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
Attributes: &ActionAttributes{
State: "REVIEW_REQUIRED",
},
},
)
```
# Server SDKs - Authenticators
Source: https://docs.authsignal.com/sdks/server/authenticators
Use Authsignal's server-side SDK methods for authenticators.
## Get Authenticators
This method lets you retrieve a list of the authenticators that a user currently has enrolled.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
};
const authenticators = await authsignal.getAuthenticators(request);
```
```csharp C# theme={null}
var request = new GetAuthenticatorsRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
);
UserAuthenticator[] authenticators = await authsignal.GetAuthenticators(request);
```
```java Java theme={null}
GetAuthenticatorsRequest request = new GetAuthenticatorsRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
UserAuthenticator[] authenticators = authsignal.getAuthenticators(request).get();
```
```ruby Ruby theme={null}
response = Authsignal.get_authenticators(user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833")
```
```python Python theme={null}
authenticators = authsignal.get_authenticators(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
)
```
```PHP PHP theme={null}
$authenticators = Authsignal::getAuthenticators([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
]);
```
```go Go theme={null}
authenticators, err := client.GetAuthenticators(
GetAuthenticatorsRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
},
)
```
## Enroll Verified Authenticator
This method lets you enroll an email or SMS-based authenticator for a user whose email address or phone number has already been verified via an external platform.
```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 should not be used if you haven't yet verified the user's email or phone number. It
does not send out an email / SMS to initiate a verification process - if you need to verify an
email address or phone number, you should use the [Web SDK](/sdks/client/web/setup), [Mobile
SDK](/sdks/client/mobile/setup), or the [pre-built
UI](/implementation-options/prebuilt-ui/overview).
## Delete Authenticator
This method lets you remove an authenticator that a user has previously enrolled.
```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",
},
)
```
# Server SDKs - Challenges
Source: https://docs.authsignal.com/sdks/server/challenges
Use Authsignal's server-side SDK methods for challenges.
## Initiate Challenge
This method lets you initiate an SMS or email OTP challenge for a given phone number or email address.
```ts Node.js theme={null}
const request = {
verificationMethod: "SMS",
action: "signInWithSms",
phoneNumber: "+64270000000",
};
const response = await authsignal.challenge(request);
const challengeId = response.challengeId;
```
```csharp C# theme={null}
var request = new ChallengeRequest(
VerificationMethod: VerificationMethod.SMS,
Action: "signInWithSms",
PhoneNumber: "+64270000000"
);
var response = await authsignal.challenge(request);
var challengeId = response.ChallengeId;
```
```java Java theme={null}
ChallengeRequest request = new ChallengeRequest();
request.verificationMethod = VerificationMethodType.SMS;
request.action = "signInWithSms";
request.phoneNumber = "+64270000000";
ChallengeResponse response = authsignal.challenge(request).get();
String challengeId = response.challengeId;
```
```ruby Ruby theme={null}
response = Authsignal.challenge(
verification_method: "SMS",
action: "signInWithSms",
phone_number: "+64270000000",
)
challenge_id = response[:challenge_id]
```
```go Go theme={null}
response, err := client.Challenge(
ChallengeRequest{
VerificationMethod: "SMS",
Action: "signInWithSms",
PhoneNumber: "+64270000000",
},
)
challengeId := response.challengeId
```
## Verify Challenge
This method lets you verify an SMS or email OTP challenge by determining if the OTP code submitted by the user is valid.
```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
```
## Claim Challenge
This method lets you claim a challenge on behalf of a user by associating it with an ID for the corresponding user record in your system.
```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",
},
)
```
## Get Challenge
This method can be used to retrieve information about a currently active challenge.
```ts Node.js theme={null}
const request = {
challengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d",
};
const response = await authsignal.getChallenge(request);
```
```csharp C# theme={null}
var request = new GetChallengeRequest(
ChallengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d"
);
var response = await authsignal.GetChallenge(request);
```
```java Java theme={null}
GetChallengeRequest request = new GetChallengeRequest();
request.challengeId = "3a991a14-690c-492b-a5e5-02b9056a4b7d";
GetChallengeResponse response = authsignal.getChallenge(request).get();
```
```ruby Ruby theme={null}
response = Authsignal.get_challenge(
challenge_id: "3a991a14-690c-492b-a5e5-02b9056a4b7d",
)
```
```go Go theme={null}
response, err := client.GetChallenge(
GetChallengeRequest{
ChallengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d",
},
)
```
## Validate Challenge
This method lets you validate server-side whether a user has successfully completed an authentication challenge via the [Authsignal pre-built UI](/implementation-options/prebuilt-ui/overview#3-backend-validate-challenge) or an [Authsignal Client SDK](/implementation-options/web-sdk/overview#2-backend-validate-challenge).
After obtaining a short-lived `token` from the pre-built UI or a Client SDK, pass this token to your server to determine the result.
```ts Node.js theme={null}
const request = {
action: "signIn",
token: "eyJhbGciOiJ...",
};
const response = await authsignal.validateChallenge(request);
if (response.state === "CHALLENGE_SUCCEEDED") {
// The user completed the challenge successfully
// Proceed with authenticated action
} else {
// The user did not complete the challenge successfully
}
```
```csharp C# theme={null}
var request = new ValidateChallengeRequest(
Action: "signIn,
Token: "eyJhbGciOiJ..."
);
var response = await authsignal.ValidateChallenge(request);
if (response.State == UserActionState.CHALLENGE_SUCCEEDED) {
// The user completed the challenge successfully
// Proceed with authenticated action
} else {
// The user did not complete the challenge successfully
}
```
```java Java theme={null}
ValidateChallengeRequest request = new ValidateChallengeRequest();
request.action = "signIn";
request.token = "eyJhbGciOiJ...";
ValidateChallengeResponse response = authsignal.validateChallenge(request).get();
if (response.state == UserActionState.CHALLENGE_SUCCEEDED) {
// The user completed the challenge successfully
// Proceed with authenticated action
} else {
// The user did not complete the challenge successfully
}
```
```ruby Ruby theme={null}
response = Authsignal.validate_challenge(
action: "signIn",
token: "eyJhbGciOiJ..."
)
if response[:state] == "CHALLENGE_SUCCEEDED"
# The user completed the challenge successfully
# Proceed with authenticated action
else
# The user did not complete the challenge successfully
end
```
```python Python theme={null}
response = authsignal.validate_challenge(
attributes={
"action": "signIn",
"token": "eyJhbGciOiJ..."
}
)
if response["state"] == "CHALLENGE_SUCCEEDED":
# The user completed the challenge successfully
# Proceed with authenticated action
else:
# The user did not complete the challenge successfully
```
```php PHP theme={null}
$response = Authsignal::validateChallenge([
'action' => "signIn",
'token' => "eyJhbGciOiJ...",
]);
if ($response["state"] === "CHALLENGE_SUCCEEDED") {
# The user completed the challenge successfully
# Proceed with authenticated action
} else {
# The user did not complete the challenge successfully
}
```
```go Go theme={null}
response, err := client.ValidateChallenge(
ValidateChallengeRequest{
Action: "signIn",
Token: "eyJhbGciOiJ...",
},
)
if response.State == "CHALLENGE_SUCCEEDED" {
// The user completed the challenge successfully
// Proceed with authenticated action
} else {
// The user did not complete the challenge successfully
}
```
# Server SDKs - Error handling
Source: https://docs.authsignal.com/sdks/server/error-handling
Learn how to handle errors when using Authsignal's server SDKs.
Authsignal Server SDKs can return errors for a variety of reasons, such as invalid requests or network interruptions, which should be handled accordingly.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
};
try {
const response = await authsignal.getUser(request);
} catch (e) {
if (e instanceof AuthsignalError) {
const statusCode = e.statusCode;
const errorCode = e.errorCode;
const errorDescription = e.errorDescription;
}
}
```
```csharp C# theme={null}
var request = new GetUserRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
);
try
{
var response = await authsignal.GetUser(request);
}
catch (AuthsignalException e)
{
var statusCode = e.StatusCode;
var errorCode = e.ErrorCode;
var errorDescription = e.ErrorDescription;
}
```
```java Java theme={null}
UserRequest request = new GetUserRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
try {
GetUserResponse response = authsignal.getUser(request).get();
} catch (ExecutionException e) {
if (e.getCause() instanceof AuthsignalException) {
AuthsignalException exception = (AuthsignalException) e.getCause();
int statusCode = exception.getStatusCode();
String errorCode = exception.getErrorCode();
String errorDescription = exception.getErrorDescription();
}
}
```
```ruby Ruby theme={null}
begin
response = Authsignal.get_user(user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833")
rescue Authsignal::ApiError => e
status_code = e.status_code
error_code = e.error_code
error_description = e.error_description
end
```
```python Python theme={null}
try:
response = authsignal.get_user(user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833")
except ApiException as e:
status_code = e.status_code
error_code = e.error_code
error_description = e.error_description
```
```PHP PHP theme={null}
try {
$response = Authsignal::getUser([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
]);
} catch(AuthsignalError $e) {
$statusCode = $e["statusCode"];
$errorCode = $e["errorCode"];
$errorDescription = $e["errorDescription"];
}
```
```go Go theme={null}
request := client.GetUserRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
}
response, err := authsignal.GetUser(request)
if err != nil {
if apiErr, ok := err.(*client.AuthsignalAPIError); ok {
statusCode := apiErr.StatusCode
errorCode := apiErr.ErrorCode
errorDescription := apiErr.ErrorDescription
}
return
}
```
## Error fields
The HTTP status code returned by the Authsignal Server API.
A code which identifies a [specific error state](#error-codes).
A free-text description of the error. This value should only be used for logging or
troubleshooting purposes.
## Error codes
Indicates that the request cannot be handled because the tenant is in an invalid configuration.
Indicates that the credential (e.g. passkey) referenced by the request is invalid for the given
user and cannot be authenticated.
Indicates that the request failed due to an invalid parameter or input.
Indicates that the server cannot respond as requests have been rate-limited due to exceeding a
threshold.
Indicates that the request is unauthorized due to invalid tenant credentials - for example if the
Server API secret key is invalid or if the region doesn't match.
Indicates that the request failed due to an error returned by an invoked webhook. The error
description will include more information about the webhook error.
# Server SDKs
Source: https://docs.authsignal.com/sdks/server/overview
Use Authsignal's server-side SDKs for Node.js, C#, Java, Ruby, Python, PHP, and Go.
Server SDKs make it easier to interact with our [REST API](/api-reference/server-api/overview) from your server-side code.
## Installation
```bash npm theme={null}
npm install @authsignal/node
```
```bash yarn theme={null}
yarn add @authsignal/node
```
```bash pnpm theme={null}
pnpm add @authsignal/node
```
```bash bun theme={null}
bun add @authsignal/node
```
```bash theme={null}
dotnet add package Authsignal.Server.Client
```
**Gradle**
Add this dependency to your project's build file:
```groovy theme={null}
implementation 'com.authsignal:authsignal-java:2.3.0'
```
**Maven**
Add this dependency to your project's POM:
```xml theme={null}
com.authsignalauthsignal-java2.3.0
```
```bash theme={null}
gem 'authsignal-ruby'
```
```bash theme={null}
pip3 install authsignal
```
```bash theme={null}
composer require authsignal/authsignal-php
```
```bash theme={null}
go get github.com/authsignal/authsignalgo/v2
```
## Initialization
Initialize the Authsignal client by providing your tenant's Server API secret key and the API URL 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",
)
```
## Regions
The API URLs for each region are defined below.
| 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` |
## Usage
## Repositories
# Server SDKs - Sessions
Source: https://docs.authsignal.com/sdks/server/sessions
Use Authsignal's server-side SDK methods for sessions.
### Create Session
This method can be used to convert a challenge into an authenticated session. If the challenge associated with the client token was successful, it will return 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
```
### Validate Session
This method can be used to 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.
```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
```
### Refresh Session
This method can be used to refresh a session for a given refresh token. This will return a new access token and refresh token, revoking any previously issued tokens.
```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
```
### Revoke Session
This method can be used to revoke a session for a given access token.
```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...",
},
)
```
### Revoke User Sessions
This method can be used to revoke all active sessions for a given 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",
},
)
```
# Server SDKs - Users
Source: https://docs.authsignal.com/sdks/server/users
Use Authsignal's server-side SDK methods for users.
## Get User
This method lets you retrieve information about a user.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
};
const response = await authsignal.getUser(request);
const isEnrolled = response.isEnrolled;
```
```csharp C# theme={null}
var request = new GetUserRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
);
var response = await authsignal.GetUser(request);
var isEnrolled = response.IsEnrolled;
```
```java Java theme={null}
UserRequest request = new GetUserRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
GetUserResponse response = authsignal.getUser(request).get();
boolean isEnrolled = response.isEnrolled;
```
```ruby Ruby theme={null}
response = Authsignal.get_user(user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833")
is_enrolled = response[:is_enrolled]
```
```python Python theme={null}
response = authsignal.get_user(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
)
is_enrolled = response["is_enrolled"]
```
```PHP PHP theme={null}
$response = Authsignal::getUser([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
]);
$isEnrolled = $response["isEnrolled"];
```
```go Go theme={null}
response, err := client.GetUser(
GetUserRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
},
)
isEnrolled := response.IsEnrolled != nil && *response.IsEnrolled
```
## Update User
This method lets you update information about a user.
Any fields which are omitted in the request will be left unchanged.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
attributes: {
email: "jane.smith@authsignal.com",
phoneNumber: "+64270000000",
displayName: "Jane Smith",
},
};
const updatedAttributes = await authsignal.updateUser(request);
```
```csharp C# theme={null}
var request = new UserRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Attributes: new UserAttributes(
Email: "jane.smith@authsignal.com",
PhoneNumber: "+64270000000",
DisplayName: "Jane Smith"
)
);
var updatedAttributes = await authsignal.UpdateUser(request);
```
```java Java theme={null}
UpdateUserRequest request = new UpdateUserRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
request.attributes = new UserAttributes();
request.attributes.email = "jane.smith@authsignal.com";
request.attributes.phoneNumber = "+64270000000";
request.attributes.displayName = "Jane Smith";
UserAttributes updatedAttributes = authsignal.updateUser(request).get();
```
```ruby Ruby theme={null}
response = Authsignal.update_user(
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
attributes: {
email: "jane.smith@authsignal.com",
phone_number: "+64270000000",
display_name: "Jane Smith"
})
```
```python Python theme={null}
response = authsignal.update_user(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
attributes={
"email": "jane.smith@authsignal.com",
"phoneNumber": "+64270000000",
"displayName": "Jane Smith"
}
)
```
```PHP PHP theme={null}
$response = Authsignal::updateUser([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'attributes' => [
"email" => "jane.smith@authsignal.com",
"phoneNumber" => "+64270000000",
"displayName" => "Jane Smith",
]
]);
```
```go Go theme={null}
updatedAttributes, err := client.UpdateUser(
UpdateUserRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Attributes: &UserAttributes{
Email: "jane.smith@authsignal.com",
PhoneNumber: "+64270000000",
DisplayName: "Jane Smith",
},
},
)
```
## Delete User
This method lets you delete a user and all their associated authenticator data.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
};
await authsignal.deleteUser(request);
```
```csharp C# theme={null}
var request = new DeleteUserRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
);
await authsignal.DeleteUser(request);
```
```java Java theme={null}
DeleteUserRequest request = new DeleteUserRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
authsignal.deleteUser(request).get();
```
```ruby Ruby theme={null}
Authsignal.delete_user(user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833")
```
```python Python theme={null}
authsignal_client.delete_user(user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833")
```
```PHP PHP theme={null}
Authsignal::deleteUser(userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833");
```
```go Go theme={null}
err := authsignal.DeleteUser(
client.UserRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
},
)
```
## Query Users
This method lets you query users based on a set of filters.
```ts Node.js theme={null}
const request = {
email: "jane.smith@authsignal.com",
};
const response = await authsignal.queryUsers(request);
const users = response.users;
```
```csharp C# theme={null}
var request = new QueryUsersRequest(
Email: "jane.smith@authsignal.com"
);
var response = await authsignal.QueryUsers(request);
var users = response.Users;
```
```java Java theme={null}
QueryUsersRequest request = new QueryUsersRequest();
request.email = "jane.smith@authsignal.com";
QueryUsersResponse response = authsignal.queryUsers(request).get();
QueryUsersResponseUser[] users = response.users;
```
```ruby Ruby theme={null}
response = Authsignal.query_users(email: "jane.smith@authsignal.com")
users = response[:users]
```
```python Python theme={null}
response = authsignal.query_users(
email="jane.smith@authsignal.com"
)
users = response["users"]
```
```php PHP theme={null}
$response = Authsignal::queryUsers([
'email' => "jane.smith@authsignal.com"
]);
$users = $response["users"];
```
```go Go theme={null}
response, err := client.QueryUsers(
QueryUsersRequest{
Email: "jane.smith@authsignal.com",
},
)
users := response.Users
```
# Server SDKs - Webhooks
Source: https://docs.authsignal.com/sdks/server/webhooks
Use Authsignal's server SDKs to verify webhook requests.
## Verifying incoming requests
The Authsignal Server SDK can be used to parse and verify incoming [webhook events](/advanced-usage/webhooks/introduction).
```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)
```
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.
To prevent against replay attacks, the SDK will error if more than **5 minutes** has elapsed since the time that the event was sent.
This threshold can be customized by passing a `tolerance` value - the time (in minutes) after which requests should be rejected.
```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)
```
# User actions
Source: https://docs.authsignal.com/users/user-actions
Learn how to track, query, and manage user actions in Authsignal
## What are user actions?
Actions represent security-relevant events in your application and serve as the foundation for implementing:
* **Multi-factor authentication (MFA)** - Challenge users after primary authentication
* **Step-up authentication** - Require additional verification for sensitive operations
* **Adaptive authentication** - Apply rules based on risk factors and context
* **Passwordless authentication** - Use Authsignal as the primary authentication method
Every action has an associated **state** that determines how your application should respond to the user's request.
## Action operations
### Track action
The primary method for recording user activities and initiating authentication challenges. This is the core operation you'll use throughout your application.
```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",
}
}
```
When you track an action, you provide:
* **User ID** - Unique identifier for the user
* **Action code** - What the user is doing (e.g., "signIn", "withdrawFunds")
* **Attributes** - Contextual information for risk assessment
### Get action
Retrieve detailed information about a previously tracked action using its unique identifiers.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
};
const response = await authsignal.getAction(request);
```
```csharp C# theme={null}
var request = new GetActionRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72"
);
var response = await authsignal.GetAction(request);
```
```java Java theme={null}
GetActionRequest request = new GetActionRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
request.action = "signIn";
request.idempotencyKey = "83d07321-9bba-4f08-a871-02e2af813b72";
GetActionResponse response = authsignal.getAction(request).get();
```
```ruby Ruby theme={null}
response = Authsignal.get_action(
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotency_key: "83d07321-9bba-4f08-a871-02e2af813b72")
```
```python Python theme={null}
response = authsignal.get_action(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="signIn",
idempotency_key="83d07321-9bba-4f08-a871-02e2af813b72"
)
```
```PHP PHP theme={null}
$response = Authsignal::getAction([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "signIn",
'idempotencyKey' => "83d07321-9bba-4f08-a871-02e2af813b72"
]);
```
```go Go theme={null}
response, err := client.GetAction(
GetActionRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
},
)
```
### Query actions
Retrieve a list of actions for a specific user to view their authentication history.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
// Optional filters
codes: "signIn,withdrawFunds", // Comma-separated list of action codes
fromDate: "2024-01-01T00:00:00Z", // ISO 8601 format
};
const actions = await authsignal.queryActions(request);
```
```csharp C# theme={null}
var request = new QueryActionsRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Codes: "signIn,withdrawFunds", // Comma-separated list of action codes
FromDate: "2024-01-01T00:00:00Z" // ISO 8601 format
);
var actions = await authsignal.QueryActions(request);
```
```java Java theme={null}
QueryActionsRequest request = new QueryActionsRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
request.codes = "signIn,withdrawFunds"; // Comma-separated list of action codes
request.fromDate = "2024-01-01T00:00:00Z"; // ISO 8601 format
QueryActionsResponse actions = authsignal.queryActions(request).get();
```
```ruby Ruby theme={null}
actions = Authsignal.query_actions(
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
codes: "signIn,withdrawFunds", # Comma-separated list of action codes
from_date: "2024-01-01T00:00:00Z" # ISO 8601 format
)
```
```python Python theme={null}
actions = authsignal.query_actions(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
codes="signIn,withdrawFunds", # Comma-separated list of action codes
from_date="2024-01-01T00:00:00Z" # ISO 8601 format
)
```
```php PHP theme={null}
$actions = Authsignal::queryActions([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'codes' => "signIn,withdrawFunds", // Comma-separated list of action codes
'fromDate' => "2024-01-01T00:00:00Z" // ISO 8601 format
]);
```
```go Go theme={null}
actions, err := client.QueryActions(
QueryActionsRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Codes: "signIn,withdrawFunds", // Comma-separated list of action codes
FromDate: "2024-01-01T00:00:00Z", // ISO 8601 format
},
)
```
### Update action
Manually modify the state of a previously tracked action. This is useful for administrative actions or custom workflows.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
attributes: {
state: "REVIEW_REQUIRED",
},
};
const updatedAttributes = await authsignal.updateAction(request);
```
```csharp C# theme={null}
var request = new UpdateActionRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
Attributes: new ActionAttributes(
state: UserActionState.REVIEW_REQUIRED
)
);
var updatedAttributes = await authsignal.UpdateAction(request);
```
```java Java theme={null}
UpdateActionRequest request = new UpdateActionRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
request.action = "signIn";
request.idempotencyKey = "83d07321-9bba-4f08-a871-02e2af813b72";
request.attributes = new ActionAttributes();
request.attributes.state = UserActionState.REVIEW_REQUIRED;
ActionAttributes updatedAttributes = authsignal.updateAction(request).get();
```
```ruby Ruby theme={null}
updatedAttributes = Authsignal.update_action(
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotency_key: "83d07321-9bba-4f08-a871-02e2af813b72",
attributes: {
state: "REVIEW_REQUIRED"
})
```
```python Python theme={null}
updatedAttributes = authsignal.update_action(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="signIn",
idempotency_key="83d07321-9bba-4f08-a871-02e2af813b72",
attributes={
"state": "REVIEW_REQUIRED"
}
)
```
```PHP PHP theme={null}
$attributes = array(
"state" => "REVIEW_REQUIRED",
);
$updatedAttributes = Authsignal::updateAction(
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
attributes: $attributes);
```
```go Go theme={null}
updatedAttributes, err := client.UpdateAction(
UpdateActionRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
Attributes: &ActionAttributes{
State: "REVIEW_REQUIRED",
},
},
)
```
## Action states
Every action results in one of these states that determine how your application should respond:
| State | Description | Recommended Action |
| -------------------- | -------------------------------------- | ------------------------------------ |
| `ALLOW` | User is trusted, no challenge required | Proceed with the requested operation |
| `CHALLENGE_REQUIRED` | User must complete authentication | Present authentication challenge |
| `REVIEW` | Action requires manual review | Queue for administrative review |
| `BLOCK` | Action is blocked for security reasons | Deny the requested operation |
### State transitions
Actions can transition between states based on user interactions and administrative actions:
```mermaid theme={null}
flowchart TD
A[Track action] --> B{Rules engine}
B --> C[ALLOW]
B --> D[CHALLENGE_REQUIRED]
B --> E[REVIEW]
B --> F[BLOCK]
D --> G[User completes challenge]
G --> H[CHALLENGE_SUCCEEDED]
G --> I[CHALLENGE_FAILED]
E --> J[Admin review]
J --> K[REVIEW_SUCCEEDED]
J --> L[REVIEW_FAILED]
```
## Action attributes
When tracking actions, you can provide contextual information that helps with risk assessment and rule evaluation:
### Standard attributes
| Attribute | Type | Description |
| -------------------- | ------- | --------------------------------------------------------- |
| `email` | string | User's email address |
| `phoneNumber` | string | User's phone number in E.164 format |
| `deviceId` | string | Unique device identifier (from Authsignal Web SDK cookie) |
| `userAgent` | string | Browser user agent string |
| `ipAddress` | string | User's IP address |
| `redirectUrl` | string | URL for redirect after pre-built UI completion |
| `redirectToSettings` | boolean | Show settings page after challenge completion |
### Custom attributes
Use the `custom` field to pass business-specific data for use in rules:
```ts theme={null}
const response = await authsignal.track({
userId: "user123",
action: "transferFunds",
attributes: {
deviceId: "device-abc",
ipAddress: "203.0.113.1",
custom: {
transferAmount: 5000,
recipientCountry: "US",
accountTier: "premium"
}
}
});
```
This custom data can then be used in [Authsignal rules](/actions-rules/rules/custom-data-points) to make intelligent authentication decisions.
## Action lifecycle
Understanding the complete action lifecycle helps you implement robust authentication flows:
### 1. Action creation
Actions are created when you call `track()`. Each action gets:
* Unique identifiers (`userId`, `action`, `idempotencyKey`)
* Initial state based on rules evaluation
* Contextual metadata (IP, user agent, custom data)
### 2. Rule evaluation
When an action is tracked, Authsignal's rules engine evaluates:
* User enrollment status
* Configured rules and conditions
* Risk factors and context
* Custom business logic
### 3. Challenge flow (if required)
For `CHALLENGE_REQUIRED` actions:
* Generate short-lived token or URL
* User completes authentication via pre-built UI or Client SDKs
* Action state updates to `CHALLENGE_SUCCEEDED` or `CHALLENGE_FAILED`
### 4. Validation
Your application validates the challenge result and proceeds based on the final state.
## Integration patterns
### Just-in-time authentication
Track actions at the point where authentication is needed:
```ts theme={null}
async function handleSensitiveOperation(userId: string) {
// Track the action first
const result = await authsignal.track({
userId,
action: "accessSensitiveData",
attributes: {
ipAddress: request.ip,
userAgent: request.headers.userAgent
}
});
// Handle based on result
switch (result.state) {
case "ALLOW":
return performSensitiveOperation();
case "CHALLENGE_REQUIRED":
return { challengeUrl: result.url };
case "BLOCK":
throw new Error("Access denied");
case "REVIEW":
return { status: "pending_review" };
}
}
```
### Pre-authentication
Track actions before performing operations to determine if additional security is needed:
```ts theme={null}
async function initiatePayment(userId: string, amount: number) {
// Check if payment requires additional authentication
const result = await authsignal.track({
userId,
action: "makePayment",
attributes: {
custom: { paymentAmount: amount }
}
});
if (result.state === "CHALLENGE_REQUIRED") {
return { requiresAuth: true, challengeUrl: result.url };
}
// Proceed with payment
return processPayment(amount);
}
```
### Idempotency
Actions are automatically deduplicated using idempotency keys. Multiple calls with the same user ID, action, and idempotency key will return the same result.
## Next steps
* [Set up your first action](/actions-rules/actions/implementing-mfa)
* [Create rules for adaptive authentication](/actions-rules/rules/adaptive-mfa)
* [Implement custom data points](/actions-rules/rules/custom-data-points)
# User management
Source: https://docs.authsignal.com/users/user-management
Learn how to manage user accounts, attributes, and data in Authsignal using Server SDK
Authsignal provides comprehensive user management capabilities through Server SDKs and APIs, allowing you to create, retrieve, update, and delete user accounts and their associated data.
## User lifecycle
### How users are created
In Authsignal, users are **automatically created** when you first track an action for them. There's no separate user creation endpoint - simply start tracking actions with a unique `userId` and Authsignal will create the user record.
```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
```
When you track an action for a new `userId`, Authsignal will:
* Create a new user record automatically
* Associate any provided attributes (email, phone number, etc.) with the user
* Return the action response based on your configured rules
## User operations
### Get user information
Retrieve detailed information about a user, including their enrollment status and attributes.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
};
const response = await authsignal.getUser(request);
const isEnrolled = response.isEnrolled;
```
```csharp C# theme={null}
var request = new GetUserRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
);
var response = await authsignal.GetUser(request);
var isEnrolled = response.IsEnrolled;
```
```java Java theme={null}
UserRequest request = new GetUserRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
GetUserResponse response = authsignal.getUser(request).get();
boolean isEnrolled = response.isEnrolled;
```
```ruby Ruby theme={null}
response = Authsignal.get_user(user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833")
is_enrolled = response[:is_enrolled]
```
```python Python theme={null}
response = authsignal.get_user(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
)
is_enrolled = response["is_enrolled"]
```
```PHP PHP theme={null}
$response = Authsignal::getUser([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
]);
$isEnrolled = $response["isEnrolled"];
```
```go Go theme={null}
response, err := client.GetUser(
GetUserRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
},
)
isEnrolled := response.IsEnrolled != nil && *response.IsEnrolled
```
The response includes:
* **`isEnrolled`** - Whether the user has enrolled at least one authentication method
* **User attributes** - Email, phone number, username, display name
* **Allowed verification methods** - Which authentication methods the user can enroll
* **Enrolled verification methods** - Which authentication methods the user has enrolled
* **Default verification method** - The authentication method that will be used for the user's next action
* **Custom** - Custom data points to use with rules
### Update user attributes
Update user information and attributes. Any fields not provided in the request will remain unchanged.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
attributes: {
email: "jane.smith@authsignal.com",
phoneNumber: "+64270000000",
displayName: "Jane Smith",
},
};
const updatedAttributes = await authsignal.updateUser(request);
```
```csharp C# theme={null}
var request = new UserRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Attributes: new UserAttributes(
Email: "jane.smith@authsignal.com",
PhoneNumber: "+64270000000",
DisplayName: "Jane Smith"
)
);
var updatedAttributes = await authsignal.UpdateUser(request);
```
```java Java theme={null}
UpdateUserRequest request = new UpdateUserRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
request.attributes = new UserAttributes();
request.attributes.email = "jane.smith@authsignal.com";
request.attributes.phoneNumber = "+64270000000";
request.attributes.displayName = "Jane Smith";
UserAttributes updatedAttributes = authsignal.updateUser(request).get();
```
```ruby Ruby theme={null}
response = Authsignal.update_user(
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
attributes: {
email: "jane.smith@authsignal.com",
phone_number: "+64270000000",
display_name: "Jane Smith"
})
```
```python Python theme={null}
response = authsignal.update_user(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
attributes={
"email": "jane.smith@authsignal.com",
"phoneNumber": "+64270000000",
"displayName": "Jane Smith"
}
)
```
```PHP PHP theme={null}
$response = Authsignal::updateUser([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'attributes' => [
"email" => "jane.smith@authsignal.com",
"phoneNumber" => "+64270000000",
"displayName" => "Jane Smith",
]
]);
```
```go Go theme={null}
updatedAttributes, err := client.UpdateUser(
UpdateUserRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Attributes: &UserAttributes{
Email: "jane.smith@authsignal.com",
PhoneNumber: "+64270000000",
DisplayName: "Jane Smith",
},
},
)
```
#### Available user attributes
| Attribute | Type | Description |
| ------------- | ------ | ------------------------------------------------------- |
| `email` | string | User's email address |
| `phoneNumber` | string | User's phone number in E.164 format (e.g., +1234567890) |
| `username` | string | Primary identifier for passkeys (usually email address) |
| `displayName` | string | Display name for passkeys (usually full name) |
| `custom` | object | Custom data points to use with rules |
**Custom attributes for rules**
The `custom` field allows you to store additional user data that can be used in [Authsignal rules](/actions-rules/rules/custom-data-points). For example, you might store user tier, account type, or other business-specific attributes.
### Delete user
Permanently delete a user and all their associated data, including authenticators and action history.
```ts Node.js theme={null}
const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
};
await authsignal.deleteUser(request);
```
```csharp C# theme={null}
var request = new DeleteUserRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833"
);
await authsignal.DeleteUser(request);
```
```java Java theme={null}
DeleteUserRequest request = new DeleteUserRequest();
request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
authsignal.deleteUser(request).get();
```
```ruby Ruby theme={null}
Authsignal.delete_user(user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833")
```
```python Python theme={null}
authsignal_client.delete_user(user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833")
```
```PHP PHP theme={null}
Authsignal::deleteUser(userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833");
```
```go Go theme={null}
err := authsignal.DeleteUser(
client.UserRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
},
)
```
User deletion is irreversible and will remove:
* The user record
* All enrolled authenticators
* All action history
* All associated session data
Consider the implications carefully before implementing user deletion in production.
## User attributes and data
### Contact information
Authsignal stores and manages contact information for authentication purposes:
* **Email address** - Used for email OTP and magic link authentication
* **Phone number** - Used for SMS/WhatsApp OTP authentication
* Must be in E.164 format (e.g., `+1234567890`)
### Passkey identifiers
For passkey authentication, Authsignal uses specific identifiers:
* **Username** - Primary identifier that uniquely identifies the user.
* **Display name** - Optional secondary identifier displayed to users to help them recognize the passkey (e.g., the user's full name).
### Custom data for rules
Store additional user attributes in the `custom` field to use with [Authsignal rules](/actions-rules/rules/custom-data-points):
```json theme={null}
{
"custom": {
"userTier": "premium",
"accountType": "business",
"riskScore": 25
}
}
```