← Console
Console/Planning/Document Auth Hardening
RESOLVING…
Server Gating →

Document Auth Hardening

Batch 12 · HMAC-signed session tokens with explicit exp · revocable on logout · disabled-user lockout · still no password · documented honestly
Spec Scaffold Batch 12 · v1.0 Planning A-owned Local/dev · no password · NOT production

Access · สิทธิ์

Overview

Batch 8 issued plain UUIDs as session tokens. Batch 12 replaces them with HMAC-SHA256-signed tokens carrying an explicit exp timestamp, per-process signing key, and a revocation map so logout actually invalidates. Disabled users (status != "enabled") are rejected even with a valid signature.

Batch 8 ออก token แบบ UUID · Batch 12 เปลี่ยนเป็น token ที่ลงนาม HMAC พร้อม exp ชัดเจน · มี key ต่อ process · มี map สำหรับ revoke ตอน logout · ถ้า status ไม่ใช่ enabled จะ reject แม้ลายเซ็นยังใช้ได้

Key Point

Signed sessions · explicit expiry · revocation on logout · disabled-user lockout. Still no password — out of scope this batch.
Token ลงนาม · หมดอายุชัด · logout ใช้งานได้จริง · disabled user ล็อก · ยังไม่มี password

Summary

Token format: <base64url(payload)>.<base64url(hmac_sha256)>. Payload: {"sub": profile_id, "iat": int, "exp": int, "v": "das-1"}. Signing key defaults to a per-process random secret (secrets.token_hex(32)), overridable via DAS_SIGNING_KEY. TTL defaults to 24h, overridable via DAS_SESSION_TTL. The Batch-8 session map is preserved as a revocation set: tokens not in the map are treated as invalid even if signed correctly (so logout works and the per-process key rotation on restart invalidates all old sessions).

What changed from Batch 8

AspectBatch 8Batch 12
Tokenplain UUID v4HMAC-SHA256 signed · body.sig
Expirynone (cookie max-age only)explicit exp claim · checked on every verify
Tamper detectionnoneconstant-time HMAC compare
Disabled usersallowed if they had a tokenrejected on every verify
Restart behaviortokens survived (UUID stayed valid)per-process random key → restart invalidates all tokens
LogoutUUID removed from mapsignature removed from revocation map

Token format

body = base64url(JSON.stringify({
  sub: "u-admin-001",
  iat: 1745083200,        // unix seconds
  exp: 1745169600,        // unix seconds · iat + 86400
  v:   "das-1"
}))
sig  = base64url(HMAC-SHA256(signing_key, body))
token = body + "." + sig

The verifier MUST: (1) split on ., (2) recompute HMAC on the raw body bytes, (3) compare with hmac.compare_digest, (4) decode payload, (5) check exp > now, (6) ensure the signature is still in the revocation set.

Config · Env vars

VarDefaultPurpose
DAS_SIGNING_KEYper-process randomShare across restarts in dev by setting explicitly. In production replace with a secret manager value.
DAS_SESSION_TTL86400Session lifetime in seconds.

Honest limits

  • Still no password. Login = email-only against the file-backed roster.
  • No refresh tokens. Expiry ⇒ re-login.
  • No key rotation mid-process. Restart = rotation but that invalidates all users.
  • Cookie is HttpOnly=false + Secure=false (dev only). In production both must be true under TLS.
  • No rate limiting on login or verify.
  • No user-level audit trail of auth events yet.
  • CORS is still * for dev.

Checklist · Batch 13+

  • ☐ Password or passkey or OAuth/SSO
  • ☐ Refresh tokens
  • ☐ TLS + HttpOnly cookies
  • ☐ Rate limiting on login + verify
  • ☐ Audit trail for auth events
  • ☐ Secret Manager-backed signing key
  • ☐ Multi-instance session store (Redis) for horizontal scale

Notes · โน้ต

Backing: backend when reachable · localStorage fallback.