Working with Security Tokens
1. Understanding Security Token Types
| Token | Purpose | Lifetime |
|---|---|---|
| Access | API authorization | Minutes |
| Refresh | Get new access | Days–months |
| ID Token | Identity assertion (OIDC) | Minutes |
| CSRF | Anti-CSRF | Per session/request |
| One-time (OTP, magic link) | Single-use action | Minutes |
| Authorization code | OAuth code flow | ≤10 min, single use |
| Device code | Device auth flow | 5–15 min |
| SAML assertion | SSO identity | Minutes |
2. Generating Cryptographically Secure Tokens
Example: CSPRNG token generation
// Node.js
import { randomBytes } from "crypto";
const token = randomBytes(32).toString("base64url"); // 256 bits
// Java
SecureRandom rng = new SecureRandom();
byte[] buf = new byte[32];
rng.nextBytes(buf);
String token = Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
// Python
import secrets
token = secrets.token_urlsafe(32)
| Use Case | Min Entropy |
|---|---|
| Session ID | 128 bits |
| CSRF token | 128 bits |
| Refresh token | 256 bits |
| Email/reset token | 128 bits |
3. Using UUID for Token Generation
| UUID Version | Source of bits | Use for tokens? |
|---|---|---|
| v1 | Time + MAC | No (predictable) |
| v4 | 122 random bits | OK (entropy < 256 bit ideal) |
| v7 NEW | Time + random | For sortable IDs, not secrets |
| CSPRNG bytes | 256-bit random | Best for secrets |
4. Implementing Token Storage
| Token | Storage |
|---|---|
| Long-lived secrets | HSM / KMS |
| Refresh tokens | DB (hashed) |
| Short-lived (auth code) | Redis with TTL |
| CSRF tokens | Session store or signed cookie |
5. Validating Token Integrity
| Mechanism | Use Case |
|---|---|
| HMAC-SHA256 | Symmetric authentication |
| Digital signature (RSA/ECDSA) | Asymmetric, public verify |
| AEAD (AES-GCM) | Confidentiality + integrity |
| Comparison | Always constant-time |
6. Setting Token Expiration
| Token | TTL |
|---|---|
| Email verification | 24 hr |
| Password reset | 15 min |
| Magic link login | 10 min |
| OTP | 30–60 sec (TOTP), 5–10 min (HOTP/SMS) |
| Device pairing | 5–15 min |
7. Implementing One-Time Tokens
Example: Single-use magic link
async function consumeToken(raw) {
const hash = sha256(raw);
// Atomic delete-if-exists ensures single use
const result = await db.query(
"DELETE FROM tokens WHERE token_hash=$1 AND expires_at > NOW() RETURNING user_id",
[hash]
);
if (!result.rowCount) throw new Error("invalid_or_expired");
return result.rows[0].user_id;
}
8. Using Signed Tokens
| Approach | Tradeoff |
|---|---|
| HMAC | Stateless, but secret distribution issue |
| Signed cookie (PASETO) | Modern alt to JWT, no algorithm confusion |
| JWS | Standard, ecosystem support |
9. Implementing Token Encryption
| Algorithm | Notes |
|---|---|
| AES-256-GCM | AEAD, modern default |
| ChaCha20-Poly1305 | Fast on devices without AES-NI |
| XChaCha20-Poly1305 | Larger nonce (random-safe) |
| RSA-OAEP / ECDH-ES | Key wrap for JWE |
10. Preventing Token Enumeration Attacks
| Vulnerability | Mitigation |
|---|---|
| Sequential IDs | Use random opaque tokens |
| Predictable format | CSPRNG only |
| Timing differences | Constant-time lookup + comparison |
| Token in URL | Use POST body or Authorization header |
| Logged tokens | Scrub from access logs |