ZitadelDocs

Create a new authentication attempt

Starts a new authentication attempt. This is the entry point for the auth_attempts state machine. An attempt is an ephemeral (15-minute TTL) state machine that drives a single authentication round. It accepts factor challenges, verifies proofs, and completes into a session or handoff token. Accepts a project_id and challenge_nonce (from POST /bootstrap/challenge). For step-up re-auth, also include session_id to add factors to an existing session.

POST
/auth_attempts

Starts a new authentication attempt. This is the entry point for the auth_attempts state machine.

An attempt is an ephemeral (15-minute TTL) state machine that drives a single authentication round. It accepts factor challenges, verifies proofs, and completes into a session or handoff token.

Accepts a project_id and challenge_nonce (from POST /bootstrap/challenge). For step-up re-auth, also include session_id to add factors to an existing session.

Authorization

oauth2 auth_attempt.write
headerAuthorizationBearer <token>

Scope: auth_attempt.write

Request Body

application/json
  1. body

Request to create a new authentication attempt

project_id*project-id
Match^[a-zA-Z0-9_-]+$
challenge_nonce?challenge-nonce

Origin-bound, single-use nonce obtained from POST /bootstrap/challenge. Binds the request to the browser that initiated the flow, preventing cross-origin replay.

session_id?|

Optional. If provided, this attempt adds factors to an existing session (step-up auth). If omitted, a new session is created implicitly on successful handoff.

Response Body

Authentication attempt created successfully

application/json
  1. response

The current state of an authentication attempt.

An attempt is an ephemeral state machine (15-minute TTL) that drives a single authentication round. It accepts factor challenges, verifies proofs, and completes into a handoff token. Clients drive the attempt by:

  1. Issuing challenges: POST /auth_attempts/{attempt_id}/challenges
  2. Verifying proofs: POST /auth_attempts/{attempt_id}/challenges/{challenge_id}/verify
  3. Completing: POST /auth_attempts/{attempt_id}/handoff
attempt_id*attempt-id

Unique identifier for this authentication attempt (dialect-minted prefixed opaque string).

Match^att_[a-zA-Z0-9_-]+$
project_id*project-id
Match^[a-zA-Z0-9_-]+$
state*string

Overall state of the authentication attempt:

  • in_progress: factors are still being collected; continue issuing challenges.
  • completed: all required factors have been verified; the attempt is ready for handoff.
  • failed: the attempt has failed (e.g. too many failed proofs) and cannot be continued.
  • expired: the 15-minute TTL has been exceeded.
Value in"in_progress""completed""failed""expired"
session_id?|

The session being authenticated into. Either newly created or passed in on POST. For step-up (re-auth), this is the existing session_id provided on creation.

user_id?|

The user ID, if known at this point in the flow. Null until an identifier challenge has been verified.

required_factors?array<>

The authentication factor methods that the project policy requires in order to complete this attempt. Computed by the server from the project's auth policy. Clients should use this to determine which challenges still need to be issued.

completed_factors?array<>

The factors that have been successfully verified in this attempt so far. Each item includes method-specific metadata in the payload field.

The flow engine uses verified_at timestamps to enforce time-based policies, such as requiring password verification within the last 5 minutes for step-up auth to access sensitive operations.

A factor appears here once its challenge reaches verified state.

challenges?array<>

All challenges issued within this attempt, in creation order. Use this to track the state of each factor challenge (pending, verified, failed, expired).

expires_at?string

When this attempt expires (15-minute TTL from creation)

Formatdate-time
created_at*string

When this attempt was created

Formatdate-time
curl -X POST 'https://example.com/auth_attempts' \  -H 'Content-Type: application/json' \  -d '{  "project_id": "string"}'
{  "attempt_id": "string",  "project_id": "string",  "state": "in_progress",  "session_id": "string",  "user_id": "string",  "required_factors": [    "identifier"  ],  "completed_factors": [    {      "method": "identifier",      "verified_at": "2019-08-24T14:15:22Z",      "payload": {        "method": "identifier",        "user_id": "string"      }    }  ],  "challenges": [    {      "challenge_id": "string",      "method": "identifier",      "state": "pending",      "created_at": "2019-08-24T14:15:22Z",      "expires_at": "2019-08-24T14:15:22Z",      "payload": {}    }  ],  "expires_at": "2019-08-24T14:15:22Z",  "created_at": "2019-08-24T14:15:22Z"}