package session
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"errors"
"strings"
)
var (
ErrMalformed = errors.New("session: malformed token")
ErrBadSig = errors.New("session: signature mismatch")
)
type Signer struct {
key []byte
}
func NewSigner(key []byte) *Signer {
return &Signer{key: key}
}
func (s *Signer) mac(encoded string) []byte {
h := hmac.New(sha256.New, s.key)
h.Write([]byte(encoded))
return h.Sum(nil)
}
func (s *Signer) Sign(payload []byte) string {
encoded := base64.RawURLEncoding.EncodeToString(payload)
sig := base64.RawURLEncoding.EncodeToString(s.mac(encoded))
return encoded + "." + sig
}
func (s *Signer) Verify(token string) ([]byte, error) {
encoded, sigPart, ok := strings.Cut(token, ".")
if !ok {
return nil, ErrMalformed
}
gotSig, err := base64.RawURLEncoding.DecodeString(sigPart)
if err != nil {
return nil, ErrMalformed
}
if !hmac.Equal(gotSig, s.mac(encoded)) {
return nil, ErrBadSig
}
return base64.RawURLEncoding.DecodeString(encoded)
}
package session
import (
"encoding/json"
"errors"
"time"
)
var ErrExpired = errors.New("session: expired")
type Session struct {
UID string `json:"uid"`
Issued time.Time `json:"iat"`
Expires time.Time `json:"exp"`
}
func (sg *Signer) Encode(s Session) (string, error) {
payload, err := json.Marshal(s)
if err != nil {
return "", err
}
return sg.Sign(payload), nil
}
func (sg *Signer) Decode(token string) (Session, error) {
var s Session
payload, err := sg.Verify(token)
if err != nil {
return s, err
}
if err := json.Unmarshal(payload, &s); err != nil {
return s, ErrMalformed
}
if time.Now().After(s.Expires) {
return s, ErrExpired
}
return s, nil
}
package session
import (
"context"
"net/http"
"time"
)
type ctxKey struct{}
const cookieName = "session"
type Manager struct {
Signer *Signer
TTL time.Duration
}
func (m *Manager) Issue(w http.ResponseWriter, uid string) error {
now := time.Now()
token, err := m.Signer.Encode(Session{
UID: uid,
Issued: now,
Expires: now.Add(m.TTL),
})
if err != nil {
return err
}
http.SetCookie(w, &http.Cookie{
Name: cookieName,
Value: token,
Path: "/",
Expires: now.Add(m.TTL),
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
})
return nil
}
func (m *Manager) Load(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c, err := r.Cookie(cookieName)
if err == nil {
if s, derr := m.Signer.Decode(c.Value); derr == nil {
r = r.WithContext(context.WithValue(r.Context(), ctxKey{}, s))
}
}
next.ServeHTTP(w, r)
})
}
func FromContext(ctx context.Context) (Session, bool) {
s, ok := ctx.Value(ctxKey{}).(Session)
return s, ok
}
A stateless session avoids server-side storage by putting the session data directly in the cookie and protecting it with a keyed message authentication code. This snippet shows how to encode, sign, and verify such a cookie in plain Go using crypto/hmac and crypto/sha256, then wire it into an net/http middleware.
In signer.go, the Signer type wraps a secret key and produces a token of the form payload.signature. Sign base64url-encodes the raw payload, computes an HMAC-SHA256 over that encoded string, and appends the encoded MAC. Verify reverses the process but is careful about two things that trip people up. First, it recomputes the expected MAC over the encoded payload rather than the raw bytes, so the signed input is unambiguous. Second, it compares MACs with hmac.Equal, a constant-time comparison that prevents timing attacks — using == or bytes.Equal here would leak how many leading bytes matched. The mac helper centralizes MAC creation so signing and verifying can never diverge.
session.go layers a typed session on top of the raw signer. Session is a small struct with a UID, an Issued timestamp, and an Expires deadline; Encode marshals it to JSON and hands the bytes to Sign. Decode verifies the signature first, then unmarshals, then checks expiry with time.Now().After(s.Expires). The ordering matters: expiry is only trusted after the signature proves the payload was not tampered with, since the expiry lives inside the signed blob. This is the core trade-off of stateless sessions — anyone can read the contents (it is only signed, not encrypted), so no secrets belong in the payload, and revocation before expiry is impossible without extra state.
middleware.go connects this to HTTP. Load reads the session cookie, decodes it, and stashes the result in the request context so handlers can call FromContext. Issue writes a fresh cookie with HttpOnly, Secure, and SameSiteLaxMode set, which blocks JavaScript access and cross-site leakage. A rotated secret invalidates every outstanding cookie at once, which is the intended kill switch when a key is compromised.
Related snips
package api
import (
"net/http"
"runtime/debug"
)
Expose build metadata for debugging deploys
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
timestamp = request.headers.fetch('X-Signature-Timestamp')
signature = request.headers.fetch('X-Signature')
payload = request.raw_post
data = "#{timestamp}.#{payload}"
expected = OpenSSL::HMAC.hexdigest('SHA256', ENV.fetch('WEBHOOK_SECRET'), data)
HMAC signed API requests for webhook and partner integrity
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
#!/usr/bin/env bash
set -euo pipefail
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"
Secrets management with environment isolation and Vault
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
Share this code
Here's the card — post it anywhere.