Skip to content

go-oidc-provider

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

go-oidc-provider mounts into an existing Go application as an http.Handler, wiring the OP endpoints to the application's own store, keyset, and logging.Your Go approuter / middlewareOIDC Providerhttp.Handler/oidc/auth /oidc/token/oidc/jwksStoreKeysetLogs / auditMount

Standard use cases ​

The five configurations people build most often with this library. Each one links to a runnable example in the upstream repository.

1. Stand up the smallest possible OP ​

go
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-minimal and Quick Start.

2. Run the library's FAPI 2.0 Baseline profile ​

go
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.

3. Issue tokens to backend services (no end user) ​

go
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-credentials and Use case: client_credentials.

go
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.

5. Persist on a real database, split hot from cold ​

go
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-volatile and Use case: hot/cold split.


Install ​

sh
go get github.com/libraz/go-oidc-provider/op@v1.2.0

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.