OAuth 2.0 for First-Party Applications: Headless Registration Flow for Private Clients
To set up a headless user registration process for an off-platform app developed by your company, use this flow, which implements the OAuth 2.0 for First-Party Applications draft standard protocol. This flow is supported only for private clients, such as client-server apps. With this flow, you can entirely control the front-end registration experience in your first-party app while Salesforce handles the backend work of authenticating users and granting access to protected resources. This flow is supported only for private clients, such as client-server apps, and is supported only for external users.
Required Editions
| Available in: both Salesforce Classic and Lightning Experience |
| Available in: Enterprise, Unlimited, and Developer Editions |
To set up headless registration for a private client, you can also use this version of the Authorization Code and Credentials Flow, which implements Headless Identity APIs. Both flows accomplish the same use case—registration for an app outside of the Salesforce platform. But there are a few key differences to keep in mind.
| OAuth For First-Party APPS | Headless Identity APIS |
|---|---|
| Supported only for private clients. | Supported for public and private clients. |
| Conforms to the OAuth 2.0 for First-Party Applications draft standard protocol. | Proprietary Salesforce flow that's built on top of the OAuth 2.0 standard. |
| Supported only for the Salesforce external client app framework. Also, the only way to configure external client app settings for this flow is via Metadata API. | Supported for both the Salesforce external client app and connected app frameworks. |
| For security, this flow always requires a client attestation JWT. Salesforce uses the client attestation JWT to validate that the app was developed by your company. | For security, requires either authentication or reCAPTCHA, but not a client attestation JWT. |
Before setting up this flow, complete these steps.
- Complete prerequisites for headless identity.
- Generate a client attestation JWT.
- Configure a Salesforce external client app.
- Configure Experience Cloud settings.
Here's an overview of how the flow works.
- Step 1: An end user opens your first-party app and clicks Register.
- Step 2: In your app, you natively display a registration form to collect user data. You design this form and customize the information that you want to collect.
- Step 3: The user enters their information in your app For example, they enter their username, password, and first name.
- Step 4: Your app mints a client attestation JWT. It also generates parameters for the Proof Key for Code Exchang (PKCE) extension.
- Step 5: To initialize registration, your app submits the user information to the services/oauth2/v1/authorization_challenge endpoint on your Experience Cloud site. The request includes a client attestation JWT.
- Step 6: To confirm that your first-party app sent the request, Salesforce validates the client attestation JWT, and then validates the other parameters sent in the request.
- Step 7: When the request is successful, Salesforce returns a response with an
auth_session. The response indicates that Salesforce initialized registration and sent a one-time password (OTP) to the user. - Step 8: In your app, you natively display a verification form. You choose how you want this form to look.
- Step 9: The user receives their OTP and enters it in the verification form.
- Step 10: To request an authorization code, your app sends another POST request to the
services/oauth2/v1/authorization_challenge endpoint. The request
includes the
auth_sessionand the OTP. - Step 11: If the request is successful, Salesforce returns an authorization code to your
app and terminates the
auth_session. - Step 12: To exchange the code for an access token, your first-party app sends a request to the /services/oauth2/token endpoint.
- Step 13: Salesforce returns a token response containing the access token.
- Step 14: Your first-party app processes the token response and creates the user's session.
- Step 15: The user is now logged in and they perform an action in your app that initiates a request for Salesforce data.
- Step 16: Your app makes an authenticated request to a protected Salesforce endpoint, such as a Salesforce API.
- Step 17: The user can now access their protected data in your app.
Step 1: User Opens First-Party App and Clicks Register
A user opens your first-party app and clicks a registration link. Or they click a link to access a resource that requires registration.
Step 2: First-Party App Displays Registration Form
In your first-party app, you natively display a registration form to collect information about the user. You control everything about this form, including its look, feel, and the user information that you want to collect.
There are a few considerations about what information you want to gather from users. When your app submits user information to the authorization challenge endpoint, Salesforce checks for an email address, username, last name, and password. You can collect this information from users or autogenerate it, but it must be included in your POST request. When deciding what information to include, make sure to collect an email address or phone number so that the user can verify their identity.
Step 3: User Enters Their Information
In your app's registration form, the user enters their information.
Step 4: First-Party App Mints a Client Attestation JWT and Generates code_verifier and code_challenge Values for PKCE
The app mints a client attestation JWT.
The app also generates values for the PKCE parameters used to verify the authorization code.
For more information on PKCE, see the specificationRFC 7636: Proof Key for Code Exchange by OAuth Public Clients provided by the Internet Engineering Task Force (IETF).
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.
Step 5: App Sends Initial Request to the Authorization Challenge Endpoint
From the browser, your app sends a POST request to the services/oauth2/v1/authorization_challenge endpoint on your Experience Cloud site.
Include this header in your request if necessary.
| Header | Required? | Description |
|---|---|---|
Authorization: Bearer
|
Required if you enable Require authentication to access this API in the headless registration section on the Experience Cloud Login & Registration page. | Contains 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 user_registration_api scope
to your connected app or external client app, or pass it as a parameter during your
flow. |
Include these parameters in the request body.
| Parameter | Required? | Description |
|---|---|---|
password
|
Yes. | The user’s password. The password is subject to any password policies configured for the profile or org. |
userdata
|
Yes. Even if you don’t collect this information from the user, you must
autogenerate it and pass it in the userdata
parameter. |
Contains all required user information. At minimum, Salesforce requires this
information in the
|
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. |
client_assertion
|
Yes. | The client attestation JWT that you generated, signed by the certificate configured for your external client app. |
login_type
|
No. If you don’t include this parameter, Salesforce defaults to verifying the user’s identity with email. | The method used to verify the user’s identity. Salesforce supports two values
for the verification method: email and sms. |
customdata
|
No. | Contains any custom user information that you collect. For example, you can include the user’s street address. |
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 custom email template developer name. This parameter can include only an email template from the allowlist. |
code_challenge
|
Required if you required PKCE for your external client app. For this flow's security features to work correctly, we strongly recommend that you always require PKCE. | Specifies the SHA256 hash value of the If a If the |
Here's an example initial registration request.
POST /services/oauth2/v1/authorization_challenge? HTTP 1.1
Host: MyExperienceCloudSite.my.site.com
{
"userdata": {
"firstname": "Janice"
"lastname": "Edwards"
"email": "janice.edwards@example.com"
"username": "jedwards@myapp.com"
}
"customdata": {
"mobilePhone"="<mobile phone number>"
}
"password":"*********"
"recaptcha": "*******"
"login_type": "email"
"emailtemplate": "unfiled$public/SalesNewCustomerEmail"
"client_assertion": "Y2xpZW50YXNzZXJ0aW9u..."
}Step 6: Salesforce Validates the Request
Salesforce first attempts to
validate the client attestation JWT by validating that the signature passed in the client_asssertion parameter matches the signature for the
certificate configured for the external client app.
If the client attestation JWT
isn't valid, Salesforce returns an invalid_attestation
error and you must resubmit the request with all of the parameters you originally submitted.
Here's an example error response.
If the client attestation JWT is valid, but there
are other issues with the request, Salesforce returns an error response specifying what was
wrong with the request. The response also includes an auth_session parameter that remains valid for 5 minutes after it's issued.
During the timeframe while the auth_session is valid,
you can use it to resubmit the request. In the corrected versions that you resubmit, include
only the corrected values for the parameters that caused the request to fail. You must also
resubmit the password with each request because
Salesforce doesn't store it. But for other parameters, if they didn't cause the request to
fail, you can leave them out. They're already represented by the auth_session.
auth_session, but you can resubmit the request without them,
unless they caused the request to fail.For example, if your client attestation JWT
is valid but the request fails because the username was incorrect, resubmit a request that
includes only the auth_session, username, and password.
Step 7: Salesforce Sends an OTP to the User
If the request is successful, Salesforce still returns an error response because it can't
yet grant the authorization code. But this time, the error response indicates that login was
initialized and that Salesforce sent an OTP to the user's email address or phone number. To
confirm that the request was successful, look out for the error code login_initialized and the state otp_sent. The response also includes an auth_session parameter, which is important for the next step.
Here's an example response.
HTTP/1.1 403 Forbidden
Content-Type: application/json
Cache-Control: no-store
{
"error": "authorization_required",
"auth_session": "uY29tL2F1dGhlbnRpY",
"error_code": "login_initialized",
"login_status": {
"type": "SMS",
"state": "otp_sent",
"displayData": "+120******58"
}
}Step 8: Your App Natively Displays a Verification Form
In your first-party app, you display a verification form where the end user can enter their OTP. The look and feel of this form are entirely up to you.
Step 9: User Enters OTP in Verification Form
The user receives the OTP and enters it in the verification form in your first-party app.
Step 10: Your App Requests an Authorization Code
To request an authorization code, your app sends the auth_session and the OTP that the user entered to the authorization challenge
endpoint using another POST request to the
services/oauth2/v1/authorization_challenge This request has no
required headers. Include these parameters in the request body.
| Parameter | Required? | Description |
|---|---|---|
auth_session
|
Yes. | Represents the login attempt. Use the auth_session value that you received in the response from your first
request to the authorization challenge endpoint. Make sure to use the auth_session from the request that indicated that
login was initialized and the OTP was sent. |
login_otp
|
Yes. | The OTP that the end user entered in your app's verification form. |
Here's an example request.
POST /authorize HTTP/1.1
Host: MyExperienceCloudSite.my.site.com
auth_session=uY29tL2F1dGhlbnRpY*
login_otp=<otp_from_sms>Step 11: Salesforce Returns an Authorization Code
If the OTP is correct and the auth_session request is
valid, Salesforce returns an authorization code and terminates the auth_session. Here's an example authorization code
response.
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
{
"authorization_code": "uY29tL2F1d******"
}Step 12: Your App Exchanges the Authorization Code for an Access Token
After you get the authorization code, your app sends a request to the Salesforce 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. |
client_secret
|
Yes. | The consumer secret of the external client 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 Callback URL field. Otherwise, the approval fails. This value must be URL encoded. |
grant_type
|
Yes. | The type of validation that the app can provide to prove it’s a safe visitor.
For this flow, the value must be authorization_code. |
code_verifier
|
Required if you required PKCE for your external client app. For this flow's security features to work correctly, we strongly recommend that you always require 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.
POST services/oauth2/token? HTTP 1.1
Host: MyExperienceCloudSite.my.site.com
code=********&
client_id=**********&
client_secret=*********&
redirect_uri=<callback_URL>&
grant_type=authorization_code&
code_verifier=*******Step 13: Salesforce Grants an Access Token
After validating the app’s credentials. Salesforce returns an access token. 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 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. |
Step 14: App Creates the User’s Session
Your first-party app processes the token response and creates the user's session.
Step 15: User Is Registered and Performs an Action in the App
Your user is now registered and logged in. They perform an action in your app that requires access to Salesforce data. For example, they click a button to view their travel booking history, which is stored in Salesforce.
Step 16: 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.
Step 17: User Can Access Salesforce Data
The user can now access protected Salesforce data in your app. For example, they can see their travel booking history.

