Personal Access Tokens¶
Every authenticated user — local or OIDC-provisioned — can generate their own Personal Access Tokens (PATs) from the account menu (account avatar → Personal Access Tokens).
A PAT is required for docker login
The registry proxy's Basic Auth only accepts a Docker-scoped PAT as
the password — a real account password (including the built-in admin's)
is rejected there. This isn't just a convenience for OIDC users, who
have no local password to begin with: every account, local or OIDC,
must generate a Docker-scoped token before it can docker login,
pull, or push against Portalcrane's registry proxy. See the
Docker CLI Walkthrough
for a full example.
The raw token value is shown only once, at creation time. Internally it
is stored as a bcrypt hash and identified by a unique signed jti claim —
Portalcrane never persists (or can display again) the plaintext token.
Scopes¶
Every token is created with exactly one of two mutually exclusive scopes:
| Scope | docker login |
REST API / Swagger | 16-char short token |
|---|---|---|---|
| Docker | ✅ | ❌ | ✅ |
| API | ❌ | ✅ | ❌ |
A token created for one scope is rejected on the other — a Docker-scoped CI credential can never authenticate against the REST API, and an API-scoped key can never be used to push or pull an image. This means a leaked CI secret has a strictly bounded blast radius.
Creating a token¶
curl -X POST http://<host>:8000/api/auth/tokens \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name": "CI pipeline", "scope": "docker", "expires_in_days": 90}'
Response (abridged) — save raw_token now, it won't be shown again:
{
"id": "b3f1...",
"name": "CI pipeline",
"scope": "docker",
"created_at": "2026-07-31T10:00:00Z",
"expires_at": "2026-10-29T10:00:00Z",
"raw_token": "pct_eyJhbGciOi...",
"short_token": "aZ3xQ...16chars",
"short_token_hint": "aZ3x…6chr"
}
expires_in_days defaults to 90 days if omitted. short_token is only
issued for docker-scoped tokens.
Using a Docker-scoped token¶
Use either the full token or the short 16-character convenience token as
the password for docker login:
docker login <host>:8000 -u alice -p pct_eyJhbGciOi...
# or, shorter:
docker login <host>:8000 -u alice -p aZ3xQ...16chars
The short token is purely a docker login convenience for typing/pasting
by hand — it is never accepted where an API scope is required.
Using an API-scoped token¶
Send it as a Bearer token on any REST API call:
In Swagger UI (/api/docs, enabled with SWAGGER_ENABLED=true): click
Authorize, choose PersonalAccessToken, paste the token, and every
request in that session is authenticated as you.
The registry proxy isn't in Swagger
The /v2/... registry proxy endpoints implement the Docker Registry
HTTP API and are intentionally hidden from Swagger — they're only
meaningful to a Docker client, not a REST API consumer.
Revoking a token¶
Users can revoke only their own tokens; admins can revoke anyone's. Expired or revoked tokens are rejected immediately on next use. Deleting a user account also revokes every one of their tokens.
Disabling the feature entirely¶
Set API_KEYS_ENABLED=false to disarm PATs system-wide: token endpoints
return 403, existing API-scoped keys stop working against the REST API,
and the token-generation panel disappears from the account menu.
This also disables docker login entirely
Since a Docker-scoped PAT is the only accepted credential for the
registry proxy, disabling PATs removes every account's ability to
docker login, pull, or push — the proxy returns 403 for every
Basic Auth attempt regardless of whether a token was already issued.
Only turn this off if you don't need Docker CLI access to Portalcrane's
registry at all (e.g. the UI is your only client).