Documentation
¶
Overview ¶
Package auth – oidc.go implements the generic OIDC browser login flow using golang.org/x/oauth2 and coreos/go-oidc.
GET /auth/login → redirects to the Provider consent screen GET /auth/callback → exchanges code for ID token, validates, creates session cookie POST /auth/logout → clears session cookie
The session is a signed cookie containing {email, domain, exp}. No server-side session store is required — the cookie is self-contained and HMAC-SHA256 signed.
Index ¶
- func GetClaims(ctx context.Context) map[string]any
- func Middleware(cfg Config, oidcHandler ...*OIDCHandler) func(http.Handler) http.Handler
- func WithClaims(ctx context.Context, claims map[string]any) context.Context
- type APIKeyConfig
- type Authenticator
- type Config
- type EnterpriseUser
- type IdentityResolver
- type JWTConfig
- type NoopResolver
- type OIDCConfig
- type OIDCHandler
- func (h *OIDCHandler) Authenticate(w http.ResponseWriter, r *http.Request) (*http.Request, *identity.Sender)
- func (h *OIDCHandler) HandleAuthInfo(w http.ResponseWriter, r *http.Request)
- func (h *OIDCHandler) HandleCallback(w http.ResponseWriter, r *http.Request)
- func (h *OIDCHandler) HandleLogin(w http.ResponseWriter, r *http.Request)
- func (h *OIDCHandler) HandleLogout(w http.ResponseWriter, r *http.Request)
- func (h *OIDCHandler) ValidateSession(r *http.Request) *sessionPayload
- type PasswordConfig
- type ResolveRequest
- type ResolverConfig
- type ResolverEntry
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GetClaims ¶
GetClaims retrieves the raw JWT claims from the context. Returns nil if no claims are present (e.g. non-JWT auth, messenger path).
func Middleware ¶
Middleware returns an http.Handler middleware that enforces authentication based on the Config. The user can only choose ONE authentication option. Resolution precedence: OIDC > API keys > JWT > Password.
func WithClaims ¶
WithClaims returns a new context containing the raw JWT claims map. This is called by the auth middleware after parsing the JWT so that downstream resolvers can read custom claims like "roles" and "groups".
Types ¶
type APIKeyConfig ¶
type APIKeyConfig struct {
// Keys is a list of static secrets accepted via the Authorization: Bearer <token>
// header or X-API-Key header.
Keys []string `yaml:"keys,omitempty" toml:"keys,omitempty"`
}
APIKeyConfig configures static API keys for machine-to-machine authentication.
func (APIKeyConfig) Enabled ¶
func (a APIKeyConfig) Enabled() bool
Enabled returns true when static API keys are configured.
type Authenticator ¶
type Authenticator interface {
// Authenticate inspects the request. Returns a (possibly updated) request and a non-nil Sender on success.
// On failure it must write the appropriate HTTP error to the ResponseWriter
// and return (r, nil).
Authenticate(w http.ResponseWriter, r *http.Request) (*http.Request, *identity.Sender)
}
Authenticator defines a pluggable authentication strategy. An authenticator is responsible for verifying the request and issuing HTTP error responses if the request is unauthorized.
type Config ¶
type Config struct {
Password PasswordConfig `yaml:"password,omitempty" toml:"password,omitempty"`
JWT JWTConfig `yaml:"jwt,omitempty" toml:"jwt,omitempty"`
APIKeys APIKeyConfig `yaml:"api_keys,omitempty" toml:"api_keys,omitempty"`
OIDC OIDCConfig `yaml:"oidc,omitempty" toml:"oidc,omitempty"`
IdentityResolver ResolverConfig `yaml:"identity_resolver,omitempty" toml:"identity_resolver,omitempty"`
}
Config holds all authentication settings for the AG-UI server. Each auth method is isolated into its own sub-config for clarity.
TOML layout:
[messenger.agui.auth.password] enabled = true [messenger.agui.auth.jwt] trusted_issuers = ["https://accounts.google.com"] [messenger.agui.auth.api_keys] keys = ["secret-1", "secret-2"] [messenger.agui.auth.oidc] issuer_url = "https://accounts.google.com" client_id = "..." client_secret = "..." allowed_domains = ["stackgen.com"]
type EnterpriseUser ¶
type EnterpriseUser struct {
identity.Sender
// Department is the organisational department (e.g. "engineering",
// "hr", "finance"). Empty if unknown.
Department string
// Groups lists the group memberships for this user (e.g.
// ["platform-team", "on-call", "sre"]). May be empty.
Groups []string
// Attributes holds arbitrary key-value metadata about the user.
// Useful for customer-specific fields that don't warrant a
// dedicated struct field (e.g. "cost_center", "manager_email").
Attributes map[string]string
}
EnterpriseUser is the resolved identity enriched with enterprise metadata. It embeds identity.Sender so it can be used as a drop-in replacement wherever a Sender is expected.
type IdentityResolver ¶
type IdentityResolver interface {
// Resolve looks up the enterprise identity for the given sender.
// Implementations MUST be safe for concurrent use.
//
// If the user cannot be found, implementations should return
// the original sender unchanged (not an error), so that the
// system degrades gracefully to unauthenticated behavior.
Resolve(ctx context.Context, req ResolveRequest) (EnterpriseUser, error)
}
IdentityResolver maps an incoming platform sender to an enterprise user.
Implementations may:
- Query a local database
- Parse JWT/OIDC claims (roles, groups)
- Call a directory API (SCIM directory)
type JWTConfig ¶
type JWTConfig struct {
// TrustedIssuers is a list of OIDC issuer URLs whose JWTs are accepted.
TrustedIssuers []string `yaml:"trusted_issuers,omitempty" toml:"trusted_issuers,omitempty"`
// AllowedAudiences is an optional list of expected "aud" claim values.
// When non-empty, JWT tokens must have an audience matching at least one entry.
AllowedAudiences []string `yaml:"allowed_audiences,omitempty" toml:"allowed_audiences,omitempty"`
}
JWTConfig configures JWT token validation for API-level authentication. Uses go-oidc for full cryptographic signature verification via JWKS auto-discovery.
type NoopResolver ¶
type NoopResolver struct{}
NoopResolver is the default IdentityResolver that passes the sender through unchanged. Use it when no enterprise identity provider is configured — the system operates in "everyone is who they say they are" mode.
func (*NoopResolver) Resolve ¶
func (n *NoopResolver) Resolve(_ context.Context, req ResolveRequest) (EnterpriseUser, error)
Resolve returns the sender as-is, wrapped in an EnterpriseUser.
type OIDCConfig ¶
type OIDCConfig struct {
// IssuerURL is the OIDC provider's discovery URL (e.g. "https://accounts.google.com",
// "https://your-tenant.okta.com", "https://dev-xxx.auth0.com").
IssuerURL string `yaml:"issuer_url,omitempty" toml:"issuer_url,omitempty"`
// ClientID is the OAuth 2.0 Client ID.
ClientID string `yaml:"client_id,omitempty" toml:"client_id,omitempty"`
// ClientSecret is the OAuth 2.0 Client Secret.
ClientSecret string `yaml:"client_secret,omitempty" toml:"client_secret,omitempty"`
// AllowedDomains restricts login to users from these domains (if supported
// by the provider via the "hd" parameter, like Google Workspace).
// When empty, any account from the provider is allowed.
AllowedDomains []string `yaml:"allowed_domains,omitempty" toml:"allowed_domains,omitempty"`
// CookieSecret is a 32+ byte key used to HMAC-sign session cookies.
// If empty, a random key is generated at startup.
CookieSecret string `yaml:"cookie_secret,omitempty" toml:"cookie_secret,omitempty"`
// RedirectURL is the full URL of /auth/callback registered in the provider.
// If empty, it's auto-detected from the incoming request Host header.
RedirectURL string `yaml:"redirect_url,omitempty" toml:"redirect_url,omitempty"`
}
OIDCConfig configures the generic OIDC browser login flow. When IssuerURL, ClientID, and ClientSecret are set, the server exposes /auth/login, /auth/callback, and /auth/logout endpoints.
func (OIDCConfig) Enabled ¶
func (o OIDCConfig) Enabled() bool
Enabled returns true when the OIDC login flow is configured.
type OIDCHandler ¶
type OIDCHandler struct {
// contains filtered or unexported fields
}
OIDCHandler manages the OIDC login flow and session cookies.
func NewOIDCHandler ¶
func NewOIDCHandler(cfg Config) *OIDCHandler
NewOIDCHandler creates an OIDCHandler from the given Config. Returns nil if OIDC is not configured.
func (*OIDCHandler) Authenticate ¶
func (h *OIDCHandler) Authenticate(w http.ResponseWriter, r *http.Request) (*http.Request, *identity.Sender)
Authenticate implements the Authenticator interface.
func (*OIDCHandler) HandleAuthInfo ¶
func (h *OIDCHandler) HandleAuthInfo(w http.ResponseWriter, r *http.Request)
HandleAuthInfo returns the current user's session info (for the UI to display).
func (*OIDCHandler) HandleCallback ¶
func (h *OIDCHandler) HandleCallback(w http.ResponseWriter, r *http.Request)
HandleCallback processes the OAuth callback from the provider.
func (*OIDCHandler) HandleLogin ¶
func (h *OIDCHandler) HandleLogin(w http.ResponseWriter, r *http.Request)
HandleLogin redirects the user to the provider's consent screen.
func (*OIDCHandler) HandleLogout ¶
func (h *OIDCHandler) HandleLogout(w http.ResponseWriter, r *http.Request)
HandleLogout clears the session cookie.
func (*OIDCHandler) ValidateSession ¶
func (h *OIDCHandler) ValidateSession(r *http.Request) *sessionPayload
ValidateSession checks if the request has a valid session cookie. Returns the session payload if valid, nil otherwise.
type PasswordConfig ¶
type PasswordConfig struct {
// Enabled turns on password protection. The password is resolved in order:
// 1. Value field (below)
// 2. AGUI_PASSWORD environment variable
// 3. OS keyring (for local/desktop use)
// 4. Auto-generated random password (logged at startup)
Enabled bool `yaml:"enabled,omitempty" toml:"enabled,omitempty"`
// Value is the plaintext shared secret. Prefer AGUI_PASSWORD env var
// for cloud/container deployments where keyring is unavailable.
Value string `yaml:"value,omitempty" toml:"value,omitempty"`
}
PasswordConfig configures password-based authentication via the X-AGUI-Password header or ?password= query param.
type ResolveRequest ¶
type ResolveRequest struct {
// Sender is the raw identity from the messenger or auth layer.
// At minimum, Sender.ID is populated.
Sender identity.Sender
// Platform is the originating platform (e.g. "slack", "agui",
// "teams", "telegram"). Implementations may use this to choose
// different resolution strategies per platform.
Platform string
// PlatformUserID is the original platform-specific user identifier
// (e.g. Slack "U_ABC123", Google OAuth subject).
//
// If empty, resolvers should fall back to Sender.ID.
PlatformUserID string
}
ResolveRequest carries the known sender information from the platform adapter or auth middleware. Implementations use this to look up the enterprise identity.
type ResolverConfig ¶
type ResolverConfig struct {
// Resolvers is an ordered list of resolver strategies.
// Valid types: "noop", "jwt_claims", "static"
Resolvers []ResolverEntry `yaml:"resolvers,omitempty" toml:"resolvers,omitempty"`
}
ResolverConfig defines an ordered chain of identity resolver strategies. The chain is tried in order; the first resolver to assign a non-empty Role wins.
Example TOML:
[messenger.agui.auth.identity_resolver]
[[messenger.agui.auth.identity_resolver.resolvers]]
type = "jwt_claims"
[messenger.agui.auth.identity_resolver.resolvers.config]
role_claim = "roles"
groups_claim = "groups"
func (ResolverConfig) Build ¶
func (rc ResolverConfig) Build() IdentityResolver
Build constructs an IdentityResolver from the configured resolver chain. If no resolvers are configured, a NoopResolver is returned so the system always has a usable identity resolver without explicit configuration.
type ResolverEntry ¶
type ResolverEntry struct {
// Type identifies the resolver implementation.
// Supported: "noop", "jwt_claims", "static"
Type string `yaml:"type" toml:"type"`
// Config holds type-specific configuration as key-value pairs.
// For "jwt_claims": role_claim, groups_claim, dept_claim
// For "noop": unused
Config map[string]string `yaml:"config,omitempty" toml:"config,omitempty"`
}
ResolverEntry is a single resolver strategy in the chain.