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.
| Tier | Options | Where |
|---|---|---|
| Start here | WithIssuer, WithStore, WithKeyset; WithCookieKeys and WithLoginFlow while authorization_code is enabled; WithProfile; then usually WithStaticClients, WithLogger, WithMountPrefix, WithTrustedProxies, WithUI, WithPrometheus, WithAuditLogger | Required and conditionally required, Profile, features, grants, Clients & registration, Observability |
| By use case | Pick 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 proxies | Authentication & login flow, UI, Tokens, Discovery & endpoints, Subject strategy, Grants, Authorization features, Encryption (JWE), mTLS / proxy / network |
| Rarely needed | Development 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 production | mTLS / 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.
- You're booting a fresh OP for the first time → start with
WithIssuer,WithStore,WithKeyset,WithCookieKeys, andWithLoginFlow. The last two are required whenauthorization_codeis 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
WithProfilerunsprofile.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, useprofile.FAPI2Baseline(orprofile.FAPI2MessageSigning,profile.FAPICIBA); these profiles auto-select DPoP unless you explicitly enable mTLS. A profile that needs a grant you did not wire failsop.Newrather than mounting the endpoint for you —profile.FAPICIBArequiresgrant.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 defaultprofile.OAuth21and 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(...)andWithTokenExchangeGrant(...)likewise mount what their grants need. - You want sender-constrained access tokens → DPoP path:
WithFeature(feature.DPoP)plus optionalWithDPoPNonceSource(op.NewInMemoryDPoPNonceSource(...)). mTLS path:WithFeature(feature.MTLS)plus optionalWithMTLSProxy(op.MTLSProxy{HeaderName: ..., TrustedProxies: ...});WithMTLSProxyandWithMTLSRootCAsneedfeature.MTLS. For a multi-replica DPoP nonce flow useStore.DPoPNonceSourcefromop/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 andWithAccessTokenFormatPerAudience(...)for RFC 8707 resource-scoped overrides. See Concepts: access-token format. - You want pairwise
subper 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 / orWithFeature(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 optionallyWithSupportedEncryptionAlgs(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: ...}). UseWithUI(op.DriverUI{Driver: ...})for a non-browser front end or a driver that renders something other than HTML. Underop.SPAUIthe SPA owns the login, consent and chooser screens, and the prompts the SPA fetches from<LoginMount>/stateare 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.Newfails 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, andWithProtectedResources. - 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.
| Option | Value | Section | Default |
|---|---|---|---|
WithDefaults | ...op.Option | marks the wrapped options as defaults. | none |
WithDefaults: A single-valued option inside gives way when an option outsideWithDefaultssets the same setting, whatever the order; additive and flag options inside apply as usual. Two defaults that set the same setting still failop.New. Use it to layer deployment overrides on a shared base option list:op.New(op.WithDefaults(baseOptions()...), op.WithAccessTokenTTL(5*time.Minute)).op/testkitpasses its own settings throughWithDefaults, sotestkit.WithOptions(...)still replaces the testkit defaults.
Required and conditionally required
| Option | Value | Section | Default |
|---|---|---|---|
WithIssuer | string | discovery issuer, JWT iss, cookie scope | — |
WithStore | store.Store | every protocol-state substore | — |
WithUserStore | store.UserStore | the source of end-user records for ID Token and /userinfo claims; wins over the inferred source | the Store of LoginFlow.Primary when it is an op.PrimaryPassword (or of the first op.PrimaryPassword inside a primary op.StepOneOf) |
WithKeyset | op.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 | — |
WithCookieKeys | 32-byte key(s) | session / CSRF cookie AES-256-GCM | required when authorization_code is enabled |
WithLoginFlow | op.LoginFlow | how a user signs in | required when authorization_code is enabled |
WithUserStore: A bundled adapter's concreteUsers()can be passed directly:op.WithUserStore(st.Users()).op.Newfails when an enabled grant resolves an end user (any grant exceptclient_credentials) and neither this option nor anop.PrimaryPasswordstore is set, so passkey-only, device-code and CIBA deployments must pass it. Default: TheWithStorebackend is never consulted. Aclient_credentials-only OP needs none.
Profile, features, grants
| Option | Value | Section | Default |
|---|---|---|---|
WithProfile | profile.Profile | declares this library's profile.OAuth21 (the default), profile.OIDCCore or a FAPI profile | profile.OAuth21 |
WithFeature | feature.Flag (one per call; repeatable) | enables PAR / DPoP / mTLS / JAR / JARM / introspect / revoke individually | conservative defaults |
WithGrants | ...grant.Type (variadic) | restricts the grant types accepted at /token; single-valued, so compose the full set before passing options to op.New | authorization_code, refresh_token |
WithScope | op.Scope (one per call; use the op.PublicScope / op.InternalScope constructors) | extends the scope catalog | openid, profile, email, address, phone, offline_access |
WithOpenIDScopeOptional | (no args) | makes pure OAuth 2.0 (scope without openid) acceptable | openid required |
WithStrictOfflineAccess | (no args) | gates refresh_token issuance behind explicit offline_access consent | lax (refresh on any openid grant) |
WithProfile:profile.OAuth21is OAuth 2.1: PKCE on every authorization-code request.profile.OIDCCoreis the OpenID Connect Core 1.0 shape: PKCE optional for confidential clients; it cannot be combined with any other profile, orop.Newfails. 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 failsop.Newinstead, with the error naming the option that activates it.WithGrants: It replaces the default set. SettingWithLoginFlow,WithUI,WithInteractionsorWithFirstPartyClientsexplicitly withoutgrant.AuthorizationCodemakesop.Newfail withop.ErrAuthorizationCodeGrantRequired; a login flow that is the claim source fordevice_codeor CIBA, and options insideWithDefaults, 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.
| Capability | Enabled by |
|---|---|
authorization_code, refresh_token | on by default; WithGrants replaces the set |
client_credentials | WithGrants(grant.ClientCredentials) |
| Device code (RFC 8628) | WithDeviceCodeGrant; it adds grant.DeviceCode to whatever WithGrants selected, in either order |
| CIBA poll mode | WithCIBAGrant; 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_type | WithCustomGrant |
| PAR, JAR, JARM, DPoP, mTLS, introspection, revocation | WithFeature(feature.X), or switched on by the profile that requires it |
| Dynamic registration, RAR, Grant Management | their own options (WithDynamicRegistration, WithAuthorizationDetailTypes, WithGrantManagement); there is no feature flag to pass for them |
| PKCE | always 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
WithGrantsalone is refused byop.New. CIBA needs a login-hint resolver (WithCIBAGrant); JWT bearer needs trusted issuers and a subject resolver (WithJWTBearerGrant). device_codehas no collaborator, so it works either way:WithGrants(..., grant.DeviceCode)orWithDeviceCodeGrant(op.DeviceCodeGrant{}).- Listing a grant in
WithGrantsand also enabling it through its dedicated option is a no-op. The dedicated option adds its grant to whateverWithGrantsselected, in either order.
Clients & registration
| Option | Value | Section | Default |
|---|---|---|---|
WithStaticClients | ...op.ClientSeed (use op.PublicClient / op.ConfidentialClient / op.PrivateKeyJWTClient) | seeds the client registry at boot | empty |
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 skip | none |
WithDynamicRegistration | op.DynamicRegistration | mounts /register (RFC 7591/7592) | disabled |
WithClientIDMetadataDocuments | op.ClientIDMetadataDocuments (struct: optional Allow func) | enables client ID metadata documents (draft-ietf-oauth-client-id-metadata-document) | disabled |
WithStaticClients:op.Newrefuses a duplicateclient_id, aConfidentialClientwith neitherSecretnorSecretHash, and a seed carrying bothjwksandjwks_uri. Once a seed is written, the stored record is authoritative: after editing a seed, runSyncStaticClientsbefore start (see Client onboarding)WithHighEntropyClientSecrets: Existing Argon2 client hashes cannot be retrofitted; reprovision every client before enabling it.WithClientIDMetadataDocuments:Allow func(ctx, clientID string) boolis consulted on every resolution before any fetch;niladmits every URL that passes the draft's URL rules. This is the registration path MCP authorization clients use: a URLclient_idresolves to a fetched document. Documents may declare only theauthorization_codeandrefresh_tokengrants and thenoneorprivate_key_jwtauthentication 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 advertisesclient_id_metadata_document_supported
Authentication & login flow
| Option | Value | Section | Default |
|---|---|---|---|
WithLoginFlow | op.LoginFlow | declarative DAG of Step + Rule | none |
WithInteractions | ...op.Interaction (variadic) | non-credential prompts (T&C, KYC) layered on top of the WithUI mode, which renders them | consent only |
WithLoginAttemptObserver | op.LoginAttemptObserver | counts failed attempts for RuleAfterFailedAttempts | none |
WithMFAEncryptionKeys | 32-byte key(s) | AES-256-GCM seal of TOTP secrets at rest | none |
WithAuthnLockoutStore | store.AuthnLockoutStore | persists the per-subject failure counter shared by the built-in password, TOTP, email-OTP and recovery-code steps | the WithStore value's AuthnLockouts(), when it has one |
WithACRPolicy | op.ACRPolicy (interface) or op.ACRLevels (map of custom acr to minimum AAL) | decides which acr a login satisfies and reports | op.DefaultACRPolicy: the canonical acr URIs only |
WithLoginFlow:LoginFlow.Primarymust be a step that establishes the subject;StepTOTP,StepEmailOTPandStepRecoveryCodeneed a known subject and run asRulesteps after a subject-binding primary, otherwiseop.Newfails.PrimaryPasskeyatPrimary(or in aStepOneOfthere) runs a passwordless login and must setRequireUserVerification, otherwiseop.Newfails. A customop.Authenticatorplugs in as anop.ExternalStep;LoginFlow.Risktakes the risk assessorWithInteractions: The namesconsentandchooserbelong to the built-in prompts; an entry using either failsop.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
| Option | Value | Section | Default |
|---|---|---|---|
WithUI | exactly one of op.HTMLUI, op.SPAUI, op.DriverUI | selects how sign-in, consent and the account chooser are rendered. | op.HTMLUI{} (built-in HTML pages) |
op.HTMLUI | struct: 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 persistence | built-in markup |
op.SPAUI | struct: LoginMount / StaticDir / ContentSecurityPolicy | mounts the SPA shell and static asset tree while the OP serves the JSON interaction state surface. LoginMount is the only route. | none |
op.DriverUI | struct: Driver interaction.Driver | renders the prompts through your own driver (server-side renderer, JSON driver behind an external front end) | none |
WithLogoutUI | op.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 templates | bundled pages |
WithCORSOrigins | ...string | explicit origins added to the API CORS allow-list. | derived for API routes; issuer-only for ceremonies |
WithDefaultLocale | op.Locale (BCP 47 tag) | canonicalized BCP 47 default UI locale when the request carries no ui_locales; an unregistered default fails op.New | "en" |
WithLocaleBundle | op.LocaleBundle (one per call; repeatable) | registers a per-locale message bundle for the bundled HTML driver | English + Japanese seed |
WithPreferredLocaleStore | op.PreferredLocaleStore | per-user locale override consulted at the head of the §L.2 chain | none |
WithUI: Single-valued: one mode is active, so there is nothing to combine. A browser sign-in option: setting it withoutauthorization_codefailsop.New. Variants below.op.HTMLUI: A screen with a nilTemplatekeeps the built-in markup.Logincovers every sign-in prompt except consent, chooser and passkey (password, TOTP, email OTP, recovery, factor choice, captcha, custom steps); see Custom login UIop.SPAUI:ContentSecurityPolicyreplaces the default policy sent with the SPA shell (see below).op.DriverUI: Pass aninteraction.TemplateOverlayDriverto lay page templates over a driver of your own or over aninteraction.HTMLDriverwith its own translatorWithLogoutUI: The confirmation template must range overFields, a declared CSP replaces the whole default header, and it applies in everyWithUImode. 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
| Option | Value | Section | Default |
|---|---|---|---|
WithAccessTokenFormat | op.AccessTokenFormat (AccessTokenFormatJWT / AccessTokenFormatOpaque) | JWT vs opaque, OP-wide | JWT |
WithAccessTokenFormatPerAudience | map[string]op.AccessTokenFormat (RFC 8707 resource → format) | mixed format by audience | OP-wide value |
WithAccessTokenRevocationStrategy | op.AccessTokenRevocationStrategy | revocation policy for issued JWT access tokens | grant tombstone |
WithAccessTokenTTL | time.Duration | access token lifetime | 5 min |
WithSigningAlgorithm | op.SigningAlgorithm (op.AlgES256 / op.AlgRS256) | the one algorithm the OP issues every JWS with | ES256 |
WithRefreshTokenTTL | time.Duration | refresh token lifetime (non-offline) | 30 days |
WithRefreshTokenOfflineTTL | time.Duration | refresh token lifetime when offline_access granted | inherits the refresh token TTL |
WithRefreshGracePeriod | time.Duration (zero disables; negative rejected; an op.New error under the FAPI 2.0 profiles, which do not rotate refresh tokens) | rotation grace window | 60 s |
WithDPoPNonceSource | op.DPoPNonceSource (interface) | server-supplied DPoP nonce store | none |
WithIDTokenScopeClaims | ...string (scope names) | also writes the claims those scopes release at /userinfo into the ID Token | off |
WithAccessTokenRevocationStrategy: The values areRevocationStrategyGrantTombstone,RevocationStrategyJTIRegistryandRevocationStrategyNone.GrantTombstone(default) needsStore.GrantRevocations(),JTIRegistryneedsStore.AccessTokens()— both checked atop.NewWithSigningAlgorithm: Fixes what the activeWithKeysetentry must be (ECDSA P-256 forES256, an RSA key of 2048 bits or more forRS256) 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 setsNotAfter.AlgRS256is refused underprofile.FAPI2Baseline,profile.FAPI2MessageSigningandprofile.FAPICIBA; any other value failsop.New. Callable onceWithRefreshTokenOfflineTTL: Default: inheritsWithRefreshTokenTTL(zero value defers).WithDPoPNonceSource:op.NewInMemoryDPoPNonceSourceprovides one.WithIDTokenScopeClaims: Pass one or more ofprofile,email,address,phone, or a registered custom scope with non-emptyClaims. The claims are written into the ID Token of theauthorization_code,refresh_token,device_codeand 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
| Option | Value | Section | Default |
|---|---|---|---|
WithEndpoints | op.Endpoints (struct: per-endpoint path overrides) | overrides default endpoint paths | spec defaults |
WithMountPrefix | string (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 discovery | omitted |
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 / PAR | enabled |
WithACRValuesSupported | ...string (variadic) | publishes acr_values_supported; when non-empty, the same allow-list is enforced on /auth, /par, and CIBA /bc-authorize | empty (omitted from discovery; no allow-list) |
WithDiscoveryMetadata | op.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 OP | none |
WithPARTTL | time.Duration | overrides the lifetime of request_uri values issued by /par | 60 s |
WithJWKSRotationActive | func() bool | predicate that flips JWKS Cache-Control to short-cache during a rotation window | always long-cache |
WithDiscoveryMetadata:UILocalesSupportedoverrides the auto-derived locale list when non-empty, andExtrakeys that collide with OP-controlled fields are rejected, includingprompt_values_supported, which the OP publishes itselfWithPARTTL: Expiry is checked when the browser presents the URI at/auth, while later code emission remains single-use
Subject strategy
| Option | Value | Section | Default |
|---|---|---|---|
WithSubjectGenerator | op.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.New | public (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
| Option | Value | Section | Default |
|---|---|---|---|
WithDeviceCodeGrant | op.DeviceCodeGrant | enables the RFC 8628 device-authorization grant; mounts /device_authorization and registers the URN at /token. Callable once | disabled |
WithCIBAGrant | op.CIBAGrant | enables CIBA poll mode; mounts /bc-authorize and registers the CIBA URN. Callable once | disabled |
WithCustomGrant | op.CustomGrantHandler | registers an embedder-defined grant_type URN at /token | none |
WithTokenExchangeGrant | op.TokenExchangeGrant (struct: Policy, required) | enables the RFC 8693 token-exchange grant; the policy decides admission per request and may narrow OP-computed defaults | disabled |
WithJWTBearerGrant | op.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 /token | disabled |
WithIDJAGIssuance | op.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 issued | disabled |
WithDeviceCodeGrant: Fields:VerificationURI(absolute URL advertised on the device's display, default<issuer>/device),TTL(theexpires_inlifetime of newly issueddevice_coderecords, independent of the access-token TTL, default 10 min),PollInterval(the advertised pollinginterval; clients polling faster receiveslow_down, default 5 s). A zero field keeps the default and a negative duration failsop.New. The grant resolves end users, soop.Newalso needsWithUserStoreor anop.PrimaryPasswordstoreWithCIBAGrant: Fields:HintResolver(required),DefaultRequestTTL(default 600 s),MaxRequestTTL(cap onrequested_expiry, no cap when zero),PollInterval(default 5 s),MaxPollViolations(default 5). A zero field keeps the default; a negative duration, or aDefaultRequestTTLaboveMaxRequestTTL, failsop.New; underprofile.FAPICIBAthe default and maximum expiry are capped at 10 minutesWithCustomGrant: Duplicate custom names and collisions with every built-in grant type are rejected at construction. The handler returns a verbatim access token or aBoundAccessTokenrequest the OP signsWithTokenExchangeGrant: Callable onceWithJWTBearerGrant: EachAssertionIssuersetsIssuer(never the OP's own issuer; unique), exactly one ofJWKSURI(https) andJWKS, a non-emptyAllowedClientslist, and aProfile(op.AssertionProfileJWT, the zero value, or the experimentalop.AssertionProfileIDJAG).ResolveSubjectmaps a verified assertion to a local subject; returnop.ErrUnknownAssertionSubjectfor an unprovisioned account. The grant resolves end users, soop.Newalso needsWithUserStoreor anop.PrimaryPasswordstore;grant.JWTBearerinWithGrantswithout this option failsop.New. Every assertionjtiis single-use (sharestore.ConsumedJTIStoreacross replicas); no refresh token or ID token is issued, and the access token is not tied to a grant record, soProvider.RevokeSubjectdoes not reach it. Callable onceWithIDJAGIssuance: The policy is the only gate on the audience, scope and resource; it returnsop.ErrIDJAGUnknownAudienceorop.ErrIDJAGDeniedto refuse. Enables the token-exchange grant on its own, withoutWithTokenExchangeGrant. 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
| Option | Value | Section | Default |
|---|---|---|---|
WithAuthorizationDetailTypes | ...op.AuthorizationDetailType | enables RFC 9396 Rich Authorization Requests; registers each accepted type with its validator | disabled |
WithGrantManagement | op.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.ProtectedResource | publishes 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_detailsis then validated at/auth,/par,/token, persisted on the grant, echoed on JWT access tokens and introspection, and advertised in discovery. A nilValidateis rejected atop.NewWithGrantManagement: Honoursgrant_management_action/grant_id, mounts the query / revoke endpoint, stampsgrant_idon the token response, and advertises the configured action set in discovery. Experimental (tracks an IETF draft)WithProtectedResources:ProtectedResource.IntrospectionClientsallows 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)
| Option | Value | Section | Default |
|---|---|---|---|
WithEncryptionKeyset | op.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 /par | none |
WithSupportedEncryptionAlgs | op.EncryptionAlgs{KeyManagement []string; ContentEncryption []string} | narrows the default allow-list (RSA-OAEP-256 / ECDH-ES{,+A128KW,+A256KW} × A{128,256}GCM); cannot extend it | full allow-list |
WithEncryptionKeyset: Outbound JWE responses use the recipient client's JWKSWithSupportedEncryptionAlgs:KeyManagementholds the JWEalgvalues andContentEncryptiontheencvalues. Each half can be narrowed once
mTLS / proxy / network
| Option | Value | Section | Default |
|---|---|---|---|
WithMTLSRootCAs | *x509.CertPool | public trust pool for OP-side validation of direct or forwarded client certificates; nil is rejected | unset (terminator validates) |
WithMTLSProxy | op.MTLSProxy{HeaderName string; TrustedProxies []string} | header-based mTLS termination at edge | none |
WithTrustedProxies | ...string (CIDRs; additive, repeated calls accumulate) | resolves X-Forwarded-For / -Proto / -Host from the listed proxies to the real client IP, scheme and host | none |
WithTrustedProxyHosts | ...string (hostnames) | extends the X-Forwarded-Host allow-list beyond the canonical issuer host when trusted proxy CIDRs are configured | issuer 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 only | literal loopback IPs only |
WithAllowPrivateNetwork | ...op.OutboundTarget | network reach: permits the named fetches and deliveries to reach a private network (RFC 1918, loopback, link-local, ULA) | all denied |
WithAllowInsecureOutboundForDev | ...op.OutboundTarget | admits plain-HTTP loopback targets for dev / CI fixtures | denied |
WithJWKSHTTPTransport | http.RoundTripper | custom transport for RP-controlled JWKS fetches used by JAR and private_key_jwt, while preserving the dial-time SSRF gate | system-trust transport |
WithBackchannelLogout | op.BackchannelLogout (struct: Transport / Timeout / FanOutBudget) | back-channel logout delivery | transport default, 5 s, 30 s |
WithMTLSRootCAs: Required fortls_client_authclients behindWithMTLSProxy. Withoutfeature.MTLSit is anop.NewerrorWithMTLSProxy: The forwarded certificate is authoritative only for requests fromTrustedProxies; when that list is empty theWithTrustedProxiesCIDRs apply, andop.Newfails if both are empty. Withoutfeature.MTLSit is anop.Newerror.(*op.Provider).MTLSProxy()reads the effective value backWithTrustedProxies: Without it the headers are ignored, and the first request carrying one logs a single warning (toslog.Default()whenWithLoggeris not set)WithAllowLocalhostRedirectURIs: Literal127.0.0.1/[::1]remain the strict defaults. Usable in productionWithAllowPrivateNetwork: Targets areop.OutboundJWKS(client JWKS),op.OutboundJAR(request_uri),op.OutboundSector(sector_identifier_uriat dynamic registration),op.OutboundClientIDMetadata(Client ID Metadata Documentclient_idURLs, which also admits thehttpscheme; experimental, like the feature),op.OutboundBackchannel(backchannel_logout_uridelivery) andop.OutboundSharedSignals(Shared Signals push endpoints; cloud metadata addresses stay refused, see Shared Signals). Repeated calls accumulate; no argument or an unknown value failsop.NewWithAllowInsecureOutboundForDev: Acceptsop.OutboundBackchannel(backchannel_logout_urivalues and delivery) andop.OutboundSharedSignals(http://push endpoints); any other target failsop.New.op.Newrefuses 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.RoundTrippercarries the fan-out requests,Timeout time.Durationbounds each RP request,FanOutBudget time.Durationbounds 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
| Option | Value | Section | Default |
|---|---|---|---|
WithLogger | *slog.Logger | structured operational log sink (handler is wrapped with the redaction middleware) | construction-time diagnostics to slog.Default(); request-time records discarded |
WithAuditLogger | *slog.Logger | dedicated audit-event log sink for OIDC-domain business events; it is not an HTTP access log | inherits WithLogger |
WithPrometheus | *prometheus.Registry | registers the curated OP counters on the caller's registry (no /metrics route, HTTP lifecycle metrics, or health endpoint is mounted) | none |
Operational posture
| Option | Value | Section | Default |
|---|---|---|---|
WithSessionDurabilityPosture | op.SessionDurabilityPosture | annotates back-channel logout audit events for SOC; it changes no runtime gate | derived from the session store |
WithSharedSignals | (no args) | turns the OP into a Shared Signals Framework 1.0 transmitter | disabled |
WithClock | op.Clock | time source for token expiry, audit record timestamps, and rate-limit windows (test injection) | time.Now |
WithSessionDurabilityPosture: The default is durable when the session store implementsstore.SessionDurabilityReporterand reportsDurable()true (the SQL and DynamoDB adapters do), otherwise volatile. Declare it to override the derived value, for example for Redis with AOF persistence.op.Newlogs one warning whenSessionDurabilityDurableis declared and the session store reports that its sessions are not durableWithSharedSignals: Receivers are OAuth clients that obtain aclient_credentialstoken with thessf.readorssf.managescope, configure a stream, and receive CAEP / RISC events by push or poll. Needs a store implementingstore.SharedSignalsStore,Grants()implementingstore.GrantClientLister, andWithCookieKeys; scope names starting withssf.are reserved. A push worker runs fromop.NewuntilProvider.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.
| Configuration | Why 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 WithLoginFlow | one login model per provider |
LoginFlow.Primary that needs an already-bound subject | there is no subject yet when the primary step runs |
WithInteractions entry named consent or chooser | reserved 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 rejects | a 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 entry | a 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 reserves | a 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 PreviousSecretHash | a 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 level | the 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 minutes | out of range |
a ConfidentialClient seed with no secret; a client record with both jwks and jwks_uri | RFC 7591 §2 forbids the pair, and an empty secret hash would accept an empty client_secret |
a WithProfile declaration whose required grant is not enabled | the error names the option that activates the grant |
profile.OIDCCore declared together with any other profile | it 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):
| Configuration | What the warning says |
|---|---|
neither WithLogger nor WithAuditLogger | request-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 enabled | token requests with that grant are refused with unsupported_grant_type; the message names the option that enables it |
feature.PKCE passed | the feature has no effect; see the PKCE row above |
WithTrustedProxies unset and a request carries X-Forwarded-* | the headers are ignored |
WithAllowPrivateNetwork | one 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/EdDSAverification set. This library's OP-issued JWTs are signed with one algorithm per OP,ES256by default orRS256throughop.WithSigningAlgorithm. No flag widens the verification surface or makes the issuing algorithm per client. See Security posture §2. - PKCE method —
S256only.plainis structurally rejected. - Cookie scheme —
__Host-prefix, AES-256-GCM, double-submit CSRF always on. See Required options §WithCookieKeys. - Random source —
crypto/randonly;math/randis forbidden by lint. /metricsmounting — 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:
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 -uThe 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.