Documentation
¶
Overview ¶
Package auth authenticates requests: guards (session for browsers, access tokens for APIs), user providers, middleware and password hashing (auth/hash).
A guard (SessionGuard, TokenGuard, or jwt.Guard in auth/jwt) loads users through a UserProvider such as ORMProvider. Routes opt in with the Required, Guest, Optional, Verified and Ability middleware, and handlers read the user with User. Apps scaffold the whole flow with `vukem install:auth`.
See https://github.com/AlexGalhardo/vukem/blob/main/docs/authentication.md.
Index ¶
- Constants
- Variables
- func Ability(guard Checker, abilities ...string) func(http.Handler) http.Handler
- func Guest(guards ...Checker) func(http.Handler) http.Handler
- func Optional(guards ...Checker) func(http.Handler) http.Handler
- func Principal(ctx context.Context) (guard, id string, ok bool)
- func Required(guards ...Checker) func(http.Handler) http.Handler
- func SetUser(c *web.Ctx, guard string, u any)
- func TokenCan(c *web.Ctx, guard, ability string) bool
- func User[U Authenticatable](c *web.Ctx, guard string) (U, error)
- func UserFrom[U any](ctx context.Context, guard string) (U, error)
- func Verified(guard Checker) func(http.Handler) http.Handler
- type Authenticatable
- type Checker
- type LoginOption
- type ORMProvider
- type PasswordUpdater
- type RememberStore
- type SessionGuard
- func (g *SessionGuard[U]) Attempt(c *web.Ctx, creds map[string]string, opts ...LoginOption) (U, error)
- func (g *SessionGuard[U]) Check(c *web.Ctx) bool
- func (g *SessionGuard[U]) Login(c *web.Ctx, u U, opts ...LoginOption) error
- func (g *SessionGuard[U]) Logout(c *web.Ctx) error
- func (g *SessionGuard[U]) Name() string
- func (g *SessionGuard[U]) SessionUserID(id string) string
- func (g *SessionGuard[U]) User(c *web.Ctx) (U, error)
- func (g *SessionGuard[U]) Validate(c *web.Ctx, creds map[string]string) (U, error)
- type Token
- type TokenGuard
- type Tokens
- func (t *Tokens) Create(ctx context.Context, userID, name string, abilities []string, ...) (string, Token, error)
- func (t *Tokens) Find(ctx context.Context, plain string) (Token, error)
- func (t *Tokens) List(ctx context.Context, userID string) ([]Token, error)
- func (t *Tokens) Revoke(ctx context.Context, id string) error
- func (t *Tokens) RevokeAll(ctx context.Context, userID string) error
- func (t *Tokens) RevokeFor(ctx context.Context, userID, id string) (bool, error)
- type UserProvider
- type Verifiable
Examples ¶
Constants ¶
const IntendedKey = "url.intended"
IntendedKey is the session key holding the URL a guest tried to open; login handlers redirect there (web.SafeRedirect) after success.
Variables ¶
var ( // ErrUnauthenticated is 401 (WWW-Authenticate is added by middleware). ErrUnauthenticated = &errs.HTTPError{Status: http.StatusUnauthorized, Message: "Unauthenticated."} // ErrUserNotFound is returned by providers for unknown users. ErrUserNotFound = errors.New("auth: user not found") )
Errors returned by guards.
Functions ¶
func Ability ¶
Ability requires the request's access token (from guard) to grant every ability; 403 otherwise. Place it after Required.
func Guest ¶
Guest sends authenticated users to the route named "home" (or "/"); use it on login and register pages.
func Optional ¶ added in v0.14.0
Optional authenticates the request when a guard can (so handlers, policies and templates see the user) and never blocks it.
func Principal ¶ added in v0.24.0
Principal returns the guard and user id of the user most recently authenticated on this request by any guard, for code that must key data by caller without knowing the guard (api.Idempotency). Combine the two unambiguously (length-prefixed or hashed): either may contain any character.
func Required ¶
Required lets the request through when any guard authenticates it. JSON and API clients get 401 (with WWW-Authenticate: Bearer when a token guard is involved); browsers are redirected to the route named "login", and the intended URL is remembered for GET requests.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/AlexGalhardo/vukem/auth"
"github.com/AlexGalhardo/vukem/router"
"github.com/AlexGalhardo/vukem/web"
)
type exUser struct{ ID, Hash string }
func (u exUser) AuthID() string { return u.ID }
func (u exUser) AuthPassword() string { return u.Hash }
// exHeaderGuard stands in for auth.NewSessionGuard or auth.NewTokenGuard,
// which need a database.
type exHeaderGuard struct{}
func (exHeaderGuard) Name() string { return "demo" }
func (exHeaderGuard) Check(c *web.Ctx) bool {
id := c.Request().Header.Get("X-Demo-User")
if id == "" {
return false
}
auth.SetUser(c, "demo", exUser{ID: id})
return true
}
func main() {
guard := exHeaderGuard{}
r := router.New(nil)
r.Get("/me", func(c *web.Ctx) error {
u, err := auth.User[exUser](c, guard.Name())
if err != nil {
return err
}
return c.String(http.StatusOK, "hello "+u.ID)
}).Use(auth.Required(guard))
anon := httptest.NewRequest(http.MethodGet, "/me", http.NoBody)
anon.Header.Set("Accept", "application/json")
rec := httptest.NewRecorder()
r.ServeHTTP(rec, anon)
fmt.Println(rec.Code)
signedIn := httptest.NewRequest(http.MethodGet, "/me", http.NoBody)
signedIn.Header.Set("X-Demo-User", "42")
rec = httptest.NewRecorder()
r.ServeHTTP(rec, signedIn)
fmt.Println(rec.Code, rec.Body.String())
}
Output: 401 200 hello 42
func SetUser ¶ added in v0.18.0
SetUser records u as the user guard authenticated for this request, for guards implemented outside this package (auth/jwt).
func User ¶
func User[U Authenticatable](c *web.Ctx, guard string) (U, error)
User returns the user authenticated by guard for this request (after Required or a guard's Check ran).
func UserFrom ¶ added in v0.6.0
UserFrom returns the user authenticated by guard from a request context (authz gates, view functions).
func Verified ¶
Verified requires the user authenticated by guard to have a verified email. Signed-in browsers without one are redirected to the route named "verification.notice" when the app has it (the install:auth scaffold does); JSON clients, and apps without that route, get 403. Place it after Required.
Types ¶
type Authenticatable ¶
type Authenticatable interface {
// AuthID is the user's primary key as a string.
AuthID() string
// AuthPassword is the stored password hash.
AuthPassword() string
}
Authenticatable is a user model.
type Checker ¶
type Checker interface {
// Name identifies the guard ("web", "api").
Name() string
// Check reports whether c is authenticated, caching the user in c.
Check(c *web.Ctx) bool
}
Checker is the non-generic part of a guard, used by middleware.
type ORMProvider ¶
type ORMProvider[U Authenticatable] struct { PasswordColumn string // contains filtered or unexported fields }
ORMProvider loads users of model U with the ORM: ByID by primary key, ByCredentials by every credential except "password" (column names are validated against U). Its PasswordColumn (default "password") receives rehashed passwords.
func NewORMProvider ¶
func NewORMProvider[U Authenticatable](ex database.Executor) *ORMProvider[U]
NewORMProvider returns the default provider for model U.
func (*ORMProvider[U]) ByCredentials ¶
ByCredentials implements UserProvider.
func (*ORMProvider[U]) ByID ¶
func (p *ORMProvider[U]) ByID(ctx context.Context, id string) (U, error)
ByID implements UserProvider.
func (*ORMProvider[U]) UpdatePassword ¶
func (p *ORMProvider[U]) UpdatePassword(ctx context.Context, u U, hash string) error
UpdatePassword implements PasswordUpdater.
type PasswordUpdater ¶
type PasswordUpdater[U Authenticatable] interface { UpdatePassword(ctx context.Context, u U, hash string) error }
PasswordUpdater is implemented by providers that can store a rehashed password after login (hash.Hasher.NeedsRehash).
type RememberStore ¶
type RememberStore struct {
// contains filtered or unexported fields
}
RememberStore keeps remember-me tokens: the cookie holds "<id>.<secret>" and only SHA-256(secret) is stored. Table (created by install:auth): remember_tokens(id TEXT PRIMARY KEY, user_id TEXT, hash TEXT, expires_at TIMESTAMP).
func NewRememberStore ¶
func NewRememberStore(ex database.Executor) *RememberStore
NewRememberStore uses the remember_tokens table.
type SessionGuard ¶
type SessionGuard[U Authenticatable] struct { // IdentifierField is where failed-login errors are reported ("email"). IdentifierField string // RememberFor is the remember-me cookie lifetime (default 30 days). RememberFor time.Duration // InsecureCookies drops the Secure flag on the remember-me cookie // (plain-HTTP development without localhost only). InsecureCookies bool // contains filtered or unexported fields }
SessionGuard authenticates browsers with the session: the user id is stored under "auth_<name>" after login.
func NewSessionGuard ¶
func NewSessionGuard[U Authenticatable](name string, p UserProvider[U], h hash.Hasher, remember *RememberStore, ) *SessionGuard[U]
NewSessionGuard returns the "web" style guard. remember may be nil to disable remember-me.
func (*SessionGuard[U]) Attempt ¶
func (g *SessionGuard[U]) Attempt(c *web.Ctx, creds map[string]string, opts ...LoginOption) (U, error)
Attempt checks creds (identifier columns + "password") and logs the user in. It returns a 422 validation error on the identifier field when they do not match, taking the same time whether or not the user exists.
func (*SessionGuard[U]) Check ¶
func (g *SessionGuard[U]) Check(c *web.Ctx) bool
Check implements Checker: session user, else a valid remember-me cookie.
func (*SessionGuard[U]) Login ¶
func (g *SessionGuard[U]) Login(c *web.Ctx, u U, opts ...LoginOption) error
Login logs u in: the session id is regenerated (fixation) and the user id stored, also under web.SessionUserKey so server-side session stores can revoke the user's other sessions (session.Manager.RevokeOthers).
func (*SessionGuard[U]) Logout ¶
func (g *SessionGuard[U]) Logout(c *web.Ctx) error
Logout clears the session (new id, no data) and the remember-me token.
func (*SessionGuard[U]) SessionUserID ¶ added in v0.24.0
func (g *SessionGuard[U]) SessionUserID(id string) string
SessionUserID is the owner recorded with server-side sessions for the user id (web.SessionUserKey): "<guard>:<id>", so guards over different user tables never share an owner. Pass it to session.Manager.RevokeOthers.
func (*SessionGuard[U]) User ¶
func (g *SessionGuard[U]) User(c *web.Ctx) (U, error)
User returns the logged-in user (cached for the request) or ErrUnauthenticated.
type Token ¶
type Token struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Name string `json:"name"`
Abilities []string `json:"abilities"`
LastUsedAt *time.Time `json:"last_used_at"`
ExpiresAt *time.Time `json:"expires_at"`
}
Token describes an access token (never the secret).
func CurrentToken ¶
CurrentToken returns the token that authenticated c with guard.
type TokenGuard ¶
type TokenGuard[U Authenticatable] struct { // contains filtered or unexported fields }
TokenGuard authenticates "Authorization: Bearer <token>" requests.
func NewTokenGuard ¶
func NewTokenGuard[U Authenticatable](name string, p UserProvider[U], tokens *Tokens) *TokenGuard[U]
NewTokenGuard returns the "api" style guard.
type Tokens ¶
type Tokens struct {
// Prefix starts every newly issued token ("vk_"), so secret scanners
// (GitHub push protection, gitleaks) can match leaked tokens. It is up
// to 32 letters, digits or underscores and ends with "_". Tokens issued
// without it, or under an earlier prefix, stay valid.
Prefix string
// contains filtered or unexported fields
}
Tokens issues and checks opaque access tokens "<prefix><id>.<secret>"; only SHA-256(secret) is stored. Table (created by install:auth / install:api): access_tokens(id TEXT PRIMARY KEY, user_id TEXT, name TEXT, hash TEXT, abilities TEXT, last_used_at TIMESTAMP NULL, expires_at TIMESTAMP NULL, created_at TIMESTAMP).
func (*Tokens) Create ¶
func (t *Tokens) Create( ctx context.Context, userID, name string, abilities []string, ttl time.Duration, ) (string, Token, error)
Create issues a token for userID and returns the plain value, shown to the client once. ttl 0 means the token never expires and stays valid until revoked; pass a ttl unless that is intended (the install:api scaffold issues 30-day tokens).
func (*Tokens) Find ¶
Find validates a plain token and returns it, or errBadToken. It updates last_used_at at most once a minute.
func (*Tokens) List ¶ added in v0.22.0
List returns userID's tokens, newest first (never the secrets).
func (*Tokens) Revoke ¶
Revoke deletes a token by id without checking who owns it. Use RevokeFor when the id comes from user input (a "revoke" button in a dashboard).
type UserProvider ¶
type UserProvider[U Authenticatable] interface { ByID(ctx context.Context, id string) (U, error) ByCredentials(ctx context.Context, creds map[string]string) (U, error) }
UserProvider loads users. ByCredentials never checks the password; the guard does, in constant time.
ByID must report a missing user as ErrUserNotFound (wrap it, or the orm.ErrNotFound / sql.ErrNoRows the lookup returned): the session guard forgets the session only then, and answers any other error with 503 while keeping the session, so returning a generic error for "not found" leaves a deleted user's session failing instead of logging out.
type Verifiable ¶
type Verifiable interface {
EmailVerified() bool
}
Verifiable is a user whose email address can be verified.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package hash hashes and verifies passwords: argon2id by default (PHC string format), bcrypt for compatibility.
|
Package hash hashes and verifies passwords: argon2id by default (PHC string format), bcrypt for compatibility. |
|
Package jwt issues and verifies JSON Web Tokens (RFC 7519) with the standard library: HS256, RS256, ES256 and EdDSA, an algorithm allowlist tied to each key (no algorithm confusion, never "none"), key ids with static or remote (JWKS) key sets that rotate, strict claim checks, a Guard for package auth, and rotating refresh tokens with reuse detection.
|
Package jwt issues and verifies JSON Web Tokens (RFC 7519) with the standard library: HS256, RS256, ES256 and EdDSA, an algorithm allowlist tied to each key (no algorithm confusion, never "none"), key ids with static or remote (JWKS) key sets that rotate, strict claim checks, a Guard for package auth, and rotating refresh tokens with reuse detection. |
|
Package totp implements time-based one-time passwords (RFC 6238, the codes of authenticator apps) for two-factor authentication: secrets, otpauth:// enrolment URIs with an SVG QR code, validation with one step of clock skew and replay protection, and hashed single-use recovery codes.
|
Package totp implements time-based one-time passwords (RFC 6238, the codes of authenticator apps) for two-factor authentication: secrets, otpauth:// enrolment URIs with an SVG QR code, validation with one step of clock skew and replay protection, and hashed single-use recovery codes. |