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
- Variables
- func Clear(session Session)
- func Get[T any](ctx Ctx) (T, bool)
- func IsAuthError(err error) bool
- func IsAuthenticated(ctx Ctx) bool
- func Login[T any](ctx Ctx, user T)
- func Logout(ctx Ctx)
- func LogoutAndBroadcast(ctx Ctx)
- func MustGet[T any](ctx Ctx) T
- func Require[T any](ctx Ctx) (T, error)
- func SessionPresenceKey() string
- func Set[T any](session Session, user T)
- func SetPrincipal(session Session, principal Principal)
- func StatusCode(err error) (int, bool)
- func WasAuthenticated(session Session) bool
- type Ctx
- type DebugProvider
- type Principal
- type Provider
- type Session
Constants ¶
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.
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 ¶
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") )
var ErrForbidden = errors.New("forbidden: insufficient permissions")
ErrForbidden is returned when authentication is present but insufficient. This typically triggers a 403 response.
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 ¶
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 ¶
IsAuthError returns true if the error is an authentication or authorization error.
func IsAuthenticated ¶
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 ¶
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 ¶
MustGet is like Get but panics if authentication fails. Use sparingly, prefer Require for proper error handling.
func Require ¶
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 ¶
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 ¶
SetPrincipal stores the principal and expiry keys on the session. This marks the session as previously authenticated.
func StatusCode ¶
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 ¶
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 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.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package sessionauth provides a session-first auth adapter for Vango.
|
Package sessionauth provides a session-first auth adapter for Vango. |