github.com/deicod/auth is a storage-agnostic authentication module that bundles the domain model, services and HTTP transport needed for user registration, login, email verification, password resets and email change flows. The package exposes a single auth.Service interface while letting you pick the persistence layer (mgo for MongoDB or pgx for PostgreSQL) at runtime.
auth.Servicedefines the full authentication surface (register, login, forgot/reset password, email verification/change) and can be backed by MongoDB or PostgreSQL without touching the rest of your code.coreholds the shared commands, results, errors and user/session representations to keep handlers and services storage-neutral.- Ready-to-use HTTP handlers (
handlers.AuthHandlers) turn the service into JSON endpoints for/register,/login,/logout,/me, email verification and password reset workflows. - Security defaults baked in: Argon2id password hashing, opaque session tokens, short-lived verification/reset/email-change tokens and SMTP integrations for transactional emails.
- Rich configuration via
auth.Configwith sensible defaults plus knobs for session lifetime, token TTLs, Argon2 parameters and mail transport. - PGX backend ships with embedded SQL migrations, while the Mongo backend wires collections, indexes and repositories for you.
| Path | Description |
|---|---|
auth.go |
Backend switch + auth.Service interface definition. |
core/ |
Domain layer: command DTOs, public models, shared errors and result structs. |
handlers/ |
JSON HTTP handlers that wrap any auth.Service. |
mgo/ |
MongoDB service implementation, repositories and configuration helpers. |
pgx/ |
PostgreSQL (pgxpool) implementation with migrations and repositories. |
sqlite/ |
SQLite (CGO) implementation with migrations and repositories. |
config/ |
Shared configuration structs for sessions, tokens, Argon2 and SMTP. |
email/ |
SMTP sender implementation with a NopSender fallback. |
go get github.com/deicod/authStart with auth.DefaultConfig() and override what you need. Important fields:
| Field | Purpose | Default |
|---|---|---|
Backend |
auth.BackendMongo, auth.BackendPostgres or auth.BackendSQLite. |
auth.BackendMongo |
Mongo |
Pointer to mgo.Config (URI, db name, collection names, operation timeout). |
mgo.DefaultConfig() |
Pgx |
Pointer to pgx.Config (DSN, pool sizing, health checks, timeouts). |
pgx.DefaultConfig() |
Sqlite |
Pointer to sqlite.Config (DSN, connection pool settings). |
sqlite.DefaultConfig() |
Session |
Controls session length (config.Session). |
30 days |
Tokens |
TTLs for verification/reset/email-change tokens (config.Tokens). |
48h/1h/24h |
Argon2 |
Cost settings for Argon2id hashing (config.Argon2). |
Time=3, Memory=64MiB |
Email |
SMTP transport (config.Mail). Empty Host disables email sending (uses email.NopSender). |
See config/mail.go |
URI: Mongo connection string.Database: database name.{Users,Sessions,Verification,PasswordReset,EmailChange}Collection: collection names (defaults provided).OperationTimeout: per call timeout for repository operations.- The Mongo service now ensures the essential indexes (unique
email/username, uniquetoken_hashvalues, TTL onexpires_at) the first time it connects, so tokens and sessions expire automatically even if you forget to add indexes manually.
DSN: PostgreSQL connection string (e.g.postgres://user:pass@localhost:5432/auth?sslmode=disable).MaxConns,MinConns,HealthCheckInterval,MaxConnLifetime,MaxConnIdleTime: passed topgxpool.OperationTimeout: context deadline used by repositories.- Embedded migrations now create helper indexes on
expires_atfor sessions and token tables, so scheduled cleanup jobs (orDELETE ... WHERE expires_at < now()) stay efficient. PostgreSQL does not support TTL indexes natively, so you still need a periodic cleanup job if you want automatic removal.
ℹ️ The pgx backend applies embedded SQL migrations automatically and expects PostgreSQL 18+ (for the
uuidv7()default). If you're on an older version, replace the default inpgx/migrations/0001_init.sqlor create the function manually.
DSN: SQLite connection string (e.g.file:auth.db?_foreign_keys=on). Use:memory:for in-memory.MaxOpenConns,MaxIdleConns,ConnMaxLifetime: Connection pool settings.OperationTimeout: Context deadline for queries.- SQL migrations are embedded and applied automatically on connection.
package main
import (
"context"
"log"
"net/http"
"github.com/deicod/auth"
"github.com/deicod/auth/handlers"
)
func main() {
ctx := context.Background()
cfg := auth.DefaultConfig()
cfg.Backend = auth.BackendMongo
cfg.Mongo.URI = "mongodb://localhost:27017"
cfg.Mongo.Database = "auth_demo"
svc, err := auth.NewService(ctx, cfg)
if err != nil {
log.Fatal(err)
}
defer closeService(ctx, svc)
authHandlers := handlers.New(svc)
mux := http.NewServeMux()
mux.Handle("/auth/register", authHandlers.Register())
mux.Handle("/auth/login", authHandlers.Login())
mux.Handle("/auth/logout", authHandlers.Logout())
mux.Handle("/auth/me", authHandlers.Me())
mux.Handle("/auth/verify", authHandlers.VerifyEmail())
mux.Handle("/auth/forgot", authHandlers.ForgotPassword())
mux.Handle("/auth/reset", authHandlers.ResetPassword())
mux.Handle("/auth/email-change", authHandlers.InitiateEmailChange())
mux.Handle("/auth/email-confirm", authHandlers.ConfirmEmailChange())
log.Println("auth API listening on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
func closeService(ctx context.Context, svc auth.Service) {
if closer, ok := svc.(interface{ Close(context.Context) error }); ok {
_ = closer.Close(ctx)
}
}Sample request/response:
curl -X POST http://localhost:8080/auth/register \
-H 'Content-Type: application/json' \
-d '{"email":"alice@example.com","username":"alice","password":"Sup3rSecret"}'
# => {"user":{"id":"...","email":"alice@example.com","username":"alice"},"session":{"id":"...","expires_at":"..."},"token":"opaque-session-token"}Emails are sent only when cfg.Email.Host is set. Without SMTP configuration the service still works, but verification/reset tokens are stored and must be delivered manually for testing.
package main
import (
"context"
"log"
"net/http"
"time"
"github.com/deicod/auth"
"github.com/deicod/auth/handlers"
)
func main() {
ctx := context.Background()
cfg := auth.DefaultConfig()
cfg.Backend = auth.BackendPostgres
cfg.Pgx.DSN = "postgres://auth:auth@localhost:5432/auth?sslmode=disable"
cfg.Pgx.MaxConns = 8
cfg.Session.Length = 14 * 24 * time.Hour
cfg.Tokens.ResetTTL = 15 * time.Minute
svc, err := auth.NewService(ctx, cfg)
if err != nil {
log.Fatal(err)
}
defer closeService(ctx, svc)
router := http.NewServeMux()
authHandlers := handlers.New(svc)
router.Handle("/auth/register", authHandlers.Register())
router.Handle("/auth/login", authHandlers.Login())
router.Handle("/auth/logout", authHandlers.Logout())
router.Handle("/auth/me", authHandlers.Me())
router.Handle("/auth/verify", authHandlers.VerifyEmail())
router.Handle("/auth/forgot", authHandlers.ForgotPassword())
router.Handle("/auth/reset", authHandlers.ResetPassword())
router.Handle("/auth/email-change", authHandlers.InitiateEmailChange())
router.Handle("/auth/email-confirm", authHandlers.ConfirmEmailChange())
log.Fatal(http.ListenAndServe(":3000", router))
}
func closeService(ctx context.Context, svc auth.Service) {
if closer, ok := svc.(interface{ Close(context.Context) error }); ok {
_ = closer.Close(ctx)
}
}The pgx service automatically runs the embedded migration (pgx/migrations/0001_init.sql) when it connects. Ensure the configured database user can create tables and that the uuid-ossp extension or PostgreSQL 16+ UUID helpers are available.
package main
import (
"context"
"log"
"net/http"
"github.com/deicod/auth"
"github.com/deicod/auth/handlers"
)
func main() {
ctx := context.Background()
cfg := auth.DefaultConfig()
cfg.Backend = auth.BackendSQLite
cfg.Sqlite.DSN = "file:auth.db?_foreign_keys=on"
svc, err := auth.NewService(ctx, cfg)
if err != nil {
log.Fatal(err)
}
defer closeService(ctx, svc)
// mount handlers...
}All handlers accept/return JSON and bubble up core errors with appropriate HTTP status codes.
| Handler | Method + Path | Request Body | Response | Rate Limit (per IP) |
|---|---|---|---|---|
Register() |
POST /auth/register |
{email, username, password} |
core.AuthResult (user, session, token). |
5 req/min |
Login() |
POST /auth/login |
{email, password} |
core.AuthResult. |
5 req/min |
Logout() |
POST /auth/logout |
Bearer token header | 204 No Content. |
20 req/min |
Me() |
GET /auth/me |
Bearer token header | {user, session}. |
60 req/min |
VerifyEmail() |
POST /auth/verify |
{token} |
core.VerifyEmailResult. |
5 req/min |
ForgotPassword() |
POST /auth/forgot |
{email} |
{"status":"email_sent"}. |
5 req/min |
ResetPassword() |
POST /auth/reset |
{token, new_password} |
core.UserPublic. |
5 req/min |
InitiateEmailChange() |
POST /auth/email-change |
{user_id, password, new_email} |
{"status":"email_sent"}. |
5 req/min |
ConfirmEmailChange() |
POST /auth/email-confirm |
{token} |
core.ChangeEmailResult. |
5 req/min |
Use the helpers in core/errors.go to distinguish conflicts, bad requests, unauthorized cases, etc., if you need to customize error rendering.
Register and Login both return an opaque session token. Store it client-side and send it as Authorization: Bearer <token> on requests that hit your own APIs. The service exposes AuthenticateSession, and the middleware package wraps it for you:
import (
"net/http"
"github.com/deicod/auth/middleware"
)
requireAuth := middleware.RequireAuth(svc)
mux.Handle("/profile", requireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if user, ok := middleware.UserFromContext(r.Context()); ok {
respondJSON(w, http.StatusOK, user)
return
}
http.Error(w, "user missing", http.StatusInternalServerError)
})))middleware.SessionFromContext is also available if you need to inspect the active session (user agent, IP, expiry, etc.). No additional database wiring is required—the middleware simply calls svc.AuthenticateSession, which hashes the bearer token, fetches the session record via the configured backend and returns the associated user.
- Sessions store a hashed token plus metadata (IP, user-agent) and respect
config.Session.Length. - Password resets revoke all of the user's existing sessions to force fresh logins.
- Verification, password reset and email-change tokens are short-lived; customize TTLs through
cfg.Tokens. - Passwords use Argon2id (via
cfg.Argon2). RaisingMemoryorTimeenforces stronger hashing requirements. - Set
cfg.Emailto an SMTP server (host, port, credentials,UseSSL) to enable automated verification/reset/email-change emails. With an empty host, theemail.NopSendersatisfies the interface so the flows still succeed in tests.
Service methods return errors from core/errors.go (ErrEmailExists, ErrInvalidCredentials, ErrTokenExpired, etc.). The provided handlers already translate them to HTTP status codes, but you can wrap the service with your own transport knowing the error contracts are consistent across backends.
- When switching backends, reuse the same handlers and only change
cfg.Backendplus backend-specific config. - For integration tests, keep SMTP disabled and read verification/reset tokens directly from the database collections/tables.
- The service implementations expose
Close(context.Context) error; type-assert the returnedauth.Serviceif you need to cleanly close Mongo clients or pgx pools at shutdown.