Security Guide

Security measures implemented in the blah.dev OAuth provider and best practices for integrating clients.

Cryptographic Standards

Password Hashing

Passwords are hashed using Argon2id, the winner of the Password Hashing Competition and recommended by OWASP.

Argon2id configuration
const ARGON2_OPTIONS = {
  memoryCost: 65536,    // 64 MB memory
  timeCost: 3,          // 3 iterations
  parallelism: 4,       // 4 parallel threads
  outputLen: 32,        // 256-bit output
};

GPU Resistant

Memory-hard algorithm

Side-Channel Safe

Resistant to timing attacks

Configurable

Memory/time tradeoffs

Well Audited

Modern, peer-reviewed algorithm

Token Storage

Tokens are never stored in plaintext. Before storage, all tokens are hashed using SHA-256:

javascript
function hashToken(token) {
  return crypto.createHash('sha256').update(token).digest('hex');
}

This means a database breach doesn't expose usable tokens — tokens cannot be reverse-engineered from their hashes.

Token Generation

Token TypeLengthEncoding
Client ID16 bytesbase64url
Client Secret32 bytesbase64url
Authorization Code32 bytesbase64url
Access Token32 bytesbase64url
Refresh Token32 bytesbase64url
CSRF Token32 bytesbase64url

Token Security

Token Lifetimes

TokenLifetimeRationale
Authorization Code10 minutesShort-lived, single-use
Access Token1 hourLimits exposure window
Refresh Token30 daysConvenience vs. security balance
Session7 daysUser experience

Single-Use Authorization Codes

Authorization codes are marked as used immediately after successful token exchange. This prevents replay attacks where an attacker intercepts the code during redirect.

Refresh Token Rotation

Each time a refresh token is used, a new one is issued and the old one is revoked. This limits exposure if a refresh token is leaked and enables detection of token theft.

Token Family Revocation

When a refresh token is revoked, all associated access tokens are also revoked. This ensures logout is complete and no orphaned access tokens remain.

Attack Prevention

CSRF Protection

The consent form is protected with server-side CSRF tokens stored in encrypted session cookies. Clients should also always use the state parameter.

CSRF validation
// Server validates CSRF token on consent submission
if (session.csrfToken !== submittedToken) {
  return error('invalid_request', 'Invalid CSRF token');
}

// Client validates state on callback
if (returnedState !== sessionStorage.getItem('oauth_state')) {
  throw new Error('State mismatch - possible CSRF attack');
}

PKCE (Proof Key for Code Exchange)

PKCE prevents authorization code interception attacks. The client generates a random verifier, creates a SHA-256 challenge, and proves knowledge of the verifier during token exchange.

PKCE flow
// Client: generate verifier and challenge
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto
  .createHash('sha256')
  .update(verifier)
  .digest('base64url');

// Send challenge in authorization request
// Send verifier in token request
// Server validates: SHA256(verifier) === challenge

Redirect URI Validation

Redirect URIs must exactly match a registered URI. No wildcards or partial matching. URIs must use HTTPS (except localhost for development). Maximum 10 URIs per client.

Token Enumeration Prevention

The introspection endpoint returns { "active": false } for all invalid tokens with no distinction between “invalid” and “expired.” The revocation endpoint always returns 200 OK regardless of whether the token existed.

Client Security

Confidential Clients

Server-side apps that can securely store a client secret. Must authenticate with secret on token requests. Secret verified with Argon2id.

Public Clients

SPAs and mobile apps that cannot securely store secrets. Must use PKCE for authorization. No client secret verification.

Secret Storage Recommendations

  • Store secrets in environment variables
  • Never commit secrets to version control
  • Use a secrets manager in production (AWS Secrets Manager, HashiCorp Vault)
  • Rotate secrets periodically

Session Security

Sessions use iron-session with AES-256-GCM encryption and HMAC integrity verification. Cookies are HttpOnly (not accessible to JavaScript), Secure in production, and SameSite=Lax.

On login, a new session is always created (session fixation prevention). On logout, the session is deleted from the database and the cookie is cleared.

Security Checklist for Integrators

Required

  • Required item iconUse HTTPS for all OAuth endpoints
  • Required item iconStore client secrets securely (not in code)
  • Required item iconValidate state parameter on callback
  • Required item iconUse PKCE for public clients
  • Required item iconValidate token responses before use
  • Required item iconHandle token expiration gracefully

Recommended

  • Recommended item iconUse PKCE for all clients (even confidential)
  • Recommended item iconImplement token refresh before expiration
  • Recommended item iconLog security-relevant events
  • Recommended item iconMonitor for unusual activity
  • Recommended item iconUse secure cookie settings for tokens

Avoid

  • Avoid item iconStoring tokens in localStorage (XSS vulnerable)
  • Avoid item iconIncluding tokens in URLs
  • Avoid item iconLogging tokens or secrets
  • Avoid item iconDisabling TLS certificate verification
  • Avoid item iconUsing "plain" PKCE method (prefer S256)

Incident Response

Suspected Token Compromise

  1. Revoke immediately via the revocation endpoint
  2. Rotate client secret if client credentials may be compromised
  3. Review access logs for unauthorized activity
  4. Notify affected users if data exposure occurred

Suspected Client Credential Compromise

  1. Delete the OAuth client immediately
  2. Create new client with new credentials
  3. Update application with new credentials
  4. Revoke all tokens issued to old client
  5. Audit access during compromise window