Package Overview: dracory/auth

Last Updated: 2026-10-29


What is dracory/auth?

dracory/auth is a batteries-included authentication library for Go that provides ready-to-use authentication flows with minimal setup. It is implementation-agnostic — you bring your own database, session store, and email service; the library handles the authentication logic, UI pages, and API endpoints.

Core Philosophy

  1. Four Login Methods, One Composable Mux
  2. Email OTP (passwordless) — email-based verification codes
  3. Magic link — single-use emailed sign-in links
  4. Username/password — traditional authentication with password storage
  5. AuthKnight — delegated hosted login (external service verifies the user)

Methods are mounted together behind a single mux via NewMultiMethodAuth. The first method owns <endpoint>/login; secondary methods mount at <endpoint>/login-<name>. Custom methods implement the public types.Method interface — no library change required.

  1. Complete Solution
  2. Pre-built HTML pages for login, registration, password reset
  3. JSON API endpoints for SPA/mobile apps
  4. Authentication middleware for protecting routes
  5. Customizable email templates
  1. Production-Grade Security
  2. Structured error handling with error codes
  3. CSRF protection and per-route rate limiting
  4. OTP codes bound to random nonces (not store keys)
  5. Session invalidation on password reset
  6. Secure cookie defaults and input validation
  7. Structured logging for audit trails
  1. Flexible Storage + Hooks
  2. You implement the storage layer via callback functions — works with any database (SQL, NoSQL, in-memory)
  3. types.PostAuthHooks drives the shared post-authentication pipeline: allowlist → find-or-create → active check → session → cookie → redirect
  4. Optional capabilities (session store, temp-key delete, post-auth hooks) are attached via type-asserted setters — the public interfaces stay stable

🏗️ Architecture Overview

Entry points
  ├── NewOtpAuth(ConfigOtp)     → AuthOtpInterface
  ├── NewUsernameAndPasswordAuth(ConfigUsernameAndPassword) → AuthPasswordInterface
  ├── NewAuthKnightAuth(ConfigAuthKnight)         → AuthAuthKnightInterface
  └── NewMultiMethodAuth(endpoint, []types.Method) → composed mux
        ├── NewOTPMethodWithPage(a, page)
        ├── NewMagicLinkMethodFromAuth(a, page, linkCallback)
        ├── NewPasswordMethod(a, page)
        └── NewAuthKnightMethod(a)

authImplementation
  ├── types/      — public configs, interfaces, Method, PostAuthHooks
  ├── internal/api/ — JSON endpoints (login, code-verify, magic-link,
  │                   register, password reset, impersonation, callbacks)
  ├── internal/ui/  — server-rendered pages (login, magic-link, register, …)
  ├── internal/core/ — shared post-auth pipeline
  └── internal/rules/ — reusable rule checks (throttles, validation, registration policy)

📦 Package Organization

types/ — Public API types

internal/api/ — JSON endpoints (one subdirectory per endpoint)

api_login, api_login_code_verify, api_magic_link_send, api_magic_link_callback, api_register, api_register_code_verify, api_logout, api_password_restore, api_password_reset, api_authenticate_via_username, api_authknight_callback, api_impersonate_start, api_impersonate_stop

internal/ui/ — Server-rendered pages

page_login, page_magic_link, page_login_code_verify, page_register, page_register_code_verify, page_logout, page_password_restore, page_password_reset, shared (page scaffold, AuthKnight redirect)

internal/ support packages

core (post-auth pipeline), rules (email send throttle, validation), links (route paths), emails, middlewares, helpers, testutils

Root package — public API


🔑 Multi-Method Composition

a, _ := auth.NewOtpAuth(cfg)          // shared impl serves otp + magiclink
p, _ := auth.NewUsernameAndPasswordAuth(pcfg)  // password impl

mma, err := auth.NewMultiMethodAuth("/auth", []types.Method{
    auth.NewPasswordMethod(p, nil),                 // primary → /auth/login
    auth.NewOTPMethodWithPage(a, nil),              // → /auth/login-otp
    auth.NewMagicLinkMethodFromAuth(a, nil, linkCallback), // → /auth/login-magiclink
})
mux.Handle("/auth/", http.StripPrefix("", mma.Router()))

🔗 Post-Auth Pipeline

Every method's verify path funnels through core.PostAuth when any PostAuthHooks are configured — one place for allowlist checks, user find-or-create, active validation, session creation, cookie writing, and redirect resolution. When no hooks are set, the legacy per-flow behavior (token + FuncUserStoreAuthToken) is used.


🔑 Key Flows

Email OTP

POST /auth/api/login → generate code → FuncTemporaryKeySet → FuncEmailSend → POST /auth/api/login-code-verify → post-auth → session/token + cookie

Magic link

POST <login path> → store MagicLinkPayload{email, token, return, remember} under a nonce key → EmailSend/EmailEnqueue the link → GET /auth/magiclink-callback?token= → post-auth → redirect

Username/password

POST /auth/api/login → FuncUserLogin(username, password) → post-auth → token + cookie. Recovery via /password-restore + /password-reset (session invalidation built in).

AuthKnight

Login page redirects to the hosted AuthKnight flow → GET /auth/authknight-callback?once= → verify once with AuthKnight's /api/who → post-auth (existing users) or register-redirect with a verified-email temp key (new users).

Verified-email registration (unified)

OTP, magic link, and AuthKnight share one register-first pipeline:

enter email → issue proof (code / link / once) → verify
  → verified-email token (verified_key, 15 min, single-use)
  → /register form (email read-only) → single INSERT

The token resolves to the verified email server-side — the form never trusts a posted email. Existing accounts authenticate normally.

ConfigShared.UserCreationPolicy selects when the user row is created: CreateUserOnAuth (default — created at verify time, incomplete profiles gated to the completion form by PostAuthHooks.RegistrationComplete) vs CreateUserOnRegister (deferred — no row until the form submits; requires a FuncUserExists or passwordless find-by-email hook to distinguish accounts). Degraded configurations fall back to session-first with a Warn log.

ConfigShared.FuncEmailCaptured(ctx, email, verified, options) is an optional lead-capture hook fired on submit (verified=false — unverified, attacker-controllable) and on successful verification (verified=true) across login, register, and magic-link paths.


🔒 Middleware

🧪 Examples

examples/ contains runnable apps: passwordless/, usernamepassword/, authknight/ — in-memory storage, email via localhost:1025, complete callback implementations.

📚 Related Documentation