DEV Community

Alex Spinov
Alex Spinov

Posted on

How I Handle API Authentication in Every Project (My Exact Pattern)

The problem: every project reinvents API auth

I've built 77+ API integrations. Each time, I used to write auth handling from scratch. Token refresh? Copy-paste. API key rotation? Rewrite. OAuth flow? Start over.

Then I standardized on ONE pattern. Now I copy it into every project and it just works.


The Universal Auth Pattern

import httpx
import time
from dataclasses import dataclass
from typing import Optional

@dataclass
class AuthConfig:
    """Works with API keys, Bearer tokens, and OAuth."""
    api_key: Optional[str] = None
    bearer_token: Optional[str] = None
    oauth_client_id: Optional[str] = None
    oauth_client_secret: Optional[str] = None
    oauth_token_url: Optional[str] = None
    _cached_token: Optional[str] = None
    _token_expires: float = 0

    def get_headers(self) -> dict:
        if self.api_key:
            return {'X-API-Key': self.api_key}

        if self.bearer_token:
            return {'Authorization': f'Bearer {self.bearer_token}'}

        if self.oauth_client_id:
            token = self._get_oauth_token()
            return {'Authorization': f'Bearer {token}'}

        return {}

    def _get_oauth_token(self) -> str:
        if self._cached_token and time.time() < self._token_expires:
            return self._cached_token

        resp = httpx.post(self.oauth_token_url, data={
            'grant_type': 'client_credentials',
            'client_id': self.oauth_client_id,
            'client_secret': self.oauth_client_secret,
        })
        data = resp.json()
        self._cached_token = data['access_token']
        self._token_expires = time.time() + data.get('expires_in', 3600) - 60
        return self._cached_token
Enter fullscreen mode Exit fullscreen mode

Usage: 3 lines to auth with any API

# API Key
auth = AuthConfig(api_key='sk-1234567890')

# Bearer Token
auth = AuthConfig(bearer_token='ghp_xxxxxxxxxxxx')

# OAuth2
auth = AuthConfig(
    oauth_client_id='your-id',
    oauth_client_secret='your-secret',
    oauth_token_url='https://auth.example.com/oauth/token'
)

# Then use it:
client = httpx.Client(headers=auth.get_headers())
resp = client.get('https://api.example.com/data')
Enter fullscreen mode Exit fullscreen mode

Why this pattern wins

  1. One interface for all auth types. Swap API key for OAuth? Change one line.
  2. Token caching built in. No more requesting a new token per request.
  3. Testable. Mock AuthConfig in tests instead of mocking HTTP calls.
  4. Copy-pasteable. Drop it into any project, works immediately.

I use this across all 77 of my Apify scrapers. Same pattern, every time.

How do you handle API auth? Do you have a go-to pattern or do you reinvent it each time?


I write about Python patterns and automation. Follow for more.


More from me: 10 Dev Tools I Use Daily | 77 Scrapers on a Schedule | 150+ Free APIs

Top comments (0)