# First-party registration (/docs/auth/flows/first-party-registration)



The first-party registration flow lets a private Experience Cloud application
collect registration data, initialize Salesforce registration headlessly, verify
the user with an OTP, and receive an authorization code.

<SalesforceReference title="Headless Registration Flow for Private Clients" href="https://help.salesforce.com/s/articleView?id=sf.remoteaccess_auth_challenge_registration.htm&language=en_US&type=5" />

## Initialize registration [#initialize-registration]

```ts
import { SalesforceAuth } from "@kysoql/auth";

export function beginRegistration(
  clientAssertion: string,
  codeChallenge: string,
) {
  const auth = new SalesforceAuth({
    loginUrl: "https://customers.example.my.site.com/customers",
    clientId: "external-client-app-id",
  });

  return auth.firstPartyAuthorizationChallenge({
    bodyFormat: "json",
    parameters: {
      userdata: {
        username: "user@example.com",
        lastName: "Example",
        email: "user@example.com",
      },
      password: "user-chosen-password",
      login_type: "email",
      client_assertion: clientAssertion,
      code_challenge: codeChallenge,
    },
  });
}
```

Use `accessToken` when the Experience Cloud registration policy requires an
authenticated integration user. Kysoql adds it as an `Authorization: Bearer`
header. Use `uvidHintToken` when registration carries guest-session context.

Salesforce strongly recommends PKCE for this flow. Keep the matching
`code_verifier` and use it when exchanging the returned authorization code.
Salesforce can return an `auth_session` while it waits for OTP verification.

## Submit the OTP [#submit-the-otp]

```ts
import { SalesforceAuth } from "@kysoql/auth";

export function submitRegistrationOtp(authSession: string, otp: string) {
  const auth = new SalesforceAuth({
    loginUrl: "https://customers.example.my.site.com/customers",
    clientId: "external-client-app-id",
  });

  return auth.firstPartyAuthorizationChallenge({
    parameters: {
      auth_session: authSession,
      login_otp: otp,
    },
  });
}
```

After Salesforce returns `kind: "authorized"`, exchange the authorization code
through [`auth.exchangeAuthorizationCode()`](/docs/auth/flows/web-server-pkce).
