Skip to content

Authentication

S3 Lens supports OpenID Connect (OIDC) sign-in with server-side session cookies and policy-based authorization. When auth.enabled is false (the default), all API routes are open — suitable for local development only.

Do not expose S3 Lens to untrusted networks without authentication enabled.

Turn on auth and connect your identity provider:

policy:
enabled: true
defaults: true
roles:
viewer:
policies: [viewer]
editor:
policies: [editor]
auth:
enabled: true
oidc:
issuer: "https://idp.example/realms/s3lens"
client_id: "s3lens"
client_secret: "${OIDC_CLIENT_SECRET}"
redirect_uri: "https://s3lens.example.com/api/auth/callback"
identity:
subject_claim: "sub"
groups_claim: "groups"
bindings:
- role: viewer
groups: ["s3lens-viewers"]
- role: editor
groups: ["s3lens-editors"]

Set S3LENS_SESSION_SECRET (32+ bytes) for production session signing.

For a visual overview of session handling, identity mapping, and how checks connect to PolicyEngine and Grants, see Authorization reference — End-to-end flow.

  1. A user opens S3 Lens and hits a protected page
  2. The browser is redirected to GET /api/auth/login
  3. S3 Lens starts an Authorization Code flow with PKCE against your OIDC provider
  4. After the user signs in, the provider redirects to GET /api/auth/callback
  5. S3 Lens validates the ID token and sets an HttpOnly s3lens_session cookie
  6. Subsequent API requests include the cookie automatically

Sign out via GET /api/auth/logout.

These routes work without a session:

  • GET /api/health
  • GET /api/auth/login, /api/auth/callback, /api/auth/logout, /api/auth/me

When auth is enabled, the UI, OpenAPI docs (/api/openapi.json, /api/scalar), and status endpoints also require a valid session.

Register an OIDC client with your provider (Keycloak, Auth0, Okta, Azure AD, etc.) using:

Setting Value
Client type Confidential (requires client secret)
Redirect URI Your S3 Lens callback URL, e.g. https://s3lens.example.com/api/auth/callback
Grant type Authorization Code
Scopes At minimum openid; include profile and a groups claim if you use group bindings

Configure the matching values under auth.oidc in s3lens.yaml:

auth:
oidc:
issuer: "https://idp.example/realms/s3lens"
client_id: "s3lens"
client_secret: "${OIDC_CLIENT_SECRET}"
redirect_uri: "https://s3lens.example.com/api/auth/callback"
identity:
subject_claim: "sub" # JWT claim for user ID
groups_claim: "groups" # JWT claim for group membership

Imported local desktop Environments still use the same /api/auth/callback route, but Electron assigns their server an ephemeral loopback port. Register a loopback pattern with the identity provider in addition to the normal browser callback. The local Keycloak setup uses http://127.0.0.1/*, which covers http://127.0.0.1:<port>/api/auth/callback.

S3 Lens separates three concepts:

Concept Example Where it lives
Role editor, viewer Top-level roles: — bundles of policies you define
IdP group s3lens-editors Your identity provider — referenced in auth.bindings
Policy media-editor, uploader policy.policies: — allow/deny rules

Map IdP groups to app roles:

auth:
bindings:
- groups: ["s3lens-viewers"]
role: viewer
- groups: ["s3lens-editors"]
role: editor

Assign policies to specific users by subject (JWT sub claim):

auth:
bindings:
- subjects: ["contractor-42"]
policies: [custom-readonly]

For testing without an IdP, assign roles directly:

auth:
local_users:
- subject: "local-editor"
roles: [editor]

Local users skip IdP group lookup. Remove local_users in production.

s3lens.yaml.example includes a full auth setup for local testing with Docker Compose and Keycloak:

Terminal window
docker compose up -d
docker compose logs keycloak-bootstrap # wait for "Done."
s3lens --config s3lens.yaml.example --host 127.0.0.1 --port 8085

Keycloak test users (password = username):

User Role Access
viewer viewer Read-only, all providers
editor editor Read, write, and delete everywhere
garage garage-reader Read-only on Garage only
seaweed seaweed-editor Write/delete on SeaweedFS only
uploader uploader uploads/ prefix only
admin admin Full admin
nopresign viewer-no-presign Read-only, presign denied

Keycloak admin console: http://127.0.0.1:8081/admin (admin / admin).

The web UI fetches allowed canonical action strings from the API (GET …/permissions), then shows or hides controls (upload, delete, download, and so on). Mutating and read routes still enforce policy on every request.

Returns session identity only — who is signed in and whether auth is enabled:

{
"authenticated": true,
"auth_enabled": true,
"subject": "local-editor",
"groups": []
}

When auth is disabled, subject is "anonymous" and auth_enabled is false. Permissions are not included on /me; fetch them from the resource endpoints below when rendering a provider or bucket page.

Returns every defined role and the normalized rules of every compiled policy. Built-in policies and imported IAM documents are represented in the same rule shape the policy engine evaluates. When authentication is enabled, the caller must have the admin role.

The provider settings Access section uses this response to navigate from a role to its referenced policies and from each policy to its allow/deny rules, actions, and resource selectors.

GET /api/providers/{provider}/permissions

{ "actions": ["providers:read", "buckets:read", "buckets:create"] }

Only allowed actions are included. Omitted actions (for example objects:write) are not evaluated on this endpoint at all.

GET /api/providers/{provider}/buckets/{bucket}/permissions

{
"actions": [
"objects:read",
"objects:write",
"objects:delete",
"objects:presign",
"buckets:delete"
]
}

Same rule: only actions from Action::bucket_endpoint_actions() that pass policy checks appear in the array.

When policy is disabled, every action in the endpoint’s list is returned.

Permission responses drive UI visibility only. Upload, delete, presign, and every other mutating route still call PolicyEngine::allowed independently.

Tools like oauth2-proxy can protect the network perimeter, but they cannot express S3 Lens action rules (per-bucket write, presign deny, provider-scoped access, and so on). Use OIDC plus access policies for application-level control. A reverse proxy can still add TLS termination or an extra authentication layer in front of S3 Lens.

Define what each role can do: Access policies

For route-to-action mapping, list visibility, and troubleshooting: Authorization reference