Use case — Minimal OP and RP pair
This example gets an Authorization Code + PKCE round-trip working with the shortest option list the library accepts. It starts the OP and a paired RP (the application that signs users in through it) in one process, so a browser can drive the whole flow with nothing else installed.
Source:
examples/01-minimal/main.go
Architecture
The store is in-memory and the keys are generated at boot. The example seeds one demo user (demo / demo) and registers a public client whose redirect_uri points back at the embedded RP.
Code (essentials)
package main
import (
"log"
"log/slog"
"net/http"
"github.com/libraz/go-oidc-provider/op"
"github.com/libraz/go-oidc-provider/op/storeadapter/inmem"
)
func main() {
keys := /* devkeys.MustEphemeral("minimal-1") in the example */
st := inmem.New()
// seedUser hashes "demo"/"demo" with op.HashPassword and stores a
// *store.User through st.PutUserWithPassword; see the example for the body.
// One Primary step and no rules is the whole of a password-only login.
flow := op.LoginFlow{
Primary: op.PrimaryPassword{Store: st.UserPasswords()},
}
provider, err := op.New(
op.WithIssuer("http://127.0.0.1:8080"),
op.WithStore(st),
op.WithKeyset(keys.Keyset()),
op.WithCookieKeys(keys.CookieKey),
op.WithLogger(slog.Default()),
op.WithLoginFlow(flow),
op.WithStaticClients(op.PublicClient{
ID: "demo-rp",
RedirectURIs: []string{"http://127.0.0.1:9090/callback"},
Scopes: []string{"openid", "profile"},
}),
)
if err != nil {
log.Fatalf("op.New: %v", err)
}
mux := http.NewServeMux()
mux.Handle("/", provider)
log.Fatal(http.ListenAndServe(":8080", mux))
}WithIssuer, WithStore, and WithKeyset are enough for /.well-known/openid-configuration and /oidc/jwks to answer. WithCookieKeys and WithLoginFlow are required once the authorization-code grant is enabled, which it is by default, so a runnable OP takes five inputs. WithLogger is not required, but without it the OP discards request-time logs and audit records and warns about it at start-up. No op.WithProfile is declared, so the OP runs profile.OAuth21 and every authorization-code request needs PKCE; the bundled RP sends it (see Declaring a security profile). Anything that involves a user (authorize, token, userinfo) also needs at least one client (WithStaticClients). Getting started: minimal OP walks through why each required option exists.
What the OP exposes
The defaults mount under /oidc (override with op.WithMountPrefix):
| Path | Purpose |
|---|---|
/.well-known/openid-configuration | Discovery (always at the root, OIDC Discovery 1.0 §4) |
/oidc/jwks | Public JWKS for ID Token / JWT access token verification |
/oidc/auth | Authorization endpoint |
/oidc/token | Token endpoint |
/oidc/userinfo | UserInfo (RFC 6749 + OIDC Core §5.3) |
/oidc/end_session | RP-Initiated Logout 1.0 |
The optional endpoints (/par, /introspect, /revoke, /register and the like) mount only when their feature or grant is enabled.
What's missing for a real deployment
| Gap | Fix |
|---|---|
| Single demo user is hard-coded | Enrol users through your own management plane, or project your existing user table (Bring your own user store). |
| Ephemeral keys: ID Tokens become unverifiable after a restart | Load keys from a vault, KMS or file. |
| In-memory store: all state is lost on restart | Switch to op/storeadapter/sql or op/storeadapter/composite (Persistent storage). |
Plain HTTP listener (http://127.0.0.1) | Front behind a TLS-terminating ingress; switch issuer to https://. |
| Single-factor (password only) | Add RuleAlways(StepTOTP{...}) — see MFA / step-up. |
Demo RP code in examples/internal/rpkit | Production RPs use golang.org/x/oauth2 + github.com/coreos/go-oidc/v3 directly. |
examples/02-bundle closes some of these gaps; see Comprehensive bundle.
Run it
git clone https://github.com/libraz/go-oidc-provider.git
cd go-oidc-provider
(cd examples/01-minimal && GOWORK=off go run -tags example .)Then, in a browser:
- Open
http://127.0.0.1:9090/(the RP) and choose "Log in via the OP". - Sign in as
demo/demo, then approve the consent prompt. - The RP lands on
/meand shows the verified ID Token claims;issishttp://127.0.0.1:8080.
To see the OP's own metadata, curl -s http://127.0.0.1:8080/.well-known/openid-configuration | jq. If the round-trip stalls, restart the process: the keys are ephemeral, so every restart invalidates in-flight sessions.