Help us shape the future of Go! Take the 2026 Go Developer Survey

oidc

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package oidc is the OpenID Connect provider: authorization code flow with PKCE (S256) and nonce, state bound to the server-side session, RP-initiated logout through the discovered end_session_endpoint and front-channel logout.

A successful callback stores an auth.Identity in the shared session.Store, so the one session.Guard of the application authenticates OIDC users like every other provider's. Several providers (one per IdP, distinct names) share one Store. Roles and groups come from the access token, else from the ID token (Keycloak's realm_access, resource_access and groups claims); set OnLogin to a keycloak.Client's Enrich to read them from the Keycloak Admin API instead.

The package never logs; outcomes go to the optional auth.Observer.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ClaimMapper

type ClaimMapper func(claims map[string]any) Profile

ClaimMapper is a pure function from ID token claims to a Profile. It must not keep or change the map.

type Options

type Options struct {
	Name                         string          // required; [a-z0-9-]{1,32}; not "password", "github", "apikey", "session", "jwt"; becomes Identity.Provider
	Issuer                       string          // required; https, or http on a loopback host; discovery at <Issuer>/.well-known/openid-configuration
	ClientID, ClientSecret       string          // required (confidential client)
	Scopes                       []string        // default ["openid","profile","email"]; "openid" added first when missing; order kept, duplicates dropped; empty or space-containing entries are an error
	ExternalURL                  string          // required: absolute public base URL; redirect URI = ExternalURL + PathPrefix + "/callback"
	PathPrefix                   string          // default "/auth/oidc/" + Name; segments of A-Z a-z 0-9 . _ ~ -, no trailing "/", no "." or ".." segment
	PostLogoutURL                string          // default ExternalURL + "/"; path or absolute http(s) URL, no fragment; a path is made absolute with ExternalURL
	ErrorURL                     string          // optional: callback failures (refusals and code=internal) redirect to ErrorURL?code=<code> instead of answering JSON; same rules as PostLogoutURL
	ClaimMapper                  ClaimMapper     // default DefaultClaimMapper
	AllowedEmailDomains          []string        // optional: requires email_verified=true and a matching domain (ASCII, case-insensitive, exact)
	DisableIframeLogoutDetection bool            // stop treating Sec-Fetch-Dest: iframe on /logout as front-channel; a cross-site iframe load of /logout is then refused (403)
	Sessions                     *session.Store  // required; not SameSite Strict (the callback would arrive without the cookie)
	HTTPClient                   *http.Client    // default 10 s timeout; used for discovery, token exchange, JWKS; a redirect fails the call
	OnLogin                      auth.Hook       // e.g. keycloak.Client.Enrich, or an app closure calling it and then provisioning
	OnLogout                     auth.LogoutHook // runs after a session was ended by /logout or /frontchannel-logout
	Observer                     auth.Observer   // optional; one outcome per request
}

Options configure a Provider.

type Profile

type Profile struct {
	Username, Name, Email, Picture string
}

Profile is what a ClaimMapper reads from ID token claims.

func DefaultClaimMapper

func DefaultClaimMapper(claims map[string]any) Profile

DefaultClaimMapper reads preferred_username, name, email and picture; a claim that is missing or not a string stays empty. Fits Keycloak and most standard providers.

func GoogleClaimMapper

func GoogleClaimMapper(claims map[string]any) Profile

GoogleClaimMapper reads name, email and picture; Username is the part of email before "@" (Google sends no preferred_username).

type Provider

type Provider struct {
	// contains filtered or unexported fields
}

Provider is one OpenID Connect identity provider. It is safe for concurrent use.

func New

func New(ctx context.Context, options *Options) (*Provider, error)

New validates options and runs discovery (network I/O with ctx; fails fast). It reads end_session_endpoint and the token endpoint's client authentication from the provider metadata and refuses discovered endpoints that are not https (or http on a loopback host), like the Issuer. The JWKS is fetched later, on the first callback, with HTTPClient. New does not modify options, and changing them afterwards has no effect.

Example
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/govengers/auth"
	"github.com/govengers/auth/internal/fakeprovider"
	"github.com/govengers/auth/oidc"
	"github.com/govengers/auth/session"
)

// startExampleIssuer serves the test fake in place of Keycloak or Google; a real application passes its
// IdP's issuer URL instead.
func startExampleIssuer() (*httptest.Server, error) {
	server := httptest.NewUnstartedServer(nil)
	handler, err := fakeprovider.NewOIDCHandler(&fakeprovider.OIDCOptions{
		IssuerURL: "http://" + server.Listener.Addr().String(), ClientID: "app", ClientSecret: "app-secret",
	})
	if err != nil {
		server.Close()
		return nil, err
	}
	server.Config.Handler = handler
	server.Start()
	return server, nil
}

func main() {
	issuer, err := startExampleIssuer()
	if err != nil {
		fmt.Println(err)
		return
	}
	defer issuer.Close()
	backend, err := session.NewMemoryBackend(nil)
	if err != nil {
		fmt.Println(err)
		return
	}
	store, err := session.NewStore(&session.StoreOptions{Backend: backend, EncryptionKey: []byte("0123456789abcdef0123456789abcdef")})
	if err != nil {
		fmt.Println(err)
		return
	}
	provider, err := oidc.New(context.Background(), &oidc.Options{
		Name:         "keycloak",
		Issuer:       issuer.URL, // e.g. https://sso.example.com/realms/staff
		ClientID:     "app",
		ClientSecret: "app-secret",
		ExternalURL:  "https://app.example.com", // register https://app.example.com/auth/oidc/keycloak/callback at the IdP
		Sessions:     store,
	})
	if err != nil {
		fmt.Println(err)
		return
	}
	guard, err := session.NewGuard(&session.GuardOptions{Store: store, LoginURL: provider.LoginURL()})
	if err != nil {
		fmt.Println(err)
		return
	}
	serveMux := http.NewServeMux()
	auth.RegisterRoutes(serveMux, provider.Routes())
	serveMux.Handle("GET /reports", guard.RequireUI()(auth.IdentityHandler()))

	recorder := httptest.NewRecorder()
	serveMux.ServeHTTP(recorder, httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/reports", nil))

	for _, route := range provider.Routes() {
		fmt.Println(route.Method, route.Path)
	}
	fmt.Println(recorder.Code, recorder.Header().Get("Location"))
}
Output:
GET /auth/oidc/keycloak/login
GET /auth/oidc/keycloak/callback
GET /auth/oidc/keycloak/logout
GET /auth/oidc/keycloak/frontchannel-logout
302 https://app.example.com/auth/oidc/keycloak/login?return_to=%2Freports

func (*Provider) LoginURL

func (p *Provider) LoginURL() string

LoginURL returns ExternalURL + PathPrefix + "/login", absolute so a forward-auth redirect reaches the browser unchanged (session.GuardOptions.LoginURL).

func (*Provider) Name

func (p *Provider) Name() string

Name returns the provider name (Identity.Provider of its users).

func (*Provider) Routes

func (p *Provider) Routes() []auth.Route

Routes returns GET {PathPrefix}/login, /callback, /logout and /frontchannel-logout.

Directories

Path Synopsis
Package keycloak enriches OIDC identities from the Keycloak Admin API: composite realm roles, client roles of one client, group paths and user attributes, and refuses users without the required roles or groups.
Package keycloak enriches OIDC identities from the Keycloak Admin API: composite realm roles, client roles of one client, group paths and user attributes, and refuses users without the required roles or groups.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL