Skip to content

OpenID Connect ​

recipe/http/oidc prepares an HTTP server to stand in for an OpenID Connect provider, for "Sign in with ..." through the authorization code flow: discovery, a JWKS, ID tokens the server signs, PKCE and userinfo. Point the issuer URL of the app under test at the server.

The server signs ID tokens with a key of its own, which the recipe can't add: give it one first, with the setting template.keys.

go
import "github.com/caffeine-projects/vlatte/recipe/http/oidc"

m := mockhttp.MustNew(mockhttp.Setup().TemplateKeys(oidc.DefaultKeyID + "=RS256"))
err := m.Prep(ctx, oidc.New(
	oidc.ClientID("my-app"),
	oidc.ClientSecret("my-secret"),
	oidc.Claims(map[string]any{"sub": "ana", "email": "ana@example.com", "email_verified": true}),
))

A server without the key fails Prep with an error naming it. In Go, a key of your own goes on the engine with vlatte.WithSigningKey; name its kid with oidc.KeyID(kid) when it isn't oidc.DefaultKeyID.

RequestAnswer
GET /.well-known/openid-configurationThe issuer, the endpoints below on it, and what the provider supports: the code flow, client_secret_post, PKCE S256 and the key's algorithm.
GET /jwksThe public half of the signing key.
GET /authorize with response_type=code, the client ID, a redirect_uri and a scope with openid302 to redirect_uri with a new code and the request's state. The nonce, code_challenge and redirect_uri are kept with the code.
The same with a code_challenge but no code_challenge_method=S256302 to redirect_uri with error=invalid_request and the request's state.
POST /token with grant_type=authorization_code, the client ID and secret, a redirect_uri and a code it issuedaccess_token, token_type, expires_in and an id_token signed with the server's key: the user's claims, iss, aud, iat, exp, and the nonce when the authorize request sent one.
The same with a code already exchanged, unknown, issued for another redirect_uri, or whose code_verifier doesn't fit its code_challenge400, invalid_grant. A code issued with a challenge needs its verifier.
GET /userinfo with Authorization: Bearer <access token>The user's claims.
Any other GET /userinfo401 with WWW-Authenticate: Bearer error="invalid_token".

Each code is exchanged once, and PKCE takes S256 only. Every mock is a template, which the admin API shows: codes, PKCE, the redirect_uri and single use are a keyed scenario named oidc-code, and tokens come from the jwt and jwks template functions.

OptionSetsDefault
ClientID(id), ClientSecret(secret)The app's credentials. ID tokens have the client ID as aud.oidc.DefaultClientID, DefaultClientSecret
KeyID(kid)The server's key ID tokens are signed with. Discovery announces its algorithm.DefaultKeyID
Issuer(url)The iss of ID tokens, and the base URL of the endpoints.The server's URL
Claims(map)The user's claims, in ID tokens and from userinfo.oidc.DefaultClaims()
AccessToken(token), TokenTTL(d)The access token issued, and how long tokens last.DefaultAccessToken, DefaultTokenTTL (1h)
WithPaths(oidc.Paths{...})Where the endpoints are, such as Google's /o/oauth2/v2/auth.oidc.DefaultPaths()

It covers the authorization code flow only: not the implicit or hybrid flows, refresh tokens, nor client_secret_basic.