Skip to content

Options reference ​

Every public option accepted by op.New, grouped by what it touches. WithIssuer, WithStore, and WithKeyset are always required. WithCookieKeys and WithLoginFlow are required when the authorization_code grant is enabled, including by the default grant set; everything else is optional and refines the defaults. The godoc of package op ("Option groups" in op/doc.go) lists the same options by group.

How to read this page

Click the option name for the deep-dive page. The "Section" column tells you which discovery / endpoint surface the option moves. "Default" is empty when the option has no built-in default — supplying it is the only way to enable the behaviour.

Which options to read first ​

The options fall into three tiers. Read them in this order.

TierOptionsWhere
Start hereWithIssuer, WithStore, WithKeyset; WithCookieKeys and WithLoginFlow while authorization_code is enabled; WithProfile; then usually WithStaticClients, WithLogger, WithMountPrefix, WithTrustedProxies, WithUI, WithPrometheus, WithAuditLoggerRequired and conditionally required, Profile, features, grants, Clients & registration, Observability
By use casePick the section that matches the feature you are adding: sign-in, UI, tokens, discovery and endpoints, subject strategy, extra grants, RAR and Grant Management, JWE, mTLS and proxiesAuthentication & login flow, UI, Tokens, Discovery & endpoints, Subject strategy, Grants, Authorization features, Encryption (JWE), mTLS / proxy / network
Rarely neededDevelopment and test switches (WithAllowInsecureOutboundForDev, WithAllowLocalhostIssuerForDev) and test injection (WithClock). Leave them off in production. WithAllowPrivateNetwork is separate: it is network reach for relying parties on a private network, legitimate in productionmTLS / proxy / network, Operational posture

What option do I need? ​

This page is a flat reference of the public op.New options. With 80+ options the table can be hard to scan when you arrive with a specific goal in mind. Use the decision tree below to find the relevant area, then jump into the matching section of the table.

Start with the options that shape everything else
The first half of a decision tree that routes a configuration goal to the op.New option that handles it: booting a fresh OP, switching on a FAPI profile, or enabling a single feature. A no at the bottom continues in the next figure.Booting a fresh OPnothing works yetyesThe required optionsissuer · store · keyset · cookie keys · loginnoTurning on FAPI 2.0 in one switchyou want the whole profile, not piecesyesWithProfile(…)applies the profile's settings at oncenoOne feature, without a profilePAR, JAR, JARM, grant management, …yesWithFeature(…)one capability, switched on by itselfnoStill no match: continue below
A profile pins several of the later answers on its own, so ask the profile question before the feature ones.
Then narrow grants, sender constraints and token format
The second half of the decision tree: restricting the grants /token accepts, adding a sender constraint, or choosing the access token format. A no at the bottom falls through to the full option table.Restricting which grants /token acceptse.g. never issue client credentialsyesWithGrants(…)unlisted grants are refusednoSender-constrained access tokensa leaked token is useless on its ownyesWithFeature(DPoP) · WithFeature(MTLS)either or both — each client picksnoChoosing between JWT and opaquea per-audience decisionyesWithAccessTokenFormat(…)trade-off: revocation reach vs latencynoNone of these: the full option table is below
Reading both figures top to bottom is also the order to configure in. Each answer names where to look; anything unmatched is in the full table below.
  • You're booting a fresh OP for the first time → start with WithIssuer, WithStore, WithKeyset, WithCookieKeys, and WithLoginFlow. The last two are required when authorization_code is enabled, which is the default grant set. See Required options and the minimal OP walkthrough.
  • You want OAuth 2.1 posture without adopting FAPI → this is the default: an OP without WithProfile runs profile.OAuth21, which requires PKCE on every authorization-code request and otherwise keeps the OIDC Core defaults. WithProfile(profile.OIDCCore) opts out for relying parties that cannot send PKCE. For this library's FAPI 2.0 profiles, use profile.FAPI2Baseline (or profile.FAPI2MessageSigning, profile.FAPICIBA); these profiles auto-select DPoP unless you explicitly enable mTLS. A profile that needs a grant you did not wire fails op.New rather than mounting the endpoint for you — profile.FAPICIBA requires grant.CIBA. See Declaring a security profile, Use case: FAPI 2.0 Baseline and Concepts: FAPI.
  • You want a single feature without committing to a profile → WithFeature(feature.PAR) / JAR / JARM / DPoP / MTLS / Introspect / Revoke. Public and native clients always require PKCE; the default profile.OAuth21 and this library's FAPI profiles require it for every authorization-code client. Dynamic Registration, RAR, and Grant Management are enabled through their dedicated options because they need extra configuration.
  • You want to restrict the grant types accepted at /token → WithGrants(grant.AuthorizationCode, grant.RefreshToken, grant.ClientCredentials, grant.DeviceCode). Grants that need a collaborator are turned on through their own option instead (WithCIBAGrant(...), WithJWTBearerGrant(...)); see Turning on a grant. WithCustomGrant(...) and WithTokenExchangeGrant(...) likewise mount what their grants need.
  • You want sender-constrained access tokens → DPoP path: WithFeature(feature.DPoP) plus optional WithDPoPNonceSource(op.NewInMemoryDPoPNonceSource(...)). mTLS path: WithFeature(feature.MTLS) plus optional WithMTLSProxy(op.MTLSProxy{HeaderName: ..., TrustedProxies: ...}); WithMTLSProxy and WithMTLSRootCAs need feature.MTLS. For a multi-replica DPoP nonce flow use Store.DPoPNonceSource from op/storeadapter/redis. See Concepts: sender-constrained tokens, DPoP, mTLS, and Use case: DPoP nonce.
  • You want JWT versus opaque access tokens → WithAccessTokenFormat(...) for the OP-wide default and WithAccessTokenFormatPerAudience(...) for RFC 8707 resource-scoped overrides. See Concepts: access-token format.
  • You want pairwise sub per sector → WithPairwiseSubject(salt) (32-byte salt minimum). See Use case: pairwise subject.
  • You want to seed clients statically at boot → WithStaticClients(op.PublicClient{...}, op.ConfidentialClient{...}, op.PrivateKeyJWTClient{...}) (struct literals). See Concepts: client types.
  • You want Dynamic Client Registration → WithDynamicRegistration(...). See Use case: Dynamic Client Registration.
  • You want token introspection or revocation endpoints → WithFeature(feature.Introspect) and / or WithFeature(feature.Revoke). The "Profile, features, grants" table below covers fine-tuning.
  • You want to extend the scope catalog → WithScope(op.PublicScope("name", "label")) for OIDC-discovery-visible scopes, WithScope(op.InternalScope("name")) for internal-only ones. See Concepts: scopes and claims and Use case: scopes.
  • You want a custom grant_type → WithCustomGrant(handler). See Use case: custom grant.
  • You want internationalization (i18n) → WithDefaultLocale(...), WithLocaleBundle(bundle), WithPreferredLocaleStore(...). See Use case: i18n.
  • You want JWE request-object decryption → WithEncryptionKeyset(...) for the OP's inbound JWE keys, and optionally WithSupportedEncryptionAlgs(op.EncryptionAlgs{KeyManagement: ..., ContentEncryption: ...}) to narrow the default allow-list. Outbound JWE responses use keys from the recipient client's JWKS. See Use case: JWE encryption.
  • You want CORS for SPA clients → WithCORSOrigins(...). See Use case: CORS for SPA.
  • You want Prometheus metrics → WithPrometheus(registry). The library does not mount /metrics; expose the registry from your own router. See Use case: Prometheus metrics.
  • You want audit logging on a separate sink from app logs → WithAuditLogger(*slog.Logger). See Audit event catalog.
  • You want a browser SPA to own the login screens → WithUI(op.SPAUI{LoginMount: ...}). Use WithUI(op.DriverUI{Driver: ...}) for a non-browser front end or a driver that renders something other than HTML. Under op.SPAUI the SPA owns the login, consent and chooser screens, and the prompts the SPA fetches from <LoginMount>/state are rendered as JSON (interaction.JSONDriver). See Use case: SPA custom interaction.

How options compose ​

Every option is one of three kinds.

  • Single-valued. An option that takes an argument sets one setting, unless its row below says it is additive. op.New fails when it is supplied twice (<Name> may be called at most once) or when two options set the same setting (WithSubjectGenerator conflicts with prior WithPairwiseSubject), so a later call never silently replaces an earlier one.
  • Additive. Repeated calls accumulate entries: WithStaticClients, WithCORSOrigins, WithTrustedProxies, WithTrustedProxyHosts, WithScope, WithFeature, WithProfile, WithInteractions, WithLoginAttemptObserver, WithFirstPartyClients, WithCustomGrant, WithAuthorizationDetailTypes, and WithProtectedResources.
  • Flag. An option with no argument is idempotent; repeating it changes nothing.

Two options are repeatable with a narrower rule. WithEndpoints may be called more than once, but each path field is single-valued. Each key of a WithLocaleBundle locale is single-valued, so two calls may supply different keys of one locale but not the same key.

OptionValueSectionDefault
WithDefaults...op.Optionmarks the wrapped options as defaults.none
  • WithDefaults: A single-valued option inside gives way when an option outside WithDefaults sets the same setting, whatever the order; additive and flag options inside apply as usual. Two defaults that set the same setting still fail op.New. Use it to layer deployment overrides on a shared base option list: op.New(op.WithDefaults(baseOptions()...), op.WithAccessTokenTTL(5*time.Minute)). op/testkit passes its own settings through WithDefaults, so testkit.WithOptions(...) still replaces the testkit defaults.

Required and conditionally required ​

OptionValueSectionDefault
WithIssuerstringdiscovery issuer, JWT iss, cookie scope—
WithStorestore.Storeevery protocol-state substore—
WithUserStorestore.UserStorethe source of end-user records for ID Token and /userinfo claims; wins over the inferred sourcethe Store of LoginFlow.Primary when it is an op.PrimaryPassword (or of the first op.PrimaryPassword inside a primary op.StepOneOf)
WithKeysetop.Keyset (an ECDSA P-256 key for ES256, the default; an RSA key of 2048 bits or more when WithSigningAlgorithm(op.AlgRS256) is set)JWKS, JWS signing—
WithCookieKeys32-byte key(s)session / CSRF cookie AES-256-GCMrequired when authorization_code is enabled
WithLoginFlowop.LoginFlowhow a user signs inrequired when authorization_code is enabled
  • WithUserStore: A bundled adapter's concrete Users() can be passed directly: op.WithUserStore(st.Users()). op.New fails when an enabled grant resolves an end user (any grant except client_credentials) and neither this option nor an op.PrimaryPassword store is set, so passkey-only, device-code and CIBA deployments must pass it. Default: The WithStore backend is never consulted. A client_credentials-only OP needs none.

Profile, features, grants ​

OptionValueSectionDefault
WithProfileprofile.Profiledeclares this library's profile.OAuth21 (the default), profile.OIDCCore or a FAPI profileprofile.OAuth21
WithFeaturefeature.Flag (one per call; repeatable)enables PAR / DPoP / mTLS / JAR / JARM / introspect / revoke individuallyconservative defaults
WithGrants...grant.Type (variadic)restricts the grant types accepted at /token; single-valued, so compose the full set before passing options to op.Newauthorization_code, refresh_token
WithScopeop.Scope (one per call; use the op.PublicScope / op.InternalScope constructors)extends the scope catalogopenid, profile, email, address, phone, offline_access
WithOpenIDScopeOptional(no args)makes pure OAuth 2.0 (scope without openid) acceptableopenid required
WithStrictOfflineAccess(no args)gates refresh_token issuance behind explicit offline_access consentlax (refresh on any openid grant)
  • WithProfile: profile.OAuth21 is OAuth 2.1: PKCE on every authorization-code request. profile.OIDCCore is the OpenID Connect Core 1.0 shape: PKCE optional for confidential clients; it cannot be combined with any other profile, or op.New fails. FAPI profiles select DPoP when they require DPoP-or-mTLS and mTLS was not explicitly enabled. Missing features a profile requires are switched on for you; a missing grant fails op.New instead, with the error naming the option that activates it.
  • WithGrants: It replaces the default set. Setting WithLoginFlow, WithUI, WithInteractions or WithFirstPartyClients explicitly without grant.AuthorizationCode makes op.New fail with op.ErrAuthorizationCodeGrantRequired; a login flow that is the claim source for device_code or CIBA, and options inside WithDefaults, are not counted

How each grant and feature is enabled ​

Three mechanisms exist: WithGrants for a grant that needs no collaborator, a dedicated option for a capability that needs one, and WithFeature (or a profile that requires the feature) for a protocol extension. device_code is the one capability both WithGrants and its dedicated option reach.

CapabilityEnabled by
authorization_code, refresh_tokenon by default; WithGrants replaces the set
client_credentialsWithGrants(grant.ClientCredentials)
Device code (RFC 8628)WithDeviceCodeGrant; it adds grant.DeviceCode to whatever WithGrants selected, in either order
CIBA poll modeWithCIBAGrant; grant.CIBA in WithGrants without it fails op.New
JWT bearer (RFC 7523)WithJWTBearerGrant; grant.JWTBearer in WithGrants without it fails op.New
Token exchange (RFC 8693)WithTokenExchangeGrant (WithIDJAGIssuance also enables the grant)
Your own grant_typeWithCustomGrant
PAR, JAR, JARM, DPoP, mTLS, introspection, revocationWithFeature(feature.X), or switched on by the profile that requires it
Dynamic registration, RAR, Grant Managementtheir own options (WithDynamicRegistration, WithAuthorizationDetailTypes, WithGrantManagement); there is no feature flag to pass for them
PKCEalways supported. feature.PKCE has no effect and op.New logs a startup warning when it is passed; code_challenge is mandatory for every client by default (profile.OAuth21) and optional for confidential clients only under WithProfile(profile.OIDCCore)

Turning on a grant ​

  • A grant that needs a collaborator is enabled through its dedicated option, and WithGrants alone is refused by op.New. CIBA needs a login-hint resolver (WithCIBAGrant); JWT bearer needs trusted issuers and a subject resolver (WithJWTBearerGrant).
  • device_code has no collaborator, so it works either way: WithGrants(..., grant.DeviceCode) or WithDeviceCodeGrant(op.DeviceCodeGrant{}).
  • Listing a grant in WithGrants and also enabling it through its dedicated option is a no-op. The dedicated option adds its grant to whatever WithGrants selected, in either order.

Clients & registration ​

OptionValueSectionDefault
WithStaticClients...op.ClientSeed (use op.PublicClient / op.ConfidentialClient / op.PrivateKeyJWTClient)seeds the client registry at bootempty
WithHighEntropyClientSecrets(no args)switches the OP-wide client-secret verifier to the high-entropy keyed-hash format.Argon2-compatible client secrets
WithFirstPartyClients...string (client IDs)grants first-party consent skipnone
WithDynamicRegistrationop.DynamicRegistrationmounts /register (RFC 7591/7592)disabled
WithClientIDMetadataDocumentsop.ClientIDMetadataDocuments (struct: optional Allow func)enables client ID metadata documents (draft-ietf-oauth-client-id-metadata-document)disabled
  • WithStaticClients: op.New refuses a duplicate client_id, a ConfidentialClient with neither Secret nor SecretHash, and a seed carrying both jwks and jwks_uri. Once a seed is written, the stored record is authoritative: after editing a seed, run SyncStaticClients before start (see Client onboarding)
  • WithHighEntropyClientSecrets: Existing Argon2 client hashes cannot be retrofitted; reprovision every client before enabling it.
  • WithClientIDMetadataDocuments: Allow func(ctx, clientID string) bool is consulted on every resolution before any fetch; nil admits every URL that passes the draft's URL rules. This is the registration path MCP authorization clients use: a URL client_id resolves to a fetched document. Documents may declare only the authorization_code and refresh_token grants and the none or private_key_jwt authentication methods; PKCE is always required and these clients never get first-party auto-consent. Registered clients take precedence over a URL of the same value. Callable once. Experimental, tracks an IETF draft; discovery advertises client_id_metadata_document_supported

Authentication & login flow ​

OptionValueSectionDefault
WithLoginFlowop.LoginFlowdeclarative DAG of Step + Rulenone
WithInteractions...op.Interaction (variadic)non-credential prompts (T&C, KYC) layered on top of the WithUI mode, which renders themconsent only
WithLoginAttemptObserverop.LoginAttemptObservercounts failed attempts for RuleAfterFailedAttemptsnone
WithMFAEncryptionKeys32-byte key(s)AES-256-GCM seal of TOTP secrets at restnone
WithAuthnLockoutStorestore.AuthnLockoutStorepersists the per-subject failure counter shared by the built-in password, TOTP, email-OTP and recovery-code stepsthe WithStore value's AuthnLockouts(), when it has one
WithACRPolicyop.ACRPolicy (interface) or op.ACRLevels (map of custom acr to minimum AAL)decides which acr a login satisfies and reportsop.DefaultACRPolicy: the canonical acr URIs only
  • WithLoginFlow: LoginFlow.Primary must be a step that establishes the subject; StepTOTP, StepEmailOTP and StepRecoveryCode need a known subject and run as Rule steps after a subject-binding primary, otherwise op.New fails. PrimaryPasskey at Primary (or in a StepOneOf there) runs a passwordless login and must set RequireUserVerification, otherwise op.New fails. A custom op.Authenticator plugs in as an op.ExternalStep; LoginFlow.Risk takes the risk assessor
  • WithInteractions: The names consent and chooser belong to the built-in prompts; an entry using either fails op.New

WithAuthnLockoutStore feeds one rolling 24-hour counter per subject: a 1-hour lock at 30 cumulative failures, a 24-hour lock at 90. The built-in PrimaryPassword, StepTOTP, StepEmailOTP and StepRecoveryCode steps all count against it, so an attacker cannot double the guess budget by switching factors. A wrong password is counted; once the subject is locked even the correct password is refused with the same response as a wrong one, and a successful password login does not reset the counter. Unknown usernames and accounts without a password hash never touch it. PrimaryPasskey is not attached; the passkey ceremony owns its own limits. An ExternalStep after Primary joins the counter when it sets CountFailures; at Primary no subject is bound, so op.New rejects the flag there and the remote verifier keeps its own lockout. A newly engaged lock emits lockout.engaged (Audit events).

When the option is not set, op.New uses the AuthnLockouts() of the WithStore value if it has one. The in-memory, SQL and DynamoDB adapters do, and composite.Store forwards the backend routed for its AuthnLockouts kind. An explicit option always wins. If no store resolves while the login flow has a counter-using step (a password or second-factor step, or an ExternalStep with CountFailures), op.New refuses to start and returns op.ErrAuthnLockoutStoreRequired; match it with errors.Is. The SQL and DynamoDB stores are durable and shared across replicas; inmem.Store.AuthnLockouts() is process-local and resets on restart.

WithACRPolicy takes any op.ACRPolicy; op.ACRLevels is the map form for custom acr values (see MFA / step-up). When Resolve returns ok=false, an essential acr request is refused with unmet_authentication_requirements and a voluntary one reports the canonical URI of the level the login reached.

Authentication-factor records are intentionally outside store.Store. StepTOTP, PrimaryPasskey, StepRecoveryCode, and StepEmailOTP receive their own stores because enrollment schema, encryption keys, and account-recovery policy belong to the embedding application. In-memory, SQL, and DynamoDB adapters expose the matching accessors. examples/27-durable-mfa-store uses the shipped SQL adapter's factor stores alongside the core OP tables; implement the factor-store contracts yourself only for another backend.

Two factor-store contracts matter for durability-sensitive deployments. store.EmailOTPStore.Get must keep records readable until EmailOTPRecord.RetainUntil, not merely until the code's ExpiresAt, so resend caps and brute-force counters survive an expired code. store.RecoveryStore.Consume must compare the presented code hash with the currently stored slot and reject stale hashes, so regenerated recovery batches revoke old leaked codes instead of burning a slot in the new batch. TOTP and email-OTP Version values are opaque, store-issued equality tokens for compare-and-swap; do not infer ordering or manufacture them. A stale CAS returns the consumed/stale error. PasskeyStore.Put must atomically refuse a duplicate credential ID when it names a different subject; PasskeyStore.Put must never move that credential between subjects.

UI ​

OptionValueSectionDefault
WithUIexactly one of op.HTMLUI, op.SPAUI, op.DriverUIselects how sign-in, consent and the account chooser are rendered.op.HTMLUI{} (built-in HTML pages)
op.HTMLUIstruct: Login / Consent / Chooser, each an op.PageTemplate (Template *html/template.Template and ContentSecurityPolicy)renders the built-in pages, replacing any screen whose Template is set; OP still owns state, CSRF, and persistencebuilt-in markup
op.SPAUIstruct: LoginMount / StaticDir / ContentSecurityPolicymounts the SPA shell and static asset tree while the OP serves the JSON interaction state surface. LoginMount is the only route.none
op.DriverUIstruct: Driver interaction.Driverrenders the prompts through your own driver (server-side renderer, JSON driver behind an external front end)none
WithLogoutUIop.LogoutUI (struct: ConfirmTemplate / SignedOutTemplate — both *html/template.Template, both required — and ContentSecurityPolicy)renders the two RP-Initiated Logout pages, the confirmation and the signed-out page, from embedder templatesbundled pages
WithCORSOrigins...stringexplicit origins added to the API CORS allow-list.derived for API routes; issuer-only for ceremonies
WithDefaultLocaleop.Locale (BCP 47 tag)canonicalized BCP 47 default UI locale when the request carries no ui_locales; an unregistered default fails op.New"en"
WithLocaleBundleop.LocaleBundle (one per call; repeatable)registers a per-locale message bundle for the bundled HTML driverEnglish + Japanese seed
WithPreferredLocaleStoreop.PreferredLocaleStoreper-user locale override consulted at the head of the §L.2 chainnone
  • WithUI: Single-valued: one mode is active, so there is nothing to combine. A browser sign-in option: setting it without authorization_code fails op.New. Variants below.
  • op.HTMLUI: A screen with a nil Template keeps the built-in markup. Login covers every sign-in prompt except consent, chooser and passkey (password, TOTP, email OTP, recovery, factor choice, captcha, custom steps); see Custom login UI
  • op.SPAUI: ContentSecurityPolicy replaces the default policy sent with the SPA shell (see below).
  • op.DriverUI: Pass an interaction.TemplateOverlayDriver to lay page templates over a driver of your own or over an interaction.HTMLDriver with its own translator
  • WithLogoutUI: The confirmation template must range over Fields, a declared CSP replaces the whole default header, and it applies in every WithUI mode. See Brand the logout pages.
  • WithCORSOrigins: Redirect-URI origins may be derived for API CORS, but interaction and end-session ceremony checks allow only the issuer origin plus these explicit origins.

WithUI takes exactly one mode, so the modes never conflict. SPAUI.LoginMount is the only SPA route. Consent and the chooser use the login interaction state surface, and the logout confirmation remains in the normal end-session flow, rendered by the server; use WithLogoutUI to change how those pages look. See Custom chooser UI.

The SPA shell (LoginMount/{uid}, when StaticDir is set) carries a default Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'. A bundle with inline script, external origins or a <base> element must set SPAUI.ContentSecurityPolicy; the value is validated like PageTemplate.ContentSecurityPolicy and a rejected value fails op.New. Asset responses carry no policy. See SPA custom interaction.

Ceremony origins must be same-site with the issuer. An issuer sibling such as login.example.com next to op.example.com is supported when it is listed here. A genuinely cross-site origin receives CORS headers without a working interaction or end-session ceremony: the __Host- cookies are SameSite-protected, so the browser withholds them and the endpoint returns 404. Host the UI on the issuer or a same-site sibling; the cookie attributes are not configurable.

WithLocaleBundle canonicalizes locale tags to lowercase, hyphen-separated BCP 47 form. Provider.SetLocaleCookie(w, locale) and Provider.ClearLocaleCookie(w) are the public cookie seams; SetLocaleCookie resolves exact and language-only matches and returns op.ErrLocaleNotRegistered when no registered locale matches. The cookie is __Host-oidc_locale, secure, HttpOnly, SameSite=Lax, and independent of the login session.

Tokens ​

OptionValueSectionDefault
WithAccessTokenFormatop.AccessTokenFormat (AccessTokenFormatJWT / AccessTokenFormatOpaque)JWT vs opaque, OP-wideJWT
WithAccessTokenFormatPerAudiencemap[string]op.AccessTokenFormat (RFC 8707 resource → format)mixed format by audienceOP-wide value
WithAccessTokenRevocationStrategyop.AccessTokenRevocationStrategyrevocation policy for issued JWT access tokensgrant tombstone
WithAccessTokenTTLtime.Durationaccess token lifetime5 min
WithSigningAlgorithmop.SigningAlgorithm (op.AlgES256 / op.AlgRS256)the one algorithm the OP issues every JWS withES256
WithRefreshTokenTTLtime.Durationrefresh token lifetime (non-offline)30 days
WithRefreshTokenOfflineTTLtime.Durationrefresh token lifetime when offline_access grantedinherits the refresh token TTL
WithRefreshGracePeriodtime.Duration (zero disables; negative rejected; an op.New error under the FAPI 2.0 profiles, which do not rotate refresh tokens)rotation grace window60 s
WithDPoPNonceSourceop.DPoPNonceSource (interface)server-supplied DPoP nonce storenone
WithIDTokenScopeClaims...string (scope names)also writes the claims those scopes release at /userinfo into the ID Tokenoff
  • WithAccessTokenRevocationStrategy: The values are RevocationStrategyGrantTombstone, RevocationStrategyJTIRegistry and RevocationStrategyNone. GrantTombstone (default) needs Store.GrantRevocations(), JTIRegistry needs Store.AccessTokens() — both checked at op.New
  • WithSigningAlgorithm: Fixes what the active WithKeyset entry must be (ECDSA P-256 for ES256, an RSA key of 2048 bits or more for RS256) and what discovery and dynamic registration advertise. Keyset entries behind the active one may be of the other algorithm's key type while an OP moves between algorithms, provided each sets NotAfter. AlgRS256 is refused under profile.FAPI2Baseline, profile.FAPI2MessageSigning and profile.FAPICIBA; any other value fails op.New. Callable once
  • WithRefreshTokenOfflineTTL: Default: inherits WithRefreshTokenTTL (zero value defers).
  • WithDPoPNonceSource: op.NewInMemoryDPoPNonceSource provides one.
  • WithIDTokenScopeClaims: Pass one or more of profile, email, address, phone, or a registered custom scope with non-empty Claims. The claims are written into the ID Token of the authorization_code, refresh_token, device_code and CIBA grants. A deliberate deviation from OIDC Core §5.4. See Public / internal scopes.

WithInMemoryDPoPNonceLogger is a helper option for op.NewInMemoryDPoPNonceSource, not an op.New option. Use it only when you use the bundled in-memory nonce source.

profile.FAPI2Baseline and profile.FAPI2MessageSigning do not rotate refresh tokens (Security Profile §5.3.2.1(9)), so they have no window to set and op.New rejects this option under them. Without those profiles, the option accepts any non-negative duration.

WithAccessTokenFormat and WithAccessTokenFormatPerAudience govern the access tokens issued by the built-in grant paths. A custom grant's BoundAccessToken is always minted as an RFC 9068 JWT, regardless of either option; a custom grant that needs an opaque value must mint it itself and return it as AccessToken, and must stamp cnf itself when it wants a sender-constrained token.

JWT access tokens issued from an authorization code or its refresh chain carry a userinfo_claims private claim: the userinfo member of the OIDC claims request, or {} when the request had none. /userinfo releases claims from it; resource servers should ignore it. The name is reserved, so a custom grant cannot set it through its extra claims.

Discovery & endpoints ​

OptionValueSectionDefault
WithEndpointsop.Endpoints (struct: per-endpoint path overrides)overrides default endpoint pathsspec defaults
WithMountPrefixstring (must start with /; pass / for root)embeds an issuer-relative path prefix for protocol endpoints; discovery paths are derived from the issuer separately/oidc
WithClaimsSupported...string (variadic)populates claims_supported in discoveryomitted
WithClaimsParameterDisabled(no args)turns claims_parameter_supported off: malformed JSON is still rejected, but the claims projection is suppressed and valid claims payloads are ignored at authorize / PARenabled
WithACRValuesSupported...string (variadic)publishes acr_values_supported; when non-empty, the same allow-list is enforced on /auth, /par, and CIBA /bc-authorizeempty (omitted from discovery; no allow-list)
WithDiscoveryMetadataop.DiscoveryMetadata (typed service_documentation, policy / TOS / UI locale / mTLS alias fields plus Extra map[string]any)injects RFC 8414 / OIDC Discovery metadata not owned by the OPnone
WithPARTTLtime.Durationoverrides the lifetime of request_uri values issued by /par60 s
WithJWKSRotationActivefunc() boolpredicate that flips JWKS Cache-Control to short-cache during a rotation windowalways long-cache
  • WithDiscoveryMetadata: UILocalesSupported overrides the auto-derived locale list when non-empty, and Extra keys that collide with OP-controlled fields are rejected, including prompt_values_supported, which the OP publishes itself
  • WithPARTTL: Expiry is checked when the browser presents the URI at /auth, while later code emission remains single-use

Subject strategy ​

OptionValueSectionDefault
WithSubjectGeneratorop.SubjectGenerator (interface)overrides the sub claim derivation; the in-tree op/subject.UUIDv7 is the default.UUIDv7 passthrough
WithPairwiseSubject[]byte salt (≥ 32 bytes)enables OIDC Core §8.1 pairwise sub derivation per sector; mid-life switching is rejected at op.Newpublic (UUIDv7)
  • WithSubjectGenerator: The generator is called at each projection and must be deterministic for identical input with no surprise I/O.

See Use case: pairwise subject.

Grants — Device Code, CIBA, Custom, Token Exchange ​

OptionValueSectionDefault
WithDeviceCodeGrantop.DeviceCodeGrantenables the RFC 8628 device-authorization grant; mounts /device_authorization and registers the URN at /token. Callable oncedisabled
WithCIBAGrantop.CIBAGrantenables CIBA poll mode; mounts /bc-authorize and registers the CIBA URN. Callable oncedisabled
WithCustomGrantop.CustomGrantHandlerregisters an embedder-defined grant_type URN at /tokennone
WithTokenExchangeGrantop.TokenExchangeGrant (struct: Policy, required)enables the RFC 8693 token-exchange grant; the policy decides admission per request and may narrow OP-computed defaultsdisabled
WithJWTBearerGrantop.JWTBearerGrant (struct: Issuers []op.AssertionIssuer, ResolveSubject func(ctx, op.AssertionSubject) (string, error))enables the RFC 7523 §2.1 JWT bearer grant and registers its URN at /tokendisabled
WithIDJAGIssuanceop.IDJAGIssuancePolicy (interface: Allow(ctx, op.IDJAGRequest) (*op.IDJAGDecision, error); non-nil)lets token exchange answer requested_token_type=urn:ietf:params:oauth:token-type:id-jag with a signed Identity Assertion JWT Authorization Grant for an ID token this OP issueddisabled
  • WithDeviceCodeGrant: Fields: VerificationURI (absolute URL advertised on the device's display, default <issuer>/device), TTL (the expires_in lifetime of newly issued device_code records, independent of the access-token TTL, default 10 min), PollInterval (the advertised polling interval; clients polling faster receive slow_down, default 5 s). A zero field keeps the default and a negative duration fails op.New. The grant resolves end users, so op.New also needs WithUserStore or an op.PrimaryPassword store
  • WithCIBAGrant: Fields: HintResolver (required), DefaultRequestTTL (default 600 s), MaxRequestTTL (cap on requested_expiry, no cap when zero), PollInterval (default 5 s), MaxPollViolations (default 5). A zero field keeps the default; a negative duration, or a DefaultRequestTTL above MaxRequestTTL, fails op.New; under profile.FAPICIBA the default and maximum expiry are capped at 10 minutes
  • WithCustomGrant: Duplicate custom names and collisions with every built-in grant type are rejected at construction. The handler returns a verbatim access token or a BoundAccessToken request the OP signs
  • WithTokenExchangeGrant: Callable once
  • WithJWTBearerGrant: Each AssertionIssuer sets Issuer (never the OP's own issuer; unique), exactly one of JWKSURI (https) and JWKS, a non-empty AllowedClients list, and a Profile (op.AssertionProfileJWT, the zero value, or the experimental op.AssertionProfileIDJAG). ResolveSubject maps a verified assertion to a local subject; return op.ErrUnknownAssertionSubject for an unprovisioned account. The grant resolves end users, so op.New also needs WithUserStore or an op.PrimaryPassword store; grant.JWTBearer in WithGrants without this option fails op.New. Every assertion jti is single-use (share store.ConsumedJTIStore across replicas); no refresh token or ID token is issued, and the access token is not tied to a grant record, so Provider.RevokeSubject does not reach it. Callable once
  • WithIDJAGIssuance: The policy is the only gate on the audience, scope and resource; it returns op.ErrIDJAGUnknownAudience or op.ErrIDJAGDenied to refuse. Enables the token-exchange grant on its own, without WithTokenExchangeGrant. Callable once. Experimental, tracks an IETF draft

Credentials from the device-code, CIBA and token-exchange grants sit under a recorded grant, so grant revocation, user deletion and pairwise sub resolution reach them. A custom grant is bound to a grant only when its handler records one, and a custom grant's own refresh rotations always mint JWT access tokens.

DeviceCodeGrant.TTL and DeviceCodeGrant.PollInterval are intentionally not derived from WithAccessTokenTTL; short-lived access tokens should not make a TV / CLI pairing ceremony expire before the user can reach the second screen. See Use case: device code, CIBA, Custom grant, Token exchange.

Authorization features — RAR, Grant Management, Protected Resource Metadata ​

OptionValueSectionDefault
WithAuthorizationDetailTypes...op.AuthorizationDetailTypeenables RFC 9396 Rich Authorization Requests; registers each accepted type with its validatordisabled
WithGrantManagementop.GrantManagement{Actions []op.GrantManagementAction; ActionRequired bool}enables the OAuth 2.0 Grant Management draft (callable once; a second call fails op.New)disabled
WithProtectedResources...op.ProtectedResourcepublishes RFC 9728 protected-resource metadata at /.well-known/oauth-protected-resource plus each resource path suffix, with the issuer in authorization_servers.none
  • WithAuthorizationDetailTypes: authorization_details is then validated at /auth, /par, /token, persisted on the grant, echoed on JWT access tokens and introspection, and advertised in discovery. A nil Validate is rejected at op.New
  • WithGrantManagement: Honours grant_management_action / grant_id, mounts the query / revoke endpoint, stamps grant_id on the token response, and advertises the configured action set in discovery. Experimental (tracks an IETF draft)
  • WithProtectedResources: ProtectedResource.IntrospectionClients allows named confidential callers to introspect access tokens whose audience is that resource; it never delegates refresh-token introspection.

op.StepUpChallenge(realm, acrValues, maxAge) is a standalone helper (not an op.New option) that builds the RFC 9470 WWW-Authenticate: Bearer challenge an embedder's resource server returns; the OP itself never emits it.

See Rich authorization requests, Grant management, Protected resource metadata, MFA / step-up.

Encryption (JWE) ​

OptionValueSectionDefault
WithEncryptionKeysetop.EncryptionKeyset (RSA ≥ 2048 / EC P-256/384/521 private keys, use=enc)publishes the OP's inbound-decryption JWKs and decrypts JWE request objects on /auth and /parnone
WithSupportedEncryptionAlgsop.EncryptionAlgs{KeyManagement []string; ContentEncryption []string}narrows the default allow-list (RSA-OAEP-256 / ECDH-ES{,+A128KW,+A256KW} × A{128,256}GCM); cannot extend itfull allow-list
  • WithEncryptionKeyset: Outbound JWE responses use the recipient client's JWKS
  • WithSupportedEncryptionAlgs: KeyManagement holds the JWE alg values and ContentEncryption the enc values. Each half can be narrowed once

See Use case: JWE encryption.

mTLS / proxy / network ​

OptionValueSectionDefault
WithMTLSRootCAs*x509.CertPoolpublic trust pool for OP-side validation of direct or forwarded client certificates; nil is rejectedunset (terminator validates)
WithMTLSProxyop.MTLSProxy{HeaderName string; TrustedProxies []string}header-based mTLS termination at edgenone
WithTrustedProxies...string (CIDRs; additive, repeated calls accumulate)resolves X-Forwarded-For / -Proto / -Host from the listed proxies to the real client IP, scheme and hostnone
WithTrustedProxyHosts...string (hostnames)extends the X-Forwarded-Host allow-list beyond the canonical issuer host when trusted proxy CIDRs are configuredissuer host only
WithAllowLocalhostRedirectURIs(no args)admits textual localhost as the host of http redirect URIs (RFC 8252 native apps and local development)strict literal loopback only
WithAllowLocalhostIssuerForDev(no args)admits textual localhost as the issuer, for local development and passkey testing. Development onlyliteral loopback IPs only
WithAllowPrivateNetwork...op.OutboundTargetnetwork reach: permits the named fetches and deliveries to reach a private network (RFC 1918, loopback, link-local, ULA)all denied
WithAllowInsecureOutboundForDev...op.OutboundTargetadmits plain-HTTP loopback targets for dev / CI fixturesdenied
WithJWKSHTTPTransporthttp.RoundTrippercustom transport for RP-controlled JWKS fetches used by JAR and private_key_jwt, while preserving the dial-time SSRF gatesystem-trust transport
WithBackchannelLogoutop.BackchannelLogout (struct: Transport / Timeout / FanOutBudget)back-channel logout deliverytransport default, 5 s, 30 s
  • WithMTLSRootCAs: Required for tls_client_auth clients behind WithMTLSProxy. Without feature.MTLS it is an op.New error
  • WithMTLSProxy: The forwarded certificate is authoritative only for requests from TrustedProxies; when that list is empty the WithTrustedProxies CIDRs apply, and op.New fails if both are empty. Without feature.MTLS it is an op.New error. (*op.Provider).MTLSProxy() reads the effective value back
  • WithTrustedProxies: Without it the headers are ignored, and the first request carrying one logs a single warning (to slog.Default() when WithLogger is not set)
  • WithAllowLocalhostRedirectURIs: Literal 127.0.0.1 / [::1] remain the strict defaults. Usable in production
  • WithAllowPrivateNetwork: Targets are op.OutboundJWKS (client JWKS), op.OutboundJAR (request_uri), op.OutboundSector (sector_identifier_uri at dynamic registration), op.OutboundClientIDMetadata (Client ID Metadata Document client_id URLs, which also admits the http scheme; experimental, like the feature), op.OutboundBackchannel (backchannel_logout_uri delivery) and op.OutboundSharedSignals (Shared Signals push endpoints; cloud metadata addresses stay refused, see Shared Signals). Repeated calls accumulate; no argument or an unknown value fails op.New
  • WithAllowInsecureOutboundForDev: Accepts op.OutboundBackchannel (backchannel_logout_uri values and delivery) and op.OutboundSharedSignals (http:// push endpoints); any other target fails op.New. op.New refuses it unless the issuer is a development host: a loopback IP literal, localhost, or a name under .localhost, .test, .invalid, or .example (RFC 6761)
  • WithBackchannelLogout: Transport http.RoundTripper carries the fan-out requests, Timeout time.Duration bounds each RP request, FanOutBudget time.Duration bounds the whole event independently of the per-RP timeout. A zero field keeps its default

WithAllowPrivateNetwork is network reach for relying parties that live on a private network (on-prem, service mesh, internal RPs). It is legitimate in production, and each target widens only the fetcher it names. op.New logs one line per enabled target naming what it widens: INFO, or WARN when open dynamic registration or Client ID Metadata Documents is enabled too. That applies to the JWKS, JAR, sector, Client ID Metadata and back-channel logout targets, whose URL a relying party chooses: an unauthenticated caller can then aim the fetch or delivery at internal services (SSRF exposure). The Shared Signals target always logs INFO.

While a target is off, a refused fetch or delivery names the option that lifts it (; pass op.WithAllowPrivateNetwork(op.OutboundJWKS) to admit private-network targets). A relying-party JWKS, request_uri or Client ID Metadata Document fetch logs it as one WARN through WithLogger, outbound fetch refused by the private-network deny-list, at most once per URL per negative-cache lifetime; sector_identifier_uri logs its own warning, and back-channel logout and Shared Signals carry it in their failure audit records. The relying party's response is unchanged. A cloud-metadata address is refused under every option and names none.

Observability ​

OptionValueSectionDefault
WithLogger*slog.Loggerstructured operational log sink (handler is wrapped with the redaction middleware)construction-time diagnostics to slog.Default(); request-time records discarded
WithAuditLogger*slog.Loggerdedicated audit-event log sink for OIDC-domain business events; it is not an HTTP access loginherits WithLogger
WithPrometheus*prometheus.Registryregisters the curated OP counters on the caller's registry (no /metrics route, HTTP lifecycle metrics, or health endpoint is mounted)none

Operational posture ​

OptionValueSectionDefault
WithSessionDurabilityPostureop.SessionDurabilityPostureannotates back-channel logout audit events for SOC; it changes no runtime gatederived from the session store
WithSharedSignals(no args)turns the OP into a Shared Signals Framework 1.0 transmitterdisabled
WithClockop.Clocktime source for token expiry, audit record timestamps, and rate-limit windows (test injection)time.Now
  • WithSessionDurabilityPosture: The default is durable when the session store implements store.SessionDurabilityReporter and reports Durable() true (the SQL and DynamoDB adapters do), otherwise volatile. Declare it to override the derived value, for example for Redis with AOF persistence. op.New logs one warning when SessionDurabilityDurable is declared and the session store reports that its sessions are not durable
  • WithSharedSignals: Receivers are OAuth clients that obtain a client_credentials token with the ssf.read or ssf.manage scope, configure a stream, and receive CAEP / RISC events by push or poll. Needs a store implementing store.SharedSignalsStore, Grants() implementing store.GrantClientLister, and WithCookieKeys; scope names starting with ssf. are reserved. A push worker runs from op.New until Provider.Shutdown. See Shared Signals

What op.New refuses ​

Beyond a missing required option, op.New returns a configuration error for the combinations below instead of accepting the option and ignoring it.

ConfigurationWhy it is refused
a typed-nil substore returned by store.Store (an interface holding a nil pointer)it passes a plain nil check and panics on first use
a second WithLoginFlowone login model per provider
LoginFlow.Primary that needs an already-bound subjectthere is no subject yet when the primary step runs
WithInteractions entry named consent or chooserreserved for the built-in prompts
a single-valued option supplied twice, or two options that set the same setting (see How options compose)the later call would silently replace the earlier one
a nil WithUI value; HTMLUI with a ContentSecurityPolicy on a page that has no Template; SPAUI with an empty LoginMount or one not starting with /, or a StaticDir that is not an accessible directory; DriverUI with a nil Driver, or an interaction.TemplateOverlayDriver with a nil Inner; any ContentSecurityPolicy that interaction.NormalizeCSP rejectsa mode that could not render, or a policy the OP cannot send
WithUI without grant.AuthorizationCode in WithGrants (outside WithDefaults)the prompts it renders belong to the authorization-code flow; op.New returns op.ErrAuthorizationCodeGrantRequired
a second WithLogoutUI; a nil ConfirmTemplate or SignedOutTemplate; a ContentSecurityPolicy that interaction.NormalizeCSP rejects; a confirmation template that does not render every Fields entrya logout page that fails at request time, or a policy the OP cannot send
a second WithIDTokenScopeClaims; no arguments; an empty name, openid, offline_access or an unregistered name; a custom scope whose Claims contain a claim the ID Token reservesa scope that releases no claims, or one that would collide with a claim the OP signs itself
a ConfidentialClient seed with both PreviousSecret and PreviousSecretHash, or either without PreviousSecretExpiresAt; under WithHighEntropyClientSecrets, a plaintext PreviousSecret (it hashes with Argon2id) or an Argon2id PreviousSecretHasha previous secret that could never be accepted, or a hash the high-entropy verifier cannot read
a PrimaryPasskey step on the built-in HTML driver (also through a TemplateOverlayDriver value or pointer)the driver renders no script, so no user could complete the WebAuthn ceremony; use SPAUI or a DriverUI whose driver runs it
a WithACRValuesSupported entry the default policy or op.ACRLevels cannot resolve; an op.ACRLevels with an empty key, a value outside AAL1–AAL3, or a canonical-URI key at the wrong levelthe OP would advertise an acr it can never report
negative CIBAGrant.DefaultRequestTTL / CIBAGrant.PollInterval; under profile.FAPICIBA, a CIBA default or maximum expiry above 10 minutesout of range
a ConfidentialClient seed with no secret; a client record with both jwks and jwks_uriRFC 7591 §2 forbids the pair, and an empty secret hash would accept an empty client_secret
a WithProfile declaration whose required grant is not enabledthe error names the option that activates the grant
profile.OIDCCore declared together with any other profileit is the explicit absence of a profile requirement, so pairing it with one is contradictory or void
no WithUserStore and no op.PrimaryPassword store in the login flow, while an enabled grant can resolve an end user (any grant except client_credentials)there is no source of end-user records; the WithStore backend is never consulted for users

A StepEmailOTP with a nil or typed-nil Store, Sender or Users likewise fails op.New. None of these waits for the first request to fail.

Startup diagnostics ​

Some configurations are accepted but almost certainly not what you meant. op.New logs a warning for each of them once, at construction, to the logger set with WithLogger (slog.Default() when none is set):

ConfigurationWhat the warning says
neither WithLogger nor WithAuditLoggerrequest-time operational logs and audit records are discarded
a WithStaticClients entry lists a grant_types value the OP has not enabled; a seed that leaves GrantTypes empty receives authorization_code and refresh_token, so it is warned when either is not enabledtoken requests with that grant are refused with unsupported_grant_type; the message names the option that enables it
feature.PKCE passedthe feature has no effect; see the PKCE row above
WithTrustedProxies unset and a request carries X-Forwarded-*the headers are ignored
WithAllowPrivateNetworkone line per target naming what it widens: INFO, or WARN (SSRF exposure) for the JWKS, JAR, sector, Client ID Metadata and back-channel logout targets when open dynamic registration or Client ID Metadata Documents is enabled; see mTLS / proxy / network

A WithKeyset whose active key does not fit the signing algorithm is an error, not a warning. An RSA key of 2048 bits or more with the default ES256 fails op.New with a message that ends with to sign with this key, add WithSigningAlgorithm(op.AlgRS256).

What you do not configure here ​

These are deliberate non-options — see the linked design rationale for why each is fixed:

  • JOSE verification allow-list — incoming client assertions, JAR request objects, and DPoP proofs use the fixed RS256 / PS256 / ES256 / EdDSA verification set. This library's OP-issued JWTs are signed with one algorithm per OP, ES256 by default or RS256 through op.WithSigningAlgorithm. No flag widens the verification surface or makes the issuing algorithm per client. See Security posture §2.
  • PKCE method — S256 only. plain is structurally rejected.
  • Cookie scheme — __Host- prefix, AES-256-GCM, double-submit CSRF always on. See Required options §WithCookieKeys.
  • Random source — crypto/rand only; math/rand is forbidden by lint.
  • /metrics mounting — your router's job, not the library's. See Use case: Prometheus metrics.

Verifying this list ​

The catalog is taken from the live source. To audit:

sh
git clone https://github.com/libraz/go-oidc-provider.git
cd go-oidc-provider
grep -hE '^func With[A-Z].*\) Option \{' $(ls op/*.go | grep -v _test.go) | sort -u

The pattern keeps the functions that return op.Option, which are the options op.New takes. WithInMemoryDPoPNonceLogger is a helper for op.NewInMemoryDPoPNonceSource and not an op.New option.

The shape (function name + receiver + first parameter type) is the canonical reference; the godoc on each function is the authoritative contract.