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 a FAPI 2.0 Baseline OP

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), // 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.

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/[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.