securelink

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Mar 11, 2026 License: MIT Imports: 5 Imported by: 2

Documentation

Overview

Package securelink provides secure, expiring URL generation and validation using JWT tokens. It enables the creation of time-limited, signed URLs that can be safely distributed and validated.

Key Features

  • Thread-safe, stateless design for concurrent access
  • Configurable JWT signing algorithms (HS256, HS384, HS512)
  • Automatic signing key length validation for security
  • Flexible URL generation (path-based or query parameter)
  • Support for custom payload data in tokens
  • Backward compatibility with legacy API

Basic Usage

The package offers two ways to create a manager:

## 1. Config Struct (Recommended for new code)

cfg := securelink.Config{
	SigningKey:    "a-very-secure-key-of-at-least-32-bytes-long", // Min 32 bytes for HS256
	Expiration:    1 * time.Hour,                                 // Token lifetime
	BaseURL:       "https://example.com",                         // Base URL for links
	QueryKey:      "token",                                       // Query parameter name (if AsQuery=true)
	Routes:        map[string]string{"activate": "/activate"},    // Route mappings
	AsQuery:       false,                                         // false=path-based, true=query-based URLs
	SigningMethod: jwt.SigningMethodHS256,                        // Optional: defaults to HS256
}
manager, err := securelink.NewManager(cfg)
if err != nil {
	log.Fatal(err)
}

// Generate secure link with payload
payload := securelink.Payload{"user_id": "123", "action": "activate"}
link, err := manager.Generate("activate", payload)
if err != nil {
	log.Fatal(err)
}
// Result: https://example.com/activate/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

// Validate token and extract payload
validatedPayload, err := manager.Validate(token)
if err != nil {
	log.Fatal(err)
}
userID := validatedPayload["user_id"] // "123"

## 2. Configurator Interface (For generated configurations)

manager, err := securelink.NewManagerFromConfig(cfg) // where cfg implements Configurator

Security Best Practices

  • Use strong signing keys with appropriate lengths:
  • HS256: minimum 32 bytes (256 bits)
  • HS384: minimum 48 bytes (384 bits)
  • HS512: minimum 64 bytes (512 bits)
  • Store signing keys securely (environment variables, key management systems)
  • Use appropriate token expiration times (shorter for sensitive operations)
  • Always validate tokens before processing requests
  • Consider using HS384 or HS512 for higher security requirements

Migration from Legacy API

The legacy API used a stateful design with method chaining:

// OLD (deprecated)
link, err := manager.WithData("user_id", 123).Generate("activate")

The new API uses a stateless design with payload parameters:

// NEW (recommended)
payload := securelink.Payload{"user_id": 123}
link, err := manager.Generate("activate", payload)

The old API is maintained for backward compatibility through the Configurator interface.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Generate

func Generate(data map[string]any, signingKey string, expiration time.Duration, signingMethod jwt.SigningMethod) (string, error)

Generate creates a JWT token containing the provided data with the specified expiration. This is a low-level function used internally by the Manager. For most use cases, prefer using Manager.Generate() which handles URL construction automatically.

The function creates a JWT token with:

  • "dat" claim containing the provided data
  • "iat" (issued at) claim with current timestamp
  • "exp" (expiration) claim with expiration timestamp

Parameters:

data: Custom data to embed in the token (can be nil for empty payload)
signingKey: Secret key for JWT signing (length must match algorithm requirements)
expiration: How long the token should remain valid
signingMethod: JWT signing algorithm (HS256, HS384, or HS512)

Returns:

string: Signed JWT token
error: Token generation failures (invalid key, signing errors)

Security note: This function does not validate key length. Use NewManager for automatic key validation.

func Validate

func Validate(tokenString, signingKey string, signingMethod jwt.SigningMethod) (map[string]any, error)

Validate verifies a JWT token and extracts the embedded data payload. This is a low-level function used internally by the Manager. For most use cases, prefer using Manager.Validate() which uses the configured signing method automatically.

The function performs comprehensive validation:

  • Verifies JWT signature using the provided key and method
  • Checks token expiration (rejects expired tokens)
  • Validates token structure and claims
  • Extracts the "dat" claim containing custom payload data

Parameters:

tokenString: JWT token to validate
signingKey: Secret key used for signature verification (must match generation key)
signingMethod: Expected JWT algorithm (must match the algorithm used for generation)

Returns:

map[string]any: Custom data that was embedded in the token during generation
error: Invalid signature, expired token, algorithm mismatch, or malformed token

Security notes:

  • Tokens signed with different algorithms are rejected
  • Expired tokens are automatically rejected
  • Error messages are generic to prevent information leakage

Types

type Config

type Config struct {
	SigningKey    string            // Secret key for JWT signing (length validated based on algorithm)
	Expiration    time.Duration     // Token lifetime (e.g., 1*time.Hour, 30*time.Minute)
	BaseURL       string            // Base URL for generated links (e.g., "https://api.example.com")
	QueryKey      string            // Query parameter name when AsQuery=true (e.g., "token", "auth")
	Routes        map[string]string // Map of route names to URL paths (e.g., {"reset": "/auth/reset"})
	AsQuery       bool              // false=path URLs (/path/{token}), true=query URLs (/path?key={token})
	SigningMethod jwt.SigningMethod // JWT algorithm (HS256, HS384, HS512). Defaults to HS256 if nil
}

Config is a struct-based configuration for simplified direct instantiation. This is the preferred way to configure the securelink manager for new code.

Example:

cfg := securelink.Config{
	SigningKey:    "your-32-byte-or-longer-secret-key-here",
	Expiration:    1 * time.Hour,
	BaseURL:       "https://api.example.com",
	QueryKey:      "token",
	Routes:        map[string]string{"reset": "/auth/reset", "activate": "/auth/activate"},
	AsQuery:       false, // Use path-based URLs: /auth/activate/{token}
	SigningMethod: jwt.SigningMethodHS256, // Optional: defaults to HS256
}

func (Config) GetAsQuery

func (c Config) GetAsQuery() bool

GetAsQuery implements the Configurator interface for the Config struct.

func (Config) GetBaseURL

func (c Config) GetBaseURL() string

GetBaseURL implements the Configurator interface for the Config struct.

func (Config) GetExpiration

func (c Config) GetExpiration() time.Duration

GetExpiration implements the Configurator interface for the Config struct.

func (Config) GetQueryKey

func (c Config) GetQueryKey() string

GetQueryKey implements the Configurator interface for the Config struct.

func (Config) GetRoutes

func (c Config) GetRoutes() map[string]string

GetRoutes implements the Configurator interface for the Config struct.

func (Config) GetSigningKey

func (c Config) GetSigningKey() string

GetSigningKey implements the Configurator interface for the Config struct.

type Configurator

type Configurator interface {
	GetSigningKey() string        // The secret key used for JWT signing (min 32 bytes for HS256)
	GetExpiration() time.Duration // How long tokens remain valid
	GetBaseURL() string           // Base URL for generated links
	GetQueryKey() string          // Query parameter name when GetAsQuery() is true
	GetRoutes() map[string]string // Map of route names to URL paths
	GetAsQuery() bool             // Whether to embed token as query param (true) or path segment (false)
}

Configurator holds configuration options for backward compatibility with the legacy API. This interface is maintained for existing code that implements these methods. For new code, prefer using the Config struct directly with NewManager.

type Manager

type Manager interface {
	// Generate creates a secure link for the specified route with optional payload data.
	// Multiple payloads can be provided and will be merged (later payloads override earlier ones).
	//
	// Parameters:
	//   route: Must match a key in the Routes configuration map
	//   payloads: Zero or more Payload maps to embed in the token
	//
	// Returns:
	//   string: Complete URL with embedded token
	//   error: Configuration errors, unknown routes, or token generation failures
	//
	// Example:
	//   payload := securelink.Payload{"user_id": "123"}
	//   link, err := manager.Generate("activate", payload)
	//   // Returns: "https://example.com/activate/eyJhbGciOi..."
	Generate(route string, payloads ...Payload) (string, error)

	// Validate verifies a JWT token and returns the embedded payload data.
	// Validates signature, expiration, and token structure.
	//
	// Parameters:
	//   token: JWT token string to validate
	//
	// Returns:
	//   map[string]any: Payload data that was embedded in the token
	//   error: Invalid signature, expired token, or malformed token
	//
	// Example:
	//   payload, err := manager.Validate("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
	//   if err == nil {
	//       userID := payload["user_id"].(string)
	//   }
	Validate(token string) (map[string]any, error)

	// GetAndValidate extracts a token using the provided function and validates it.
	// This is a convenience method for web handlers that need to extract tokens
	// from HTTP requests.
	//
	// Parameters:
	//   fn: Function that extracts the token (e.g., from query params, headers, etc.)
	//
	// Returns:
	//   Payload: Validated payload data from the token
	//   error: Token extraction, validation, or parsing errors
	//
	// Example:
	//   payload, err := manager.GetAndValidate(func(key string) string {
	//       return r.URL.Query().Get(key) // Extract from HTTP query params
	//   })
	GetAndValidate(fn func(string) string) (Payload, error)

	// GetExpiration returns the token lifetime configured for this manager.
	// This can be useful for displaying expiration information to users.
	//
	// Returns:
	//   time.Duration: How long tokens remain valid
	GetExpiration() time.Duration
}

Manager provides secure link generation and validation capabilities. Implementations are thread-safe and can be used concurrently from multiple goroutines.

The Manager interface represents the core functionality for creating time-limited, cryptographically signed URLs that can carry custom payload data.

func NewManager

func NewManager(cfg Config) (Manager, error)

NewManager creates a new secure link manager from a Config struct. This is the recommended constructor for new code as it provides direct configuration without requiring interface implementations.

The function performs comprehensive validation:

  • Validates BaseURL format
  • Ensures signing key meets minimum length requirements for the chosen algorithm
  • Sets up proper JWT signing method (defaults to HS256 if not specified)

Parameters:

cfg: Configuration struct with all required settings

Returns:

Manager: Thread-safe manager instance ready for use
error: Configuration validation errors (invalid URL, weak signing key, etc.)

Example:

cfg := securelink.Config{
    SigningKey: "your-super-secret-key-here-32-bytes",
    Expiration: 2 * time.Hour,
    BaseURL:    "https://api.myapp.com",
    Routes:     map[string]string{"verify": "/auth/verify"},
}
manager, err := securelink.NewManager(cfg)

func NewManagerFromConfig

func NewManagerFromConfig(cfg Configurator) (Manager, error)

NewManagerFromConfig creates a manager instance using the Configurator interface. This constructor follows the configurator pattern used throughout the application and allows for flexible configuration implementations that can be generated automatically.

Parameters:

cfg: Any type that implements the Configurator interface

Returns:

Manager: Thread-safe manager instance
error: Configuration validation errors

Example:

type MyConfig struct { /* implement Configurator methods */ }
config := &MyConfig{...}
manager, err := securelink.NewManagerFromConfig(config)

type Payload

type Payload map[string]any

Payload represents custom data that can be embedded in secure links. It is used both for input when generating tokens and output when validating them.

Example usage:

// Creating payload for token generation
payload := securelink.Payload{
	"user_id":    "12345",
	"action":     "password_reset",
	"expires_at": time.Now().Add(1*time.Hour).Unix(),
}

// Using payload from token validation
validatedPayload, err := manager.Validate(token)
if err == nil {
	userID := validatedPayload["user_id"].(string)
}

func (Payload) GetString

func (p Payload) GetString(key string) (val string, err error)

GetString safely extracts a string value from the payload. Returns an error if the key doesn't exist or the value is not a string.

Example:

payload := securelink.Payload{"user_id": "123", "count": 42}
userID, err := payload.GetString("user_id") // Returns "123", nil
count, err := payload.GetString("count")    // Returns "", error (not a string)

Jump to

Keyboard shortcuts

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