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.
// Track an action on your backend
const result = await authsignal.track({
  userId: "0272c312-e181-4cad-a494-43647b503a0a",
  action: "withdraw-funds",
  attributes: {
    deviceId: "deviceId",
    ipAddress: "ipAddress",
  },
});

// 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"
  };
}
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.
const request = {
  userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
  action: "signIn",
  idempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
};

const response = await authsignal.getAction(request);

Query actions

Retrieve a list of actions for a specific user to view their authentication history.
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);

Update action

Manually modify the state of a previously tracked action. This is useful for administrative actions or custom workflows.
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);

Action states

Every action results in one of these states that determine how your application should respond:
StateDescriptionRecommended Action
ALLOWUser is trusted, no challenge requiredProceed with the requested operation
CHALLENGE_REQUIREDUser must complete authenticationPresent authentication challenge
REVIEWAction requires manual reviewQueue for administrative review
BLOCKAction is blocked for security reasonsDeny the requested operation

State transitions

Actions can transition between states based on user interactions and administrative actions:

Action attributes

When tracking actions, you can provide contextual information that helps with risk assessment and rule evaluation:

Standard attributes

AttributeTypeDescription
emailstringUser’s email address
phoneNumberstringUser’s phone number in E.164 format
deviceIdstringUnique device identifier (from Authsignal Web SDK cookie)
userAgentstringBrowser user agent string
ipAddressstringUser’s IP address
redirectUrlstringURL for redirect after pre-built UI completion
redirectToSettingsbooleanShow settings page after challenge completion

Custom attributes

Use the custom field to pass business-specific data for use in rules:
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 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:
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:
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