README
¶
central-auth
A lightweight, reusable authentication library for Go applications. Provides JWT-based authentication with RS256/HS256 support, secure password hashing via Argon2id, and flexible storage backends.
Features
- JWT Authentication - Generate and validate access/refresh tokens with RS256 or HS256
- Secure Password Hashing - Argon2id with configurable parameters
- HTTP Handlers - Ready-to-use registration, login, refresh, and logout endpoints
- Auth Middleware - Drop-in middleware for protecting routes
- Multiple Storage Backends - PostgreSQL, MySQL, and SQLite implementations
- Refresh Token Rotation - Automatic token rotation for security
- JWKS Support - Remote JWKS validation for microservices
Installation
go get github.com/ethan-mdev/central-auth
Quick Start
package main
import (
"database/sql"
"net/http"
"time"
authhttp "github.com/ethan-mdev/central-auth/http"
"github.com/ethan-mdev/central-auth/jwt"
"github.com/ethan-mdev/central-auth/middleware"
"github.com/ethan-mdev/central-auth/password"
"github.com/ethan-mdev/central-auth/storage"
"github.com/ethan-mdev/central-auth/tokens"
_ "github.com/lib/pq"
)
func main() {
// Setup database
db, _ := sql.Open("postgres", "postgres://user:pass@localhost/dbname?sslmode=disable")
// Initialize repositories
users := storage.NewPostgresUserRepository(db)
users.CreateTable()
refreshTokens := tokens.NewPostgresRefreshRepository(db)
refreshTokens.CreateTable()
// Load or generate RSA keys
privateKey, _ := jwt.LoadPrivateKey([]byte(privateKeyPEM))
// Create JWT manager
jwtManager, _ := jwt.NewManager(jwt.Config{
Algorithm: "RS256",
PrivateKey: privateKey,
KeyID: "key-1",
})
// Create auth handler
authHandler := &authhttp.AuthHandler{
Users: users,
RefreshTokens: refreshTokens,
Hash: password.Default(),
JWT: jwtManager,
AccessExpiry: 15 * time.Minute,
RefreshExpiry: 7 * 24 * time.Hour,
}
// Setup routes
mux := http.NewServeMux()
mux.HandleFunc("POST /register", authHandler.Register())
mux.HandleFunc("POST /login", authHandler.Login())
mux.HandleFunc("POST /refresh", authHandler.RefreshToken())
mux.HandleFunc("POST /logout", authHandler.Logout())
mux.Handle("POST /change-password", middleware.Auth(jwtManager, authHandler.ChangePassword()))
// Protected route
mux.Handle("GET /profile", middleware.Auth(jwtManager, http.HandlerFunc(profileHandler)))
http.ListenAndServe(":8080", mux)
}
func profileHandler(w http.ResponseWriter, r *http.Request) {
claims, _ := middleware.GetClaims(r.Context())
// Use claims.UserID, claims.Username, claims.Role
}
Storage Backends
PostgreSQL
import _ "github.com/lib/pq"
db, _ := sql.Open("postgres", "postgres://user:pass@localhost/dbname?sslmode=disable")
users := storage.NewPostgresUserRepository(db)
users.CreateTable()
MySQL
import _ "github.com/go-sql-driver/mysql"
db, _ := sql.Open("mysql", "user:pass@tcp(localhost:3306)/dbname")
users := storage.NewMySQLUserRepository(db)
users.CreateTable()
SQLite
import _ "modernc.org/sqlite"
db, _ := sql.Open("sqlite", "./auth.db")
users := storage.NewSQLiteUserRepository(db)
users.CreateTable()
Note: Use
modernc.org/sqlitefor pure Go implementation, orgithub.com/mattn/go-sqlite3if you have CGO available.
JWT Configuration
RS256 (Recommended for production)
// Generate keys or load from file
privateKey, _ := jwt.GenerateRSAKey(2048)
jwtManager, _ := jwt.NewManager(jwt.Config{
Algorithm: "RS256",
PrivateKey: privateKey,
KeyID: "key-1",
})
// Export JWKS for other services
jwks, _ := jwtManager.JWKS()
HS256 (Simpler, single service)
jwtManager, _ := jwt.NewManager(jwt.Config{
Algorithm: "HS256",
Secret: []byte("your-secret-key"),
})
Middleware
Basic Auth Protection
mux.Handle("GET /protected", middleware.Auth(jwtManager, protectedHandler))
Role-Based Access Control
// Single role
mux.Handle("GET /admin", middleware.Auth(jwtManager,
middleware.RequireRole("admin")(adminHandler)))
// Multiple roles (allows any of them)
mux.Handle("POST /articles", middleware.Auth(jwtManager,
middleware.RequireRole("admin", "editor")(createArticleHandler)))
// Get user info from claims
func someHandler(w http.ResponseWriter, r *http.Request) {
claims, _ := middleware.GetClaims(r.Context())
// claims.UserID, claims.Username, claims.Role
}
Microservices with JWKS
For distributed systems, use JWKS to validate tokens without sharing private keys:
Auth Service:
// Expose JWKS endpoint
mux.HandleFunc("GET /.well-known/jwks.json", func(w http.ResponseWriter, r *http.Request) {
jwks, _ := jwtManager.JWKS()
w.Header().Set("Content-Type", "application/json")
w.Write(jwks)
})
Other Services:
import "github.com/ethan-mdev/central-auth/middleware"
jwksAuth, _ := middleware.NewJWKSAuth(
context.Background(),
"http://auth-service:8080/.well-known/jwks.json",
15*time.Minute, // refresh interval
)
mux.Handle("GET /api/data", jwksAuth.Auth(dataHandler))
Extending the User Schema
To add custom fields (e.g., profile_image, bio), skip CreateTable() and manage your own schema:
Custom Migration:
CREATE TABLE users (
id VARCHAR(36) PRIMARY KEY,
username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password TEXT NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'user',
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
-- Custom fields
profile_image TEXT DEFAULT NULL,
bio TEXT DEFAULT NULL
);
Extended Repository:
type ExtendedUserRepository struct {
storage.UserRepository
db *sql.DB
}
func NewExtendedUserRepository(repo storage.UserRepository, db *sql.DB) *ExtendedUserRepository {
return &ExtendedUserRepository{
UserRepository: repo,
db: db,
}
}
func (r *ExtendedUserRepository) GetUserWithProfile(userID string) (map[string]interface{}, error) {
var id, username, email, role string
var profileImage sql.NullString
err := r.db.QueryRow(`
SELECT id, username, email, role, profile_image
FROM users WHERE id = $1
`, userID).Scan(&id, &username, &email, &role, &profileImage)
if err != nil {
return nil, err
}
return map[string]interface{}{
"id": id,
"username": username,
"email": email,
"role": role,
"profile_image": profileImage.String,
}, nil
}
func (r *ExtendedUserRepository) UpdateProfileImage(userID, image string) error {
_, err := r.db.Exec("UPDATE users SET profile_image = $1 WHERE id = $2", image, userID)
return err
}
Usage:
// Initialize base repository (don't call CreateTable)
baseUsers := storage.NewPostgresUserRepository(db)
// Wrap with extended functionality
users := NewExtendedUserRepository(baseUsers, db)
// Use in auth handler
authHandler := &authhttp.AuthHandler{
Users: users, // ExtendedUserRepository satisfies UserRepository interface
// ...
}
This pattern keeps central-auth's core simple while allowing full customization.
API Endpoints
| Endpoint | Method | Description | Auth Required |
|---|---|---|---|
/register |
POST | Create new user | No |
/login |
POST | Authenticate user | No |
/refresh |
POST | Refresh access token | No |
/logout |
POST | Invalidate refresh token | No |
/change-password |
POST | Change user password | Yes |
Request/Response Examples
Register:
POST /register
{
"username": "john",
"email": "[email protected]",
"password": "securepassword"
}
Response: 201 Created
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "random-base64-string",
"expires_in": 900
}
Login:
POST /login
{
"username": "john",
"password": "securepassword"
}
Response: 200 OK
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "random-base64-string",
"expires_in": 900
}
Password Hashing
Uses Argon2id with configurable parameters:
hasher := password.Default()
// Hash password
hash, _ := hasher.Hash("mypassword")
// Verify password
valid := hasher.Verify("mypassword", hash) // true
Custom parameters:
hasher := &password.Hasher{
Memory: 128 * 1024, // 128 MB
Iterations: 4,
SaltLength: 16,
KeyLength: 32,
Parallelism: 4,
}
License
MIT License - see LICENSE for details.