oidc

package
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package oidc implements human SSO for Wardyn via OpenID Connect (Dex-compatible).

CSRF posture (v0)

CSRF protection is two-layered:

  1. State parameter: a random 128-bit value stored in an HttpOnly SameSite=Lax cookie ("wardyn_oidc_state") and compared to the IdP callback parameter.
  2. SameSite=Lax on all session cookies: protects all same-site navigations from cross-site request forgery without requiring a synchronizer token.

A PKCE code_challenge (S256) is included in the authorization request and verified by the token endpoint. This provides additional security even when the state check is bypassed (e.g. by a mix-up attack).

Known gap: the nonce is verified in the ID token but is not bound to the device (mitigated by state + PKCE). A future milestone can pin it.

Session storage

Sessions live entirely in a signed HttpOnly SameSite=Lax cookie named "wardyn_session". The cookie payload is a JSON struct containing sub, email, and expiry, HMAC-SHA256 signed with the key passed to New. The key is never logged and never leaves the process.

Integration

The Middleware exposed here accepts either a valid session cookie (sets a HumanPrincipal on the context via a package-private key) or falls through to the next handler (which the integrator wraps with the existing adminAuth bearer path). Use PrincipalFromContext to read the principal; it returns "" when no SSO session is present so the caller can fall through gracefully.

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidSession = errors.New("oidc: invalid session cookie")

ErrInvalidSession is returned by decodeSession when the cookie is present but tampered, malformed, or uses a different HMAC key.

View Source
var ErrNoSession = errors.New("oidc: no session cookie")

ErrNoSession is returned by decodeSession when no session cookie is present.

Functions

func PrincipalFromContext

func PrincipalFromContext(ctx context.Context) string

PrincipalFromContext returns the human principal set by Middleware, or "" if no SSO session is present on the context. The returned value is the OIDC "sub" claim (a stable opaque identifier from the IdP).

Integration note: internal/api's principalFromRequest should call this first and fall back to the admin-token path when the result is "".

Types

type Authenticator

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

Authenticator provides OIDC login, callback, logout, and session-check handlers.

func New

func New(ctx context.Context, cfg Config, hmacKey []byte) (*Authenticator, error)

New constructs an Authenticator by performing OIDC discovery against cfg.IssuerURL. hmacKey is the secret used to sign session cookies; it must be provided by the caller (e.g. loaded from the secret store). The key is never logged.

func (*Authenticator) CallbackHandler

func (a *Authenticator) CallbackHandler(w http.ResponseWriter, r *http.Request)

CallbackHandler handles the IdP redirect. It:

  1. Verifies the state parameter against the state cookie (CSRF).
  2. Exchanges the code for tokens using PKCE.
  3. Verifies the ID token signature, issuer, audience, expiry, and nonce.
  4. Optionally checks email domain (fail closed when AllowedEmailDomains is set).
  5. Creates a signed Wardyn session cookie.

func (*Authenticator) LoginHandler

func (a *Authenticator) LoginHandler(w http.ResponseWriter, r *http.Request)

LoginHandler initiates the OIDC authorization code flow. It generates a random state and nonce, stores them in HttpOnly SameSite=Lax cookies, and redirects the user to the IdP authorization endpoint.

func (*Authenticator) LogoutHandler

func (a *Authenticator) LogoutHandler(w http.ResponseWriter, r *http.Request)

LogoutHandler clears the Wardyn session cookie and redirects to "/".

func (*Authenticator) Middleware

func (a *Authenticator) Middleware(next http.Handler) http.Handler

Middleware returns an http.Handler wrapper that:

  • If a valid (non-expired, correctly signed) session cookie is present, sets the HumanPrincipal on the request context and calls next.
  • Otherwise falls through to next without a principal, allowing the integrator's adminAuth bearer path to handle the request.

This design lets the integrator compose: oidc.Middleware(adminAuth(handler)).

type Config

type Config struct {
	// IssuerURL is the OIDC provider's PUBLIC issuer — the URL the user's
	// browser is redirected to and the value of the "iss" claim in ID tokens
	// (e.g. http://localhost:5556). Must match the IdP's configured issuer.
	IssuerURL string
	// InternalIssuerURL, when set, is the address at which wardynd itself
	// reaches the IdP for server-side calls (discovery, token exchange, JWKS)
	// — e.g. http://dex:5556 on a Docker network. It solves the split-horizon
	// problem where the browser and the control plane reach the IdP at
	// different hostnames: the browser-facing endpoints keep the public
	// IssuerURL, while wardynd's HTTP client transparently rewrites the public
	// authority to this internal one. Empty => IssuerURL is used for both.
	InternalIssuerURL string
	// ClientID is the OAuth2 client identifier registered with the IdP.
	ClientID string
	// ClientSecret is the OAuth2 client secret. Never log this value.
	ClientSecret string
	// RedirectURL is the callback URL registered with the IdP.
	// Must be <wardynd-base>/auth/callback.
	RedirectURL string
	// AllowedEmailDomains, when non-empty, restricts login to email addresses
	// whose domain suffix matches one of the listed values (e.g. "example.com").
	// An empty list allows any verified email. Fail closed: if the IdP does not
	// return a verified email and AllowedEmailDomains is non-empty, login is denied.
	AllowedEmailDomains []string
	// SecureCookies, when true, marks every cookie Wardyn issues (the session
	// cookie and the one-time login state/nonce/pkce cookies) with the Secure
	// attribute, so browsers only send them over HTTPS. It MUST be true exactly
	// when the connection is TLS-protected — either wardynd serves TLS directly
	// or TLS terminates at an upstream reverse proxy. CRITICAL: Secure cookies
	// are never sent over plain HTTP, so leaving this false (the default) is
	// required for plain-HTTP demo deployments — otherwise login silently breaks.
	SecureCookies bool
}

Config holds the OIDC client configuration. All fields except AllowedEmailDomains are required.

type Session

type Session struct {
	Sub    string    `json:"sub"`
	Email  string    `json:"email"`
	Expiry time.Time `json:"expiry"`
}

Session is the content of the wardyn_session cookie, signed and stored client-side. Only sub, email, and expiry are persisted.

Jump to

Keyboard shortcuts

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