main.go
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"strings"
	"time"
)

func sign(secret []byte, data string) string {
	m := hmac.New(sha256.New, secret)
	m.Write([]byte(data))
	return base64.RawURLEncoding.EncodeToString(m.Sum(nil))
}

func mintToken(secret []byte, subject string, ttl time.Duration) (string, error) {
	payload := map[string]any{
		"sub": subject,
		"exp": time.Now().Add(ttl).Unix(),
	}
	b, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	body := base64.RawURLEncoding.EncodeToString(b)
	sig := sign(secret, body)
	return body + "." + sig, nil
}

func verifyToken(secret []byte, token string) (string, error) {
	parts := strings.Split(token, ".")
	if len(parts) != 2 {
		return "", errors.New("invalid token format")
	}
	body, sig := parts[0], parts[1]
	want := sign(secret, body)
	if !hmac.Equal([]byte(sig), []byte(want)) {
		return "", errors.New("invalid signature")
	}
	raw, err := base64.RawURLEncoding.DecodeString(body)
	if err != nil {
		return "", err
	}

	var p map[string]any
	if err := json.Unmarshal(raw, &p); err != nil {
		return "", err
	}

	sub, _ := p["sub"].(string)
	expFloat, ok := p["exp"].(float64)
	if !ok {
		return "", errors.New("missing exp")
	}
	if time.Now().Unix() > int64(expFloat) {
		return "", errors.New("token expired")
	}
	if sub == "" {
		return "", errors.New("missing sub")
	}
	return sub, nil
}

func main() {
	secret := []byte("change-me-in-production")

	tok, err := mintToken(secret, "user-123", 2*time.Minute)
	if err != nil {
		log.Fatalf("mint: %v", err)
	}
	fmt.Println("token:", tok)

	sub, err := verifyToken(secret, tok)
	if err != nil {
		log.Fatalf("verify: %v", err)
	}
	fmt.Println("subject:", sub)
}

How It Works

Creates stateless tokens signed with HMAC-SHA256 and expiration timestamps, plus verification helpers using constant-time comparisons.

Builds payloads with data and expiry, base64url-encodes them, computes an HMAC signature, concatenates payload and signature, and verifies by recomputing the MAC and checking expiry on decode.

Key Concepts

  • 1HMAC protects integrity without server-side session storage.
  • 2Base64url payloads are URL-safe for cookies or links.
  • 3Constant-time comparison thwarts timing attacks on signatures.

When to Use This Pattern

  • Temporary download links or password reset tokens.
  • API authentication for internal services without database lookups.
  • One-time tokens for CSRF or signup confirmation flows.

Best Practices

  • Keep secrets out of code and rotate them.
  • Validate expiration and reject stale tokens promptly.
  • Keep payloads small and avoid sensitive data unless encrypted.
Go Version1.18+
Difficultyintermediate
Production ReadyYes
Lines of Code83