package webauth import ( "context" "errors" "fmt" "github.com/coreos/go-oidc/v3/oidc" "golang.org/x/oauth2" ) // Claims is what the master needs from the identity provider. type Claims struct { Subject string Name string Email string Groups []string } // Provider performs the OIDC authorization code flow with PKCE. Declared as // an interface so handlers can be tested without a real identity provider. type Provider interface { AuthCodeURL(state, verifier string) string Exchange(ctx context.Context, code, verifier string) (Claims, error) } // OIDCConfig configures the real provider. type OIDCConfig struct { Issuer string // e.g. https://auth.example.com/application/o/package-updater/ ClientID string ClientSecret string RedirectURL string // PUBLIC_URL + /auth/callback } type oidcProvider struct { oauth *oauth2.Config verifier *oidc.IDTokenVerifier } // NewOIDCProvider discovers the issuer's endpoints. It makes a network call, // so the master fails at startup if Authentik is unreachable. func NewOIDCProvider(ctx context.Context, cfg OIDCConfig) (Provider, error) { p, err := oidc.NewProvider(ctx, cfg.Issuer) if err != nil { return nil, fmt.Errorf("oidc discovery: %w", err) } return &oidcProvider{ oauth: &oauth2.Config{ ClientID: cfg.ClientID, ClientSecret: cfg.ClientSecret, Endpoint: p.Endpoint(), RedirectURL: cfg.RedirectURL, Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, }, verifier: p.Verifier(&oidc.Config{ClientID: cfg.ClientID}), }, nil } func (p *oidcProvider) AuthCodeURL(state, verifier string) string { return p.oauth.AuthCodeURL(state, oauth2.S256ChallengeOption(verifier)) } func (p *oidcProvider) Exchange(ctx context.Context, code, verifier string) (Claims, error) { tok, err := p.oauth.Exchange(ctx, code, oauth2.VerifierOption(verifier)) if err != nil { return Claims{}, fmt.Errorf("exchange code: %w", err) } raw, ok := tok.Extra("id_token").(string) if !ok { return Claims{}, errors.New("token response has no id_token") } idt, err := p.verifier.Verify(ctx, raw) if err != nil { return Claims{}, fmt.Errorf("verify id_token: %w", err) } var c struct { Name string `json:"name"` Email string `json:"email"` Groups []string `json:"groups"` } if err := idt.Claims(&c); err != nil { return Claims{}, fmt.Errorf("read claims: %w", err) } return Claims{Subject: idt.Subject, Name: c.Name, Email: c.Email, Groups: c.Groups}, nil }