Skip to content

Choose a configuration ​

Seven deployment shapes cover most OPs. Answer the questions to narrow them to one, or scan the cards, then read the guide linked from the card that matches yours. Every value on a card is the choice that shape needs, not the only one the library allows.

Configuration finderStep 1

Does an end user sign in?

01Guide →

First-party web SSO

Client type (seed)
op.ConfidentialClient for a server-side app, or op.PublicClient
Profile
profile.OAuth21 (the default)
Consent
skipped for the clients in WithFirstPartyClients
Sender constraint
none
Subject type
public
Tables beyond the default set
none
02Guide →

B2B SaaS where the tenants are RPs of one OP

Client type (seed)
one op.ConfidentialClient per tenant with SubjectType: "pairwise"; tenants onboard through dynamic registration
Profile
profile.OAuth21
Consent
shown; a dynamically registered client cannot be first-party
Sender constraint
none
Subject type
pairwise (WithPairwiseSubject(salt))
Tables beyond the default set
oidc_initial_access_tokens and oidc_registration_access_tokens when tenants register dynamically
03Guide →

Multi-tenant where each tenant owns its users and its issuer

Client type (seed)
the tenant's own seeds, in that tenant's provider
Profile
profile.OAuth21
Consent
shown
Sender constraint
none
Subject type
public
Tables beyond the default set
one full table set per tenant (oidcsql.WithTablePrefix), or one database per tenant
Example
none; one op.Provider per issuer, see Tenants that own their users
04Guide →

Mobile or native app

Client type (seed)
op.PublicClient with ApplicationType: "native"
Profile
profile.OAuth21
Consent
shown
Sender constraint
none; DPoP is optional and binds a public client's refresh chain
Subject type
public
Tables beyond the default set
none
Example
38-native-app: a public client with PKCE and a loopback 127.0.0.1 redirect
05Guide →

SPA

Client type (seed)
op.PublicClient
Profile
profile.OAuth21
Consent
shown
Sender constraint
none; DPoP is optional
Subject type
public
Tables beyond the default set
none
Example
14-cors-spa; the login screens as a SPA: 10-react-login
06Guide →

Machine-to-machine

Client type (seed)
op.ConfidentialClient, or op.PrivateKeyJWTClient
Profile
profile.OAuth21, with WithGrants(grant.ClientCredentials); WithGrants replaces the default grant set, so also list grant.AuthorizationCode (and grant.RefreshToken) when browser login must keep working
Consent
none; no user takes part
Sender constraint
none; DPoP or mTLS is optional
Subject type
not applicable
Tables beyond the default set
none; the four tables only browser logins use stay empty
07Guide →

FAPI 2.0, open banking

Client type (seed)
op.PrivateKeyJWTClient; with feature.MTLS, op.TLSClientAuthClient or op.SelfSignedTLSClientAuthClient
Profile
profile.FAPI2Baseline, or profile.FAPI2MessageSigning (guide)
Consent
shown; WithFirstPartyClients is refused
Sender constraint
DPoP (enabled by the profile) or mTLS (feature.MTLS)
Subject type
public
Tables beyond the default set
oidc_par_records

The default set is the 13 tables a code-flow OP with password login reads; TABLES.md lists them, and the other tables an option adds. Apply every migration file whichever shape you pick. A table that no feature of your configuration writes to stays empty. A bundled oidc_users table is needed only when you pass the adapter's own user store, and your own users table (the usual case, see Integrate with an existing database) needs none.

Reading the card fields ​

  • Client type is the seed passed to op.WithStaticClients. The seed fixes the token-endpoint authentication method, so op.PublicClient means none with PKCE, op.ConfidentialClient a client secret, and the private-key and certificate seeds an asymmetric method. Dynamic registration and an admin API create the same kinds of client at run time (Client onboarding).
  • Profile is one per OP, not per client. profile.OAuth21 is what an OP gets without op.WithProfile; it requires PKCE and nothing else. A FAPI profile narrows client authentication to asymmetric methods for every client, so public clients and shared-secret clients cannot share an OP with it. Run a SPA and a FAPI API as two OPs, or leave the FAPI profile out.
  • Consent is shown unless a static client is listed in op.WithFirstPartyClients. The list accepts only statically seeded clients and is refused together with a FAPI 2.0 profile.
  • Sender constraint binds a token to a key or certificate. A FAPI profile requires one; the other shapes run on bearer tokens unless you enable feature.DPoP or feature.MTLS. Sender constraint compares the two.
  • Subject type is public unless op.WithPairwiseSubject is set and the client asks for pairwise. The mode is fixed once grants exist, so decide it before launch.

Mixing shapes ​

One OP serves several client types at once under profile.OAuth21: a first-party web app, a SPA, a native app and a machine client differ only in their seed. The profile and the grant set are the OP-wide settings. op.WithGrants replaces the default grant set, so an OP that has browser logins and machine clients lists all of them: grant.AuthorizationCode, grant.RefreshToken and grant.ClientCredentials.

Tenants that own their users ​

An OP has exactly one issuer: op.WithIssuer is single-valued, and the issuer is the iss of every token and the root of the discovery document. When each tenant owns its users and must be its own issuer, run one op.Provider per tenant. Give each its own store, or its own table prefix on a shared database with oidcsql.WithTablePrefix, and mount it under its issuer path (https://login.example.com/tenant-a, https://login.example.com/tenant-b). Issuer explains the subpath form and the two discovery locations. This differs from the B2B SaaS card above, where the tenants are relying parties of one OP and only their sub values are kept apart.