Headless Identity APIs: Headless Passwordless Login Flow for Public Clients
Make it easy for customers and partner users to log in to an off-platform app with the Headless Passwordless Login Flow. With this flow, users log in by entering their email address or phone number and verifying their identity with a one-time password (OTP). You control the front-end 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 public client, like a single-page app, that can’t keep information private.
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 mobile 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 public client that can’t keep the app’s consumer secret safe, like a mobile app or single-page app, there are some extra security considerations when you build the flow. When you configure your Experience Cloud Login & Registration settings, you must enable at least one of these security options: Require authentication to access this API or Require reCAPTCHA to access this API. For public clients, we recommend that you always enable Require reCAPTCHA to access this API, which requires your app to include a reCAPTCHA token in your initial request to Headless Passwordless Login API. We never recommend that you enable Require authentication to access this API for public clients. This setting requires your request to include an access token issued to an internal integration user, and public clients can’t keep the access token secret.
To further secure your flow, we always recommend implementing the OAuth 2.0 Proof Key for Code Exchange (PKCE) extension. Traditionally, a private client app uses the consumer secret as a password to securely access Salesforce. But public clients can’t keep the consumer secret safe because they don’t have a private backend. PKCE helps you close this gap with parameters that only your app and Salesforce can verify. These parameters help you ensure that the client that initiates the flow is the same client that completes it.
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 public 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 and 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 callback endpoint (9).
- A callback endpoint extracts the authorization code and returns it to your app (10).
- Your app receives the authorization code and initiates a code exchange by sending the code
and other parameters in a POST request to the Salesforce token endpoint (
services/oauth2/token) (11). - Salesforce validates the token request and returns an access token and state (12).
- Your app processes the access token response and creates the user’s session (13).
- The user is now logged in and performs an action in your app that requires access to Salesforce data, such as 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, this flow requires a callback endpoint that can handle the 302
redirect and return the authorization code and other parameters to your app. For
implementations where you complete the code exchange in your browser, you can use the
Salesforce /services/oauth2/echo endpoint. This endpoint
automatically parses the 302 redirect, extracts the parameters, and returns them to your app
in JSON format. We use the echo endpoint in the code examples for this flow.
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 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 custom 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 custom email templates, 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. 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
{
"verificationmethod": "email",
"username": "janice.edwards@example.com",
"recaptcha": "***********",
"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
{
"verificationmethod": "email",
"login_hint": "<user identifier such as email address, phone number, order number>",
"recaptcha": "***********",
"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.
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 (UVID) tied to a guest user’s identity. By passing the UVID into a named user flow, you can carry contextual information from a guest user session, like the user’s cookie preferences, into a named user session. Instead of passing a JWT-based token with a UVID in a header, you can also pass the plain UVID value in the request body. |
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. Because this flow is a
variation of the Authorization Code and Credentials Flow, this value must be code_credentials. |
client_id
|
Yes. | The consumer key of the external client app or connected app. |
redirect_uri
|
Yes. | The URL where users are redirected after successful authentication. The For this flow, you can use the echo
endpoint, |
code_challenge
|
Only if you’re using PKCE. PKCE is strongly recommended, especially for public clients. | 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 UVID value tied to a guest user’s identity, carrying contextual information from a guest user session into a named user session. Instead of passing the UVID in the request body, you can also pass it
in a JWT-based token with a UVID via 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.MyExperienceCloudSite.my.site.com/services/oauth2/echo&
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 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=0DBxxxxxxxxxxxxCallback Endpoint Sends the Authorization Code to Your App
The callback endpoint extracts the authorization code and returns it to your app. In these
code examples, the redirect URL points to the /services/oauth2/echo callback endpoint on the Experience Cloud site. This
endpoint automatically parses the 302 redirect, extracts the authorization code and other
parameters, and returns them to your app in JSON format.
App Initiates Token Exchange
Your app receives the code response. To get an access token, the app initiates the code
exchange with a headless POST request to the /services/oauth2/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. |
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 this flow, you can use the echo endpoint, |
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=**********&
redirect_uri=https://MyExperienceCloudSite.my.site.com/services/oauth2/echo&
grant_type=authorization_code&
code_verifier=*******Salesforce Grants an Access Token
After validating the app’s credentials. Salesforce returns an access token to the browser. 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. |
App Creates the User’s Session
The browser stores the information from the token response and creates the user session. Your app calls the Salesforce User Info endpoint to confirm that the login was successful.
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.

