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), // PAR + JAR, DPoP default, ES256, FAPI narrowing
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) enables the features the profile requires (PAR, JAR) and intersects token_endpoint_auth_methods_supported with the FAPI allow-list. It selects DPoP unless you explicitly enabled mTLS, and tightens the discovery surface to match. 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/[email protected]Stable 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.