Blog | G5 Cyber Security

OAuth Tokens: Secure Generation & Storage

TL;DR

This guide covers how to securely generate and store authorization codes and access tokens in an OAuth server. We’ll focus on strong randomness, encryption, token formats (JWTs), database best practices, and rotation strategies.

1. Authorization Code Generation

  1. Randomness is Key: Use a cryptographically secure random number generator (CSPRNG). Avoid predictable sequences.
  2. Code Length: Generate codes that are sufficiently long – at least 256 bits (32 characters) is recommended.
  3. Character Set: Include a wide range of characters (uppercase, lowercase, numbers, special symbols) to increase entropy.
  4. Example (Python):
    import secrets
    import string
    
    def generate_code(length=32):
      alphabet = string.ascii_letters + string.digits + string.punctuation
      return ''.join(secrets.choice(alphabet) for i in range(length))
    
  5. Store with User Context: Associate the code with the correct user, client ID, redirect URI, and any requested scopes.
  6. Expiration: Set a short expiration time (e.g., 10-15 minutes) for authorization codes to limit their exposure.

2. Access Token Generation

  1. Token Format: Use JSON Web Tokens (JWTs). They are self-contained and can be verified without querying the database for every request.
    • Header: Specify the algorithm (e.g., HS256, RS256) used for signing.
    • Payload: Include essential information like user ID, client ID, scopes, expiration time, and issuer. Avoid storing sensitive data directly in the payload; use a unique identifier instead.
    • Signature: Sign the token using a strong secret key or private key.
  2. Example (Python – using PyJWT):
    import jwt
    import datetime
    
    payload = {
      'user_id': '12345',
      'client_id': 'your_client_id',
      'scopes': ['read', 'write'],
      'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    }
    
    secret = 'your-very-secret-key'
    token = jwt.encode(payload, secret, algorithm='HS256')
    
  3. Token Length: While JWTs have a standard structure, ensure the payload isn’t excessively large to avoid performance issues.
  4. Audience (aud) Claim: Include an ‘aud’ claim in the JWT to specify which clients are allowed to use the token.

3. Secure Storage

  1. Database Choice: Use a secure database system (e.g., PostgreSQL, MySQL) with appropriate encryption at rest and in transit.
    • Avoid storing secrets in plain text!
  2. Hashing Secrets: Hash the client secret using a strong hashing algorithm (e.g., bcrypt, Argon2). Never store the raw secret.
  3. Encryption of Tokens: Consider encrypting access tokens at rest if your database doesn’t provide sufficient protection.
    • Use a dedicated encryption key managed securely (e.g., using a Hardware Security Module – HSM).
  4. Token Revocation List: Implement a token revocation list to invalidate tokens before their natural expiration time (e.g., when a user revokes access).
  5. Database Indexing: Properly index database columns used for token lookup (e.g., access token, refresh token) to optimize performance.
    • Avoid indexing sensitive data directly.

4. Token Rotation

  1. Refresh Tokens: Use refresh tokens to obtain new access tokens without requiring the user to re-authenticate.
    • Longer Expiration: Refresh tokens should have a longer expiration time than access tokens, but still be limited (e.g., 30 days).
    • Rotation on Use: Rotate refresh tokens each time they are used – issue a new refresh token along with the new access token. This limits the impact of compromised refresh tokens.
  2. Short-Lived Access Tokens: Keep access token lifetimes short (e.g., 15 minutes to 1 hour) to minimize the window of opportunity for attackers.

5. Additional Considerations

Exit mobile version