Passwordless Authentication on Database Connections with Authentication API
Use the Auth0 Authentication API to implement embedded passwordless login with email OTP, SMS OTP, or voice OTP on a database connection.
Native and back-end applications with a custom login UI can authenticate users with a one-time password (OTP) sent to an email or phone directly through the Auth0 Authentication API, with no redirect to Universal Login. This supports email OTP, SMS OTP, and voice OTP on standard database connections.This lets you use one-time-code authentication from the same database connection that already holds your users. If you previously used /passwordless/start with a dedicated passwordless connection, you can consolidate onto your existing database connection instead. To use passwordless authentication with Universal Login, read Passwordless Authentication on Database Connections.
The Passwordless OTP grant isn’t available for single-page application (SPA) type applications. If your frontend is a single-page application, make the /otp/challenge and /oauth/token calls from a back-end application that has the Passwordless OTP grant enabled.
Authentication is a two-call flow:
POST /otp/challenge — send a one-time code to the user’s email or phone.
POST /oauth/token — exchange the code the user entered for tokens.
The Auth0 Authorization Server sends a one-time code to the user’s email or phone.
The Auth0 Authorization Server responds with an auth_session value. Store this value; no other state is required.
User receives the code and enters it into your application’s UI.
Your application calls the POST /oauth/token endpoint with the auth_session and the user-entered code.
The Auth0 Authorization Server verifies the code against the auth_session and responds with an ID token and access token (and optionally, a refresh token).
The flow is stateless from your application’s perspective. The only value you carry between the two calls is the auth_session string returned in step 4. Auth0 determines whether the request is a login or a signup, and whether MFA is required — you don’t need to track any of that yourself. To learn more, read How Auth0 determines login vs. signup.
Enable the Passwordless OTP grant in Auth0 Dashboard or Management API. To learn more, read Update Grant Types.
Confidential applications, such as back-end web applications, must send client_secret on both calls. Public clients, such as native applications, do not.
/otp/challenge returns 200 OK whether or not the user exists. This prevents user enumeration. An attacker can’t use the endpoint to discover which identifiers have accounts. Treat auth_session as an opaque string: store it and pass it to the next call unchanged.
By default, Authentication API authenticates existing users only with allow_signup: false. You can pass allow_signup: true on POST /otp/challenge to let a successful OTP verification create the account when the user doesn’t yet exist, and signup is enabled on the connection.
allow_signup: false: Auth0 never creates a user. Unknown identifiers fail at token exchange.
allow_signup: true: If the user doesn’t exist and the connection allows signup, the account is created when the OTP is verified and tokens are issued in the same step.
Your application always makes the same two calls regardless of whether the user is new or returning. Auth0 resolves the intent at challenge time and records it server-side against the auth_session. At token exchange, Auth0 looks up the session and takes the correct action.
Situation at /otp/challenge
Outcome at /oauth/token (with correct OTP)
User exists
At login, Auth0 issues tokens for the existing user.
User not found, allow_signup: true, signup enabled
At signup, Auth0 creates an account and issues tokens.
The blocked case returns the same error as a wrong OTP, by design, so the response never reveals whether an account exists.
Both endpoints use the OAuth 2.0 error format based on RFC 6749: an error code and a human-readable error_description. Parameter-validation failures (400) also include a validation_errors array identifying the specific fields.
{ "error": "invalid_request", "error_description": "Either email or phone_number must be provided", "validation_errors": [ { "field": "email", "message": "Either email or phone_number must be provided" } ]}
POST /otp/challenge is limited to 50 requests per hour per IP address, in addition to the global Authentication API rate limits. Exceeding the limit returns 429 Too Many Requests.Rate-limited responses include these headers:
Single-page applications (SPAs) can’t use these endpoints directly because the Passwordless OTP grant can’t be enabled on SPA-type applications. Route the calls through a back-end application with the enabled grant.
Confidential applications must send client_secret on both calls.
Authentication API authenticates against database connections with email or phone OTP configured. It doesn’t replace /passwordless/start for dedicated email or SMS passwordless connections.