API Design & REST

Authentication, Rate Limiting and Security

40 min

API keys

For server-to-server access, issue API keys:

  • Generate long random keys: secrets.token_urlsafe(32), with a recognisable prefix (pd_live_...).
  • Store only a hash of the key (like a password); show the full key once at creation.
  • Attach scopes (projects:read, projects:write) and an owner.
  • Allow revocation and rotation; record last_used_at.
import hashlib, hmac
def require_api_key(scope):
    def deco(view):
        @wraps(view)
        def wrapped(*a, **kw):
            raw = request.headers.get("Authorization", "").removeprefix("Bearer ").strip()
            digest = hashlib.sha256(raw.encode()).hexdigest()
            key = db.session.scalar(select(ApiKey).filter_by(key_hash=digest, revoked=False))
            if not key or scope not in key.scopes:
                return api_error(401, "unauthorized", "Invalid or missing API key.")
            g.api_user = key.owner
            return view(*a, **kw)
        return wrapped
    return deco

User tokens

For apps acting on behalf of users, use OAuth 2.0 (with PKCE for mobile and single-page apps) or short-lived signed tokens (JWT) plus refresh tokens. Keep access tokens short-lived (minutes), validate signature, expiry, issuer and audience on every request, and never put secrets inside a JWT payload (it is only encoded, not encrypted).

Rate limiting

limiter.limit("100 per minute", key_func=lambda: g.get("api_user_id") or get_remote_address())

Return 429 with Retry-After, and include remaining-quota headers so clients can slow down. Use a shared store (Redis) when running more than one process.

The OWASP API Security Top 10, in practice

Risk Defence
Broken object-level authorisation (BOLA) check ownership of every object id in every request
Broken authentication strong tokens, rate-limited auth endpoints, no keys in URLs
Broken property-level authorisation explicit input schemas (no mass assignment) and output serialisers
Unrestricted resource consumption rate limits, pagination caps, payload size limits, timeouts
Broken function-level authorisation separate admin endpoints with role checks
Server-side request forgery never fetch arbitrary user-supplied URLs; allowlist hosts
Security misconfiguration no debug output, strict CORS, security headers
Improper inventory document every endpoint and version; retire old ones
Unsafe consumption of other APIs validate data from third-party APIs too

BOLA is the most common API vulnerability: GET /v1/invoices/1002 returning another customer's invoice because the code checked only that the user was logged in.

Logging and auditing

Log authentication failures, permission denials and key usage with a request ID; never log full keys or tokens.

Exercise

Add hashed, scoped API keys to your Flask API with a management page to create and revoke them, per-key rate limiting, and tests proving a key without projects:write cannot create projects and that one user's key cannot read another user's private project.

Check yourself

  • Why store only a hash of each API key?
  • What is BOLA, and how do you prevent it?
  • Why must JWT payloads not contain secrets?

Seu progresso fica salvo apenas neste navegador.

Enviar para revisão (10 pontos)