feat(master): OIDC login with group check, sessions and CSRF protection
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user