Headless Identity APIs: Headless Passwordless Login Flow for Private Clients
To simplify the login process for your off-platform app, configure the Headless Passwordless Login Flow. Users log in to your app by entering their email address or phone number and verifying their identity with a one-time password (OTP). On the front end, you control the user experience in your app. On the backend, your app calls Headless Passwordless Login API via an Experience Cloud site to log the user in. These steps show you how the flow works with a private client, like a traditional client-server app, that can keep information confidential.
Required Editions
| Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience |
| Available in: Enterprise, Unlimited, and Developer Editions |
This flow is a variation of the Authorization Code and Credentials Flow, which extends the OAuth 2.0 Authorization Code grant type. Like other variations, it includes calls to Salesforce endpoints to get an authorization code and exchange it for an access token. In this variation, your app exchanges a request identifier and OTP for the authorization code, instead of a username and password as it does in a more traditional headless login flow. The code is then exchanged for an access token.
Before setting up this flow, complete these steps.
- Complete Prerequisites for Headless Identity
- Integrate your off-platform app with Salesforce using one of these options.
- Configure Experience Cloud Settings for Headless Passwordless Login
Here’s an example use case for the Headless Passwordless Login Flow. You work for a travel company that manages customer information in Salesforce, and you already implement headless login and registration in your off-platform app. During your registration process, you collect the user’s email address. To make the login process easier, you configure headless passwordless login with email as the verification method. Now, when users visit your app, they can enter their email address and receive an OTP. After they enter the OTP in your app, and Salesforce verifies their identity, the user is logged in and can access protected Salesforce data, like their travel history.
Optionally, for even more flexibility with your user experience, you can use a headless user discovery Apex handler to retrieve users. Develop your handler to look up users based on their email address, phone number, or any other identifier that can be linked to a Salesforce user. For example, prompt users to log in with their travel confirmation number. When the user enters their confirmation number, Salesforce finds the associated username and sends an OTP to the user's email address. For more information about how to develop your Apex handler, see Auth.HeadlessUserDiscoveryHandler in the Apex reference guide.
For a private client that can safely store the app’s consumer secret on its own backend, we
recommend that you secure your flow by authenticating your calls to Headless Passwordless Login
API. Always enable Require authentication to access this API in your
Experience Cloud settings, and include an access token issued to an internal integration user in
your initial request to the services/auth/headless/init/passwordless/login endpoint. To get the access token, use
a standard OAuth 2.0 flow, like the user-agent flow. For the external client app or connected app
that you use for the standard OAuth flow, include the pwdless_login_api scope so that it’s reflected in your access token.
Optionally, you can add an extra layer of security by enabling Require reCAPTCHA to access this API in your Experience Cloud settings and implementing reCAPTCHA on your app. But authenticated API calls are your best primary line of defense for a private client.
To further secure your flow, we always recommend implementing the OAuth 2.0 Proof Key for Code Exchange (PKCE) extension.
To expand your email template options for the one-time password (OTP) email sent to end users during the flow, opt in to email template allowlisting and create an allowlist with custom templates. See Use Multiple Email Templates for Headless Flows.
Here’s an overview of the Headless Passwordless Login Flow for a private client.
- An end user opens your app and sees a login form natively displayed within your app. They enter their email address or phone number as requested by the login form (1).
- If you're not using a headless user discovery handler, your app finds the username associated with the user’s phone number or email address (2).
- Your app submits a headless POST request to Headless Passwordless Login API (the
services/auth/headless/init/passwordless/loginendpoint on your Experience Cloud site (3). - (Optional) If you're using a headless user discovery handler, the handler finds the username
associated with the data passed in the
login_hintparameter and verifies that the email address or phone number associated with the user is verified. - Salesforce returns a success message to your app. It also sends a request identifier, which is used later in the flow (4a).
- Depending on the verification method, Salesforce sends either an email or SMS text message with an OTP to the user (4b).
- Your app natively displays an OTP verification form (5).
- The user receives their OTP and enters it in your app (6).
- If you’re using PKCE, which we strongly recommend, your app generates PKCE parameters (7).
- Your app kicks off the Authorization Code and Credentials Flow with a POST or GET request to
the Salesforce authorization endpoint (
services/oauth2/authorize). The request includes the request ID and OTP, as well as other parameters to identify the app and specify the type of request (8). - Salesforce verifies the request ID and OTP and returns a 302 redirect to a preconfigured URL that contains the authorization code. If the flow is being executed in the browser, the 302 redirect is processed within the browser, and the response is delivered headlessly to the server-side callback endpoint (9).
- Your server-side callback handler extracts the code and other parameters from the 302 redirect and initiates the code exchange with a POST request to the token endpoint (10).
- Salesforce validates the token request and returns an access token and state (11).
- The server-side callback handler processes the token response and returns the logged-in state to the app. This response can include session details, user info, and possibly the access token (12).
- Your app receives the token response and creates the user’s session (13).
- The user is now logged in and performs an action in your app, like clicking a button to see their order history (14).
- Your app sends an authenticated request to a protected Salesforce API (15).
- The user can now access their Salesforce data in your off-platform app (16).
As mentioned in step 10, for a private client, your build a server-side callback handler that can extract the 302 redirect and return an authorization code to your app. To see how you can build this handler, see Authorization Code and Credentials Flow for Private Clients, which includes an example Apex handler exposed as a REST resource.
End User Enters Their Email Address or Phone Number for Login
The flow starts when an end user visits your app. Your app natively displays a login form requesting only the user’s email address or phone number. Or, if you're using a headless user discovery handler that looks up users based on a different identifier, such as an order number, your app can display a login form prompting the user for the identifier. The end user enters their information and clicks a button to log in.
Your App Finds the Username
If you're not using a headless user discovery handler, your app looks up the user’s email address or phone number and finds the associated username.
If you're using a headless user discovery handler, your app skips this step. Your handler looks up the user after you submit the initial request.
Your App Sends a Request to Headless Passwordless Login API
From the browser, your app sends a headless POST request to the headless passwordless login endpoint (services/auth/headless/init/passwordless/login) on your Experience Cloud site.
Include a header to authenticate your request.
| Parameter | Required? | Description |
|---|---|---|
Authorization
|
Required if you enable Require authentication to access this API in your Experience Cloud settings, which we recommend for private clients. | A Bearer header containing an access token issued to an internal integration user. To
get the access token, you can use any standard OAuth flow that Salesforce supports. Ensure
that you assign the pwdless_login_api scope to your
external client app or connected app, or pass it as a parameter during your flow. |
Include these parameters in the request body.
| Parameter | Required? | Description |
|---|---|---|
verificationmethod
|
Yes. | The method that you want to use to verify the user’s identity. You can use
email or sms. |
username
|
Required if you're not using a headless user discovery handler. | The username tied to the email address or phone number that the user submitted. |
recaptcha
|
Required if these conditions apply to you.
|
An encrypted token issued by the Google reCAPTCHA API when a user completes a reCAPTCHA challenge. |
recaptchaevent |
Required if these conditions apply to you:
|
A JSON object containing these subparameters.
For more information, see Google's reCAPTCHA documentation. |
emailtemplate
|
Required to specify multiple email templates if email template allowlisting is enabled. If you didn’t enable email template allowlisting, you can’t include this parameter. If you don’t include this parameter, Salesforce uses the default email template configured in your Experience Cloud settings, regardless of whether allowlisting is enabled. If there’s no template configured, Salesforce uses a default OTP email template. The email template language for default templates is controlled by the user’s language settings in Salesforce. |
The custom email template developer name. This parameter can include only an email template from the allowlist. To control the language for a custom email template, create templates in the desired language |
login_hint
|
Required if you're using a headless user discovery Apex handler. | An identifier that your Apex handler can use to find a user's Salesforce
account. For example, collect a user's order number in your app and pass it in the
login_hint parameter. We send the login_hint
value straight to your Apex handler. |
Here’s an example request to Headless Passwordless Login API. In this example, authentication is required but reCAPTCHA isn’t. This request is for an implementation that doesn't use a headless user discovery handler.
POST /services/auth/headless/init/passwordless/login? HTTP 1.1
Host: MyExperienceCloudSite.my.site.com
Authorization: Bearer <access token>
{
"verificationmethod": "email",
"username": "janice.edwards@example.com",
"emailtemplate": "unfiled$public/SalesNewCustomerEmail"
}Here's an example request if you’re using a headless user discovery handler.
POST /services/auth/headless/init/passwordless/login? HTTP 1.1
Host: MyExperienceCloudSite.my.site.com
Authorization: Bearer <access token>
{
"verificationmethod": "email",
"login_hint": "<user identifier such as email address, phone number, order number>",
"emailtemplate": "unfiled$public/SalesNewCustomerEmail"
}(Optional) Headless User Discovery Handler Finds User
If you're using a headless user discovery handler, the handler takes the login_hint parameter and finds the associated user. The handler
confirms that the email address or phone number for the user is verified.
For an example handler, see Auth.HeadlessUserDiscoveryHandler in the Apex Reference Guide.
Salesforce Returns a Request Identifier to Your App
If the request is successful, Salesforce sends back a success message that includes an identifier for the request, which is important later in the flow
when it’s exchange for an access token. Here’s an example success message.
{
"status": "success",
"email": "jedwards@example.com",
"identifier": "0RXXXXXXXX"
}Salesforce Sends an OTP to the User
Immediately after Salesforce sends the request identifier to your app, it also sends the user an email or SMS text message with the OTP.
Your App Displays a Verification Form
In your app, you natively display a verification form where the user can enter their OTP.
End User Enters OTP
The end user receives the OTP via email or text message and enters it in the verification form in your app.
Your App Generates Parameters for PKCE
If you’re using the PKCE extension—which we strongly recommend— your app generates code_verifier and code_challenge parameters.
The PKCE specification defined in RFC 7636 also includes an optional
code_challenge_method parameter that you can send in the
authorization request. Salesforce ignores any value that you send in this parameter and defaults
to SHA256.
Your App Sends a Request to the Authorization Endpoint
As soon as the user verifies their identity by entering the OTP, your app initializes the Authorization Code and Credentials Flow with a request to the authorization endpoint, where it exchanges the request ID and OTP for an authorization code.
Include these headers in the authorization request.
| Header | Required? | Description |
|---|---|---|
Auth-Request-Type
|
Yes. | Specifies the type of request you want to make to Salesforce. For headless
passwordless login, this value must be set to passwordless-login. |
Auth-Verification-Type
|
Yes. | The method used to verify the user’s identity. Salesforce supports two values for the
verification method: email and sms. |
Authorization
|
Yes. | A Basic header identifying the passwordless login request so that Salesforce can link it to the user’s stored information. Include the request identifier ( |
Uvid-Hint
|
No. If you implement the guest user flow on your app, you can optionally use this
header to pass in a JSON Web Token (JWT)-based access token containing a unique visitor ID
( Instead of passing a JWT-based token with a |
A JWT-based access token containing a UVID value,
which is a Version 4 universally unique identifier (UUID) that’s generated and managed
entirely by your app. To get an access token with a UVID, you must enable your external client app or connected app to issue
JWT-based access tokens and implement the headless guest flow on your app. |
Include these parameters in the request body.
| Parameter | Required? | Description |
|---|---|---|
response_type
|
Yes. | The OAuth 2.0 grant type that your app requests. For the Authorization Code and
Credentials Flow, this value must be code_credentials. |
client_id
|
Yes. | The external client app or connected app consumer key. |
redirect_uri
|
Yes. | The URL where users are redirected after successful authentication. The For private clients, use a |
code_challenge
|
Only if you’re using PKCE. PKCE is strongly recommended. | Specifies the SHA256 hash value of the If a If the |
uvid_hint
|
No. If you implement the guest user flow on your app, you can optionally use this
parameter to pass in a Instead of passing the |
A plain UVID value, which is a Version 4 UUID
that’s generated and managed entirely by your app. To get a UVID, you must enable your external client app or connected app to issue
JWT-based access tokens and implement the headless guest flow on your app. |
Here’s an example request to the authorization endpoint. This example includes PKCE.
POST /services/oauth2/authorize? HTTP 1.1
Host: MyExperienceCloudSite.my.site.com
Auth-Request-Type: passwordless-login
Auth-Verification-Type: email
Authorization: Basic <base64-encoded identifier:OTP
response_type=code_credentials&
client_id=***********&
redirect_uri=https://www.MyDomainName.my.site.com/services/apexrest/code/exchange&
code_challenge=********
Salesforce Verifies the Request and Returns a 302 Redirect
When the request hits the authorization endpoint, Salesforce verifies the request identifier and OTP. Salesforce then returns an HTTP 302 redirect to a preconfigured URL containing the authorization code. If the flow is happening in the browser, the 302 redirect is processed in the browser and Salesforce automatically sends the redirect response to the redirect URL, which is the server-side callback endpoint. Here’s an example preconfigured URL.
https://www.MyDomainName.my.site.com/services/apexrest/code/exchange?code=aPrxC1*******
&sfdc_community_url=https%3A%2F%2FMyDomainName.my.site.com&sfdc_community_id=0DBxxxxxxxxxxxxServer-Side Callback Handler Extracts Code and Performs Code Exchange
The server-side callback handler extracts the authorization code and other parameters from the 302 redirect. It then initiates the code exchange by sending a headless POST request to the token endpoint.
This request has no headers. Include these parameters in the request body.
| Parameter | Required? | Description |
|---|---|---|
code
|
Yes. | The authorization server creates an authorization code, which is a short-lived token, and passes it to the client after successful authentication. The client sends the authorization code to the authorization server to obtain an access token and, optionally, a refresh token. |
client_id
|
Yes. | The consumer key of the external client app or connected app. |
client_secret
|
Yes. | The consumer secret of the external client app or connected app. In this flow, it acts as a password that the app uses to access Salesforce. |
redirect_uri
|
Yes. | The URL where users are redirected after a successful authentication. The redirect URI must match one of the values in the external client app or connected app Callback URL field. Otherwise, the approval fails. This value must be URL encoded. For private clients, use a
|
grant_type
|
Yes. | The type of validation that the app can provide to prove it’s a safe visitor. For the
Authorization Code and Credentials Flow, the value must be authorization_code. |
code_verifier
|
Only if you’re using PKCE. | Specifies 128 bytes of random data with high entropy to make guessing the code value difficult. Set this parameter to help prevent authorization code interception attacks. The value must be base64url-encoded as defined in https://datatracker.ietf.org/doc/html/rfc4648#section-5. If there’s a If the |
Here’s an example token request. This example uses PKCE.
POST services/oauth2/token? HTTP 1.1
Host: MyExperienceCloudSite.my.site.com
code=********&
client_id=**********&
client_secret=*********&
redirect_uri=https://www.MyDomainName.my.site.com/services/apexrest/code/exchange&
grant_type=authorization_code&
code_verifier=*******Salesforce Grants an Access Token
After validating the app’s credentials. Salesforce returns an access token to the server-side callback handler. Here’s an example access token response in JSON format.
{
"access_token":"*******************",
"sfdc_community_url":"https://MyDomainName.my.site.com",
"sfdc_community_id":"0DBxxxxxxxxxxxx",
"signature":"ts6wm/svX3jXlCGR4uu+SbA04M6qhD1SAgVTEwZ59P4=",
"scope":"openid api",
"id_token":"XXXXXX",
"instance_url":"https://yourInstance.salesforce.com",
"id":"https://yourInstance.salesforce.com/id/00Dxxxxxxxxxxxx/005xxxxxxxxxxxx",
"token_type":"Bearer",
"issued_at":"1667600739962"
}The access token response contains these parameters.
| Parameter | Required? | Description |
|---|---|---|
access_token
|
Yes. | OAuth token that an external client app or connected app uses to request access to a protected resource on behalf of the client application. Additional permissions in the form of scopes can accompany the access token. |
id
|
Yes. | An identity URL that can be used to identify the user and to query for more information about the user. See Identity URLs. |
id_token
|
No. | A signed data structure that contains authenticated user attributes, including a unique identifier for the user and a timestamp indicating when the token was issued. It also identifies the requesting app. See OpenID Connect specifications. |
instance_url
|
Yes. | A URL indicating the instance of the user’s org. For example, https://yourInstance.salesforce.com/. |
issued_at
|
Yes. | A timestamp of when the signature was created, expressed as the number of milliseconds from 1970-01-01T0:0:0Z UTC. |
refresh_token
|
No. | Token obtained from the web server, user-agent, or hybrid app token flow. This value
is a secret. Take appropriate measures to protect it. This parameter is returned only if
your external client app or connected app is set up with a refresh_token scope. |
signature
|
Yes. | Base64-encoded HMAC-SHA256 signature signed with the client_secret. The signature can include the concatenated ID and issued_at value, which you can use to verify that the identity
URL hasn’t changed since the server sent it. |
sfdc_community_url
|
Yes. | The URL of the Experience Cloud site. |
sfdc_community_id
|
Yes. | The user’s Experience Cloud site ID. |
state
|
No. | The state requested by the client. This value is included only if the state parameter is included in the original query
string. |
token_type
|
Yes. | A Bearer token type, which is used for all
responses that include an access token. |
Server-Side Callback Handler Processes the Token Response
To get the access token, the callback handler processes the response and then returns the access token and state to the browser, along with user data, tokens, and session data. As a best practice, we recommend that you configure your server to store the access token, create a session for the app, and return the session to the app, instead of returning the access token. As the developer, you’re in full control of creating the session, storing the access token, and managing the logged-in state, so your specific implementation is up to you.
Here’s an example of a successful response in the browser console log.
{
"success":true,
"state":"https://MyExperienceCloudSite.my.site.com/",
"errMsg":"null",
"access_token":"00*******"
}
App Creates the User’s Session
The app receives the access token response and creates the user’s session.
The User Is Logged In and Performs an Action in the App
The end user is now logged in, and they perform an action in your app that requires access to Salesforce data. For example, they click a button to view their order history, which is stored in Salesforce.
App Makes an Authenticated Call to a Salesforce Endpoint
To access the user’s Salesforce data, your app uses the access token to make an authenticated call to a protected Salesforce endpoint, such as a Salesforce API.
User Can Access Salesforce Data
The user can now access protected Salesforce data in your app. For example, they can see their order history. From the user’s perspective, the entire process from logging in to accessing their data happened without ever requiring them to leave the app.

