tokenmanager

package module
v0.0.0-...-203c3db Latest Latest
Warning

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

Go to latest
Published: Apr 16, 2026 License: MIT Imports: 11 Imported by: 1

README

TokenManager Package

Go Reference Go Version Go Report Card License

The tokenmanager package provides a flexible and generic implementation for token management. It allows you to create, validate, and revoke signed tokens using an abstract storage interface (ITokenStore). This package supports additional data (using Go generics) to be stored along with token metadata, such as user ID, issue time, expiry, and token type.

Features

  • Abstract Storage Interface (ITokenStore):
    Supports any storage implementation (e.g., Redis, Memcached). An in‑memory implementation (MemoryTokenStore) is provided for testing and simple use cases.

  • Signed Tokens:
    Tokens are signed using HMAC-SHA256 to ensure integrity and to verify that they were issued by your backend.

  • Generic Additional Data:
    Token data can include additional custom information using Go generics.

  • Token Types:
    Supports different token types (e.g., access and refresh tokens).

AI Agent Skills

This repository includes AI agent skills with documentation and usage examples for all packages. Install them with the skills CLI:

go install github.com/sxwebdev/skills/cmd/skills@latest
skills init
skills repo add sxwebdev/tokenmanager

Installation

go get github.com/sxwebdev/tokenmanager@latest

Usage

Below is a simple example demonstrating how to create a TokenManager with in‑memory storage and how to create, validate, and revoke tokens.

package main

import (
  "fmt"
  "time"

  "github.com/sxwebdev/tokenmanager"
)

func main() {
  // Initialize the in-memory token store.
  store := tokenmanager.NewMemoryTokenStore()

  ctx := context.Background()

  // Create a new TokenManager with a secret key.
  // Here, we use `any` for the additional data type, but you can replace it with a custom type.
  manager := tokenmanager.NewTokenManager[map[string]any](store, "your_very_secret_key", time.Minute * 15)

  userAdditionalData := map[string]any{
    "username": "John Doe",
    "isActive": true,
    "age": 38,
  }

  // Create an access token with additional data (can be nil or a custom type).
  token, err := manager.CreateToken(ctx, "user123", userAdditionalData, tokenmanager.AccessTokenType)
  if err != nil {
    panic(err)
  }
  fmt.Println("Created token:", token)

  // Validate the token.
  data, valid := manager.ValidateToken(ctx, token, tokenmanager.AccessTokenType)
  if !valid {
    fmt.Println("Token is invalid or expired")
    return
  }
  fmt.Printf("Token is valid. UserID: %s, IssuedAt: %s, Expires: %s\n", data.UserID, data.IssuedAt, data.Expiry)

  // Revoke the token.
  manager.RevokeToken(token)

  // Try validating the revoked token.
  _, valid = manager.ValidateToken(ctx, token, tokenmanager.AccessTokenType)
  if !valid {
    fmt.Println("Token has been successfully revoked.")
  } else {
    fmt.Println("Token is still valid.")
  }
}

Running Tests

The package includes tests for token creation, validation, expiration, and revocation. To run the tests, execute the following command in your terminal:

go test -v ./...

License

This package is provided as-is without any warranty. Use it at your own risk.

---

This README now reflects the module path `github.com/sxwebdev/tokenmanager`.

Documentation

Overview

Package tokenmanager provides a secure, flexible, and type-safe token management system for Go applications. It supports creating, validating, revoking, and updating tokens with customizable additional data using Go generics.

Overview

The tokenmanager package implements a token-based authentication system with the following key features:

  • HMAC-SHA256 signed tokens for cryptographic security
  • Generic type support for custom token payload data
  • Pluggable storage backend via the ITokenStore interface
  • Built-in in-memory storage implementation (MemoryTokenStore)
  • Support for access and refresh token types
  • Automatic token expiration handling
  • Thread-safe operations

Token Format

Tokens are generated as signed strings in the format:

{payload}.{signature}

Where:

  • payload: 128-character hex string (64 random bytes from crypto/rand)
  • signature: 64-character hex string (HMAC-SHA256 of the payload bytes)

This format provides 512 bits of entropy in the payload, making tokens practically impossible to guess or brute-force.

Architecture

The package follows a clean separation of concerns:

┌─────────────────────────────────────────────────────────────┐
│                    Manager[TAdditionalData]                 │
│  - CreateToken()    - ValidateToken()                       │
│  - RevokeToken()    - UpdateAdditionalData()                │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                      ITokenStore                            │
│  Interface for pluggable storage backends                   │
└─────────────────────────────────────────────────────────────┘
          │                                    │
          ▼                                    ▼
┌──────────────────────┐          ┌──────────────────────────┐
│  MemoryTokenStore    │          │  Custom Implementation   │
│  (built-in)          │          │  (Redis, PostgreSQL...)  │
└──────────────────────┘          └──────────────────────────┘

Basic Usage

Creating a token manager with custom additional data:

// Define your custom data structure
type UserClaims struct {
    Role        string   `json:"role"`
    Permissions []string `json:"permissions"`
}

// Create storage and manager
store := tokenmanager.NewMemoryTokenStore()
manager := tokenmanager.New[UserClaims](
    store,
    "your-secret-key-here",
    15*time.Minute,
)

// Create a token
ctx := context.Background()
token, data, err := manager.CreateToken(
    ctx,
    "user-123",
    UserClaims{Role: "admin", Permissions: []string{"read", "write"}},
    tokenmanager.AccessTokenType,
)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Token: %s\n", token)
fmt.Printf("Expires: %s\n", data.Expiry)

Token Validation

Validating a token and retrieving its data:

data, valid := manager.ValidateToken(ctx, token, tokenmanager.AccessTokenType)
if !valid {
    // Token is invalid, expired, revoked, or wrong type
    return errors.New("invalid token")
}

fmt.Printf("User ID: %s\n", data.UserID)
fmt.Printf("Role: %s\n", data.AdditionalData.Role)

Validation checks performed:

  • Token format (must contain exactly one dot separator)
  • Payload is valid hex encoding
  • HMAC-SHA256 signature matches (constant-time comparison)
  • Token exists in storage
  • Token type matches expected type
  • Token has not expired

Token Revocation

Revoking a token (e.g., on logout):

err := manager.RevokeToken(ctx, token)
if err != nil {
    log.Printf("Failed to revoke token: %v", err)
}

After revocation, the token will fail validation even if it hasn't expired.

Updating Token Data

Updating the additional data of an existing token:

newClaims := UserClaims{
    Role:        "superadmin",
    Permissions: []string{"read", "write", "delete", "admin"},
}
err := manager.UpdateAdditionalData(ctx, token, newClaims)
if err != nil {
    log.Printf("Failed to update token: %v", err)
}

This is useful for updating user permissions without requiring re-authentication.

Token Types

The package provides two predefined token types:

Token type validation prevents type confusion attacks where an attacker might try to use a refresh token as an access token or vice versa.

Example of implementing a refresh token flow:

// Create separate managers for access and refresh tokens
accessManager := tokenmanager.New[UserClaims](store, secret, 15*time.Minute)
refreshManager := tokenmanager.New[UserClaims](store, secret, 7*24*time.Hour)

// Issue both tokens on login
accessToken, _, _ := accessManager.CreateToken(ctx, userID, claims, tokenmanager.AccessTokenType)
refreshToken, _, _ := refreshManager.CreateToken(ctx, userID, claims, tokenmanager.RefreshTokenType)

// Refresh endpoint
func RefreshTokens(ctx context.Context, refreshToken string) (string, string, error) {
    data, valid := refreshManager.ValidateToken(ctx, refreshToken, tokenmanager.RefreshTokenType)
    if !valid {
        return "", "", errors.New("invalid refresh token")
    }

    // Revoke old refresh token
    refreshManager.RevokeToken(ctx, refreshToken)

    // Issue new tokens
    newAccess, _, _ := accessManager.CreateToken(ctx, data.UserID, data.AdditionalData, tokenmanager.AccessTokenType)
    newRefresh, _, _ := refreshManager.CreateToken(ctx, data.UserID, data.AdditionalData, tokenmanager.RefreshTokenType)

    return newAccess, newRefresh, nil
}

Custom Storage Backend

Implement the ITokenStore interface to use a custom storage backend:

type RedisTokenStore struct {
    client *redis.Client
}

func (r *RedisTokenStore) Get(ctx context.Context, key []byte) ([]byte, error) {
    return r.client.Get(ctx, string(key)).Bytes()
}

func (r *RedisTokenStore) Set(ctx context.Context, key, value []byte, exp time.Duration) error {
    return r.client.Set(ctx, string(key), value, exp).Err()
}

func (r *RedisTokenStore) Delete(ctx context.Context, key []byte) error {
    return r.client.Del(ctx, string(key)).Err()
}

// ... implement remaining interface methods

// Use with manager
redisStore := &RedisTokenStore{client: redisClient}
manager := tokenmanager.New[UserClaims](redisStore, secret, duration)

Security Considerations

The package implements several security best practices:

Cryptographic Security:

  • Uses crypto/rand for generating random payloads (cryptographically secure)
  • HMAC-SHA256 for token signing (industry standard)
  • Constant-time signature comparison via hmac.Equal (prevents timing attacks)
  • 512-bit entropy in token payloads (impossible to brute-force)

Token Validation:

  • Fail-secure design: returns false for ANY validation failure
  • Token type validation prevents type confusion attacks
  • Automatic expiration checking
  • Storage-backed validation (revoked tokens fail immediately)

Recommendations:

  • Use a strong, randomly generated secret key (at least 32 bytes)
  • Store the secret key securely (environment variable, secrets manager)
  • Use short expiration times for access tokens (5-15 minutes)
  • Implement token refresh for long sessions
  • Use HTTPS to prevent token interception
  • Consider implementing rate limiting for token creation

Thread Safety

All operations in Manager are thread-safe. The built-in MemoryTokenStore uses sync.RWMutex for safe concurrent access. Custom storage implementations should also ensure thread safety.

Memory Management

The MemoryTokenStore includes an automatic cleanup goroutine that runs every minute to remove expired tokens. This prevents memory leaks from accumulated expired tokens.

For production environments with high token volumes, consider using a dedicated storage backend like Redis, which handles expiration natively and provides persistence.

Error Handling

The package defines the following sentinel errors:

The [ValidateToken] method does not return errors; instead, it returns a boolean indicating validity. This fail-secure design ensures that any unexpected condition results in token rejection.

Data Storage Format

Token data is stored as JSON with the following structure:

{
    "user_id": "user-123",
    "issued_at": "2024-01-15T10:30:00Z",
    "expiry": "2024-01-15T10:45:00Z",
    "token_type": "access_token",
    "additional_data": { ... }
}

Storage keys use the prefix "tokenmanager:" followed by the token payload:

tokenmanager:{payload_hex}

Performance

The package is designed for high performance:

  • O(1) token validation (single storage lookup after signature check)
  • Minimal allocations during token operations
  • Read-write mutex in MemoryTokenStore optimizes concurrent reads

For benchmarks, run:

go test -bench=. -benchmem

Installation

go get github.com/sxwebdev/tokenmanager@latest

Requirements

  • Go 1.25.1 or later (for generics support)
  • No external dependencies (standard library only)

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrKeyNotFound = errors.New("key not found")
	ErrKeyExpired  = errors.New("key expired")
)

Functions

This section is empty.

Types

type Data

type Data[TAdditionalData any] struct {
	UserID         string          `json:"user_id"`
	IssuedAt       time.Time       `json:"issued_at"`
	Expiry         time.Time       `json:"expiry"`
	TokenType      TokenType       `json:"token_type"`
	AdditionalData TAdditionalData `json:"additional_data"`
}

Data stores the token-related data.

type ITokenStore

type ITokenStore interface {
	Get(ctx context.Context, key []byte) ([]byte, error)
	Set(ctx context.Context, key []byte, value []byte, expiration time.Duration) error
	Delete(ctx context.Context, key []byte) error
	Keys(ctx context.Context, prefix []byte) ([]string, error)
	KeysAndValues(ctx context.Context, prefix []byte) (map[string][]byte, error)
	GetFromJSON(ctx context.Context, key []byte, dst any) error
	SetJSON(ctx context.Context, key []byte, value any, expiration time.Duration) error
	Exists(ctx context.Context, key []byte) (bool, error)
}

ITokenStore is the interface for an abstract token storage. In a real system, this could be Redis, Memcached, etc.

type Manager

type Manager[TAdditionalData any] struct {
	// contains filtered or unexported fields
}

Manager uses an ITokenStore for managing token state. It can create, validate, and revoke tokens.

func New

func New[TAdditionalData any](store ITokenStore, secretKey string, tokenDuration time.Duration) *Manager[TAdditionalData]

New returns a new token manager with the provided storage and secret key.

func (*Manager[TAdditionalData]) CreateToken

func (tm *Manager[TAdditionalData]) CreateToken(
	ctx context.Context,
	userID string,
	additionalData TAdditionalData,
	tokenType TokenType,
) (string, Data[TAdditionalData], error)

CreateToken generates a new token of the specified type, saves the token data into the storage, and returns the signed token.

func (*Manager[TAdditionalData]) RevokeToken

func (tm *Manager[TAdditionalData]) RevokeToken(ctx context.Context, signedToken string) error

RevokeToken verifies the token signature and removes it from the storage.

func (*Manager[TAdditionalData]) UpdateAdditionalData

func (tm *Manager[TAdditionalData]) UpdateAdditionalData(
	ctx context.Context,
	signedToken string,
	newAdditionalData TAdditionalData,
) error

UpdateAdditionalData verifies the token signature and updates the token's additional data in the storage. The original TTL is preserved (not reset).

func (*Manager[TAdditionalData]) ValidateToken

func (tm *Manager[TAdditionalData]) ValidateToken(ctx context.Context, signedToken string, expectedType TokenType) (*Data[TAdditionalData], bool)

ValidateToken checks that the token has a valid format, that its signature is correct, that the corresponding data can be retrieved from storage, and that it matches the expected type and is not expired.

type MemoryTokenStore

type MemoryTokenStore struct {
	// contains filtered or unexported fields
}

MemoryTokenStore is an in‑memory implementation of ITokenStore. It can be replaced with a Redis-based store or another external storage.

func NewMemoryTokenStore

func NewMemoryTokenStore(cleanupInterval ...time.Duration) *MemoryTokenStore

NewMemoryTokenStore creates a new in‑memory store. An optional cleanup interval can be provided; defaults to 1 minute.

func (*MemoryTokenStore) Close

func (mts *MemoryTokenStore) Close()

Close stops the background cleanup goroutine.

func (*MemoryTokenStore) Delete

func (mts *MemoryTokenStore) Delete(_ context.Context, key []byte) error

Delete removes the key from the storage.

func (*MemoryTokenStore) Exists

func (mts *MemoryTokenStore) Exists(ctx context.Context, key []byte) (bool, error)

Exists checks if a key exists in the store and is not expired.

func (*MemoryTokenStore) Get

func (mts *MemoryTokenStore) Get(ctx context.Context, key []byte) ([]byte, error)

Get returns the value for the given key if it exists and is not expired.

func (*MemoryTokenStore) GetFromJSON

func (mts *MemoryTokenStore) GetFromJSON(ctx context.Context, key []byte, dst any) error

GetFromJSON retrieves the value for the given key and unmarshals it into dst.

func (*MemoryTokenStore) Keys

func (mts *MemoryTokenStore) Keys(_ context.Context, prefix []byte) ([]string, error)

Keys returns all non-expired keys that start with the given prefix.

func (*MemoryTokenStore) KeysAndValues

func (mts *MemoryTokenStore) KeysAndValues(_ context.Context, prefix []byte) (map[string][]byte, error)

KeysAndValues returns a map of all keys that start with the given prefix and their corresponding values. Expired items are skipped.

func (*MemoryTokenStore) Set

func (mts *MemoryTokenStore) Set(_ context.Context, key []byte, value []byte, duration time.Duration) error

Set stores the key and value for the specified duration.

func (*MemoryTokenStore) SetJSON

func (mts *MemoryTokenStore) SetJSON(ctx context.Context, key []byte, value any, expiration time.Duration) error

SetJSON marshals the given value to JSON and stores it with the specified expiration.

type TokenType

type TokenType string

TokenType represents the type of token.

const (
	AccessTokenType  TokenType = "access_token"
	RefreshTokenType TokenType = "refresh_token"
)

func (TokenType) IsValid

func (t TokenType) IsValid() bool

func (TokenType) String

func (t TokenType) String() string

Jump to

Keyboard shortcuts

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