---
title: Authentication
path: getting-started/authentication
status: published
---

Every ScaiDrive request (except `/health` and public share-link endpoints) needs a Bearer token issued by ScaiKey. This page covers how to get one and how to keep using it.

## Token format

ScaiDrive tokens are standard RS256- or ES256-signed JWTs issued by ScaiKey. The server validates them on every request by fetching ScaiKey's JWKS and verifying the signature.

Claims ScaiDrive reads:

| Claim | Meaning |
|-------|---------|
| `sub` | User ID (`usr_...`) |
| `tenant_id` | Tenant the user belongs to (`tnt_...`) |
| `partner_id` | Partner (managed service provider) the tenant belongs to |
| `email`, `name` | Profile |
| `groups` | Array of group IDs (`grp_...`) |
| `scope` | Space-separated scopes — see below |
| `iss` | Must match ScaiKey's URL (or the tenant-scoped issuer `{scaikey}/tenants/{tenant_id}`) |
| `exp` | Expiration — expired tokens are rejected with `401` |
| `act` | RFC 8693 actor claim. Present when the token reached us through a token-exchange flow (e.g., ScaiSpeak acting on behalf of a user). ScaiDrive reads `act.client_id` (falling back to `act.sub`) and records it on every audit event as `service_account`, so the audit trail names the delegating service alongside the acting user. The audience claim (`aud`) is **not** checked — narrow audience at exchange time for ScaiKey's records, but ScaiDrive doesn't gate on it |

## Scopes

ScaiDrive recognizes these application scopes:

| Scope | Grants |
|-------|--------|
| `files:read` | Read files, list folders, list shares |
| `files:write` | Create, update, delete files and folders |
| `tenant:admin` | Manage users, groups, quotas, connectors within the tenant |
| `partner:admin` | Manage all tenants owned by the caller's partner |
| `admin:*` | Full platform admin |
| `*` | Superuser (reserved for platform operators) |

A token with no application scopes gets `tenant:admin` by default on the assumption that ScaiKey already gates who can call ScaiDrive — i.e., ScaiKey is the enforcement point for "is this user allowed to use ScaiDrive at all." You can narrow the scope at token-issue time if you want to restrict a service token.

OIDC scopes like `openid`, `profile`, `email`, `offline_access` are recognized but not used for authorization — they only matter to ScaiKey.

## How end users get a token

End users don't get tokens directly — their client does, through OAuth 2.0 Authorization Code with PKCE. The flow:

```mermaid
sequenceDiagram
  participant U as User
  participant C as Client app
  participant SD as ScaiDrive
  participant SK as ScaiKey
  C->>SD: GET /api/v1/auth/config
  SD-->>C: authorize_url, token_url, client_id
  C->>C: generate PKCE verifier + challenge
  C->>SK: redirect /authorize<br/>(code_challenge)
  U->>SK: complete SSO
  SK-->>C: redirect with auth code
  C->>SD: POST /api/v1/auth/token<br/>(code + code_verifier)
  SD->>SK: exchange code (server-side)
  SK-->>SD: access_token, refresh_token
  SD-->>C: tokens
```

1. Client calls `GET /api/v1/auth/config` to discover ScaiKey's authorize/token URLs and the registered client ID.
2. Client generates a PKCE code verifier and challenge, redirects the user to ScaiKey's authorize endpoint.
3. User completes SSO at ScaiKey. ScaiKey redirects to the registered callback URL with an authorization code.
4. Client exchanges the code at `POST /api/v1/auth/token` (ScaiDrive proxies this to ScaiKey so clients only need the ScaiDrive URL).
5. Response contains `access_token`, `refresh_token`, `expires_in`.

The web client does this automatically. The desktop and mobile clients do it via a browser pop-up. For your own integration, the OAuth dance only matters if you're building another end-user app.

### Discover OAuth config

```bash
curl $SCAIDRIVE_URL/api/v1/auth/config
```

```json
{
  "authorize_url": "https://scaikey.scailabs.ai/oauth2/authorize",
  "token_url": "https://scaidrive.scailabs.ai/api/v1/auth/token",
  "client_id": "scaidrive-desktop",
  "desktop_callback_url": "https://scaidrive.scailabs.ai/api/v1/auth/callback"
}
```

### Exchange authorization code

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/auth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=<authorization_code>" \
  -d "redirect_uri=<your_callback>" \
  -d "code_verifier=<pkce_verifier>"
```

```python
import httpx, os

resp = httpx.post(
    f"{os.environ['SCAIDRIVE_URL']}/api/v1/auth/token",
    data={
        "grant_type": "authorization_code",
        "code": os.environ["CODE"],
        "redirect_uri": "http://localhost:8765/callback",
        "code_verifier": os.environ["VERIFIER"],
    },
)
tokens = resp.json()
print(tokens["access_token"])
```

```typescript
const resp = await fetch(`${process.env.SCAIDRIVE_URL}/api/v1/auth/token`, {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    code: process.env.CODE!,
    redirect_uri: "http://localhost:8765/callback",
    code_verifier: process.env.VERIFIER!,
  }),
});
const tokens = await resp.json();
```

Response:

```json
{
  "access_token": "eyJhbGc...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rt_...",
  "id_token": "eyJhbGc..."
}
```

## How services get a token

For machine-to-machine (a backend service calling ScaiDrive), ask your tenant admin to create a **service token** in ScaiKey. Service tokens belong to a synthetic user — no human interaction, no refresh flow required — and have a long or configurable lifetime.

Use it the same way as a user token:

```bash
curl -H "Authorization: Bearer $SCAIDRIVE_SERVICE_TOKEN" \
     $SCAIDRIVE_URL/api/v1/users/me
```

**Never put service tokens in client-side code.** A JavaScript bundle in a browser leaks the token to anyone who opens DevTools. Keep service tokens on the server.

## Refreshing a token

Access tokens expire (default: 1 hour). Refresh tokens last longer and rotate on use.

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/auth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=<your_refresh_token>"
```

```python
resp = httpx.post(
    f"{scaidrive_url}/api/v1/auth/token",
    data={"grant_type": "refresh_token", "refresh_token": refresh_token},
)
new_tokens = resp.json()
```

Your client should refresh proactively before expiration, or reactively when a request returns `401 AUTH_TOKEN_EXPIRED`. Don't busy-refresh — if a refresh fails, the user re-authenticates.

## Authorization — what the token is allowed to do

Authentication answers "who is this request from." Authorization answers "what can they do." ScaiDrive resolves authorization in this order:

1. **Global admin role.** Users with the `super_admin` role (set in the ScaiDrive database, not in the JWT) can do anything. `tenant_admin` can do anything within their tenant.
2. **Share membership.** The user's role in the share (`owner`, `admin`, `contributor`, `reader`) defines the default permissions for every resource in that share.
3. **ACL entries.** Files and folders can override share-level permissions with per-resource allow/deny entries. Inheritance applies unless explicitly broken.
4. **External link scope.** Requests authenticated with a link token (not a user JWT) are restricted to the link's allowed actions and resource scope.

See [Permissions and ACLs](/docs/scaidrive/core-concepts/permissions-and-acls) for the full resolution rules.

## JIT provisioning

The first time a user presents a valid ScaiKey JWT that ScaiDrive has never seen, ScaiDrive creates a local user record automatically. No admin action is needed.

Subsequent ScaiKey webhooks on `user.updated`, `group.updated`, etc. keep the local records in sync. If you disable a user in ScaiKey, ScaiDrive sees the webhook, marks them suspended, and subsequent requests with their (still-unexpired) JWT fail with `AUTHZ_USER_SUSPENDED`.

## What's next

- [Your First Integration](/docs/scaidrive/getting-started/your-first-integration) — end-to-end integration example.
- [Authentication Reference](/docs/scaidrive/reference/authentication) — all auth endpoints in detail.
- [Permissions and ACLs](/docs/scaidrive/core-concepts/permissions-and-acls) — the authorization model.