Skip to content

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:

curl -H "Authorization: Bearer pct_eyJhbGciOi..." http://<host>:8000/api/auth/me

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

curl -X DELETE http://<host>:8000/api/auth/tokens/<token_id> \
  -H "Authorization: Bearer <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).