package auth import ( "context" "fmt" "github.com/coreos/go-oidc/v3/oidc" "golang.org/x/oauth2" ) // OIDCProvider wraps an OIDC provider and OAuth2 configuration. type OIDCProvider struct { provider *oidc.Provider oauth2Config oauth2.Config verifier *oidc.IDTokenVerifier } // OIDCConfig holds the configuration needed to set up an OIDC provider. type OIDCConfig struct { IssuerURL string ClientID string ClientSecret string RedirectURL string } // OIDCUserInfo represents the user information extracted from an OIDC ID token. type OIDCUserInfo struct { Subject string `json:"sub"` Email string `json:"email"` Username string `json:"preferred_username"` Name string `json:"name"` } // NewOIDCProvider initializes an OIDC provider using the discovery URL. func NewOIDCProvider(ctx context.Context, cfg OIDCConfig) (*OIDCProvider, error) { provider, err := oidc.NewProvider(ctx, cfg.IssuerURL) if err != nil { return nil, fmt.Errorf("create oidc provider: %w", err) } oauth2Config := oauth2.Config{ ClientID: cfg.ClientID, ClientSecret: cfg.ClientSecret, RedirectURL: cfg.RedirectURL, Endpoint: provider.Endpoint(), Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, } verifier := provider.Verifier(&oidc.Config{ClientID: cfg.ClientID}) return &OIDCProvider{ provider: provider, oauth2Config: oauth2Config, verifier: verifier, }, nil } // AuthCodeURL returns the URL to redirect the user to for OIDC authentication. func (op *OIDCProvider) AuthCodeURL(state string) string { return op.oauth2Config.AuthCodeURL(state) } // Exchange trades an authorization code for tokens and returns the user info // extracted from the ID token. func (op *OIDCProvider) Exchange(ctx context.Context, code string) (OIDCUserInfo, error) { token, err := op.oauth2Config.Exchange(ctx, code) if err != nil { return OIDCUserInfo{}, fmt.Errorf("exchange code: %w", err) } rawIDToken, ok := token.Extra("id_token").(string) if !ok { return OIDCUserInfo{}, fmt.Errorf("no id_token in response") } idToken, err := op.verifier.Verify(ctx, rawIDToken) if err != nil { return OIDCUserInfo{}, fmt.Errorf("verify id_token: %w", err) } var userInfo OIDCUserInfo if err := idToken.Claims(&userInfo); err != nil { return OIDCUserInfo{}, fmt.Errorf("parse id_token claims: %w", err) } return userInfo, nil }