rommapp_docs/docs/developers/api-authentication.md
Claude f65b062cce
docs: draft Wave 1 anchor pages (landing + quick-start + install/admin/dev)
First batch of real Wave 1 content for the v5.0 overhaul. These pages
establish the tone and cross-linking pattern for the rest of the wave.

- index.md: routing landing with intent-based cards
- getting-started/quick-start.md: 15-min docker-compose happy path,
  reusing the existing compose snippet and asciinema cast
- install/docker-compose.md: canonical production reference with
  volume table, env var table, and hardening checklist
- install/image-variants.md: slim vs full matrix + what's actually
  different between them
- install/reverse-proxy.md: Caddy / nginx / Traefik / NPM recipes
  with ROMM_BASE_URL guidance
- administration/users-and-roles.md: full 19-scope matrix across
  Viewer/Editor/Admin, invite flow, OIDC hooks
- developers/api-authentication.md: session / Basic / OAuth2 /
  Client API Token / OIDC flows with error semantics

Builds clean with --strict.
2026-04-18 16:15:48 +00:00

151 lines
5.5 KiB
Markdown

---
title: API Authentication
description: How to authenticate to the RomM REST API — session cookies, Basic, OAuth2 tokens, client tokens, and OIDC.
---
# API Authentication
RomM's REST API accepts four authentication modes. Pick the one that matches your client:
| Mode | Who it's for | How the credential is carried |
| --- | --- | --- |
| **Session cookie** | Browser UI | `Cookie: session=…` after `POST /api/auth/login` |
| **HTTP Basic** | Quick scripts, curl one-liners | `Authorization: Basic <base64(user:pass)>` |
| **OAuth2 Bearer** | Automation, CI, third-party apps | `Authorization: Bearer <jwt>` |
| **Client API Token** | Companion apps (Argosy, Grout, Playnite, custom scripts) | `Authorization: Bearer rmm_<token>` |
| **OIDC session** | Users who sign in via SSO | Same as session cookie, but issued after OIDC callback |
All of them resolve to the same scope model — see the [scope matrix in Users & Roles](../administration/users-and-roles.md#scope-matrix). A request is allowed if the active identity holds all scopes the endpoint requires.
## Base URL
```text
https://<your-instance>/api
```
When RomM is behind a reverse proxy (as it should be in production), that's your public URL. When running locally without a proxy, the container listens on port `8080`.
## Session login (browsers)
```http
POST /api/auth/login
Content-Type: application/x-www-form-urlencoded
username=alice&password=s3cret
```
Response sets a `session` cookie. Subsequent requests from the same browser are authenticated automatically.
Log out:
```http
POST /api/auth/logout
```
For OIDC logins, hitting `/api/auth/logout` also triggers RP-Initiated Logout if your OIDC provider supports it (configured via `OIDC_END_SESSION_ENDPOINT`).
## HTTP Basic
Fine for quick scripts. Avoid in shared environments — the credentials are sent on every request.
```bash
curl -u alice:s3cret https://romm.example.com/api/roms
```
```python
import requests
from requests.auth import HTTPBasicAuth
r = requests.get("https://romm.example.com/api/roms",
auth=HTTPBasicAuth("alice", "s3cret"))
```
## OAuth2 Bearer token
The RomM backend implements the OAuth2 password grant. Exchange credentials for a short-lived access token and a refresh token:
```http
POST /api/token
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=alice&password=s3cret&scope=roms.read%20roms.write
```
```json
{
"access_token": "eyJhbGciOi...",
"token_type": "bearer",
"expires_in": 900,
"refresh_token": "eyJhbGciOi..."
}
```
Access tokens are HS256-signed JWTs valid for ~15 minutes. Send them as:
```
Authorization: Bearer eyJhbGciOi...
```
Refresh before expiry:
```http
POST /api/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=eyJhbGciOi...
```
Request only the scopes you need — RomM will issue a token with the intersection of what you asked for and what the user has.
## Client API tokens (for companion apps)
For anything long-lived — a running companion app, a cron job, a CI integration — use **Client API Tokens** instead of OAuth2. They're issued per-user from **Administration → Client API Tokens**, carry a subset of the user's scopes, and don't expire unless you set an expiry.
Token format: `rmm_` + 40 hex chars. Use it as a bearer:
```bash
curl -H "Authorization: Bearer rmm_abcdef0123456789..." \
https://romm.example.com/api/roms
```
Each user gets up to 25 active tokens. Tokens can be paired with a device via the [pairing flow](../ecosystem/client-api-tokens.md) — useful when you don't want to type a long token on a handheld.
## OIDC
Users signing in through an OIDC provider get a regular RomM session, same as username/password login. For the API side this means you can't use an OIDC access token directly — authenticate the user through the browser first (they'll be redirected to the OIDC provider, then back to RomM), then use the resulting session cookie, **or** mint a Client API Token for programmatic use.
OIDC provider setup lives in [Administration → OIDC](../administration/oidc/index.md).
## Which scopes do I need?
Every endpoint in the [API Reference](api-reference.md) lists its required scopes. The short version:
- **Read**-ish endpoints want the matching `*.read` scope.
- **Write**-ish endpoints want `*.write`.
- **Admin** endpoints (anything under `/api/users` beyond `me`, `/api/tasks/run`) want `users.read`, `users.write`, or `tasks.run`.
A token that holds `users.write` also implicitly grants lesser scopes like `users.read` or `me.read` — RomM doesn't require you to list them all. But a token that holds only `roms.read` can't write, even if the underlying user is an Admin.
## Errors
| HTTP | Meaning |
| --- | --- |
| `401 Unauthorized` | No credential, expired credential, bad credential. |
| `403 Forbidden` | Authenticated, but the identity lacks a required scope. |
| `404 Not Found` | The resource doesn't exist — or, for privacy, the identity can't see it. |
When debugging a 403, check:
1. The **user's role** in Administration → Users.
2. The **token's scopes** (for OAuth2/Client API Tokens) — scopes are narrower than the user's role by default.
3. The endpoint's scope requirements in the [API Reference](api-reference.md).
## OpenAPI
The full machine-readable schema is served at `/openapi.json`. It's the source of truth for generated clients, Postman collections, and the in-docs [API Reference](api-reference.md).
```bash
curl https://romm.example.com/openapi.json > romm-openapi.json
```
See [Consuming OpenAPI](openapi.md) for codegen tips.