> ## Documentation Index
> Fetch the complete documentation index at: https://proxy-hopper.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Managed Auth

> How Proxy Hopper offloads per-request Authorization header lifecycle management to an external token server you implement.

## What is managed auth?

**Managed auth** lets Proxy Hopper automatically attach a fresh, valid `Authorization` header (or `Cookie`, or any other auth header) to every request forwarded to a target — without your application code fetching or refreshing that token itself.

You implement a small HTTP service, a **token server**, that knows how to acquire and refresh whatever credential the upstream API requires. Proxy Hopper calls it before forwarding a request, caches the result, and refreshes it before it expires — all outside your application's request path.

```
Your app ──► Proxy Hopper ──► external proxy IP ──► upstream API
                  │
                  │  cached token missing or near expiry?
                  ▼
            Token server   POST /token → { headers, expires_at, cursor }
```

<Note>
  Managed auth is unrelated to [Authentication](/admin/authentication/overview) (who's allowed to send traffic *to* Proxy Hopper) and to [Client Identities](/concepts/identities) (the browser persona Proxy Hopper presents *to* a target). Managed auth is specifically about credentials the *upstream API* requires — OAuth access tokens, session cookies, rotating API keys — that Proxy Hopper acquires on your behalf via your token server.
</Note>

## Why each proxy IP needs its own token

This is the actual reason managed auth exists, not just a convenience feature — read this before the rest of the page.

Proxy Hopper's whole purpose is to spread requests across a rotating pool of IPs so a target site can't easily tell they all come from the same caller — to stay under per-IP rate limits, or to avoid one address getting flagged for excessive use. Your application doesn't choose which pool IP handles a given request; Proxy Hopper does.

If every request carried the *same* `Authorization` header no matter which IP sent it, that token would be the one constant thread running through an otherwise-rotating pool — trivially letting the target correlate every IP back to the same caller regardless of how many different addresses were used. That silently defeats the entire reason Proxy Hopper exists.

Managed auth prevents this structurally: Proxy Hopper acquires and caches a **separate token per `(target, proxy-IP)` pair**, never one shared token reused across the whole pool. Combined with [client identities](/concepts/identities) (a consistent browser fingerprint and cookie jar, also scoped per IP) and, where the target's login flow allows it, [IP-pinned acquisition](/developers/token-server#ip-pinned-acquisition) (logging in *from* the same IP that will later use the resulting token), each proxy IP can present as a fully independent, self-consistent caller — fingerprint, cookies, login, and auth token all bound together, none of it shared with any other IP in the pool.

<Warning>
  This only holds if your token server actually issues a **distinct, IP-scoped credential**. Proxy Hopper caches whatever `get_token()` returns under a per-IP key regardless — but a `TokenProvider` that ignores `req.ip` and returns the same static token for every IP quietly reintroduces the exact correlation the pool was rotating IPs to avoid. This is the single most important thing to get right when implementing one — see [Building a Token Server — scope credentials per IP](/developers/token-server#scope-credentials-per-ip).
</Warning>

## Other reasons to centralise this in a token server

Even setting the correlation problem aside, offloading token lifecycle to a dedicated service beats fetching it in your application directly:

* **Refresh happens off the request path.** Proxy Hopper refreshes proactively, before expiry — your application never blocks on a token fetch or retries because of a `401`.
* **Credentials never leave the token server.** Client applications never see the upstream credentials, only Proxy Hopper does, and only in memory (or Redis, if configured) as the resolved header value.
* **No duplicated fetch/refresh logic.** Every service routing through Proxy Hopper for a target reuses whichever IP's token is already cached and fresh, instead of each one independently implementing the same OAuth/session dance.

## How caching works

Tokens are cached per `(target, proxy-IP)` pair — never globally and never per-target-only. Each IP in the pool carries and refreshes its own credential independently of every other IP.

A cached token is reused until it nears `expires_at`, at which point Proxy Hopper proactively calls the token server again — before the current token actually expires, so in-flight requests never hit a `401` from a token that ran out mid-request.

## The cursor mechanism

Your token server is stateless from Proxy Hopper's perspective, but many real auth flows *aren't* — a refresh token, a session ID, a call counter. The **cursor** is an opaque JSON object Proxy Hopper stores per `(target, proxy-IP)` pair and echoes back on every subsequent token request for that pair, so your token server can carry state between calls without running its own database.

```python theme={}
async def get_token(self, req: TokenRequest) -> TokenResponse:
    refresh_token = req.cursor.get("refresh_token")
    # ... use it, or fetch a fresh one on first call (cursor is {} initially) ...
    return TokenResponse(
        headers={"Authorization": f"Bearer {access_token}"},
        expires_at=expires,
        cursor={"refresh_token": new_refresh_token},
    )
```

## Broken-state and recovery

If the token server fails (errors, times out, or is unreachable), Proxy Hopper tracks failures per IP rather than failing every request immediately:

1. Each failure is retried after a cooldown, up to a configured limit.
2. Once that limit is reached, the IP enters an `AUTH_BROKEN` state — requests for that IP fail fast with `502` without calling the token server again.
3. After a further cooldown, Proxy Hopper retries; success returns the IP to normal rotation.

Only the *auth layer* for that IP is affected — the IP itself is not quarantined, and the pool keeps routing requests through other IPs normally. See [Token Server (Admin) — Broken-state and recovery](/admin/token-server/overview#broken-state-and-recovery) for the exact thresholds and how to monitor this state.

## Enabling it

Managed auth is opt-in per target. Point `server.authServer` at your token server, then flag the targets that need it:

```yaml theme={}
server:
  authServer:
    url: "http://token-server:9000"

targets:
  - name: my-api
    regex: 'api\.example\.com'
    ipPool: my-pool
    authManaged: true    # Proxy Hopper injects token headers automatically
```

See [Config Reference](/admin/configuration/reference#authserver) for the full field list, [Token Server (Admin)](/admin/token-server/overview) for deployment and operations, and [Building a Token Server](/developers/token-server) for implementing one.

## When to use managed auth

Use managed auth when the target requires:

* An OAuth access token that expires and needs periodic refresh
* A session cookie obtained by logging in, that multiple requests should reuse
* Any credential your application shouldn't need to know how to fetch itself

For targets that need no per-request auth beyond what's baked into `proxyProviders` (e.g. proxy-level Basic auth to your IP supplier), or that use a static, never-expiring API key, managed auth adds a moving part you don't need — set that key as a normal header on your client requests instead.
