> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tortus.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Mint short-lived launch tokens on your backend to authenticate the SDK.

The SDK authenticates each session with a short-lived **launch token**. You mint this token on your **secure backend** and hand it to the client through the [`fetchClientSecrets`](/configuration) callback.

<Warning>
  Never expose your client ID or client secret to the frontend, and never mint launch tokens in the
  browser. Always retrieve them from a backend you control.
</Warning>

## How it fits together

<Steps>
  <Step title="Your frontend asks your backend for credentials">
    The SDK calls your `fetchClientSecrets` function, which hits a route on your own server.
  </Step>

  <Step title="Your backend calls the TORTUS launch endpoint">
    Authenticated with your client ID and secret, your server requests a launch token.
  </Step>

  <Step title="Your backend returns the launch token">
    The SDK exchanges the token to load and authenticate the embedded app.
  </Step>
</Steps>

## The launch endpoint

Authenticate with **HTTP Basic Authentication** using your TORTUS-provided credentials:

| Part     | Value                                     |
| -------- | ----------------------------------------- |
| Username | Your client ID (e.g. `cli_abc123xyz`)     |
| Password | Your client secret (e.g. `sec_def456uvw`) |

Base64-encode `clientId:clientSecret` and send it in the `Authorization` header.

<CodeGroup>
  ```bash Production theme={null}
  POST https://api.tortus.ai/v1/oauth/launch
  ```

  ```bash Sandbox theme={null}
  POST https://api.staging.tortus.ai/v1/oauth/launch
  ```
</CodeGroup>

### Request body

All fields are optional. Send what's relevant to your integration.

<ParamField body="userId" type="string">
  To help identify and resume the correct user, send their first and last name on every launch. After their first launch, also send the returned userId. Include any other fields relevant to your integration.
</ParamField>

<Warning>
  If you don't send `userId` for someone who has launched before, TORTUS creates a **new user**
  every time. Store the `user_id` you get back and resend it on every launch. See [Persisting and
  resuming users](#persisting-and-resuming-users).
</Warning>

<ParamField body="externalUserId" type="string">
  Your own internal user ID, kept as a reference label on the TORTUS user. This is **not** a resume
  or lookup key: sending it does not match an existing user. Only `userId` resumes a user.
</ParamField>

<ParamField body="userPayload" type="object">
  User information to associate with this session.

  <Expandable title="userPayload">
    <ParamField body="email" type="string">User's email address.</ParamField>
    <ParamField body="firstName" type="string">User's first name.</ParamField>
    <ParamField body="lastName" type="string">User's last name.</ParamField>

    <ParamField body="externalOrgId" type="string">
      External organisation identifier (e.g. ODS code, facility ID).
    </ParamField>

    <ParamField body="externalOrgName" type="string">
      External organisation name, for display purposes.
    </ParamField>

    <ParamField body="metadata" type="object">
      Custom metadata, such as department or role.
    </ParamField>
  </Expandable>
</ParamField>

### Response

If the user doesn't already exist, TORTUS creates one and returns it in the response. A newly created user only becomes valid once the launch token has been exchanged.

```json theme={null}
{
  "launch_token": "AbCdEfGhIjKlMnOp.QrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYz",
  "user_id": "tortus|a1b2c3d4e5f6g7h8",
  "expires_in": 300
}
```

<ResponseField name="launch_token" type="string">
  The token to pass to the SDK. Valid for **5 minutes**.
</ResponseField>

<ResponseField name="user_id" type="string">
  The TORTUS user ID for this session. Save it to resume the session later via `userId`.
</ResponseField>

<ResponseField name="expires_in" type="number">
  Seconds until the token expires.
</ResponseField>

## Persisting and resuming users

Each launch either resumes an existing TORTUS user or creates a new one. Which one you get depends entirely on whether you send `userId`.

The rule is simple:

1. **First launch for a person:** call the endpoint without `userId` (optionally with `externalUserId` and `userPayload`). Then use `user_id` from the response and save it against that person in your own system.
2. **Every later launch for the same person:** send that saved value as `userId` to resume them.

Skip step 1's save, or forget to send `userId` in step 2, and you create a duplicate user on every login.

```mermaid theme={null}
sequenceDiagram
    participant B as Your backend
    participant T as TORTUS launch endpoint
    Note over B,T: First launch for a person
    B->>T: POST /v1/oauth/launch (no userId)
    T-->>B: launch_token + user_id
    B->>B: Save user_id against this person
    Note over B,T: Every later launch
    B->>T: POST /v1/oauth/launch (userId = saved user_id)
    T-->>B: launch_token (same user resumed)
```

Here is the same loop in your launch route:

```ts theme={null}
// server-side route, e.g. GET /api/tortus/client-secrets
const credentials = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64');

// Look up the TORTUS user ID you saved for this person, if any.
const savedUserId = await getTortusUserId(currentUser.id);

const response = await fetch('https://api.tortus.ai/v1/oauth/launch', {
  method: 'POST',
  headers: {
    Authorization: `Basic ${credentials}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    // Resume the existing user when you have their ID.
    ...(savedUserId ? { userId: savedUserId } : {}),
    externalUserId: currentUser.id,
    userPayload: { email: currentUser.email },
  }),
});

const { launch_token, user_id } = await response.json();

// First time round, persist the returned ID so future launches resume this user.
if (!savedUserId) {
  await saveTortusUserId(currentUser.id, user_id);
}

// Return { launchToken: launch_token } to your frontend.
```

## Wiring it into the SDK

Return the token from `fetchClientSecrets`. The SDK calls this whenever it needs fresh credentials.

```ts theme={null}
const client = await loadTortus({
  publishableKey: 'pk_your_key_here',
  container: '#tortus-container',
  environment: 'production',
  fetchClientSecrets: async () => {
    const response = await fetch('/api/tortus/client-secrets');
    const data = await response.json();
    return { launchToken: data.launchToken };
  },
});
```

<Tip>
  Because tokens are short-lived, have your backend mint a fresh one on each call rather than
  caching it client-side.
</Tip>
