Skip to content

Minimal OP

The shortest path to a running OP. op.New returns an error if WithIssuer, WithStore, or WithKeyset is missing. WithCookieKeys is required too whenever the authorization_code grant is enabled, which it is by default (along with refresh_token) — so for this minimal, default-grants config all four are effectively mandatory.

Four values in, one handler out
A minimal OP takes four values — an issuer, a store, a keyset and cookie keys — into op.New, which validates them and returns an ordinary HTTP handler.Issuerthe public https URL of the OPStorecodes · sessions · clients · chainsKeysetES256 signing keysCookie keysAES-256-GCM sealingop.New(…)validates the configuration up frontand refuses to start on a bad oneHTTP handlerhttp.Handlermount it, or serve it directly
Nothing here is a framework. What comes back is an http.Handler like any other, which is why the OP can sit anywhere in a router you already have.
go
package main

import (
  "crypto/ecdsa"
  "crypto/elliptic"
  "crypto/rand"
  "log"
  "net/http"

  "github.com/libraz/go-oidc-provider/op"
  "github.com/libraz/go-oidc-provider/op/storeadapter/inmem"
)

func main() {
  // Ephemeral ECDSA P-256 (ES256) — replace with a vault / KMS key in
  // production. The Keyset is a slice of {KeyID, Signer}.
  priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  cookieKey := make([]byte, 32) // AES-256-GCM
  if _, err := rand.Read(cookieKey); err != nil {
    log.Fatal(err)
  }

  st := inmem.New()
  handler, err := op.New(
    op.WithIssuer("https://op.example.com"),
    op.WithStore(st),
    op.WithKeyset(op.Keyset{{KeyID: "k1", Signer: priv}}),
    op.WithCookieKeys(cookieKey),
    op.WithLoginFlow(op.LoginFlow{
      Primary: op.PrimaryPassword{Store: st.UserPasswords()},
    }),
  )
  if err != nil {
    log.Fatal(err)
  }
  log.Fatal(http.ListenAndServe(":8080", handler))
}
go
package main

import (
  "crypto/ecdsa"
  "crypto/elliptic"
  "crypto/rand"
  "log"
  "net/http"

  "github.com/go-chi/chi/v5"
  "github.com/libraz/go-oidc-provider/op"
  "github.com/libraz/go-oidc-provider/op/storeadapter/inmem"
)

func main() {
  priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  cookieKey := make([]byte, 32)
  if _, err := rand.Read(cookieKey); err != nil {
    log.Fatal(err)
  }

  st := inmem.New()
  handler, err := op.New(
    op.WithIssuer("https://op.example.com"),
    op.WithStore(st),
    op.WithKeyset(op.Keyset{{KeyID: "k1", Signer: priv}}),
    op.WithCookieKeys(cookieKey),
    op.WithLoginFlow(op.LoginFlow{
      Primary: op.PrimaryPassword{Store: st.UserPasswords()},
    }),
  )
  if err != nil {
    log.Fatal(err)
  }

  r := chi.NewRouter()
  r.Mount("/", handler)
  log.Fatal(http.ListenAndServe(":8080", r))
}
go
package main

import (
  "crypto/ecdsa"
  "crypto/elliptic"
  "crypto/rand"
  "log"
  "net/http"

  "github.com/gin-gonic/gin"
  "github.com/libraz/go-oidc-provider/op"
  "github.com/libraz/go-oidc-provider/op/storeadapter/inmem"
)

func main() {
  priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  cookieKey := make([]byte, 32)
  if _, err := rand.Read(cookieKey); err != nil {
    log.Fatal(err)
  }

  st := inmem.New()
  handler, err := op.New(
    op.WithIssuer("https://op.example.com"),
    op.WithStore(st),
    op.WithKeyset(op.Keyset{{KeyID: "k1", Signer: priv}}),
    op.WithCookieKeys(cookieKey),
    op.WithLoginFlow(op.LoginFlow{
      Primary: op.PrimaryPassword{Store: st.UserPasswords()},
    }),
  )
  if err != nil {
    log.Fatal(err)
  }

  r := gin.New()
  r.Any("/*path", gin.WrapH(handler))
  log.Fatal(http.ListenAndServe(":8080", r))
}

Production caveats

  • Keys: ephemeral here; load from a vault / KMS in production.
  • Store: in-memory; use op/storeadapter/sql, op/storeadapter/dynamodb, or composite in production. Redis is a volatile tier for composite deployments.
  • Listener: plain HTTP; front behind a TLS-terminating ingress.
What you can do with this OP right now
  1. curl http://localhost:8080/.well-known/openid-configuration — discovery, always mounted at the root regardless of mount prefix.
  2. curl http://localhost:8080/oidc/jwks — public JWKS for verifying ID tokens.
  3. The default mount prefix is /oidc — change it with op.WithMountPrefix("/").
  4. Authorization will return errors until you register a client and seed a user; the password authenticator is configured above.

Run the upstream example

sh
git clone https://github.com/libraz/go-oidc-provider.git
cd go-oidc-provider
(cd examples/01-minimal && GOWORK=off go run -tags example .)

The upstream 01-minimal example uses examples/internal/devkeys for the ephemeral keys and examples/internal/serve for the listener boilerplate, so the main.go file stays focused on op.New.

Next