Table of Contents
- Admin UI: OIDC Single Sign-On
Admin UI: OIDC Single Sign-On
Enterprise feature. OIDC Authorization Code login on the
weed adminUI is part of SeaweedFS Enterprise (chrislusf/seaweedfs-enterprise). The OSSweed adminbinary only supports the local-adminUser/-adminPasswordflow.For S3-side OIDC (token exchange, STS, role assumption), see OIDC Integration — that is a separate, OSS feature and is unrelated to admin-UI login.
This page covers configuring OIDC sign-in for the Admin UI: which keys are required, how to inject the client secret from a Kubernetes Secret, and how role mapping works.
How it works
- When OIDC is enabled, the login page renders a Sign in with OIDC button next to the local username/password form.
- Clicking it starts an Authorization Code flow against your IdP (Keycloak, Okta, Azure AD, Auth0, Google, AWS Cognito, …).
- On callback, the ID token is validated (signature via JWKS,
nonce,state, expiration). - Claims are mapped to an admin role (
adminorreadonly) usingrole_mappingrules (or theadmin_groups/readonly_groupsshortcuts). - The admin session lifetime is capped by the ID token's
exp— when the token expires, the user is signed out.
Local and OIDC auth can be enabled together. Local logins continue to work even if OIDC discovery fails at startup (the OIDC button is hidden and a warning is logged, instead of refusing to start).
Minimum configuration to make the OIDC button appear
The button is rendered only when an OIDC service is successfully constructed at startup. The minimum set is:
admin.oidc.enabled = trueadmin.oidc.issuer(HTTPS, except forlocalhostdevelopment)admin.oidc.client_idadmin.oidc.client_secretadmin.oidc.redirect_url(must match what the IdP has registered)- Either
admin.oidc.role_mapping.default_roleor one or more rules / group entries (so users can be mapped to a role)
If any required key is missing or invalid, the UI starts without the button and a warning is logged. Check the admin container logs at startup — that is where OIDC: Enabled (issuer: …) or the rejection reason will appear.
Configuration reference
Every key below can be set in security.toml under [admin.oidc] or as an environment variable. Environment variables use the WEED_ADMIN_OIDC_ prefix with dots replaced by underscores; env values override security.toml.
Key in security.toml |
Environment variable | Type | Notes |
|---|---|---|---|
enabled |
WEED_ADMIN_OIDC_ENABLED |
bool | Master switch. |
issuer |
WEED_ADMIN_OIDC_ISSUER |
string | Base URL; used for OIDC discovery (/.well-known/openid-configuration). |
client_id |
WEED_ADMIN_OIDC_CLIENT_ID |
string | OAuth client registered with the IdP. |
client_secret |
WEED_ADMIN_OIDC_CLIENT_SECRET |
string | OAuth client secret. Inject from a Secret in Kubernetes — see below. |
redirect_url |
WEED_ADMIN_OIDC_REDIRECT_URL |
string | Must match the IdP-registered URL exactly. Path is /login/oidc/callback (or <urlPrefix>/login/oidc/callback). |
scopes |
WEED_ADMIN_OIDC_SCOPES |
list / comma-separated | Defaults to openid,profile,email. openid is always added. |
jwks_uri |
WEED_ADMIN_OIDC_JWKS_URI |
string | Optional override; otherwise read from discovery. |
tls_ca_cert |
WEED_ADMIN_OIDC_TLS_CA_CERT |
string | Absolute path to a CA bundle (PEM) for the IdP's TLS cert. |
tls_insecure_skip_verify |
WEED_ADMIN_OIDC_TLS_INSECURE_SKIP_VERIFY |
bool | Skip IdP TLS verification. Testing only. |
role_mapping.default_role |
WEED_ADMIN_OIDC_ROLE_MAPPING_DEFAULT_ROLE |
admin | readonly |
Role assigned when no rule matches. Leave unset to require an explicit match. |
admin_groups |
WEED_ADMIN_OIDC_ADMIN_GROUPS |
list / comma-separated | Shortcut: each entry becomes a rule claim=groups, value=<entry>, role=admin. |
readonly_groups |
WEED_ADMIN_OIDC_READONLY_GROUPS |
list / comma-separated | Shortcut: each entry becomes a rule claim=groups, value=<entry>, role=readonly. |
[[admin.oidc.role_mapping.rules]] |
(not env-settable) | array of {claim, value, role} |
Use this for custom claims or non-groups matching. |
Precedence: CLI flag (where applicable) > env var > security.toml > default.
The
WEED_ADMIN_OIDC_ADMIN_GROUPS/WEED_ADMIN_OIDC_READONLY_GROUPSenv vars are convenience shortcuts. If your IdP exposes roles under a claim other thangroups, or you need regex/wildcard matching, use[[admin.oidc.role_mapping.rules]]insecurity.tomlinstead.
Role mapping
After token validation, the user's claims are evaluated against the rules:
[admin.oidc.role_mapping]
default_role = "readonly"
[[admin.oidc.role_mapping.rules]]
claim = "groups"
value = "seaweedfs-admin"
role = "admin"
[[admin.oidc.role_mapping.rules]]
claim = "groups"
value = "seaweedfs-readonly"
role = "readonly"
- If any rule produces role
admin, the user getsadmin. - Otherwise, if any rule produces
readonly, the user getsreadonly. - Otherwise,
default_roleis used. - If no rule matches and no
default_roleis set, the login is rejected.
The convenience env vars expand at startup into equivalent rules with claim=groups. So this:
env:
- name: WEED_ADMIN_OIDC_ADMIN_GROUPS
value: "seaweedfs-admin,platform-admins"
- name: WEED_ADMIN_OIDC_READONLY_GROUPS
value: "seaweedfs-readonly"
…is equivalent to the security.toml block above (plus an extra platform-admins → admin rule). They can be combined with explicit rules; both sets are evaluated.
security.toml example
[admin.oidc]
enabled = true
issuer = "https://keycloak.example.com/realms/seaweed"
client_id = "seaweedfs-admin-ui"
client_secret = "..."
redirect_url = "https://admin.example.com/login/oidc/callback"
scopes = ["openid", "profile", "email"]
[admin.oidc.role_mapping]
default_role = "readonly"
[[admin.oidc.role_mapping.rules]]
claim = "groups"
value = "seaweedfs-admin"
role = "admin"
Place it in one of: ., $HOME/.seaweedfs/, /usr/local/etc/seaweedfs/, or /etc/seaweedfs/. Generate a starter with weed scaffold -config=security.
Environment-only example (no security.toml)
For container deployments, security.toml is often inconvenient. Set everything via env:
WEED_ADMIN_OIDC_ENABLED=true
WEED_ADMIN_OIDC_ISSUER=https://keycloak.example.com/realms/seaweed
WEED_ADMIN_OIDC_CLIENT_ID=seaweedfs-admin-ui
WEED_ADMIN_OIDC_CLIENT_SECRET=... # inject from a Secret, see Helm below
WEED_ADMIN_OIDC_REDIRECT_URL=https://admin.example.com/login/oidc/callback
WEED_ADMIN_OIDC_SCOPES=openid,profile,email
WEED_ADMIN_OIDC_ROLE_MAPPING_DEFAULT_ROLE=readonly
WEED_ADMIN_OIDC_ADMIN_GROUPS=seaweedfs-admin
WEED_ADMIN_OIDC_READONLY_GROUPS=seaweedfs-readonly
Helm / Kubernetes
The admin Helm template supports both extraEnvironmentVars (plain values) and secretExtraEnvironmentVars (values from a Kubernetes Secret). Put non-sensitive settings in the first, the client secret in the second.
# values.yaml
admin:
enabled: true
ingress:
enabled: true
host: admin-seaweedfs.example.com
className: openshift-default
annotations:
route.openshift.io/termination: edge
extraEnvironmentVars:
WEED_ADMIN_OIDC_ENABLED: "true"
WEED_ADMIN_OIDC_ISSUER: "https://keycloak.example.com/realms/seaweed"
WEED_ADMIN_OIDC_CLIENT_ID: "seaweedfs-admin-ui"
WEED_ADMIN_OIDC_REDIRECT_URL: "https://admin-seaweedfs.example.com/login/oidc/callback"
WEED_ADMIN_OIDC_SCOPES: "openid,profile,email"
WEED_ADMIN_OIDC_ROLE_MAPPING_DEFAULT_ROLE: "readonly"
WEED_ADMIN_OIDC_ADMIN_GROUPS: "seaweedfs-admin"
WEED_ADMIN_OIDC_READONLY_GROUPS: "seaweedfs-readonly"
secretExtraEnvironmentVars:
WEED_ADMIN_OIDC_CLIENT_SECRET:
secretKeyRef:
name: seaweedfs-admin-oidc
key: client_secret
Create the backing Secret separately (out-of-band, via ExternalSecret, sealed-secrets, etc.):
apiVersion: v1
kind: Secret
metadata:
name: seaweedfs-admin-oidc
type: Opaque
stringData:
client_secret: "...the OAuth client secret from your IdP..."
secretExtraEnvironmentVars requires chart version with the admin secret-env support (added 2026-05; see PR in seaweedfs/seaweedfs). On older charts you must inline the client secret in extraEnvironmentVars, which is not GitOps-friendly.
Identity provider configuration
In your IdP, register the admin UI as a confidential OAuth client with:
- Client type / Access type: confidential (must have a client secret)
- Grant types / Flow: Authorization Code (with PKCE optional — SeaweedFS uses
state+nonce) - Redirect URI: the exact value you put in
WEED_ADMIN_OIDC_REDIRECT_URL. If the admin runs behind a reverse proxy under a sub-path, include that path:https://host/seaweedfs/login/oidc/callback. - Token signing algorithm: RS256 / RS384 / RS512 / ES256 / ES384 / ES512 are supported.
- Groups claim: if you use
admin_groups/readonly_groupsor rules withclaim=groups, configure the IdP to emit agroupsclaim in the ID token. In Keycloak this is a Group Membership mapper added to the client scope; verify with the Evaluate tab.
Keycloak quick checklist
- Realm → Clients → Create:
- Client ID:
seaweedfs-admin-ui - Client authentication: On (this makes it confidential)
- Authentication flow: Standard flow (only)
- Client ID:
- Settings → Valid redirect URIs:
https://admin.example.com/login/oidc/callback - Credentials → copy the Client secret into the Kubernetes Secret.
- Client scopes →
<client>-dedicated→ Add mapper → Group Membership:- Name:
groups - Token Claim Name:
groups - Full group path: Off (so the claim values match short names like
seaweedfs-admin, not/seaweedfs-admin) - Add to ID token: On
- Name:
- Realm → Groups → create
seaweedfs-admin,seaweedfs-readonly(or whatever you reference). Assign users. - Test with Evaluate that the ID token contains
"groups": ["seaweedfs-admin"].
The issuer value is what Keycloak prints under Realm settings → OpenID Endpoint Configuration → issuer. It must exactly match the iss claim in issued tokens — including trailing slash semantics.
OpenShift Routes / reverse proxies
Edge-terminated OpenShift Routes (and other HTTPS-terminating proxies) work fine with admin OIDC, but two things have to line up:
redirect_urlis what the browser sees. Use the public HTTPS host of the Route, not the in-cluster service. The IdP will redirect the user's browser there.- The admin pod must be able to reach the IdP to fetch discovery and JWKS. If the IdP is reachable only through the same Route or a TLS-terminating proxy with a private CA, set
WEED_ADMIN_OIDC_TLS_CA_CERTto a mounted CA bundle (use aConfigMapmounted as a file) — otherwise discovery will fail with x509 errors at startup.
The Route's TLS does not need to be mutual; the OIDC flow is between the user's browser and the IdP. The admin pod only makes server-to-IdP HTTPS calls (discovery, JWKS, code exchange).
global.enableSecurity: true (mTLS for SeaweedFS internal gRPC) does not interfere with OIDC. Those certificates secure the worker/master/filer/admin gRPC channels — they are unrelated to the HTTPS calls the admin makes to your IdP, and they are unrelated to the user-facing HTTPS terminated by the Route.
Troubleshooting
The login page renders the OIDC button only when the admin process successfully constructed an OIDC service at startup. If the button is missing:
| Symptom | Where to look | Likely cause |
|---|---|---|
Startup logs say OIDC: Enabled (issuer: …) but no button on /login |
Admin container logs around the OIDC: Enabled line; look for Warning: disabling admin OIDC authentication: … |
Discovery fetch failed (network / CA), config validation failed, or enableUI is false. The startup banner is printed from a direct env-var read; the service is only created if validation and discovery succeed. |
No OIDC: Enabled line at all |
Verify WEED_ADMIN_OIDC_ENABLED=true is actually present in the pod (`kubectl exec … env |
grep WEED_ADMIN_OIDC`) |
admin.oidc.role_mapping must include at least one rule or default_role |
Admin container logs | Add WEED_ADMIN_OIDC_ROLE_MAPPING_DEFAULT_ROLE=readonly or WEED_ADMIN_OIDC_ADMIN_GROUPS=…, or define rules in security.toml. |
admin.oidc.redirect_url must use HTTPS |
Admin container logs | Use HTTPS (or http://localhost… for dev). The validator only allows HTTP for localhost. |
fetch OIDC discovery document: … x509: certificate signed by unknown authority |
Admin container logs | Set WEED_ADMIN_OIDC_TLS_CA_CERT to a path inside the pod that contains the IdP's CA chain in PEM. Use tls_insecure_skip_verify only for short-term debugging. |
Browser redirects to IdP, then back with error=invalid_redirect_uri |
IdP audit log | The redirect URL registered in the IdP doesn't byte-match what SeaweedFS sends. Trailing slashes, scheme, port, and any urlPrefix all matter. |
Login succeeds at IdP but admin redirects back with OIDC login failed |
Admin container logs (look for OIDC callback failed) |
Common causes: clock skew (token exp already past), nonce mismatch (session lost between auth start and callback — check that the cookie's Path covers /login/oidc/callback), or no role rule matches and no default_role is set. |
OIDC user does not map to an allowed admin role |
Admin container logs | The token's claims don't satisfy any rule and default_role is unset. Verify the IdP is emitting the expected claim (e.g. groups) and that the value spelling matches. |
To inspect what the IdP is actually emitting, paste a sample ID token into jwt.io (or weed jwt decode) and compare the claim names/values against your rules.
Quick liveness check
# 1. Is the env actually in the pod?
kubectl exec deploy/seaweedfs-admin -- env | grep WEED_ADMIN_OIDC | sort
# 2. What does discovery look like from the pod's network namespace?
kubectl exec deploy/seaweedfs-admin -- \
wget -O- -q "$WEED_ADMIN_OIDC_ISSUER/.well-known/openid-configuration" | head -c 200
# 3. Does the /login page render the OIDC button?
curl -sk https://admin.example.com/login | grep -i oidc
If step 2 fails, fix networking/CA before anything else — the admin can't validate tokens without JWKS.
See also
- Admin UI — the main admin UI reference
- OIDC Integration — OIDC on the S3 side (separate, OSS feature)
weed scaffold -config=security— generate an annotatedsecurity.toml
Introduction
- Quick Start with weed mini
- Simplest S3 Bucket and User Setup
- Components
- Getting Started
- Production Setup
- A typical step‐by‐step example
- Benchmarks
- FAQ
- Applications
API
Configuration
- Replication
- Store file with a Time To Live
- Failover Master Server
- Erasure coding for warm storage
- EC Bitrot Detection
- Server Startup via Systemd
- Environment Variables
Filer
- Filer Setup
- Directories and Files
- File Operations Quick Reference
- Data Structure for Large Files
- Filer Data Encryption
- Filer Commands and Operations
- Filer JWT Use
- TUS Resumable Uploads
Filer Stores
- Filer Cassandra Setup
- Filer Redis Setup
- Super Large Directories
- Path-Specific Filer Store
- Choosing a Filer Store
- Customize Filer Store
Management
Advanced Filer Configurations
- Migrate to Filer Store
- Add New Filer Store
- Filer Store Replication
- Filer Active Active cross cluster continuous synchronization
- Filer as a Key-Large-Value Store
- Path Specific Configuration
- Filer Change Data Capture
- Filer Operation Serialization
FUSE Mount
- FIO benchmark
- fstab and systemd mount
- POSIX Compliance
- Distributed POSIX Locks
- P2P reading in weed mount
WebDAV
SFTP Server
Cloud Drive
- Cloud Drive Benefits
- Cloud Drive Architecture
- Configure Remote Storage
- Mount Remote Storage
- Cache Remote Storage
- Cloud Drive Quick Setup
- Gateway to Remote Object Storage
AWS S3 API
- Amazon S3 API
- Supported APIs vs Minio
- S3 Lifecycle
- S3 Lifecycle vs Volume TTL
- S3 Conditional Operations
- S3 CORS
- S3 Object Lock and Retention
- S3 Object Versioning
- S3 API Benchmark
- S3 API FAQ
- S3 Bucket Quota
- S3 Rate Limiting
- S3 API Audit log
- S3 Nginx Proxy
- Docker Compose for S3
S3 Table Bucket
- S3 Table Bucket
- S3 Table Bucket Commands
- S3 Tables Security
- SeaweedFS Iceberg Catalog
- Iceberg Table Maintenance
Iceberg Integrations
- Spark Iceberg Integration
- Trino Iceberg Integration
- Dremio Iceberg Integration
- DuckDB Iceberg Integration
- Doris Iceberg Integration
- RisingWave Iceberg Integration
- Lakekeeper Iceberg Integration
S3 Authentication & IAM
- S3 Configuration - Start Here
- S3 Credentials (
-s3.config) - OIDC Integration (
-s3.iam.config) - Kubernetes ServiceAccount Authentication (IRSA-style)
- S3 Policy Variables
- S3 Policy Conditions
- S3 Bucket Policies
- Amazon IAM API
- AWS IAM CLI
- weed shell - Shell IAM Commands
Server-Side Encryption
S3 Client Tools
- AWS CLI with SeaweedFS
- s3cmd with SeaweedFS
- rclone with SeaweedFS
- restic with SeaweedFS
- nodejs with Seaweed S3
Machine Learning
HDFS
- Hadoop Compatible File System
- run Spark on SeaweedFS
- run HBase on SeaweedFS
- run Presto on SeaweedFS
- Hadoop Benchmark
- HDFS via S3 connector
Replication and Backup
- Async Replication to another Filer [Deprecated]
- Async Backup
- Async Filer Metadata Backup
- Async Replication to Cloud [Deprecated]
- Kubernetes Backups and Recovery with K8up
Metadata Change Events
Messaging
- Structured Data Lake with SMQ and SQL
- Seaweed Message Queue
- SQL Queries on Message Queue
- SQL Quick Reference
- PostgreSQL-compatible Server weed db
- Pub-Sub to SMQ to SQL
- Kafka to Kafka Gateway to SMQ to SQL
Use Cases
Operations
- System Metrics
- weed shell
- Data Backup
- Deployment to Kubernetes and Minikube
- Deployment with seaweed-up
Rust Volume Server
Advanced
- Large File Handling
- Optimization
- Optimization for Many Small Buckets
- Volume Management
- Tiered Storage
- Cloud Tier
- Cloud Monitoring
- Load Command Line Options from a file
- SRV Service Discovery
- Volume Files Structure
Security
- Security Overview
- Security Configuration
- Cryptography and FIPS Compliance
- Run Blob Storage on Public Internet