---
name: "api_auth_patterns"
description: "Authenticate to third-party APIs correctly: API keys, OAuth2 with PKCE, HMAC signing, token refresh, and secret storage."
---

# API Auth Patterns

Most API integrations fail at auth, and most auth failures are the same
five mistakes. Learn the patterns once; apply everywhere.

## The patterns

### 1. API keys (simplest — use when offered)

```bash
curl -H "Authorization: Bearer $API_KEY" https://api.example.com/v1/...
# or, if the API specifies it:
curl -H "X-API-Key: $API_KEY" https://api.example.com/v1/...
```

- The key goes in a header, never in the URL. URLs land in logs,
  caches, and browser history.
- Use the exact header the docs name. `Authorization: Bearer` vs
  `X-API-Key` vs `?api_key=` are not interchangeable — read the docs.
- Scope keys to minimum permissions. A read-only key for a read-only
  job.

### 2. OAuth 2.0/2.1 authorization code + PKCE (user-delegated access)

The standard flow when acting on behalf of a user. Current best practice
(OAuth 2.1) makes PKCE mandatory for *all* clients:

1. Generate a `code_verifier` (43+ random chars) and its SHA-256
   `code_challenge`:
   ```python
   import secrets, hashlib, base64
   verifier = secrets.token_urlsafe(64)
   challenge = base64.urlsafe_b64encode(
       hashlib.sha256(verifier.encode()).digest()
   ).rstrip(b"=").decode()
   ```
2. Send the user to the authorization URL with `code_challenge`,
   `code_challenge_method=S256`, a random `state`, and the exact
   pre-registered `redirect_uri`.
3. Verify `state` matches on the callback (CSRF protection), then POST
   the `code` + `code_verifier` to the token endpoint.
4. Store the refresh token; use short-lived access tokens (15–60 min).

Dead ends to avoid:
- **Implicit grant** (token in the URL fragment): removed from the spec.
  Never use it.
- **Password grant** (collecting the user's raw password): deprecated.
  If a flow asks you to handle user passwords, it's the wrong flow.
- Tokens in query strings: prohibited. Header or POST body only.

### 3. HMAC request signing (crypto exchanges, webhooks, some APIs)

```python
import hmac, hashlib, time
ts = str(int(time.time() * 1000))
payload = ts + method + path + body
sig = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()
# send ts and sig in the headers the API names
```

- The signed content is exact: method + path + timestamp + body, in the
  order the docs specify. One extra newline breaks the signature.
- Include a timestamp and reject skew server-side; signatures without
  timestamps are replayable.
- Never log the secret or the full signed payload at debug level.

### 4. Token refresh (the part everyone gets wrong)

- Refresh *before* expiry with a margin (at ~80% of token lifetime),
  not after a 401. 401-driven refresh races under concurrency.
- Serialize refreshes: one refresh in flight at a time, or parallel
  requests stampede the token endpoint and invalidate each other.
- If the provider rotates refresh tokens, persist the *new* refresh
  token immediately — the old one is dead.
- On `invalid_grant` during refresh: the grant is revoked. Stop
  retrying and surface it — no amount of retry fixes a revoked grant.

## Secret storage

- Secrets live in environment variables or a secret manager. Never in
  code, never in logs, never in a chat transcript.
- `.env` files are gitignored on sight. If a secret was ever committed,
  rotate it — removing the commit isn't enough.
- When a credential stops working: check the *request* first (was the
  header actually attached? right header name? not expired?), then
  suspect the key. An unsigned request looks exactly like a bad key.

## Operating Rules

1. Read the provider's auth docs before writing any code. Auth is the
   one place where guessing wastes the most time.
2. Never print, log, or echo a secret — not even partially, not "to
   check."
3. One credential, one purpose. Don't reuse a personal API key for an
   agent's background job.
4. Test auth in isolation first: one authenticated `GET` proving the
   credential works, before building the integration on top of it.
5. Know the failure signatures: 401 = bad/missing credential, 403 =
   valid credential but not allowed, 429 = slow down. Don't retry a 401
   as if it were a 429.
