Files
artifactapi/docs/common/auth.md
T
unkin-agent f0ee593164 Gate local repo writes behind OIDC tokens (#169)
Local repo writes (file uploads/deletes, object evictions, native publishes, docker push) are open to anyone who can reach the service; only remote/virtual config is token-gated.

- add `ARTIFACTAPI_AUTH_UPLOADS` (default false) gating every local write path behind the OIDC verifier
- accept the token as Bearer, Token, Basic password (any user) or bare header value on every gated path, including remote/virtual config writes
- serve a `/v2/token` Bearer challenge flow so `docker login` with the token enables push; missing, expired or invalid credentials get an anonymous token so pulls stay open
- document minting a Vault identity token and passing it per client

Reviewed-on: #169
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-10-25 15:17:32 +11:00

2.1 KiB

Write authentication

artifactapi verifies writes against an OIDC issuer's JWKS, such as a Vault identity OIDC role. Reads, probes and anonymous pulls never need a token.

Variable Gates
ARTIFACTAPI_OIDC_ISSUER + ARTIFACTAPI_OIDC_AUDIENCE creating, updating and deleting remotes and virtuals
ARTIFACTAPI_AUTH_UPLOADS=true also every local repo write: PUT/DELETE /api/v2/remotes/<name>/files/*, DELETE /api/v2/{remotes,locals}/<name>/objects/*, PUT/POST/DELETE /api/v1/local/<repo>/* (npm publish, gem push/yank, ansible-galaxy publish) and Docker Registry V2 push/delete under /v2

Any token from the issuer whose aud is in ARTIFACTAPI_OIDC_AUDIENCE may write to every repo.

Minting a token

vault read -field=token identity/oidc/token/<role> > token

The role's client_id must be one of the configured audiences.

Passing the token

The token is accepted as Authorization: Bearer <token>, as Token <token>, as the password of HTTP Basic credentials with any username, or as a bare header value.

# curl
curl -fsSL -H "Authorization: Bearer $(cat token)" --upload-file f.tgz \
  https://artifactapi.example.com/api/v2/remotes/<repo>/files/f.tgz
curl -fsSL -u "ci:$(cat token)" --upload-file f.tgz \
  https://artifactapi.example.com/api/v2/remotes/<repo>/files/f.tgz

# docker / podman / helm (OCI): any username, token as password
docker login artifactapi.example.com -u ci --password-stdin < token
helm registry login artifactapi.example.com -u ci --password-stdin < token

# npm (.npmrc)
//artifactapi.example.com/api/v1/local/<repo>/:_authToken=<token>

# gem
GEM_HOST_API_KEY="$(cat token)" gem push --host https://artifactapi.example.com/api/v1/local/<repo> mygem-1.0.0.gem

# ansible-galaxy
ansible-galaxy collection publish c.tar.gz \
  --server https://artifactapi.example.com/api/v1/local/<repo>/ --api-key "$(cat token)"

With uploads gated, /v2/ answers anonymous requests with a Bearer challenge pointing at /v2/token. That endpoint returns the verified Basic password as the registry token, or an anonymous token that only reads, so docker pull keeps working without a login.