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.
Enable authentication
Section titled “Enable authentication”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.
How sign-in works
Section titled “How sign-in works”For a visual overview of session handling, identity mapping, and how checks connect to PolicyEngine and Grants, see Authorization reference — End-to-end flow.
- A user opens S3 Lens and hits a protected page
- The browser is redirected to
GET /api/auth/login - S3 Lens starts an Authorization Code flow with PKCE against your OIDC provider
- After the user signs in, the provider redirects to
GET /api/auth/callback - S3 Lens validates the ID token and sets an HttpOnly
s3lens_sessioncookie - Subsequent API requests include the cookie automatically
Sign out via GET /api/auth/logout.
Public endpoints
Section titled “Public endpoints”These routes work without a session:
GET /api/healthGET /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.
Identity provider setup
Section titled “Identity provider setup”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 membershipImported 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.
Mapping users to roles
Section titled “Mapping users to roles”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 |
Group bindings
Section titled “Group bindings”Map IdP groups to app roles:
auth: bindings: - groups: ["s3lens-viewers"] role: viewer - groups: ["s3lens-editors"] role: editorDirect policy assignment
Section titled “Direct policy assignment”Assign policies to specific users by subject (JWT sub claim):
auth: bindings: - subjects: ["contractor-42"] policies: [custom-readonly]Local users (development)
Section titled “Local users (development)”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.
Built-in roles and test setup
Section titled “Built-in roles and test setup”s3lens.yaml.example includes a full auth setup for local testing with Docker Compose and Keycloak:
docker compose up -ddocker compose logs keycloak-bootstrap # wait for "Done."s3lens --config s3lens.yaml.example --host 127.0.0.1 --port 8085Keycloak 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).
UI permissions
Section titled “UI permissions”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.
GET /api/auth/me
Section titled “GET /api/auth/me”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.
GET /api/auth/access
Section titled “GET /api/auth/access”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.
Provider permissions
Section titled “Provider permissions”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.
Bucket permissions
Section titled “Bucket permissions”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.
Permissions vs enforcement
Section titled “Permissions vs enforcement”Permission responses drive UI visibility only. Upload, delete, presign, and every other mutating route still call PolicyEngine::allowed independently.
Reverse-proxy authentication
Section titled “Reverse-proxy authentication”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.
Next steps
Section titled “Next steps”Define what each role can do: Access policies
For route-to-action mapping, list visibility, and troubleshooting: Authorization reference