- Updated JWT README.md with proper format and version number - Updated HWS README.md and created comprehensive doc.go - Updated HWSAuth README.md and doc.go with proper environment variable documentation - All documentation now follows GOLIB rules format 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
213 lines
6.6 KiB
Go
213 lines
6.6 KiB
Go
// Package hwsauth provides JWT-based authentication middleware for the hws web framework.
|
|
//
|
|
// # Overview
|
|
//
|
|
// hwsauth integrates with the hws web server to provide secure, stateless authentication
|
|
// using JSON Web Tokens (JWT). It supports both access and refresh tokens, automatic
|
|
// token rotation, and flexible transaction handling compatible with any database or ORM.
|
|
//
|
|
// # Key Features
|
|
//
|
|
// - JWT-based authentication with access and refresh tokens
|
|
// - Automatic token rotation and refresh
|
|
// - Generic over user model and transaction types
|
|
// - ORM-agnostic transaction handling
|
|
// - Environment variable configuration
|
|
// - Middleware for protecting routes
|
|
// - Context-based user retrieval
|
|
// - Optional SSL cookie security
|
|
//
|
|
// # Quick Start
|
|
//
|
|
// First, define your user model:
|
|
//
|
|
// type User struct {
|
|
// UserID int
|
|
// Username string
|
|
// Email string
|
|
// }
|
|
//
|
|
// func (u User) ID() int {
|
|
// return u.UserID
|
|
// }
|
|
//
|
|
// Configure the authenticator using environment variables or programmatically:
|
|
//
|
|
// // Option 1: Load from environment variables
|
|
// cfg, err := hwsauth.ConfigFromEnv()
|
|
// if err != nil {
|
|
// log.Fatal(err)
|
|
// }
|
|
//
|
|
// // Option 2: Create config manually
|
|
// cfg := &hwsauth.Config{
|
|
// SSL: true,
|
|
// TrustedHost: "https://example.com",
|
|
// SecretKey: "your-secret-key",
|
|
// AccessTokenExpiry: 5, // 5 minutes
|
|
// RefreshTokenExpiry: 1440, // 1 day
|
|
// TokenFreshTime: 5, // 5 minutes
|
|
// LandingPage: "/dashboard",
|
|
// }
|
|
//
|
|
// Create the authenticator:
|
|
//
|
|
// // Define how to begin transactions
|
|
// beginTx := func(ctx context.Context) (hwsauth.DBTransaction, error) {
|
|
// return db.BeginTx(ctx, nil)
|
|
// }
|
|
//
|
|
// // Define how to load users from the database
|
|
// loadUser := func(ctx context.Context, tx *sql.Tx, id int) (User, error) {
|
|
// var user User
|
|
// err := tx.QueryRowContext(ctx, "SELECT id, username, email FROM users WHERE id = ?", id).
|
|
// Scan(&user.UserID, &user.Username, &user.Email)
|
|
// return user, err
|
|
// }
|
|
//
|
|
// // Create the authenticator
|
|
// auth, err := hwsauth.NewAuthenticator[User, *sql.Tx](
|
|
// cfg,
|
|
// loadUser,
|
|
// server,
|
|
// beginTx,
|
|
// logger,
|
|
// errorPage,
|
|
// )
|
|
// if err != nil {
|
|
// log.Fatal(err)
|
|
// }
|
|
//
|
|
// # Middleware
|
|
//
|
|
// Use the Authenticate middleware to protect routes:
|
|
//
|
|
// // Apply to all routes
|
|
// server.AddMiddleware(auth.Authenticate())
|
|
//
|
|
// // Ignore specific paths
|
|
// auth.IgnorePaths("/login", "/register", "/public")
|
|
//
|
|
// Use route guards for specific protection requirements:
|
|
//
|
|
// // LoginReq: Requires user to be authenticated
|
|
// protectedHandler := auth.LoginReq(myHandler)
|
|
//
|
|
// // LogoutReq: Redirects authenticated users (for login/register pages)
|
|
// loginHandler := auth.LogoutReq(loginPageHandler)
|
|
//
|
|
// // FreshReq: Requires fresh authentication (for sensitive operations)
|
|
// changePasswordHandler := auth.FreshReq(changePasswordHandler)
|
|
//
|
|
// # Login and Logout
|
|
//
|
|
// To log a user in:
|
|
//
|
|
// func loginHandler(w http.ResponseWriter, r *http.Request) {
|
|
// // Validate credentials...
|
|
// user := getUserFromDatabase(username)
|
|
//
|
|
// // Log the user in (sets JWT cookies)
|
|
// err := auth.Login(w, r, user, rememberMe)
|
|
// if err != nil {
|
|
// // Handle error
|
|
// }
|
|
//
|
|
// http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
|
|
// }
|
|
//
|
|
// To log a user out:
|
|
//
|
|
// func logoutHandler(w http.ResponseWriter, r *http.Request) {
|
|
// tx, _ := db.BeginTx(r.Context(), nil)
|
|
// defer tx.Rollback()
|
|
//
|
|
// err := auth.Logout(tx, w, r)
|
|
// if err != nil {
|
|
// // Handle error
|
|
// }
|
|
//
|
|
// tx.Commit()
|
|
// http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
// }
|
|
//
|
|
// # Retrieving the Current User
|
|
//
|
|
// Access the authenticated user from the request context:
|
|
//
|
|
// func dashboardHandler(w http.ResponseWriter, r *http.Request) {
|
|
// user := auth.CurrentModel(r.Context())
|
|
// if user.ID() == 0 {
|
|
// // User not authenticated
|
|
// return
|
|
// }
|
|
//
|
|
// fmt.Fprintf(w, "Welcome, %s!", user.Username)
|
|
// }
|
|
//
|
|
// # ORM Support
|
|
//
|
|
// hwsauth works with any ORM that implements the DBTransaction interface.
|
|
//
|
|
// GORM Example:
|
|
//
|
|
// beginTx := func(ctx context.Context) (hwsauth.DBTransaction, error) {
|
|
// return gormDB.WithContext(ctx).Begin().Statement.ConnPool.(*sql.Tx), nil
|
|
// }
|
|
//
|
|
// loadUser := func(ctx context.Context, tx *gorm.DB, id int) (User, error) {
|
|
// var user User
|
|
// err := tx.First(&user, id).Error
|
|
// return user, err
|
|
// }
|
|
//
|
|
// auth, err := hwsauth.NewAuthenticator[User, *gorm.DB](...)
|
|
//
|
|
// Bun Example:
|
|
//
|
|
// beginTx := func(ctx context.Context) (hwsauth.DBTransaction, error) {
|
|
// return bunDB.BeginTx(ctx, nil)
|
|
// }
|
|
//
|
|
// loadUser := func(ctx context.Context, tx bun.Tx, id int) (User, error) {
|
|
// var user User
|
|
// err := tx.NewSelect().Model(&user).Where("id = ?", id).Scan(ctx)
|
|
// return user, err
|
|
// }
|
|
//
|
|
// auth, err := hwsauth.NewAuthenticator[User, bun.Tx](...)
|
|
//
|
|
// # Environment Variables
|
|
//
|
|
// The following environment variables are supported when using ConfigFromEnv:
|
|
//
|
|
// - HWSAUTH_SSL: Enable SSL secure cookies (default: false)
|
|
// - HWSAUTH_TRUSTED_HOST: Full server address for SSL (required if SSL is true)
|
|
// - HWSAUTH_SECRET_KEY: Secret key for signing JWT tokens (required)
|
|
// - HWSAUTH_ACCESS_TOKEN_EXPIRY: Access token expiry in minutes (default: 5)
|
|
// - HWSAUTH_REFRESH_TOKEN_EXPIRY: Refresh token expiry in minutes (default: 1440)
|
|
// - HWSAUTH_TOKEN_FRESH_TIME: Token fresh time in minutes (default: 5)
|
|
// - HWSAUTH_LANDING_PAGE: Redirect destination for authenticated users (default: "/profile")
|
|
// - HWSAUTH_DATABASE_TYPE: Database type - postgres, mysql, sqlite, mariadb (default: "postgres")
|
|
// - HWSAUTH_DATABASE_VERSION: Database version string (default: "15")
|
|
// - HWSAUTH_JWT_TABLE_NAME: Custom JWT blacklist table name (default: "jwtblacklist")
|
|
//
|
|
// # Security Considerations
|
|
//
|
|
// - Always use SSL in production (set HWSAUTH_SSL=true)
|
|
// - Use strong, randomly generated secret keys
|
|
// - Set appropriate token expiry times based on your security requirements
|
|
// - Use FreshReq middleware for sensitive operations (password changes, etc.)
|
|
// - Store refresh tokens securely in HTTP-only cookies
|
|
//
|
|
// # Type Parameters
|
|
//
|
|
// hwsauth uses Go generics for type safety:
|
|
//
|
|
// - T Model: Your user model type (must implement the Model interface)
|
|
// - TX DBTransaction: Your transaction type (must implement DBTransaction interface)
|
|
//
|
|
// This allows compile-time type checking and eliminates the need for type assertions
|
|
// when working with your user models.
|
|
package hwsauth
|