OpenID Connect Provider you mount on any Go http.Handler.
Works with net/http, chi, gin, or any router. PAR · JAR · DPoP · mTLS · PKCE built in. Targets FAPI 2.0 Baseline & Message Signing.
FAPI 2.0 BASELINE · OFCS 210 PASSED / 271 MODULES · 9 PLANS · APACHE-2.0 · GO ≥ 1.25
op.New(...) returns http.Handler — net/http, chi, ginPROFILEop.WithProfile(profile.FAPI2Baseline) — PAR · JAR · DPoP at one switchSTORAGEBundled full stores — inmem · SQL · DynamoDB; Redis is a volatile tier through compositeTOKENSRefresh rotation, reuse detection, offline_access TTL bucketCONFORMANCEOFCS 210 PASSED / 271 MODULES · 9 plans · 0 strict release blockersSPAHeadless interaction driver — drive login from a React SPAThe five configurations people build most often with this library. Each one links to a runnable example in the upstream repository.
package main
import (
"log"
"net/http"
"github.com/libraz/go-oidc-provider/op"
"github.com/libraz/go-oidc-provider/op/storeadapter/inmem"
)
func main() {
st := inmem.New()
handler, err := op.New(
op.WithIssuer("https://op.example.com"),
op.WithStore(st),
op.WithKeyset(myKeyset), // see Quick Start: ephemeral key generation
op.WithCookieKeys(cookieKey), // 32 bytes — AES-256-GCM
op.WithLoginFlow(op.LoginFlow{
Primary: op.PrimaryPassword{Store: st.UserPasswords()},
}),
)
if err != nil {
log.Fatal(err)
}
log.Fatal(http.ListenAndServe(":8080", handler))
}See
examples/01-minimaland Quick Start.
st := inmem.New()
clientJWKS, _ := op.LoadPublicJWKS("conformance/keys/fapi-client.jwks.json")
handler, _ := op.New(
op.WithIssuer("https://op.example.com"),
op.WithStore(st),
op.WithKeyset(myKeyset),
op.WithCookieKeys(cookieKey),
op.WithLoginFlow(op.LoginFlow{
Primary: op.PrimaryPassword{Store: st.UserPasswords()},
}),
op.WithProfile(profile.FAPI2Baseline), // library profile: PAR + JAR, DPoP default, ES256
op.WithStaticClients(op.PrivateKeyJWTClient{
ID: "demo-fapi",
JWKS: clientJWKS,
RedirectURIs: []string{"https://app.example.com/callback"},
Scopes: []string{"openid", "profile"},
}),
)Why one switch is enough
op.WithProfile(profile.FAPI2Baseline) applies this library's FAPI profile policy: it enables PAR and JAR, intersects token_endpoint_auth_methods_supported with the profile allow-list, and selects DPoP unless you explicitly enable mTLS. It also keeps OP-issued JWT signing on ES256. The final FAPI 2.0 Security Profile requires PAR, PKCE with S256, and sender-constrained tokens; JAR and ES256-only signing are choices made by this library's profile. See Use case: FAPI 2.0 Baseline.
handler, _ := op.New(
op.WithIssuer("https://op.example.com"),
op.WithStore(inmem.New()),
op.WithKeyset(myKeyset),
op.WithGrants(grant.ClientCredentials),
op.WithStaticClients(op.ConfidentialClient{
ID: "backend-service",
Secret: "cc-demo-secret-rotate-me",
AuthMethod: op.AuthClientSecretBasic,
GrantTypes: []string{"client_credentials"},
Scopes: []string{"api:read"},
}),
op.WithScope(op.PublicScope("api:read", "Read API resources")),
)See
examples/05-client-credentialsand Use case: client_credentials.
st := inmem.New()
handler, _ := op.New(
op.WithIssuer("https://op.example.com"),
op.WithStore(st),
op.WithKeyset(myKeyset),
op.WithCookieKeys(cookieKey),
op.WithLoginFlow(op.LoginFlow{
Primary: op.PrimaryPassword{Store: st.UserPasswords()},
}),
op.WithSPAUI(op.SPAUI{
LoginMount: "/login",
StaticDir: "../internal/webui/static",
}),
)UI ownership options
op.WithSPAUI, op.WithConsentUI, and op.WithChooserUI cover the common UI ownership modes: OP-mounted SPA shell, custom consent template, and custom account chooser template. interaction.JSONDriver is still the lower-level route when you want your own router to serve the shell. See examples/10-react-login, Use case: SPA, and Custom consent UI.
import (
"context"
"github.com/libraz/go-oidc-provider/op"
"github.com/libraz/go-oidc-provider/op/storeadapter/composite"
oidcredis "github.com/libraz/go-oidc-provider/op/storeadapter/redis"
oidcsql "github.com/libraz/go-oidc-provider/op/storeadapter/sql"
)
durable, _ := oidcsql.New(db, oidcsql.MySQL())
volatile, _ := oidcredis.New(context.Background(),
oidcredis.WithDSN("rediss://redis:6380/0"),
oidcredis.WithRedisAuth(redisUser, redisPassword),
)
combined, _ := composite.New(
composite.WithDefault(durable),
composite.With(composite.Sessions, volatile),
composite.With(composite.Interactions, volatile),
composite.With(composite.ConsumedJTIs, volatile),
)
loginFlow := op.LoginFlow{Primary: op.PrimaryPassword{Store: durable.UserPasswords()}}
handler, _ := op.New(
op.WithIssuer("https://op.example.com"),
op.WithStore(combined),
op.WithKeyset(myKeyset),
op.WithCookieKeys(cookieKey),
op.WithLoginFlow(loginFlow),
)See
examples/09-redis-volatileand Use case: hot/cold split.
go get github.com/libraz/go-oidc-provider/op@v1.2.0Stable API
A configuration built with op.New and the bundled storage adapters generally upgrades unchanged. If you bring your own store or another extension, read the current migration guidance in CHANGELOG.md before upgrading. Symbols marked Experimental: in godoc remain the ordinary minor-release exemption.
Apache-2.0. Source: libraz/go-oidc-provider. Vulnerability disclosure: SECURITY.md.