r/django•
ELMG006

How to Handle OAuth 2.0 Authentication to Third-Party APIs with Asstgr (self-hosted)

Apps

If you've ever had to integrate an OAuth 2.0-protected third-party API into multiple projects, you know the drill: handle the authorization flow, store the tokens, track their expiration, build automatic refresh logic... and start over on every new project.

Asstgr is a self-hosted API gateway (Django + DRF) that centralizes all of this. The idea: you register a third-party API once in Asstgr, describe its endpoints, and then call it through a unified REST interface — Asstgr takes care of authentication, quota, and logging on your behalf.

In this article, we'll focus on one specific use case: how to connect an OAuth 2.0-protected API to Asstgr, and call it without ever handling a token by hand.

The concept

Your app  ──►  Asstgr (/api/v1/...execute/)  ──►  OAuth2-protected third-party API
                  │
                  ├─ Auth (API Key or OAuth2)
                  ├─ Quota
                  ├─ Logs
                  └─ Response formatting

Your application only needs to know one thing: your Asstgr API key (sk-...). Asstgr internally handles all exchanges with the third-party API's OAuth server (fetching, caching, and refreshing tokens).

Supported flows

Asstgr supports the three most common OAuth 2.0 grants, with automatic token refresh:

  • client_credentials — for server-to-server integrations (the most common case)
  • authorization_code — for APIs requiring explicit user authorization
  • password — for legacy ROPC-based APIs

Step 1 — Register the API

We start like with any other API in Asstgr:

POST /api/v1/apis/
{
  "name": "My Protected API",
  "url": "https://api.example.com/v1",
  "auth_required": true,
  "quota_cost": 2
}

Step 2 — Configure OAuth 2.0

This is where it gets interesting. We attach an OAuth configuration to the API through the dedicated endpoint:

POST /api/v1/apis/{api_id}/oauth/
{
  "grant_type": "client_credentials",
  "token_url": "https://api.example.com/oauth/token",
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  "scope": "read write"
}

The client_secret is stored encrypted server-side (client_secret_encrypted in the database). Once this configuration is saved, Asstgr knows how to obtain a token for this API.

Checking token status

You can check at any time whether a valid token is currently cached:

GET /api/v1/apis/{api_id}/oauth/token/

Forcing a refresh

If needed (debugging, secret rotation on the provider's side, etc.), you can manually force a token refresh:

POST /api/v1/apis/{api_id}/oauth/token/

Under normal circumstances this isn't necessary: Asstgr's internal service (OAuthService) checks token expiration (token_expires_at) before every call and refreshes it automatically if needed, completely transparently.

Step 3 — Describe the endpoint and its parameters

Just like with a regular API, we add the endpoint and its parameters:

POST /api/v1/apis/{api_id}/endpoints/
{
  "path": "/protected-resource",
  "description": "OAuth2-protected resource"
}


POST /api/v1/apis/{api_id}/endpoints/{endpoint_id}/parameters/
{
  "name": "resource_id",
  "param_type": "query",
  "data_type": "STRING",
  "required": true
}


POST /api/v1/apis/{api_id}/endpoints/{endpoint_id}/methods/
{ "method": "GET" }

Step 4 — Execute the call

And here's the main payoff: from your application, a single call, using your Asstgr key — no OAuth token to manage:

POST /api/v1/apis/{api_id}/endpoints/{endpoint_id}/execute/
Authorization: Api-Key sk-xxxxxxxxxxxxxxxxxxxxxxxx

{
  "method": "GET",
  "params": { "resource_id": "42" },
  "display_format": "standard"
}

Asstgr will, in order:

  1. Verify your API key and remaining quota
  2. Fetch (or refresh) the OAuth2 token associated with that third-party API
  3. Build the actual HTTP request with that token in the Authorization header
  4. Call the third-party API
  5. Log the call (APILog: user, endpoint, method, status, response size)
  6. Format and return the response

​

{
  "status_code": 200,
  "result": "...",
  "quota": {
    "used": 4,
    "remaining": 96,
    "limit": 100,
    "usage_pct": 4.0
  }
}

Why centralize this instead of handling OAuth in every service?

  • A single place to store secrets — your client_id / client_secret don't end up scattered across ten different microservices' codebases
  • Automatic, shared refresh — the token is cached and refreshed once, even if multiple services call the same API
  • Unified quota and logging — you know exactly who's calling what, and how much it costs in credits
  • Transparent provider changes — if the third-party API changes its token URL or scopes, there's only one place to update

Going further

Everything else (API keys, quotas, rate limiting, response formats) follows the same simple pattern: register declaratively once, then call it through /execute/.

The project is open source (Django 5.x + DRF, PostgreSQL):

👉 github.com/asstgr/asstgropensource

If you find the project useful, a ⭐ goes a long way, and you can follow updates on u/asstgrio.

Posted Aug 13, 2026, 8:34 PM UTC

20
Loading comments…