How to Secure Claude API Endpoints With OAuth
If you're searching for "how to secure Claude API endpoints with OAuth," you're almost certainly not trying to add OAuth to Anthropic's own API — Claude's API authenticates with a static API key in the x-api-key header, and that isn't going to change per-request. What you actually need to secure is the layer you build on top of Claude: the backend endpoints your web app, mobile app, or partner integrations call before your server talks to Claude. That's where OAuth belongs, and that's what this guide covers.
The short answer: put an OAuth 2.0-protected gateway in front of your Claude integration. Your users or client apps authenticate with OAuth (authorization code + PKCE for user-facing apps, client credentials for service-to-service), your server validates the access token, checks scopes and rate limits, and only then forwards a request to Claude using a server-side key that the client never sees. The rest of this article shows how to build that correctly.
Why OAuth sits on top of, not inside, the Claude API
Anthropic's API has no OAuth endpoints, no token exchange, no refresh tokens. It's a straightforward bearer-key model. That's fine for server-to-server calls, but it's dangerous to expose directly to browsers, mobile clients, or third-party partners, because anyone with the key can run unlimited requests on your account.
OAuth solves a different problem than Claude's API key does:
- API key — proves your backend is allowed to call Anthropic.
- OAuth access token — proves a specific user or client app is allowed to call your backend, with specific scopes and limits.
Confusing these two layers is the most common mistake teams make. Never hand out your Anthropic (or SubToAPI) key to a frontend, mobile app, or external partner. Issue OAuth tokens instead, and keep the model key server-side only.
The architecture pattern
Client (web/mobile/partner)
→ OAuth access token
Your API Gateway / BFF
→ validates token, checks scope, applies rate limits
→ calls Claude (or SubToAPI) with server-side key
Claude API / SubToAPI
Your gateway is the trust boundary. It's the only thing holding the real model-provider key, whether that's Anthropic's raw API or a managed layer like SubToAPI that exposes Claude as a clean HTTPS API with its own sub_live_... application keys. Either way, OAuth governs who's allowed to reach your gateway — not who's allowed to reach Claude directly.
Choosing the right OAuth flow
- Authorization Code + PKCE — for first-party web and mobile apps where a human logs in. Use this when end users need individual accounts and you want per-user audit trails.
- Client Credentials — for machine-to-machine integrations: a partner's backend calling your Claude-powered endpoint without a human in the loop. Issue each partner their own
client_id/client_secretpair. - Avoid Implicit flow entirely. It leaks tokens in URLs and has no place in a 2024+ security design.
Most teams wrapping Claude in a product end up running both: authorization code for their own app's users, client credentials for API partners.
Validating tokens on your Claude-facing endpoints
Every endpoint that triggers a Claude call should validate the OAuth token before doing anything else. A minimal middleware pattern:
async function requireOAuth(req, res, next) {
const token = (req.headers.authorization || '').replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'missing_token' });
const claims = await verifyAccessToken(token); // JWT verify or introspection call
if (!claims) return res.status(401).json({ error: 'invalid_token' });
if (!claims.scope?.includes('claude:invoke')) {
return res.status(403).json({ error: 'insufficient_scope' });
}
req.client = claims.sub;
next();
}
app.post('/chat', requireOAuth, async (req, res) => {
const response = await callClaude(req.body.messages, req.client);
res.json(response);
});
Two details matter here:
- Scope your tokens narrowly. A
claude:invokescope that only allows calling your chat endpoint is safer than a genericapi:allscope. - Short-lived access tokens with refresh tokens. 15–60 minute access tokens limit the damage if one leaks, since the refresh token (which should be stored more securely) is needed to get a new one.
Rate limiting and per-client quotas
OAuth gives you an identity (client_id or sub) to key your rate limits on. Enforce limits per client, not just globally — one noisy or compromised client shouldn't be able to exhaust your Claude quota for everyone else:
const limiter = rateLimit({
keyGenerator: (req) => req.client,
windowMs: 60_000,
max: 30, // requests per client per minute
});
If you're routing Claude traffic through a managed API layer like SubToAPI, you get usage metadata per application key out of the box, which pairs well with OAuth-scoped client identities — your OAuth layer controls who can call the endpoint, and the underlying key's dashboard tells you how much they're actually using. See /docs for how usage and streaming are exposed.
Protecting the key behind the gateway
OAuth protects the front door; you still need to protect the Claude key itself:
- Store it in a secrets manager, never in source control or environment files committed to a repo.
- Rotate it on a schedule and immediately after any suspected leak.
- Prefer a provider that issues scoped application keys per project or team rather than one shared key for everything — SubToAPI's
sub_live_...keys work this way, so a compromised key for one app doesn't expose every integration you run. Check /pricing for how seats and keys map to plans. - Log every call with the OAuth client identity attached, so you can trace unusual Claude usage back to a specific partner or user cohort.
Putting it together
A secure setup looks like: OAuth 2.0 authorization code or client credentials flow issuing short-lived, narrowly scoped access tokens → middleware validating those tokens and enforcing per-client rate limits → a server-side call to Claude (directly or via a managed layer) using a key that never leaves your infrastructure. Each layer does one job. Mixing them — for example, trying to use an Anthropic API key as if it were an OAuth token, or skipping scopes because "it's just internal" — is where most incidents start.
If you want the Claude-calling layer itself handled for you — streaming, tool use, and per-application keys with a dashboard — start with /signup and sit your own OAuth gateway in front of it; see /docs/quickstart for the request format.
Questions
Does Anthropic's Claude API support OAuth natively? No. Claude's API authenticates with a static API key sent in a header. OAuth is something you add in your own backend layer to control which users or client apps can reach the endpoints that call Claude.
Where should I store the Claude API key if I'm using OAuth for my endpoints? Server-side only, in a secrets manager, never exposed to any client. OAuth tokens authenticate callers to your gateway; the gateway alone holds the Claude key and makes the outbound call.
Should every client get the same OAuth scope? No. Scope tokens to the minimum action needed, such as claude:invoke, and tie rate limits to the client identity in the token so one integration can't consume another's quota.