Skip to content

Auth

Authentication Configuration

Protects all channels and monitoring endpoints with token or credential validation. Channels inherit these settings and can override them per-channel.

Enable auth with environment variables:

SCRIBE_AUTH_ENABLED=true
SCRIBE_AUTH_ISSUER=https://auth.example.com/realms/mycompany

Or in HOCON:

auth {
enabled = true
providers.default.issuer = "https://auth.example.com/realms/mycompany"
}
Your situationUse thisWhy
Modern apps with OAuth/OIDCbearerTokens validated offline, no IdP calls per request
Legacy apps sending username/passwordropcExchanges creds for tokens at runtime
Internal tools, no IdP availableldapBinds directly to LDAP, no tokens needed

Most deployments should use bearer tokens. Add ropc or ldap only for clients that cannot obtain tokens themselves.

Channels inherit from this section and can override settings:

channels.rest.auth {
enabled = true
}

See also: Production Checklist in the documentation.

Bearer token validation

Settings for validating bearer tokens (JWT or opaque).

Opaque token cache

Caches introspection results to reduce IdP calls. Only applies to opaque tokens; JWTs are validated locally.

PropertyValue
Default${auth.cache}

Inherits from: auth.cache

auth.bearer.cache = ${auth.cache}

Opaque token cache

Caches introspection results to reduce IdP calls. Only applies to opaque tokens; JWTs are validated locally.

Maximum cache entries (LRU eviction) Inherits from auth.cache.max-size if not set.

Priority: SCRIBE_AUTH_BEARER_CACHE_MAX_SIZE > auth.cache.max-size

PropertyValue
OverrideSCRIBE_AUTH_BEARER_CACHE_MAX_SIZE (optional)
auth.bearer.cache.max-size = ${?SCRIBE_AUTH_BEARER_CACHE_MAX_SIZE}

How long to cache successful introspection results Inherits from auth.cache.ttl if not set.

Priority: SCRIBE_AUTH_BEARER_CACHE_TTL > auth.cache.ttl

PropertyValue
OverrideSCRIBE_AUTH_BEARER_CACHE_TTL (optional)
auth.bearer.cache.ttl = ${?SCRIBE_AUTH_BEARER_CACHE_TTL}

Provider for opaque token introspection When a bearer token is opaque (not a JWT), Scribe calls the provider’s introspection endpoint to validate it. Requires client credentials. Auto-selection:

  • If only one provider has client credentials → auto-selected
  • If multiple providers have credentials → must be explicitly set

JWTs are validated offline via JWKS and don’t require this setting.

Priority: SCRIBE_AUTH_BEARER_PROVIDER > config

PropertyValue
Default"default"
OverrideSCRIBE_AUTH_BEARER_PROVIDER (optional)
auth.bearer.provider = ${?SCRIBE_AUTH_BEARER_PROVIDER}

Auth result cache (global defaults) ======================================================================= Global cache settings for authentication results. ropc.cache, bearer.cache, and ldap.cache inherit from here but can override via environment variables. Sub-caches have NO hardcoded values—only env var overrides. This ensures changes to global defaults propagate automatically.

Maximum cache entries (LRU eviction) Size based on expected concurrent users. Each entry is ~500 bytes (principal + metadata).

Priority: SCRIBE_AUTH_CACHE_MAX_SIZE > config

PropertyValue
Default1000
OverrideSCRIBE_AUTH_CACHE_MAX_SIZE (optional)
auth.cache.max-size = 1000
auth.cache.max-size = ${?SCRIBE_AUTH_CACHE_MAX_SIZE}

How long to cache successful authentications Short values (1-5m) balance performance with security. Longer values reduce backend load but delay credential changes.

Priority: SCRIBE_AUTH_CACHE_TTL > config

PropertyValue
Default5m
OverrideSCRIBE_AUTH_CACHE_TTL (optional)
auth.cache.ttl = 5m
auth.cache.ttl = ${?SCRIBE_AUTH_CACHE_TTL}

OIDC Discovery cache

Controls caching of IdP metadata (/.well-known/openid-configuration). Endpoints like token_endpoint, introspection_endpoint, and userinfo_endpoint are discovered from the cached metadata. Each provider can override these settings in their discovery {} block.

Fallback refresh interval

Used when the IdP doesn’t send Cache-Control headers. Most IdPs send max-age, so this is rarely needed.

Priority: SCRIBE_AUTH_DISCOVERY_REFRESH_INTERVAL > config

PropertyValue
Default5m
OverrideSCRIBE_AUTH_DISCOVERY_REFRESH_INTERVAL (optional)
auth.discovery.refresh-interval = 5m
auth.discovery.refresh-interval = ${?SCRIBE_AUTH_DISCOVERY_REFRESH_INTERVAL}

How long stale data can be used After metadata expires (per Cache-Control headers), it remains usable as “stale” for this duration while a refresh happens in the background.

Priority: SCRIBE_AUTH_DISCOVERY_STALE_TTL > config

PropertyValue
Default1h
OverrideSCRIBE_AUTH_DISCOVERY_STALE_TTL (optional)
auth.discovery.stale-ttl = 1h
auth.discovery.stale-ttl = ${?SCRIBE_AUTH_DISCOVERY_STALE_TTL}

Caching strategy

How to handle expired discovery metadata: stale-while-revalidate - Return stale immediately, refresh async in background Best for low latency, eventual consistency stale-if-error - Return stale if refresh fails (IdP unavailable) Best for resilience, availability strict - Block until fresh data, fail if unavailable Best for strong consistency

Priority: SCRIBE_AUTH_DISCOVERY_STRATEGY > config

PropertyValue
Defaultstale-while-revalidate
OverrideSCRIBE_AUTH_DISCOVERY_STRATEGY (optional)
auth.discovery.strategy = stale-while-revalidate
auth.discovery.strategy = ${?SCRIBE_AUTH_DISCOVERY_STRATEGY}

Enable authentication

When false: auth subsystem is off, no credentials extracted, no principal set. Browser UIs (/ui, /observe) remain accessible without login. When true: credentials are extracted and validated per configured methods

Priority: SCRIBE_AUTH_ENABLED > config

PropertyValue
Defaultfalse
OverrideSCRIBE_AUTH_ENABLED (optional)
auth.enabled = false
auth.enabled = ${?SCRIBE_AUTH_ENABLED}

Auth failure delay

Delay before responding to failed authentication attempts. This mitigates brute-force password guessing attacks by making each failed attempt slower, similar to standard LDAP server behavior. Applies to all channels (LDAP, REST, GraphQL, MCP) and monitoring endpoints. Set to 0 to disable the delay (e.g., for testing).

Priority: SCRIBE_AUTH_FAILURE_DELAY > config

PropertyValue
Default2s
OverrideSCRIBE_AUTH_FAILURE_DELAY (optional)
auth.failure-delay = 2s
auth.failure-delay = ${?SCRIBE_AUTH_FAILURE_DELAY}

HTTP client settings (global defaults) ======================================================================= Shared HTTP client configuration for all auth-related network calls: discovery, ROPC token exchange, token introspection, and userinfo. Method-specific overrides: auth.ropc.http, auth.bearer.http, auth.discovery.http

Connection timeout

How long to wait when establishing a connection to the IdP.

Priority: SCRIBE_AUTH_HTTP_CONNECT_TIMEOUT > config

PropertyValue
Default10s
OverrideSCRIBE_AUTH_HTTP_CONNECT_TIMEOUT (optional)
auth.http.connect-timeout = 10s
auth.http.connect-timeout = ${?SCRIBE_AUTH_HTTP_CONNECT_TIMEOUT}

Request timeout

Maximum time for a complete request (connect + send + response).

Priority: SCRIBE_AUTH_HTTP_REQUEST_TIMEOUT > config

PropertyValue
Default30s
OverrideSCRIBE_AUTH_HTTP_REQUEST_TIMEOUT (optional)
auth.http.request-timeout = 30s
auth.http.request-timeout = ${?SCRIBE_AUTH_HTTP_REQUEST_TIMEOUT}

SSL/TLS settings

Inherits from global ssl {} section by default.

Override here for IdP-specific certificates.

Skip certificate verification (INSECURE - dev only)

Priority: SCRIBE_AUTH_HTTP_SSL_INSECURE > config

PropertyValue
Defaultfalse
OverrideSCRIBE_AUTH_HTTP_SSL_INSECURE (optional)
auth.http.ssl.insecure = false
auth.http.ssl.insecure = ${?SCRIBE_AUTH_HTTP_SSL_INSECURE}

Trust store path (PEM or JKS) If not set, uses system default trust store.

Priority: SCRIBE_AUTH_HTTP_SSL_TRUST_STORE > ssl.trust-store

trust-store = ${?ssl.trust-store}

PropertyValue
OverrideSCRIBE_AUTH_HTTP_SSL_TRUST_STORE (optional)
auth.http.ssl.trust-store = ${?SCRIBE_AUTH_HTTP_SSL_TRUST_STORE}

JWT validation settings

These settings control how bearer tokens are validated. Most defaults follow security best practices - only change if you understand the implications.

Allowed signing algorithms

Allowed algorithms for token signatures. Tokens signed with other algorithms are rejected.

Common algorithms:

RS256, RS384, RS512 - RSA with SHA-256/384/512 (recommended) ES256, ES384, ES512 - ECDSA with SHA-256/384/512 PS256, PS384, PS512 - RSA-PSS with SHA-256/384/512

Default: All RS*, ES*, PS* algorithms

PropertyValue
Default[RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512]
OverrideSCRIBE_AUTH_JWT_ALLOWED_ALGORITHMS (optional)
auth.jwt.allowed-algorithms = [RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512]
auth.jwt.allowed-algorithms = ${?SCRIBE_AUTH_JWT_ALLOWED_ALGORITHMS}

Clock skew tolerance (in seconds) Allows for minor time differences between Scribe and the IdP. The exp, nbf, and iat claims are validated with this tolerance.

Default: 60 seconds (1 minute)

PropertyValue
Default60
OverrideSCRIBE_AUTH_JWT_CLOCK_SKEW (optional)
auth.jwt.clock-skew = 60
auth.jwt.clock-skew = ${?SCRIBE_AUTH_JWT_CLOCK_SKEW}

Maximum token lifetime (in minutes) Tokens with ‘exp’ claim further than this into the future are rejected. Set to 0 to disable this check.

Default: 1440 (24 hours)

PropertyValue
Default1440
OverrideSCRIBE_AUTH_JWT_MAX_LIFETIME (optional)
auth.jwt.max-lifetime = 1440
auth.jwt.max-lifetime = ${?SCRIBE_AUTH_JWT_MAX_LIFETIME}

Require expiration claim

When true (default), tokens without an ‘exp’ claim are rejected. Strongly recommended to keep enabled for security.

PropertyValue
Defaulttrue
OverrideSCRIBE_AUTH_JWT_REQUIRE_EXPIRATION (optional)
auth.jwt.require-expiration = true
auth.jwt.require-expiration = ${?SCRIBE_AUTH_JWT_REQUIRE_EXPIRATION}

Require issued-at claim

When true, tokens without an ‘iat’ claim are rejected.

PropertyValue
Defaultfalse
OverrideSCRIBE_AUTH_JWT_REQUIRE_ISSUED_AT (optional)
auth.jwt.require-issued-at = false
auth.jwt.require-issued-at = ${?SCRIBE_AUTH_JWT_REQUIRE_ISSUED_AT}

Require not-before claim

When true, tokens without an ‘nbf’ claim are rejected. The nbf claim is validated when present regardless of this setting.

PropertyValue
Defaultfalse
OverrideSCRIBE_AUTH_JWT_REQUIRE_NOT_BEFORE (optional)
auth.jwt.require-not-before = false
auth.jwt.require-not-before = ${?SCRIBE_AUTH_JWT_REQUIRE_NOT_BEFORE}

LDAP Bind Authentication

Validates credentials by binding directly to an LDAP directory. No IdP needed—authentication happens against the upstream LDAP server. LDAP-only deployments do not require auth.providers or OIDC credentials.

  • Client sends: Authorization: Basic <base64(username:password)>
  • Scribe searches for the user’s DN using a service account
  • Scribe binds as that DN with the provided password
  • On success, extracts attributes → AuthPrincipal

Inherits from root ldap {} config:

  • url (LDAP server)
  • bind-dn, bind-password (service account used to search for user DNs)
  • ssl (TLS settings)

The service account must have read access to search under auth.ldap.base. Override auth.ldap.url here only if auth uses a different server than sync.

auth.ldap {
base = "ou=users,dc=example,dc=com"
filter = "(objectClass=inetOrgPerson)"
bind-attribute = "uid"
}

User “alice” → search for (uid=alice) → bind as found DN

Attribute Mapping

Maps LDAP attributes to identity claims. Defaults work for standard LDAP directories. See: LDAP Attributes

Email address

PropertyValue
Defaultmail
OverrideSCRIBE_AUTH_LDAP_EMAIL_ATTR (optional)
auth.ldap.attributes.email = mail
auth.ldap.attributes.email = ${?SCRIBE_AUTH_LDAP_EMAIL_ATTR}

Last name

PropertyValue
Defaultsn
OverrideSCRIBE_AUTH_LDAP_FAMILY_NAME_ATTR (optional)
auth.ldap.attributes.family-name = sn
auth.ldap.attributes.family-name = ${?SCRIBE_AUTH_LDAP_FAMILY_NAME_ATTR}

First name

PropertyValue
DefaultgivenName
OverrideSCRIBE_AUTH_LDAP_GIVEN_NAME_ATTR (optional)
auth.ldap.attributes.given-name = givenName
auth.ldap.attributes.given-name = ${?SCRIBE_AUTH_LDAP_GIVEN_NAME_ATTR}

Display name

PropertyValue
Defaultcn
OverrideSCRIBE_AUTH_LDAP_NAME_ATTR (optional)
auth.ldap.attributes.name = cn
auth.ldap.attributes.name = ${?SCRIBE_AUTH_LDAP_NAME_ATTR}

Subject identifier (user ID)

PropertyValue
Defaultuid
OverrideSCRIBE_AUTH_LDAP_SUBJECT_ATTR (optional)
auth.ldap.attributes.subject-id = uid
auth.ldap.attributes.subject-id = ${?SCRIBE_AUTH_LDAP_SUBJECT_ATTR}

Base DN for user search (REQUIRED to enable LDAP auth) The starting point for finding user entries. Setting this value enables LDAP authentication. If not set, LDAP auth is disabled even if other auth.ldap settings are present.

Example: “ou=users,dc=example,dc=com”

Priority: SCRIBE_AUTH_LDAP_BASE > config

PropertyValue
OverrideSCRIBE_AUTH_LDAP_BASE (optional)
auth.ldap.base = ${?SCRIBE_AUTH_LDAP_BASE}

Username attribute

LDAP attribute used to search for users during authentication. When a user logs in with “john.doe”, Scribe searches for entries where this attribute equals “john.doe”.

Two configuration styles:

bind-attribute = uid

All logins search by uid. Good when users log in consistently (e.g., always username, never email).

Pattern-based (attribute depends on username format)
Section titled “Pattern-based (attribute depends on username format)”
bind-attribute {
mail = "*@*" # contains @ -> search by mail
workforceId = "/\\d+/" # digits only -> search by workforceId
uid = true # everything else -> search by uid
}

Patterns are evaluated top-to-bottom. First match wins. Use this when users might log in with email, employee ID, or username.

PatternTypeMatches
trueboolEverything (use as fallback)
"*"globEverything (shorthand for true)
*@*globContains @ (emails)
admin*globStarts with “admin”
*.corpglobEnds with “.corp”
/\d+/regexDigits only (employee IDs)
/[A-Z]{2}\d+/regexTwo letters + digits (e.g., AB123)
/pattern/iregexCase-insensitive regex

Glob patterns: * matches any characters, ? matches one character.

Globs are case-insensitive.

Regex patterns: Wrapped in /slashes/, optionally with flags. Flags: i (case-insensitive), m (multiline), s (dotall). Regex must match the entire username (anchored).

AttributeDirectoryNotes
uidPOSIX/OpenLDAPDefault, most common
sAMAccountNameActive DirectoryPre-Windows 2000 name
userPrincipalNameActive DirectoryUPN format (user@domain)
mailAnyEmail address
cnAnyCommon name

Priority: SCRIBE_AUTH_LDAP_BIND_ATTR > config

PropertyValue
Defaultuid
OverrideSCRIBE_AUTH_LDAP_BIND_ATTR (optional)
auth.ldap.bind-attribute = uid
auth.ldap.bind-attribute = ${?SCRIBE_AUTH_LDAP_BIND_ATTR}

Credential cache

Caches successful bind operations to reduce LDAP server load. The cache key is a hash of (username, password)—credentials are never stored in plaintext. Trade-offs:

  • Reduces LDAP bind operations and latency
  • Password changes take effect after TTL expires
  • Locked/disabled accounts remain valid until TTL expires

Set ttl = 0 to disable caching.

PropertyValue
Default${auth.cache}

Inherits from: auth.cache

auth.ldap.cache = ${auth.cache}

Credential cache

Caches successful bind operations to reduce LDAP server load. The cache key is a hash of (username, password)—credentials are never stored in plaintext. Trade-offs:

  • Reduces LDAP bind operations and latency
  • Password changes take effect after TTL expires
  • Locked/disabled accounts remain valid until TTL expires

Set ttl = 0 to disable caching.

Maximum cache entries (LRU eviction) Inherits from auth.cache.max-size if not set.

Priority: SCRIBE_AUTH_LDAP_CACHE_MAX_SIZE > auth.cache.max-size

PropertyValue
OverrideSCRIBE_AUTH_LDAP_CACHE_MAX_SIZE (optional)
auth.ldap.cache.max-size = ${?SCRIBE_AUTH_LDAP_CACHE_MAX_SIZE}

How long to cache successful authentications Inherits from auth.cache.ttl if not set.

Priority: SCRIBE_AUTH_LDAP_CACHE_TTL > auth.cache.ttl

PropertyValue
OverrideSCRIBE_AUTH_LDAP_CACHE_TTL (optional)
auth.ldap.cache.ttl = ${?SCRIBE_AUTH_LDAP_CACHE_TTL}

User filter (optional)

Restricts which entries can authenticate. Combined with the username lookup as: (&{filter}({bind-attribute}={username}))

Examples:

(objectClass=person) - Any person entry (employeeStatus=active) - Only active employees (!(accountLocked=TRUE)) - Exclude locked accounts (&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))

  • AD: enabled accounts only

Priority: SCRIBE_AUTH_LDAP_FILTER > config

PropertyValue
OverrideSCRIBE_AUTH_LDAP_FILTER (optional)
auth.ldap.filter = ${?SCRIBE_AUTH_LDAP_FILTER}

Role Extraction

Extracts roles from LDAP group memberships using pattern-based rules. Rules evaluate top-to-bottom; first match wins. Unmatched groups are excluded (explicit inclusion model). See: Roles and Scopes

All groups become roles, extracted as RDN:

auth.ldap.roles {
from = memberOf
}

Group DN cn=admins,ou=groups,dc=example,dc=com becomes role admins.

auth.ldap.roles {
from = memberOf
rules = [
# Exclude audit groups
{ match = "*,ou=audit,*", format = false }
# Strip ROLE_ prefix using regex capture
{ match = "/cn=ROLE_([^,]+),/", format = "$1" }
# Add prefix for team groups
{ match = "*,ou=teams,*", format = "team:$rdn" }
# Catch-all: use RDN
{ match = "*", format = "$rdn" }
]
}
FieldDescription
matchPattern: glob (*,ou=groups,*), regex (/cn=([^,]+)/), bool
formatTemplate with $rdn, $cn, $dn, $1, $2. Use false to exclude
VariableValue
$rdnFirst RDN value (e.g., admins)
$cn, $ou, $uid, …Any RDN component by attribute name
$dnFull DN
$1, $2Regex capture groups (when match is a regex)

Any RDN attribute type works: $sAMAccountName, $userPrincipalName, etc.

Source attribute containing group DNs

Priority: SCRIBE_AUTH_LDAP_ROLES_ATTR > config

PropertyValue
DefaultmemberOf
OverrideSCRIBE_AUTH_LDAP_ROLES_ATTR (optional)
auth.ldap.roles.from = memberOf
auth.ldap.roles.from = ${?SCRIBE_AUTH_LDAP_ROLES_ATTR}

Search scope

base - Only the base DN itself one - One level below base DN sub - Entire subtree (default)

Priority: SCRIBE_AUTH_LDAP_SCOPE > config

PropertyValue
Defaultsub
OverrideSCRIBE_AUTH_LDAP_SCOPE (optional)
auth.ldap.scope = sub
auth.ldap.scope = ${?SCRIBE_AUTH_LDAP_SCOPE}

Scope Extraction

Extracts OAuth scopes from LDAP group memberships. Uses the same syntax as role extraction above.

auth.ldap.scopes {
from = memberOf
rules = [
{ match = "*,ou=scopes,*", format = "$rdn" }
# No catch-all: only explicit scope groups included
]
}

Source attribute containing scope group DNs

Priority: SCRIBE_AUTH_LDAP_SCOPES_ATTR > config

PropertyValue
DefaultmemberOf
OverrideSCRIBE_AUTH_LDAP_SCOPES_ATTR (optional)
auth.ldap.scopes.from = memberOf
auth.ldap.scopes.from = ${?SCRIBE_AUTH_LDAP_SCOPES_ATTR}

Connection settings fall back to root ldap {} config Override LDAP URL if auth uses a different server

Priority: SCRIBE_AUTH_LDAP_URL > config > ldap.url

url = ${ldap.url}

PropertyValue
OverrideSCRIBE_AUTH_LDAP_URL (optional)

Inherits from: ldap

auth.ldap.url = ${?SCRIBE_AUTH_LDAP_URL}

OAuth metadata endpoint

Configures the /.well-known/oauth-authorization-server endpoint (RFC 8414). MCP clients use this for OAuth discovery.

Provider for metadata endpoint Which provider’s OIDC metadata to expose at /.well-known/oauth-authorization-server. The public-issuer (if set) replaces the internal issuer in the response. Auto-selection:

  • If only one provider exists → auto-selected
  • If multiple providers exist → must be explicitly set

Priority: SCRIBE_AUTH_METADATA_PROVIDER > config

PropertyValue
Default"default"
OverrideSCRIBE_AUTH_METADATA_PROVIDER (optional)
auth.metadata.provider = ${?SCRIBE_AUTH_METADATA_PROVIDER}

Authentication methods

How clients can authenticate. Scribe validates credentials and creates an AuthPrincipal for downstream handlers.

MethodHeaderHow it works
bearerAuthorization: Bearer <jwt>JWT validated offline via JWKS
ropcAuthorization: Basic <base64>Exchanges credentials with IdP for JWT
ldapAuthorization: Basic <base64>Validates credentials via LDAP bind

Client sends a JWT. Scribe validates the signature against the IdP’s public keys (fetched from JWKS), checks claims, and extracts identity. No per-request calls to the IdP after initial key fetch.

ropc (Resource Owner Password Credentials)
Section titled “ropc (Resource Owner Password Credentials)”

Client sends username:password via HTTP Basic auth. Scribe exchanges these with the IdP’s token endpoint for a JWT, then validates it normally. Useful for legacy apps or CLI tools that can’t do browser-based OAuth. Security: ROPC is deprecated in OAuth 2.1 because it exposes passwords to the client application. Use bearer tokens when possible.

Client sends username:password via HTTP Basic auth. Scribe searches for the user’s DN using a service account, then binds as that user to verify the password. No IdP needed—authentication happens against LDAP directly. Multiple methods can be enabled. Bearer tokens are checked first. If no Bearer token, Basic auth is tried against ropc or ldap. Supports both array and comma-separated string formats:

methods = [bearer, basic]
methods = "bearer,basic"
SCRIBE_AUTH_METHODS=bearer,basic

Priority: SCRIBE_AUTH_METHODS > config

PropertyValue
Default[bearer]
OverrideSCRIBE_AUTH_METHODS (optional)
auth.methods = [bearer]
auth.methods = ${?SCRIBE_AUTH_METHODS}

Named OIDC providers

Each key is the provider name, used for referencing in ropc.provider, bearer.provider, and metadata.provider. Multiple providers are supported for multi-tenant or migration scenarios. Provider names must be valid HOCON keys (alphanumeric, hyphens, underscores). Example with multiple providers:

providers {
internal {
issuer = "https://internal-auth.example.com"
audiences = ["scribe"]
}
partner {
issuer = "https://partner-auth.example.com"
audiences = ["partner-access"]
}
}

Default provider (named “default” for simplicity) The name “default” is used when no explicit provider is configured for ropc.provider or bearer.provider and only one provider exists.

Expected audiences (validated against ‘aud’ claim) The token’s ‘aud’ claim must contain at least one of these values. Supports HOCON array or comma-separated string.

Priority: SCRIBE_AUTH_AUDIENCES > config

PropertyValue
Default["scribe"]
OverrideSCRIBE_AUTH_AUDIENCES (optional)
auth.providers.default.audiences = ["scribe"]
auth.providers.default.audiences = ${?SCRIBE_AUTH_AUDIENCES}

OAuth client credentials (required for ROPC or opaque token introspection) If this provider is used for ropc.provider or bearer.provider, both client-id and client-secret must be set. Validated at startup. Must be a confidential client configured in your IdP with:

  • For ROPC: “Resource Owner Password” grant enabled
  • For bearer introspection: Token Introspection permission
PropertyValue
OverrideSCRIBE_AUTH_CLIENT_ID (optional)
auth.providers.default.client-id = ${?SCRIBE_AUTH_CLIENT_ID}

PropertyValue
OverrideSCRIBE_AUTH_CLIENT_SECRET (optional)
auth.providers.default.client-secret = ${?SCRIBE_AUTH_CLIENT_SECRET}

Whether this provider is active. Uses same env var as auth.enabled - when auth is enabled globally, the default provider is also enabled (if issuer is configured). Set explicitly to override.

PropertyValue
OverrideSCRIBE_AUTH_ENABLED (optional)
auth.providers.default.enabled = ${?SCRIBE_AUTH_ENABLED}

Issuer URL (internal - used by Scribe for JWKS fetch and validation) This is the URL Scribe uses to fetch the OIDC discovery document and validate the ‘iss’ claim in tokens.

PropertyValue
OverrideSCRIBE_AUTH_ISSUER (optional)
auth.providers.default.issuer = ${?SCRIBE_AUTH_ISSUER}

auth.providers.default.jwks-refresh-interval

Section titled “auth.providers.default.jwks-refresh-interval”

JWKS refresh interval (fallback when IdP sends no Cache-Control headers) How often to check for updated signing keys. HTTP caching headers (Cache-Control, ETag) from the IdP take precedence when present.

PropertyValue
Default5m
OverrideSCRIBE_AUTH_JWKS_REFRESH_INTERVAL (optional)
auth.providers.default.jwks-refresh-interval = 5m
auth.providers.default.jwks-refresh-interval = ${?SCRIBE_AUTH_JWKS_REFRESH_INTERVAL}

Public issuer URL (external - returned to clients for OAuth flows) If your IdP is behind a reverse proxy or load balancer, clients may need a different URL than Scribe uses internally. If not set, falls back to the issuer URL.

PropertyValue
Default"https://auth.example.com/application/o/scribe/"
OverrideSCRIBE_AUTH_PUBLIC_ISSUER (optional)
auth.providers.default.public-issuer = ${?SCRIBE_AUTH_PUBLIC_ISSUER}

ROPC (Resource Owner Password Credentials)

Exchanges username/password for an access token via the IdP’s token endpoint. The returned JWT is validated like any bearer token.

  • Client sends: Authorization: Basic <base64(username:password)>
  • Scribe POSTs to token endpoint with grant_type=password
  • IdP returns access_token (JWT)
  • Scribe validates JWT via JWKS → AuthPrincipal
  • Legacy applications that can’t implement OAuth flows
  • CLI tools where browser redirects aren’t practical
  • Migration path from Basic auth to OAuth

ROPC is deprecated in OAuth 2.1. It exposes user credentials to Scribe (the client), which violates the principle that only the IdP should see passwords. Prefer bearer tokens with proper OAuth flows when possible. Credentials are never stored—they’re exchanged immediately and discarded.

Credential cache

Caches successful authentications to reduce IdP calls. The cache key is a hash of (username, password)—credentials are never stored in plaintext. Trade-offs:

  • Reduces IdP load and latency for repeated requests
  • Password changes take effect after TTL expires
  • Revoked users remain valid until TTL expires

Set ttl = 0 to disable caching.

PropertyValue
Default${auth.cache}

Inherits from: auth.cache

auth.ropc.cache = ${auth.cache}

Credential cache

Caches successful authentications to reduce IdP calls. The cache key is a hash of (username, password)—credentials are never stored in plaintext. Trade-offs:

  • Reduces IdP load and latency for repeated requests
  • Password changes take effect after TTL expires
  • Revoked users remain valid until TTL expires

Set ttl = 0 to disable caching.

Maximum cache entries (LRU eviction) Size based on expected concurrent users. Each entry is ~500 bytes (principal + metadata). Inherits from auth.cache.max-size if not set.

Priority: SCRIBE_AUTH_ROPC_CACHE_MAX_SIZE > auth.cache.max-size

PropertyValue
OverrideSCRIBE_AUTH_ROPC_CACHE_MAX_SIZE (optional)
auth.ropc.cache.max-size = ${?SCRIBE_AUTH_ROPC_CACHE_MAX_SIZE}

How long to cache successful authentications Short values (1-5m) balance performance with security. Longer values reduce IdP load but delay credential changes. Inherits from auth.cache.ttl if not set.

Priority: SCRIBE_AUTH_ROPC_CACHE_TTL > auth.cache.ttl

PropertyValue
OverrideSCRIBE_AUTH_ROPC_CACHE_TTL (optional)
auth.ropc.cache.ttl = ${?SCRIBE_AUTH_ROPC_CACHE_TTL}

Provider name for ROPC authentication

References a provider from auth.providers by name. The provider must have client-id and client-secret configured. Auto-selection:

  • If only one provider has client credentials → auto-selected
  • If multiple providers have credentials → must be explicitly set

The token_endpoint is discovered from the provider’s OIDC metadata.

Priority: SCRIBE_AUTH_ROPC_PROVIDER > config

PropertyValue
Default"default"
OverrideSCRIBE_AUTH_ROPC_PROVIDER (optional)
auth.ropc.provider = ${?SCRIBE_AUTH_ROPC_PROVIDER}

OAuth scopes to request Space-separated list of scopes. Most IdPs require “openid” for JWT tokens.

Priority: SCRIBE_AUTH_ROPC_SCOPES > config

PropertyValue
Default"openid profile email"
OverrideSCRIBE_AUTH_ROPC_SCOPES (optional)
auth.ropc.scopes = "openid profile email"
auth.ropc.scopes = ${?SCRIBE_AUTH_ROPC_SCOPES}

Access Rules (ABAC)

Fine-grained access control using attribute-based policies. Rules are evaluated top-to-bottom; first matching rule wins (firewall style). If no rule matches, access is denied (secure by default).

auth.rules = [
# Allow admin role everywhere
{ action = allow, where = "subject.roles = admin" }
# Allow anonymous on GraphQL schema introspection
{ action = allow, where = "subject.anonymous = true and request.channel = graphql and request.operation = schema" }
# Allow authenticated users to read
{ action = allow, where = "subject.authenticated = true and request.operation in [search, lookup]" }
# Deny writes outside business hours
{ action = deny, where = "request.operation in [modify, add, delete] and (time.hour < 9 or time.hour >= 17)" }
# Catch-all deny (explicit, but also the default if no rule matches)
{ action = deny }
]
FieldRequiredDescription
actionYesallow or deny
whereNoFilter expression (matches all if omitted)
idNoRule identifier for telemetry/logging (default: Rule #N)

The where field accepts any filter syntax. We recommend FleX for readability. See Filters Reference for syntax details.

Example with IDs:

auth.rules = [
{ id = "admin-bypass", action = allow, where = "subject.roles = admin" }
{ id = "deny-anon", action = deny, where = "subject.anonymous = true" }
{ action = allow } # id defaults to "Rule #3"
]
NamespaceAttributeExample
subject.*anonymoustrue (no credentials)
subject.*authenticatedtrue (valid credentials)
subject.*principal.iduser@example.com
subject.*roles”admin”, “reader”
subject.*scopes”read:users”
service.*principal.id”hr-portal” (service client)
request.*channel”ldap”, “rest”, “graphql”
request.*operation”search”, “lookup”, “modify”
request.*method”GET”, “POST” (HTTP only)
request.*path”/api/entries/users”
request.*host”api.example.com”
request.*client.ip”192.168.1.100”
request.*securetrue (TLS active)
request.*protocol”http/1.1”, “http/2”
time.*hour14 (0-23)
time.*dayOfWeek”monday”…“sunday”
time.*weekendtrue (Saturday or Sunday)
time.*date”2026-01-29”
env.*(any env var)env.FEATURE_BETA = “true”

Allow public health checks:

{ action = allow, where = "request.path startswith /health" }

Restrict by IP:

{ action = allow, where = "request.client.ip startswith 10.0." }

Feature flag from environment:

{ action = allow, where = "env.FEATURE_BETA = true and subject.roles = beta-tester" }

Default: allow any authenticated (non-anonymous) access.

Anonymous requests are denied (secure by default). Add rules before the default to customize access:

auth.rules = [
{ id = "admin-bypass", action = allow, where = "subject.roles = admin" }
] ${auth.rules}
PropertyValue
Default[{, id, ...]
auth.rules = [ { id = "authenticated-access", action = allow, where = "subject.anonymous = false" }
]

Browser Session (Cookie + Login)

Browser session management for authenticated users. Provides:

  • Cookie-based authentication (encrypted session cookie)
  • Login endpoints (/auth/login, /auth/callback, /auth/logout, /auth/whoami)

Flow: Browser login → encrypted cookie → subsequent requests authenticated Auto-detection:

  • Session is auto-enabled when any browser UI is active (site, REST Scalar,

GraphQL GraphiQL, or Observe dashboard)

  • Sockets and allowed return paths are auto-detected from enabled UIs

Security:

  • Cookie uses __Host- prefix (requires Secure, same-origin)
  • HttpOnly prevents JavaScript access
  • SameSite=Lax prevents CSRF for safe methods
  • JWE encryption (A256KW + A256GCM) protects contents

Endpoints (registered on sockets where UIs are enabled): GET /auth/config - Auth configuration for clients GET /auth/login - OIDC redirect or login form

POST /auth/login - Username/password authentication

GET /auth/callback - OIDC callback handler POST /auth/logout - Clears auth cookie GET /auth/whoami - Current authenticated identity

Encryption keys for cookie JWE

Array of 32-byte keys for A256KW. A random key is selected for each encryption; all keys are tried for decryption (enables zero-downtime key rotation). Format: base64-encoded keys, comma-separated or as array If not set, keys are derived from a SHA-256 chain seeded by the auth config hash. The derived keys are 256-bit AES keys - cryptographically equivalent to explicit keys. They’re stable across restarts (same config = same keys), so cookies survive application restarts. When to set explicit keys:

  • Multiple instances with different configs (load-balanced deployments)
  • You rotate config frequently and want cookies to survive
  • Compliance requires documented key management

To generate a key: openssl rand -base64 32 Key rotation procedure (zero-downtime):

  • Add new key to end of array (old keys remain for decryption)
cookie-keys = ["old-key-1", "new-key-1"]
  • Restart application - new sessions use new key, old cookies still work
  • Wait for session-ttl (default 10h) for all old cookies to expire
  • Remove old keys from array
cookie-keys = ["new-key-1"]
  • Restart application

Priority: SCRIBE_AUTH_SESSION_KEYS > config

PropertyValue
OverrideSCRIBE_AUTH_SESSION_KEYS (optional)
auth.session.cookie-keys = ${?SCRIBE_AUTH_SESSION_KEYS}

Cookie name

The base name for the session cookie. In production (HTTPS), the cookie uses __Host- prefix for security (e.g., __Host-scribe-auth).

Default: “scribe-auth”

PropertyValue
Default"scribe-auth"
auth.session.cookie-name = "scribe-auth"

Enable browser session management When enabled, cookie authentication and /auth/* routes are active. Browser UI gating (/ui, /observe, etc.) requires both auth.enabled and session.enabled; when auth.enabled=false, /ui routes remain accessible.

Default behavior (auto):

  • Enabled when any browser UI is active (site, REST Scalar, GraphQL

GraphiQL, or Observe dashboard)

  • Disabled otherwise

Set explicitly to override auto-detection:

enabled = true # Always enable browser sessions
enabled = false # Never enable browser sessions (header-based auth only)

Priority: SCRIBE_AUTH_SESSION_ENABLED > config > auto-detect

enabled = auto # (default: auto-detect based on UI settings)
PropertyValue
OverrideSCRIBE_AUTH_SESSION_ENABLED (optional)
auth.session.enabled = ${?SCRIBE_AUTH_SESSION_ENABLED}

Login method

Which authentication method to use for browser login. Defaults to first method in auth.methods if not specified. Options: bearer - Redirect to OIDC provider (recommended) ldap - Username/password form → LDAP bind ropc - Username/password form → OIDC token exchange

Fallback behavior:

  • If auth.methods = [ldap], browser login uses LDAP form
  • If auth.methods = [bearer, ldap], browser uses OIDC redirect
  • To use OIDC when LDAP is first, set method = bearer explicitly

Priority: SCRIBE_AUTH_SESSION_METHOD > config > auth.methods[0]

PropertyValue
OverrideSCRIBE_AUTH_SESSION_METHOD (optional)
auth.session.method = ${?SCRIBE_AUTH_SESSION_METHOD}

OAuth scopes to request Space-separated list of scopes for OIDC authorization. Most IdPs require “openid” for JWT tokens.

Default: “openid profile email”

PropertyValue
Default"openid profile email"
OverrideSCRIBE_AUTH_SESSION_SCOPES (optional)
auth.session.scopes = "openid profile email"
auth.session.scopes = ${?SCRIBE_AUTH_SESSION_SCOPES}

Session TTL

How long the session cookie remains valid. After expiry, user must re-authenticate.

Default: 10 hours

PropertyValue
Default10h
OverrideSCRIBE_AUTH_SESSION_TTL (optional)
auth.session.session-ttl = 10h
auth.session.session-ttl = ${?SCRIBE_AUTH_SESSION_TTL}

OIDC state TTL

How long the OIDC state parameter is valid during authorization flow. This protects against replay attacks by limiting the time window for a callback to be valid after initiating login. Only applies when method = bearer (OIDC flow).

Relationship with session-ttl:

  • state-ttl (default 5m): How long user has to complete login at IdP
  • session-ttl (default 10h): How long session remains valid after login
  • state-ttl should be much shorter than session-ttl
  • If user takes longer than state-ttl at IdP, they must restart login

Default: 5 minutes

PropertyValue
Default5m
OverrideSCRIBE_AUTH_SESSION_STATE_TTL (optional)
auth.session.state-ttl = 5m
auth.session.state-ttl = ${?SCRIBE_AUTH_SESSION_STATE_TTL}