HMAC-Signed Expiring Tokens (Stateless)
Creates stateless tokens signed with HMAC-SHA256 and expiration timestamps, plus verification helpers using constant-time comparisons.
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