Authentication¶
Daimyo supports OAuth2/OIDC authentication for two purposes:
- Client sessions — CLI users log in once; tokens are stored securely and renewed automatically, so subsequent commands work without re-prompting.
- Scope access control — scopes can declare required JWT roles; the REST server validates Bearer tokens and rejects unauthorised requests.
Prerequisites¶
Configure the OIDC issuer and client before using auth commands:
# .daimyo/config/settings.toml
oidc_issuer_url = "https://auth.example.com/realms/my-realm"
oidc_client_id = "daimyo-cli"
Or via environment variables:
export DAIMYO_OIDC_ISSUER_URL="https://auth.example.com/realms/my-realm"
export DAIMYO_OIDC_CLIENT_ID="daimyo-cli"
Daimyo discovers all OIDC endpoints automatically from
<oidc_issuer_url>/.well-known/openid-configuration. No endpoint URLs need to be hardcoded.
Login¶
Browser flow (PKCE)¶
Opens the system browser for the authorization code + PKCE flow. After the user authenticates,
the provider redirects to http://localhost:<oidc_redirect_port> (default port 8555).
Tokens are stored immediately on success.
Custom redirect URI¶
By default the redirect URI is constructed as http://localhost:{oidc_redirect_port}. If the
provider requires a different URI — a specific path, 127.0.0.1 instead of localhost, or an
alternate port — override it explicitly:
The local callback server still listens on localhost:oidc_redirect_port; the custom URI is only
sent to the authorization server and included in the token exchange request. Ensure the URI you
configure here matches exactly what is registered with the identity provider.
Device authorization flow (headless)¶
Uses RFC 8628. Daimyo prints a device code and a verification URL; the user opens the URL on any browser and enters the code. The CLI polls until the authorization completes or times out. Use this in CI environments, SSH sessions, or containers where a browser cannot be opened.
Logout¶
Removes all stored tokens from the configured backend. If no tokens are found the command exits cleanly with an informational message.
Token Storage¶
Tokens are persisted across invocations so the user does not need to log in for every command.
Keyring (default)¶
token_storage = "keyring" # default
token_storage_keyring_service = "daimyo" # service name in the keyring
Delegates to the platform secret store:
- Linux: Freedesktop Secret Service (e.g., GNOME Keyring, KWallet)
- macOS: macOS Keychain
- Windows: Windows Credential Manager
File¶
The file is created with mode 0600 (owner read/write only). Use this backend on systems where
a keyring daemon is unavailable (e.g., minimal Docker images).
Warning
The file backend stores tokens in plain JSON. Ensure the file is on an encrypted volume or otherwise protected at the OS level.
Silent Token Renewal¶
When the stored access token has expired, daimyo automatically exchanges the refresh token for a new access token before proceeding. This happens transparently — no user interaction is required unless the refresh token itself has expired, in which case daimyo prompts the user to log in again.
OIDC Provider SSL¶
By default daimyo verifies SSL certificates when connecting to the OIDC provider's endpoints (discovery, token, and JWKS). Two settings control this behaviour:
Custom CA bundle¶
When the identity provider uses a certificate signed by a private or internal CA:
The file must be a PEM-encoded CA certificate bundle. When set, it takes precedence over the system CA store for all OIDC provider requests.
Disable verification¶
For development environments with self-signed certificates where a CA bundle is unavailable:
Warning
Disabling SSL verification removes protection against man-in-the-middle attacks. Never use
oidc_ssl_verify = false in production or against a provider that handles real credentials.
These settings apply to every outbound HTTP call daimyo makes to the OIDC provider: the discovery document, the token endpoint, and the JWKS endpoint used for server-side JWT validation.
Authenticated Federation¶
When a valid session exists, daimyo injects the access token as a Bearer header into every
request sent to the master server:
If the master server returns 401 (token expired), daimyo refreshes the token and retries once.
If the server returns 401 or 403 after the retry, the error is logged as a warning and the
remote scope is skipped — local scopes are never affected by remote authentication failures.
See Server Federation for master server configuration.
Protecting Scopes¶
Declare an auth block in a scope's metadata.yml to require a valid JWT with specific roles:
name: my-secure-scope
description: Restricted scope
auth:
accepted_roles:
- "admin"
- "daimyo-.*" # regex patterns are supported
roles_claim: roles # optional; defaults to global oidc_roles_claim setting
How it works:
accepted_rolesis a list of regex patterns.- The server extracts the value of the JWT claim named by
roles_claim(typically a list of strings). - A request is authorised if any role value in the token matches any pattern in
accepted_roles. - Scopes without an
authblock are publicly accessible — no token is required.
Role patterns follow Python re syntax. Examples:
| Pattern | Matches |
|---|---|
admin |
Exact string admin |
daimyo-.* |
Any role starting with daimyo- |
(admin\|superuser) |
Either admin or superuser |
Nested claim paths¶
roles_claim supports dot-notation to reach values nested inside the JWT payload. This is
necessary for providers such as Keycloak, which places realm roles under realm_access.roles:
Configure the path with dots:
Each segment is resolved in sequence; if any intermediate key is absent or is not an object, the claim resolves to an empty list and authorization fails. A plain (non-dotted) name still works as before.
Server-side JWT Validation¶
For scopes with auth.accepted_roles, the REST API validates incoming Bearer tokens:
- The server fetches the OIDC JWKS document from
<oidc_issuer_url>/.well-known/openid-configuration → jwks_uri. - JWKS keys are cached in memory for
jwks_cache_ttlseconds (default 300). - The token signature, expiry, and audience (if
oidc_audienceis set) are verified. - Role values from the configured claim are matched against
accepted_roles. - A missing or invalid token returns
401 Unauthorized; a valid token with no matching role returns403 Forbidden.
Note
The MCP server does not perform token validation. Scope access control via auth only
applies to the REST API.
Troubleshooting 401 Errors¶
If clients receive 401 Unauthorized even with recently issued, apparently valid tokens, work
through the causes below in order.
Issuer URL mismatch¶
The most common cause. The oidc_issuer_url setting must match exactly the value the
identity provider places in the iss claim of every token it issues.
In containerised or reverse-proxied deployments the URL daimyo uses to reach the IdP internally can differ from the public URL the IdP writes into tokens:
oidc_issuer_url (what daimyo uses) |
Token iss claim (what IdP writes) |
Result |
|---|---|---|
http://keycloak:8080/realms/myrealm |
https://auth.example.com/realms/myrealm |
401 |
https://auth.example.com/realms/myrealm |
https://auth.example.com/realms/myrealm |
OK |
To diagnose the mismatch, start daimyo with debug logging and send a request with your token:
When a mismatch occurs the log shows both values:
DEBUG Issuer mismatch — configured: http://keycloak:8080/realms/myrealm, token iss: https://auth.example.com/realms/myrealm
Set oidc_issuer_url to the value shown as token iss. If the IdP is only reachable at an
internal address, configure it there and ensure the IdP issues tokens with that same URL as
iss (Keycloak: set the Frontend URL or use realm hostname overrides).
Audience mismatch¶
If oidc_audience is set, the token's aud claim must match. Decode your token (e.g., with
jwt.io) and compare the aud value against your oidc_audience setting. Leave
oidc_audience empty to skip this check when the server is an internal tool.
Roles claim not found¶
If the token is accepted (200 OK) when no accepted_roles are configured but fails with
403 Forbidden when roles are required, the roles_claim path is likely wrong.
Decode the token and locate where roles are stored. For Keycloak that is usually
realm_access.roles; for other providers it may be groups, roles, or a custom claim. Set
roles_claim in metadata.yml to the correct path, using dot-notation for nested values.
OIDC provider unreachable¶
If the OIDC provider's JWKS endpoint is temporarily unavailable, daimyo returns 401 (not
500). Check provider connectivity and retry; JWKS are cached for jwks_cache_ttl seconds so
a brief outage only affects the first request after the cache expires.