auth

package
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2025 License: BSD-3-Clause Imports: 11 Imported by: 0

README

HTTP Authentication Package

The HTTP Authentication package provides comprehensive authentication utilities for Go web applications, including Basic Auth, Bearer token, and JWT authentication mechanisms. It is designed for production use, with flexible configuration, context integration, and custom error handling.

Features

  • Multiple authentication methods: Basic Auth (with SHA-256 hashed passwords), Bearer tokens, JWT
  • Context-based claims and user storage
  • Customizable error handling and optional authentication
  • Middleware for route protection and claims propagation
  • Type-safe context integration

Quick Start

package main

import (
    "net/http"
    "time"
    "github.com/alextanhongpin/core/http/auth"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/api/resource", protectedHandler)

    // JWT Authentication
    jwt := auth.NewJWT([]byte("your-secret-key"))
    token, _ := jwt.Sign(auth.Claims{Subject: "user@example.com"}, time.Hour)
    handler := auth.BearerHandler(mux, []byte("your-secret-key"))

    // Basic Authentication (store SHA-256 hashes)
    credentials := map[string]string{
        "admin": auth.HashPasswordSHA256("password123"),
        "user":  auth.HashPasswordSHA256("userpass"),
    }
    handler = auth.BasicHandler(handler, credentials)

    http.ListenAndServe(":8080", handler)
}

func protectedHandler(w http.ResponseWriter, r *http.Request) {
    claims, ok := auth.ClaimsContext.Value(r.Context())
    if !ok {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }
    fmt.Fprintf(w, "Hello, %s", claims.Subject)
}

API Reference

JWT Authentication
  • NewJWT(secret []byte) *JWT — Create a JWT handler
  • Sign(claims Claims, expiry time.Duration) (string, error) — Sign claims
  • Verify(token string) (Claims, error) — Verify JWT token
Bearer Token Middleware
  • BearerHandler(next http.Handler, secret []byte) http.Handler — Middleware for JWT Bearer tokens
  • RequireBearerHandler(next http.Handler) http.Handler — Enforce authentication on protected routes
Basic Authentication
  • BasicHandler(next http.Handler, credentials map[string]string) http.Handler — Middleware for Basic Auth (credentials must be SHA-256 hashes)
  • HashPasswordSHA256(password string) string — Utility to hash passwords for storage
Context Utilities
  • ClaimsContext — Type-safe context key for JWT claims
  • UsernameContext — Type-safe context key for Basic Auth username

Best Practices

  • Use context keys for claims and user data
  • Always validate and sanitize input
  • Store Basic Auth passwords as SHA-256 hashes using HashPasswordSHA256
  • Customize error handling for better UX
  • Prefer JWT for stateless APIs, Basic Auth for simple admin panels

See Also

Documentation

Overview

Package auth provides authentication and authorization utilities for HTTP handlers.

Package auth provides authentication and authorization utilities for HTTP handlers. It supports multiple authentication methods including Basic Auth, Bearer tokens, and JWT.

Index

Constants

View Source
const Bearer = "Bearer"

Bearer is the expected authorization scheme for bearer token authentication.

Variables

View Source
var (
	// ClaimsContext stores JWT claims in the request context.
	// Set by BearerHandler after successful token verification.
	// Use: claims, ok := auth.ClaimsContext.Value(ctx)
	ClaimsContext contextkey.Key[*Claims] = "claims"

	// UsernameContext stores the authenticated username in the request context.
	// Set by BasicHandler after successful basic authentication.
	// Use: username, ok := auth.UsernameContext.Value(ctx)
	UsernameContext contextkey.Key[string] = "username"

	// LoggerContext stores a structured logger in the request context.
	// Used by authentication middleware for consistent logging.
	// Use: logger, ok := auth.LoggerContext.Value(ctx)
	LoggerContext contextkey.Key[*slog.Logger] = "logger"
)

Context keys for storing authentication-related data in HTTP request contexts. These provide type-safe storage and retrieval of authentication information that can be used by downstream handlers and middleware.

View Source
var (
	// ErrClaimsInvalid indicates that the JWT claims are malformed or invalid.
	ErrClaimsInvalid = errors.New("auth: invalid claims")

	// ErrTokenInvalid indicates that the JWT token is malformed, has invalid signature, or other issues.
	ErrTokenInvalid = errors.New("auth: invalid token")

	// ErrTokenExpired indicates that the JWT token has passed its expiration time.
	ErrTokenExpired = errors.New("auth: token expired")

	// ErrNoSecret indicates that no signing secret was provided for JWT operations.
	ErrNoSecret = errors.New("auth: no secret provided")
)

JWT-related errors that can occur during token operations.

Functions

func BasicHandler

func BasicHandler(h http.Handler, credentials map[string]string) http.Handler

BasicHandler creates an HTTP Basic Authentication middleware using the provided username/password credentials. This is a convenience function that uses default configuration with realm "User Visible Realm".

The middleware performs constant-time password comparison to prevent timing attacks. Upon successful authentication, the username is stored in the request context and can be retrieved using auth.UsernameFromContext().

Example:

credentials := map[string]string{
    "admin": "secret123",
    "user":  "password456",
}
handler := auth.BasicHandler(myHandler, credentials)
http.ListenAndServe(":8080", handler)

func BasicHandlerWithConfig

func BasicHandlerWithConfig(h http.Handler, config BasicAuthConfig) http.Handler

BasicHandlerWithConfig creates an HTTP Basic Authentication middleware with customizable configuration. This allows control over the authentication realm and credential storage.

The middleware follows RFC 7617 and performs these steps:

  1. Extracts credentials from the Authorization header
  2. Validates credentials against the configured username/password map
  3. Uses constant-time comparison to prevent timing attacks
  4. Sets WWW-Authenticate header on authentication failures
  5. Stores authenticated username in request context on success

Example:

config := auth.BasicAuthConfig{
    Credentials: map[string]string{"admin": "secret"},
    Realm:       "Admin Panel",
}
handler := auth.BasicHandlerWithConfig(myHandler, config)

func BearerAuth

func BearerAuth(r *http.Request) (string, bool)

BearerAuth extracts and validates a bearer token from the Authorization header.

This function parses the Authorization header looking for a bearer token in the format "Bearer <token>". It returns the token and a boolean indicating whether a valid bearer token was found.

Parameters:

  • r: The HTTP request to extract the bearer token from

Returns:

  • The bearer token string (empty if not found or invalid)
  • A boolean indicating whether a valid bearer token was found

Example:

// Authorization: Bearer abc123xyz
token, ok := auth.BearerAuth(r)
if ok {
	// token = "abc123xyz"
	// Validate token...
}

The function validates that: - The Authorization header is present - The header starts with "Bearer " - There is a non-empty token after "Bearer "

func BearerHandler

func BearerHandler(h http.Handler, secret []byte) http.Handler

BearerHandler creates a middleware that validates JWT bearer tokens using the provided secret. This is a convenience function that creates optional authentication - requests without tokens are allowed to proceed. For required authentication, use BearerHandlerWithConfig with Optional: false.

Example:

handler := auth.BearerHandler(myHandler, []byte("my-secret-key"))
http.ListenAndServe(":8080", handler)

func BearerHandlerWithConfig

func BearerHandlerWithConfig(h http.Handler, config BearerAuthConfig) http.Handler

BearerHandlerWithConfig creates a bearer token authentication middleware with customizable configuration. This allows fine-grained control over authentication behavior including optional vs required authentication and custom error handling.

The middleware performs the following steps:

  1. Extracts the bearer token from the Authorization header
  2. If no token is present and authentication is optional, allows the request to proceed
  3. If no token is present and authentication is required, calls the error handler
  4. Verifies the JWT token signature and claims
  5. Adds verified claims to the request context for use by downstream handlers

Example:

config := auth.BearerAuthConfig{
    Secret:   []byte("my-secret-key"),
    Optional: false, // Require authentication
    OnError:  myCustomErrorHandler,
}
handler := auth.BearerHandlerWithConfig(myHandler, config)

func ComparePasswordHashSHA256

func ComparePasswordHashSHA256(hashedPassword, password string) bool

ComparePasswordHashSHA256 compares a SHA-256 hashed password with its possible plaintext equivalent.

func DefaultBearerErrorHandler

func DefaultBearerErrorHandler(w http.ResponseWriter, r *http.Request, err error)

DefaultBearerErrorHandler provides a standard RFC 6750 compliant error response for bearer token authentication failures. It sets the appropriate WWW-Authenticate header and logs the error if a logger is available in the request context.

func HashPasswordSHA256

func HashPasswordSHA256(password string) string

HashPasswordSHA256 hashes a plain-text password using SHA-256. Use this when storing credentials in production.

func RequireBearerHandler

func RequireBearerHandler(h http.Handler) http.Handler

RequireBearerHandler enforces that the request has valid JWT claims in its context. This middleware should be used after BearerHandler to ensure that only authenticated requests proceed. It checks for the presence of claims in the request context that were added by a previous authentication middleware.

This is useful for protecting routes that must have authentication, while allowing the initial bearer handler to be optional for some routes.

Example:

// Allow optional authentication
optionalAuth := auth.BearerHandler(myHandler, secret)

// Require authentication for specific routes
requiredAuth := auth.RequireBearerHandler(protectedHandler)

mux.Handle("/public", optionalAuth)
mux.Handle("/protected", chain.Handler(requiredAuth, optionalAuth))

func RequireBearerHandlerWithOptions

func RequireBearerHandlerWithOptions(h http.Handler, options RequireBearerOptions) http.Handler

RequireBearerHandlerWithOptions creates a customizable middleware that enforces the presence of valid JWT claims in the request context. This provides more granular control over error handling when authentication is missing.

Example:

options := auth.RequireBearerOptions{
    ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
        // Custom JSON error response
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusUnauthorized)
        json.NewEncoder(w).Encode(map[string]string{
            "error": "authentication_required",
            "message": "This endpoint requires valid authentication",
        })
    },
}
handler := auth.RequireBearerHandlerWithOptions(myHandler, options)

Types

type BasicAuthConfig

type BasicAuthConfig struct {
	// Credentials maps usernames to their corresponding passwords.
	// Passwords should be stored securely (hashed) in production environments.
	Credentials map[string]string

	// Realm is the authentication realm displayed to users in browser dialogs.
	// This helps users understand what they're authenticating for.
	// If empty, defaults to "Restricted".
	Realm string
}

BasicAuthConfig holds configuration for HTTP Basic Authentication middleware. Basic authentication uses username/password credentials encoded in the Authorization header as specified in RFC 7617.

type BearerAuthConfig

type BearerAuthConfig struct {
	// Secret is the HMAC secret key used to sign and verify JWT tokens.
	// This should be a cryptographically secure random string of at least 32 bytes.
	Secret []byte

	// Optional determines whether authentication is required for the protected route.
	// When true, requests without tokens are allowed to proceed.
	// When false, all requests must have valid bearer tokens.
	Optional bool

	// OnError is called when authentication fails or tokens are invalid.
	// This allows for custom error responses and logging behavior.
	// If nil, DefaultBearerErrorHandler will be used.
	OnError func(w http.ResponseWriter, r *http.Request, err error)
}

BearerAuthConfig holds configuration for bearer token authentication middleware. This configuration allows for flexible authentication behavior including optional authentication and custom error handling.

type Claims

type Claims = jwt.RegisteredClaims

Claims extends the standard JWT RegisteredClaims with commonly used fields. This type alias allows for easy extension while maintaining compatibility with the jwt-go library's standard claims.

type JWT

type JWT struct {
	Secret        []byte
	SigningMethod jwt.SigningMethod
	Issuer        string
	ExpiryLeeway  time.Duration
}

JWT manages the signing and verification of JWT tokens with configurable options. It provides a high-level interface for common JWT operations while allowing fine-grained control over token behavior.

func NewJWT

func NewJWT(secret []byte) *JWT

NewJWT creates a new JWT manager with default settings (HS256 signing method). This is a convenience function for simple use cases where default configuration is sufficient.

Example:

jwt := auth.NewJWT([]byte("my-secret-key"))
token, err := jwt.Sign(auth.Claims{Subject: "user123"}, time.Hour)

func NewJWTWithConfig

func NewJWTWithConfig(config JWTConfig) *JWT

NewJWTWithConfig creates a JWT manager with custom configuration options. This allows full control over signing methods, issuers, and validation behavior.

Example:

config := auth.JWTConfig{
    Secret:        []byte("my-secret-key"),
    SigningMethod: jwt.SigningMethodHS384,
    Issuer:        "my-service",
    ExpiryLeeway:  30 * time.Second,
}
jwt := auth.NewJWTWithConfig(config)

func (*JWT) Sign

func (j *JWT) Sign(claims Claims, ttl time.Duration) (string, error)

Sign creates a signed JWT token with the provided claims and time-to-live duration. It automatically sets standard claims like issued-at, not-before, and expiration times. The subject claim is required and an error is returned if it's empty.

Example:

claims := auth.Claims{
    Subject: "user123",
    Issuer:  "my-service", // Optional, will use JWT.Issuer if not set
}
token, err := jwt.Sign(claims, 24*time.Hour)

func (*JWT) SignWithCustomClaims

func (j *JWT) SignWithCustomClaims(claims jwt.Claims, ttl time.Duration) (string, error)

SignWithCustomClaims creates a signed JWT token with custom claims structure. This allows for non-standard claims beyond the registered claims set. Note: TTL parameter is not used here as custom claims should handle expiration.

Example:

type CustomClaims struct {
    jwt.RegisteredClaims
    Role        string   `json:"role"`
    Permissions []string `json:"permissions"`
}

claims := CustomClaims{
    RegisteredClaims: jwt.RegisteredClaims{
        Subject:   "user123",
        ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
    },
    Role:        "admin",
    Permissions: []string{"read", "write"},
}
token, err := jwt.SignWithCustomClaims(claims, time.Hour)

func (*JWT) Verify

func (j *JWT) Verify(bearerToken string) (*Claims, error)

Verify validates a JWT token string and returns the parsed registered claims. It performs comprehensive validation including:

  • Signature verification using the configured secret
  • Signing method validation to prevent algorithm confusion attacks
  • Expiration time validation with optional leeway for clock skew
  • Standard claims validation (nbf, iat, etc.)

Example:

claims, err := jwt.Verify(tokenString)
if err != nil {
    // Handle invalid token
    return
}
userID := claims.Subject

func (*JWT) VerifyWithCustomClaims

func (j *JWT) VerifyWithCustomClaims(bearerToken string, claims jwt.Claims) error

VerifyWithCustomClaims validates a JWT token with custom claims structure. Unlike Verify(), this method allows you to parse tokens with non-standard claims while still performing all security validations. The claims parameter should be a pointer to your custom claims struct.

Example:

type CustomClaims struct {
    jwt.RegisteredClaims
    Role        string   `json:"role"`
    Permissions []string `json:"permissions"`
}

var claims CustomClaims
err := jwt.VerifyWithCustomClaims(tokenString, &claims)
if err != nil {
    // Handle invalid token
    return
}
userRole := claims.Role

type JWTConfig

type JWTConfig struct {
	// Secret is the HMAC secret key used to sign and verify JWT tokens.
	// For HMAC algorithms (HS256, HS384, HS512), this should be a cryptographically
	// secure random string of appropriate length (32+ bytes recommended).
	Secret []byte

	// SigningMethod specifies the algorithm used to sign tokens.
	// Common values: jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512
	// Default: HS256 if not specified.
	SigningMethod jwt.SigningMethod

	// Issuer is the 'iss' (issuer) claim added to generated tokens.
	// This identifies the service that issued the token and can be used for validation.
	Issuer string

	// ExpiryLeeway is additional time added to token expiration during validation.
	// This accounts for clock skew between systems. Common values: 30s-5m.
	ExpiryLeeway time.Duration
}

JWTConfig holds configuration options for JWT token management. This allows customization of signing methods, issuers, and validation behavior.

type RequireBearerOptions

type RequireBearerOptions struct {
	// ErrorHandler is called when authentication is missing or invalid.
	// If nil, DefaultBearerErrorHandler will be used.
	ErrorHandler func(w http.ResponseWriter, r *http.Request, err error)
}

RequireBearerOptions configures the behavior of the require bearer handler.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL