auth

package
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Feb 24, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package auth provides type-safe authentication helpers for Vango.

The auth package is designed to be auth-system agnostic. It doesn't validate tokens or manage sessions—that's the responsibility of HTTP middleware in Layer 1 (the infrastructure layer). Instead, it provides type-safe access to the user data that was hydrated via the Context Bridge.

SSR and WebSocket Compatibility

The auth package works seamlessly in both SSR and WebSocket modes:

  • auth.Get and auth.IsAuthenticated use ctx.User() as the source of truth
  • ctx.User() checks: per-request user (SetUser) → session data → HTTP request context (SSR)
  • This means the same component code works in both modes

The Two-Layer Architecture

Vango operates across two distinct lifecycles:

  • Layer 1 (HTTP): Runs ONCE per session during initial GET + WebSocket upgrade. Standard HTTP middleware (Chi, Gorilla, etc.) handles authentication here.

  • Layer 2 (WebSocket): Runs HUNDREDS of times per session for each event. The auth package operates here, providing type-safe access to user data.

The Context Bridge

Data flows from Layer 1 to Layer 2 via the Context Bridge:

OnSessionStart: func(httpCtx context.Context, session *vango.Session) {
    if user := myauth.UserFromContext(httpCtx); user != nil {
        auth.Set(session, user)  // Copy to session
    }
}

After this runs, r.Context() is dead (HTTP request complete), but the session persists with the hydrated user data.

Session Resume

When a client reconnects (e.g., after a page refresh within ResumeWindow), auth data is revalidated via OnSessionResume:

OnSessionResume: func(httpCtx context.Context, session *vango.Session) error {
    user, err := myauth.ValidateFromContext(httpCtx)
    if err != nil {
        return err  // Reject resume if previously authenticated
    }
    if user != nil {
        auth.Set(session, user)
    }
    return nil
}

Important: User objects are NOT serialized to session storage. They must be rehydrated from cookies/headers on each handshake and resume. This ensures auth state is always validated against the current request.

Strict-vs-trust resume policy:

  • AuthResumePolicyStrict (default): previously authenticated sessions must be revalidated and rehydrated on resume.
  • AuthResumePolicyTrustSessionID: detached in-memory sessions can resume using session ID alone, which weakens immediate logout/revocation guarantees. Use only with explicit risk acceptance and short resume windows.

Principal + expiry keys (SessionKeyPrincipal, SessionKeyExpiryUnixMs) are runtime-only and must also be rehydrated on start/resume. Session persistence MUST skip these keys, and runtime session KV is non-durable by default. RuntimeOnlySessionKeys enumerates keys that must never be restored as durable auth state.

To enable auth freshness checks, set a Principal with an explicit expiry:

auth.SetPrincipal(session, auth.Principal{
    ID:              user.ID,
    Email:           user.Email,
    ExpiresAtUnixMs: expiresAt.UnixMilli(),
})

If ExpiresAtUnixMs is zero (or negative), SetPrincipal clears SessionKeyExpiryUnixMs, so passive expiry checks remain disabled unless you set the key explicitly.

Auth Freshness (Passive + Active)

Passive expiry is enforced on every WebSocket event when SessionKeyExpiryUnixMs is present.

Active revalidation is configured via SessionConfig.AuthCheck:

app, err := vango.New(vango.Config{
    Session: vango.SessionConfig{
        AuthCheck: &vango.AuthCheckConfig{
            Interval: 2 * time.Minute,
            Check:    myProvider.Verify,
            OnExpired: vango.AuthExpiredConfig{
                Action: vango.ForceReload,
            },
        },
    },
})
if err != nil {
    log.Fatal(err)
}

For high-value operations, use ctx.RevalidateAuth() to force an immediate active check (fail-closed). To avoid missing-principal failures, ensure your session bridge sets auth.SessionKeyPrincipal via auth.SetPrincipal on session start/resume.

AuthCheck panics are recovered by the server and treated as check failures: active checks follow FailureMode/MaxStale, and RevalidateAuth returns ErrAuthCheckPanicked.

Session-First Adapter

The sessionauth package provides a reference adapter for session stores:

provider := sessionauth.New(store)
r.Use(provider.Middleware())
principal, ok := provider.Principal(r.Context())

Basic Usage

Use middleware to protect routes that require authentication:

// In app/routes/dashboard/middleware.go
func Middleware() []router.Middleware {
    return []router.Middleware{authmw.RequireAuth}
}

// In app/routes/dashboard/page.go
func Dashboard(ctx vango.Ctx) vango.Component {
    // Middleware guarantees user is authenticated
    user, _ := auth.Get[*models.User](ctx)
    return renderDashboard(user)
}

For routes where auth is optional (guest allowed):

func HomePage(ctx vango.Ctx) vango.Component {
    user, ok := auth.Get[*models.User](ctx)
    if ok {
        return renderLoggedInHome(user)
    }
    return renderGuestHome()
}

Login and Logout

Use auth.Login to authenticate a user during the session:

func HandleLogin(ctx vango.Ctx, email, password string) error {
    user, err := validateCredentials(email, password)
    if err != nil {
        return err
    }
    auth.Login(ctx, user)  // Sets both request context and session
    ctx.Navigate("/dashboard")
    return nil
}

Use auth.Logout to clear authentication (broadcasts logout to other tabs when a session is present):

func HandleLogout(ctx vango.Ctx) error {
    auth.Logout(ctx)
    ctx.Navigate("/")
    return nil
}

Middleware

Use authmw middleware to protect entire route segments:

// In app/routes/admin/middleware.go
func Middleware() []router.Middleware {
    return []router.Middleware{
        authmw.RequireAuth,
        authmw.RequireRole(func(u *models.User) bool {
            return u.IsAdmin
        }),
    }
}

Error Handling

Auth errors are mapped to appropriate HTTP status codes:

  • auth.ErrUnauthorized → 401 Unauthorized
  • auth.ErrForbidden → 403 Forbidden

In SSR and API routes, these errors produce the correct HTTP status. In WebSocket navigation, they produce protocol.ErrNotAuthorized.

Use auth.Require in action handlers for explicit error handling:

func DeleteProject(ctx vango.Ctx, id int) error {
    user, err := auth.Require[*models.User](ctx)
    if err != nil {
        return err  // Returns 401/ErrNotAuthorized
    }
    // ... delete logic
}

Type Safety

The auth package uses Go generics for type-safe user retrieval:

  • auth.Get[*User](ctx) returns (*User, bool)
  • auth.Require[*User](ctx) returns (*User, error)

In debug mode (ServerConfig.DebugMode = true), the package logs warnings when type assertions fail, helping catch common mistakes like storing a value type but requesting a pointer type.

Index

Constants

View Source
const (
	SessionKeyPrincipal = "vango:auth:principal"
	// SessionKeyExpiryUnixMs is the hard expiry timestamp in unix milliseconds.
	// Stored as int64 to remain robust even if an app accidentally serializes it.
	SessionKeyExpiryUnixMs = "vango:auth:expiry_unix_ms"

	// SessionKeyHadAuth is a non-authoritative marker indicating the session
	// previously had authenticated state (useful for logs/telemetry and resume UX).
	// This key MAY be persisted (it's a simple boolean), but it MUST NOT be an authority source.
	SessionKeyHadAuth = "vango:auth:had_auth"
)

Session keys — presence of SessionKeyExpiryUnixMs enables automatic passive checks.

View Source
const SessionKey = "vango_auth_user"

SessionKey is the standard session key for the authenticated user. The Context Bridge should use this key when storing user data.

Variables

View Source
var (
	// RuntimeOnlySessionKeys lists keys that MUST NOT be persisted by session serializers.
	RuntimeOnlySessionKeys = []string{
		SessionKeyPrincipal,
		SessionKeyExpiryUnixMs,
	}

	// ErrSessionExpired indicates the session is no longer valid due to expiry.
	ErrSessionExpired = errors.New("session expired")

	// ErrSessionRevoked indicates the session is no longer valid due to revocation.
	ErrSessionRevoked = errors.New("session revoked")
)
View Source
var ErrForbidden = errors.New("forbidden: insufficient permissions")

ErrForbidden is returned when authentication is present but insufficient. This typically triggers a 403 response.

View Source
var ErrUnauthorized = errors.New("unauthorized: authentication required")

ErrUnauthorized is returned when authentication is required but not present. This typically triggers a 401 response or redirect to login.

Functions

func Clear

func Clear(session Session)

Clear removes the authenticated user from the session. Also clears the auth presence flag. Call this on logout.

Example:

func Logout(ctx vango.Ctx) error {
    auth.Clear(ctx.Session())
    ctx.Navigate("/login")
    return nil
}

func Get

func Get[T any](ctx Ctx) (T, bool)

Get retrieves the authenticated user from the context. Works in both SSR and WebSocket modes by using ctx.User() as the source of truth.

Returns (user, true) if authenticated, (zero, false) otherwise.

In debug mode, logs a warning if a value exists but type assertion fails, helping developers catch common value/pointer mismatches.

Example:

user, ok := auth.Get[*models.User](ctx)
if !ok {
    // User not authenticated
}

func IsAuthError

func IsAuthError(err error) bool

IsAuthError returns true if the error is an authentication or authorization error.

func IsAuthenticated

func IsAuthenticated(ctx Ctx) bool

IsAuthenticated returns whether the context has an authenticated user. Works in both SSR and WebSocket modes.

Example:

if auth.IsAuthenticated(ctx) {
    // Show logged-in UI
}

func Login

func Login[T any](ctx Ctx, user T)

Login authenticates the user for both the current request and the session. Use this in login handlers to establish authentication.

This is the recommended way to authenticate a user during a session: - Sets the user on the current request context - Persists to the session (if available) for subsequent requests - Sets the presence flag for resume validation

Example:

func HandleLogin(ctx vango.Ctx, email, password string) error {
    user, err := validateCredentials(email, password)
    if err != nil {
        return err
    }
    auth.Login(ctx, user)
    ctx.Navigate("/dashboard")
    return nil
}

func Logout

func Logout(ctx Ctx)

Logout clears authentication from both the request context and session. It also broadcasts a logout signal (when a session is present) so other tabs reload. This is the recommended way to log out a user.

Example:

func HandleLogout(ctx vango.Ctx) error {
    auth.Logout(ctx)
    ctx.Navigate("/")
    return nil
}

func LogoutAndBroadcast

func LogoutAndBroadcast(ctx Ctx)

LogoutAndBroadcast clears authentication and notifies other tabs to reload. This is an alias of Logout for clarity.

func MustGet

func MustGet[T any](ctx Ctx) T

MustGet is like Get but panics if authentication fails. Use sparingly, prefer Require for proper error handling.

func Require

func Require[T any](ctx Ctx) (T, error)

Require returns the authenticated user or an error. Use in middleware or action handlers that require authentication.

Note: Page handlers in Vango don't return errors. Use authmw.RequireAuth middleware to protect routes, then use auth.Get in the handler.

Example (middleware):

func Middleware() []router.Middleware {
    return []router.Middleware{authmw.RequireAuth}
}

Example (action handler):

func DeleteProject(ctx vango.Ctx, id int) error {
    user, err := auth.Require[*models.User](ctx)
    if err != nil {
        return err
    }
    // ...
}

func SessionPresenceKey

func SessionPresenceKey() string

SessionPresenceKey returns the key used to track auth presence. Exported for session serialization to skip the user object but keep the flag.

func Set

func Set[T any](session Session, user T)

Set stores the authenticated user in the session. Also sets an auth presence flag that survives session serialization.

Typically called from OnSessionStart or OnSessionResume in the Context Bridge.

Example:

OnSessionStart: func(httpCtx context.Context, session *vango.Session) {
    if user := myauth.UserFromContext(httpCtx); user != nil {
        auth.Set(session, user)
    }
}

func SetPrincipal

func SetPrincipal(session Session, principal Principal)

SetPrincipal stores the principal and expiry keys on the session. This marks the session as previously authenticated.

func StatusCode

func StatusCode(err error) (int, bool)

StatusCode returns the appropriate HTTP status code for an auth error. Returns (statusCode, true) for auth errors, (0, false) otherwise.

Example:

if code, ok := auth.StatusCode(err); ok {
    w.WriteHeader(code)
}

func WasAuthenticated

func WasAuthenticated(session Session) bool

WasAuthenticated checks if the session had authentication before. Used internally by resume logic to detect "was authenticated but auth now invalid." Returns false if session is nil.

Types

type Ctx

type Ctx interface {
	User() any
	SetUser(user any)
}

Ctx provides minimal context access needed by auth helpers.

type DebugProvider

type DebugProvider interface {
	AuthDebug() bool
}

DebugProvider optionally exposes debug behavior for auth helpers. When AuthDebug returns true, auth.Get logs type mismatch warnings.

type Principal

type Principal struct {
	// User identity
	ID    string `json:"id"`
	Email string `json:"email"`
	Name  string `json:"name"`

	// Authorization
	Roles    []string `json:"roles,omitempty"`
	TenantID string   `json:"tenant_id,omitempty"`

	// Provider session (for active verification)
	SessionID string `json:"session_id,omitempty"`

	// Expiration
	ExpiresAtUnixMs int64 `json:"expires_at_unix_ms"`
	AuthVersion     int   `json:"auth_version,omitempty"`
}

Principal represents the authenticated identity. Intentionally minimal — no catch-all Claims map to prevent leakage.

type Provider

type Provider interface {
	// Middleware validates HTTP requests and populates context.
	Middleware() func(http.Handler) http.Handler

	// Principal extracts identity from validated request context.
	Principal(ctx context.Context) (Principal, bool)

	// Verify checks if session is still valid (for active revalidation).
	Verify(ctx context.Context, p Principal) error
}

Provider adapts an identity provider to Vango.

type Session

type Session interface {
	Get(key string) any
	Set(key string, value any)
	Delete(key string)
}

Session provides minimal session access needed by auth helpers.

Directories

Path Synopsis
Package sessionauth provides a session-first auth adapter for Vango.
Package sessionauth provides a session-first auth adapter for Vango.

Jump to

Keyboard shortcuts

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