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

# Login button

> Add Sign in with CabalSpy ID to any website with two lines of HTML. The widget runs PKCE for browser apps and shows an account menu once signed in.

The login button is a small script that renders a **Sign in with CabalSpy ID** button. Browser apps get the whole OpenID Connect flow with PKCE, state and nonce out of the box. Server-side apps point the button at their own login route. Once someone is signed in, the button turns into their avatar with a menu.

<Note>
  On the CabalSpy ID sign-in page people choose how to sign in: with X, with Telegram through the CabalSpy ID bot, or with Google. Nothing changes for your platform, the button and the flow are the same. Users without X arrive with `x` set to null, see [Users without X](/cabalspy-id/openid-connect#users-without-x).
</Note>

## Add the button

```html theme={null}
<div data-csid-login data-client-id="csid_..."></div>
<script src="https://id.cabalspy.xyz/widget.js" async></script>
```

Every element with `data-csid-login` becomes a button. The design comes from the button builder in the dashboard, the `data-*` attributes below override it per element. The button renders in a shadow root, so your page styles do not leak into it.

## Attributes

<ParamField path="data-client-id" type="string" required>
  Your Instance ID (`csid_...`).
</ParamField>

<ParamField path="data-href" type="string">
  Server-side apps: your own login route, for example `/auth/csid/login`. A click navigates there and your server starts the flow with its client secret.
</ParamField>

<ParamField path="data-redirect-uri" type="string">
  Browser apps: where CabalSpy ID sends the user back. Defaults to the current page (origin plus path). Must be registered exactly as a redirect URI.
</ParamField>

<ParamField path="data-scope" default="openid profile" type="string">
  Browser apps: the scopes to request, separated by spaces.
</ParamField>

<ParamField path="data-theme" type="string">
  `light` draws an outlined white button, any other value a filled button in `data-color`.
</ParamField>

<ParamField path="data-color" type="string">
  Button color, for example `#01b400`.
</ParamField>

<ParamField path="data-text-color" type="string">
  Text color.
</ParamField>

<ParamField path="data-shape" type="string">
  One of `square`, `rounded` or `pill`.
</ParamField>

<ParamField path="data-size" type="string">
  One of `sm`, `md` or `lg`.
</ParamField>

<ParamField path="data-label" default="Sign in with CabalSpy ID" type="string">
  Button text.
</ParamField>

<ParamField path="data-full-width" type="string">
  `true` stretches the button to the width of its container.
</ParamField>

<ParamField path="data-show-logo" type="string">
  `false` hides the ID mark.
</ParamField>

<ParamField path="data-user" type="string">
  Server-side apps: the `sub` of the signed-in person. The button then shows their avatar and menu.
</ParamField>

<ParamField path="data-logout-href" type="string">
  Server-side apps: where **Sign out** in the menu navigates, for example `/logout`.
</ParamField>

## Browser apps

For a public client (no secret), the widget does everything. On click it creates a PKCE verifier, state and nonce, sends the user to CabalSpy ID, exchanges the code when they come back, checks the issuer, nonce and audience, loads the user info and fires `csid:login`.

```javascript theme={null}
window.addEventListener("csid:login", (e) => {
  const { user, idToken, accessToken, expiresIn } = e.detail;
  // send idToken to your backend and verify it there
});

window.addEventListener("csid:error", (e) => {
  console.warn("CabalSpy ID sign-in failed", e.detail.error, e.detail.description);
});
```

| Field | Meaning |
| - | - |
| `user` | The user info response, see [claims](/cabalspy-id/openid-connect#claims). |
| `idToken` | The signed ID token (ES256). |
| `accessToken` | Access token for `/oauth/userinfo`, valid for `expiresIn` seconds. |
| `expiresIn` | Lifetime of the access token in seconds (900). |

<Warning>
  The `user` object comes from the browser. Before you trust it, send `idToken` to your backend and verify it with the public keys at `https://id.cabalspy.xyz/oauth/jwks`, see the [Quickstart](/cabalspy-id/quickstart).
</Warning>

Register the page as a redirect URI and its origin as a web origin of your instance, the token request is sent from the browser.

## Server-side apps

A confidential client keeps its secret on the server, so the button only starts the flow.

```html theme={null}
<div data-csid-login data-client-id="csid_..." data-href="/auth/csid/login"></div>
<script src="https://id.cabalspy.xyz/widget.js" async></script>
```

Your `/auth/csid/login` route redirects to the authorization endpoint, and your callback exchanges the code with the client secret. See [OpenID Connect](/cabalspy-id/openid-connect) for the requests.

To show the signed-in state, render the person's `sub` and your logout route on the element:

```html theme={null}
<div data-csid-login data-client-id="csid_..."
     data-user="USER_SUB" data-logout-href="/logout"></div>
```

## Signed-in menu

Once signed in, the button shows the person's avatar, name and KOL badge. The menu has three entries.

| Entry | Target |
| - | - |
| View profile | The profile page set for your instance in the dashboard, otherwise the CabalSpy ID profile `https://id.cabalspy.xyz/u/<handle>`. |
| Settings | The person's CabalSpy ID settings. |
| Sign out | Fires `csid:logout`. With `data-logout-href` the browser goes there. |

For users beyond your plan's user limit the button shows **Signed in** without a name or avatar, because you receive no profile data for them.

## JavaScript API

The script exposes `window.CSID`.

| Member | Description |
| - | - |
| `CSID.login({ clientId, redirectUri, scope })` | Starts the browser flow from your own UI. |
| `CSID.onLogin(callback)` | Calls `callback` with the same object as `csid:login`, also if the sign-in already happened. |
| `CSID.setUser(clientId, user)` | Shows someone as signed in, for apps that restore their own session. |
| `CSID.signOut(clientId)` | Forgets the signed-in user on this device and fires `csid:logout`. |
| `CSID.mount()` | Renders buttons added to the page after load. |
| `CSID.user` | The result of the last sign-in on this page, or null. |

| Event | Detail |
| - | - |
| `csid:login` | `{ user, idToken, accessToken, expiresIn }` |
| `csid:logout` | `{ clientId }` |
| `csid:error` | `{ error, description }` |

In browser apps the signed-in user is remembered on the device until `CSID.signOut()` is called or the user signs out from the menu.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.