# Sign in with dzbot

Let your players sign in to your own website or app with their dzbot account. They sign in on your server's dzbot page — linking their game account there if they haven't yet — confirm what your app may do, and land back in your app. You build no login, no account linking and no alt detection: dzbot is where your players are managed anyway.

## Set up

In your server's admin area, open **Developers**, create an [app](https://developers.dzbot.de/api/apps) and open its **Sign in with dzbot** tab:

- **Redirect URIs** — where dzbot sends the user back, e.g. `https://your-site.example/auth/callback`. https only; `http://localhost` works for development.
- **Browser origins** — for apps that call the API from the browser, e.g. `https://your-site.example`.
- **Client secret** — only for apps with a backend (confidential clients). A browser-only app has none and relies on PKCE.

The sign-in works on the servers the app is enabled for. The token it produces is bound to the one server the user signed in on.

## Scopes are access rights

A scope is a dzbot access right, the same keys you see in the group editor — wildcards included: `player.shop.*`, `player.tickets.create`, `admin.tickets.view`. Who the user is (player, main account, linked accounts) needs no scope.

What a token may do is the overlap of three things: the scopes the user confirmed, the app's own rights ceiling, and the user's real rights on the server. A player who confirms `admin.*` hands over nothing an admin could do, because they cannot do it themselves.

The consent page lists every concrete right a request covers within the ceiling, and exactly those are granted — a wildcard is resolved at that moment, so rights added to dzbot or to the ceiling later need a new sign-in.

Some endpoints stay closed to sign-in tokens whatever their scopes: the account, OAuth and organization areas, faction actions, live rooms, the claim, event sign-ups, impersonation and magic links, the AI Companion and the developer apps themselves (`403 app_token_path_not_allowed`).

## In the browser

A public client: no secret, PKCE, tokens in `sessionStorage`. dzbot is your whole backend.

```
import { Dzbot, OAuthClient } from '@dzbot/sdk'

const oauth = new OAuthClient({
  clientId: 'your-client-id',
  redirectUri: 'https://your-site.example/auth/callback',
  server: 'your-server',
})

// Sign-in button
location.href = await oauth.authorizeUrl({ scope: ['player.shop.access', 'player.shop.checkout'] })

// On /auth/callback
await oauth.handleCallback(location.href)

const dzbot = new Dzbot({ server: 'your-server', oauth })
const me = await dzbot.me()
console.log(me.player?.name, me.accounts)
```

## On a server

A confidential client with its secret. Give every browser session its own `TokenStore` (memory for a single process, Redis or your database otherwise) — the store keeps the pending sign-in between redirect and callback and the tokens afterwards.

```
const oauth = new OAuthClient({
  clientId: process.env.DZBOT_CLIENT_ID,
  clientSecret: process.env.DZBOT_CLIENT_SECRET,
  redirectUri: 'https://your-site.example/auth/callback',
  server: 'your-server',
  store: sessionStore(req), // get(key), set(key, value), remove(key)
})
```

The [starter bot](https://github.com/siemendev/dzbot-starter) does exactly this with Express.

## Tokens

| Method | Does |
| --- | --- |
| `authorizeUrl({ scope, state? })` | The URL to send the user to, on `https://<server>.dzbot.de/oauth/authorize` |
| `handleCallback(url)` | Checks `state`, exchanges the code, stores the tokens. A refused consent throws `OAuthError` with code `access_denied` |
| `accessToken()` | A valid access token, refreshed a minute before it runs out |
| `refresh()` | Refreshes now; concurrent calls share one request. When dzbot refuses, the user is signed out (`login_required`) |
| `isSignedIn()` / `tokens()` | Whether tokens are stored, and which |
| `revoke()` | Signs out at dzbot and forgets the tokens |

Access tokens live one hour. Refresh tokens rotate on every use and live 30 days for confidential clients, 7 days for public ones. Users see and revoke connected apps on their profile.

## Without the SDK

Standard OAuth 2 authorization code with PKCE (S256):

```
GET https://<server>.dzbot.de/oauth/authorize?response_type=code&client_id=…&redirect_uri=…
    &scope=player.shop.access%20player.tickets.create&state=…&code_challenge=…&code_challenge_method=S256

POST https://api.dzbot.de/oauth/token   (application/x-www-form-urlencoded)
  grant_type=authorization_code&code=…&redirect_uri=…&client_id=…&code_verifier=…[&client_secret=…]
  grant_type=refresh_token&refresh_token=…&client_id=…[&client_secret=…]
→ { token_type, expires_in, access_token, refresh_token, scope }

POST https://api.dzbot.de/oauth/revoke  token=…
```

Call the API with `Authorization: Bearer <access_token>` and the `X-Server-Subdomain` of the server the user signed in on; another server answers `401 token_server_mismatch`.

---

Web page: https://developers.dzbot.de/sdk/oauth · All pages: https://developers.dzbot.de/llms.txt
