auth

package
v0.1.8-rc.10 Latest Latest
Warning

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

Go to latest
Published: Mar 15, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

README

Authentication and OIDC Setup

The pkg/security/auth package provides generic OIDC (OpenID Connect) authentication for Genie. It handles browser login, session management via signed cookies, and integrates with standard OIDC providers.

Configuration

To enable OIDC, you need to configure the application with the following parameters:

  • IssuerURL: The discovery endpoint for your OIDC provider (e.g., https://accounts.google.com).
  • ClientID: Your application's OAuth client ID.
  • ClientSecret: Your application's OAuth client secret.
  • RedirectURL: (Optional) The explicit redirect URL. If omitted, it automatically detects it based on the incoming request protocol and host.
  • CookieSecret: A secret used to sign the session cookies. If not provided via configuration, it can be set via the AGUI_COOKIE_SECRET environment variable. If missing entirely, a random ephemeral secret is generated on startup (meaning sessions will invalidate on restarts).
  • AllowedDomains: An optional list of allowed domains or email addresses to restrict who can log in.
Example Configuration
oidc:
  enabled: true
  issuer_url: "https://accounts.google.com"
  client_id: "your-client-id.apps.googleusercontent.com"
  client_secret: "your-client-secret"
  allowed_domains:
    - "stackgen.com"

Provider Setup Instructions

1. Google (Google Workspace / GCP)

Google is a natively supported OIDC provider.

  1. Go to the Google Cloud Console.
  2. Navigate to APIs & Services -> Credentials.
  3. Click Create Credentials -> OAuth client ID.
  4. Set the Application type to Web application.
  5. Give the application a name (e.g., "Genie").
  6. Under Authorized redirect URIs, add your application's Callback URL (see Redirect URLs).
  7. Copy the generated Client ID and Client Secret into your Genie configuration.
  8. Set the IssuerURL in your configuration to https://accounts.google.com.
2. GitHub (Via Dex or similar Identity Broker)

Note: The coreos/go-oidc library strictly requires OpenID Connect discovery (/.well-known/openid-configuration), which GitHub's native OAuth2 does not provide. To use GitHub authentication with this package, you must use an identity broker like Dex.

  1. Deploy and configure Dex with the GitHub connector.
  2. Register an OAuth application in GitHub:
    • Go to Developer Settings -> OAuth Apps -> New OAuth App.
    • Set the callback URL to your Dex instance's callback endpoint.
  3. In Genie, configure OIDC to point at your Dex instance:
    • IssuerURL: URL of your Dex instance (e.g., https://dex.yourdomain.com).
    • ClientID / ClientSecret: The static credentials you configured for Genie inside Dex.

Redirect URLs

When configuring your OAuth application in the provider console (Google, Dex, Okta, etc.), you must whitelist the Authorized redirect URIs.

The redirect URL for Genie follows this format:

https://<your-host-or-domain>/auth/callback

Examples:

  • Local Development: http://localhost:8080/auth/callback
  • Production Deployment: https://genie.stackgen.com/auth/callback

If you haven't explicitly set the RedirectURL config variable, Genie automatically determines the host and protocol from the incoming HTTP request (using headers like X-Forwarded-Proto for HTTPS detection behind proxies).

Authentication Flow Endpoints

The auth package implements the following endpoints:

  • GET /auth/login — Redirects the user to the OIDC provider's consent screen.
  • GET /auth/callback — Handles the callback, exchanges the code for tokens, verifies claims, and sets the genie_session cookie.
  • GET /auth/logout — Clears the genie_session cookie.
  • GET /auth/info — Returns the current user's authenticated session state in JSON format.

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetClaims

func GetClaims(ctx context.Context) map[string]any

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

func Middleware(cfg Config, oidcHandler ...*OIDCHandler) func(http.Handler) http.Handler

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

func WithClaims(ctx context.Context, claims map[string]any) context.Context

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.

func (JWTConfig) Enabled

func (j JWTConfig) Enabled() bool

Enabled returns true when JWT validation is configured.

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 NewNoopResolver

func NewNoopResolver() *NoopResolver

NewNoopResolver creates a NoopResolver.

func (*NoopResolver) Resolve

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.

Directories

Path Synopsis
Code generated by counterfeiter.
Code generated by counterfeiter.

Jump to

Keyboard shortcuts

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