How to protect your API with your login?
On every call, check the token your login gave the user: its signature, its expiry, who issued it and who it’s for. Then your code knows exactly who is calling.
Every provider documents how. Doing it right in every service, and keeping it right, is the hard part. Here’s why, and the easy way to solve it.
Skip to the one-command wayChecking a token is several checks. Missing one is a way in.
A token is only as safe as the code that checks it. Here is one taken apart, with every check your code has to make:
// the token your login hands the user, sent on every call
eyJhbGciOiJSUzI1NiIsImtpZCI6Ims3In0.eyJpc3MiOiJodHRwczovL25pbm9waXp6YXMudXMuYXV0aDAuY29tLyIsImF1ZCI6Imh0dHBzOi8vYXBpLm5pbm9waXp6YXMuY29tIiwiZXhwIjoxNzkzMDAwMDAwLCJzdWIiOiJ1c2VyX2FuYSJ9.Qm9fk3TzV1xR7aLpW2c…
Header
- "alg": "RS256"
- "kid": "k7"
Payload
- "iss": "https://ninopizzas.us.auth0.com/"
- "aud": "https://api.ninopizzas.com"
- "exp": 1793000000
- "sub": "user_ana"
Signature
made with your provider’s private key
- 1
Refuse the tricks
A token that says “no signature”, or names the wrong kind, is the classic way forged tokens get in.
- 2
Verify the signature with the right public key
Fetch your provider’s keys and cache them, and pick up new ones when it rotates them. Miss that, and every user is locked out that day.
- 3
Check the expiry, with a little leeway
Too strict and calls fail at random; too loose and old tokens keep working.
- 4
Check who issued it and who it’s for
A token meant for another app must never work on yours.
Then all four, in every service
Each copy of this code is one more place to get a check wrong.
- bookings
- menus
- payments
- reviews
This ends up in every service, and every copy has to stay right
A library helps, but each service still needs its own copy of this check. If one copy falls out of date, that’s where a bad token gets in.
import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://YOUR_DOMAIN/.well-known/jwks.json'),
);
app.use(async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).end();
try {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://YOUR_DOMAIN/',
audience: 'bookings-api',
});
req.userId = payload.sub;
next();
} catch {
res.status(401).end();
}
});Protect your API with your login with one command
APIblaze makes every one of those checks, on every call. Point it at your API, then tell it about your login:
Put APIblaze in front of your API
$ npx apiblaze@latest create --target https://ninopizzas.com/openapi.yamlPaste it in a terminal. No sign-up, no servers. The link is a sample restaurant API; swap in your own. Then add your login in the project’s Authentication tab.
APIblaze is an API proxy: every request passes through it first
It sits between your users and your API. Every call brings its token to APIblaze, and only the calls whose token passes every check reach your code.
Callers
- Ana, signed in with Auth0asks to view her bookings
- A token from yesterdayasks to view bookings
- A token for another appasks to view bookings
APIblaze
The API proxy
Checks every token’s signature, expiry, issuer and audience, then tells your code who is calling.
Your API
Only sees the calls APIblaze lets through.
Never reached your code.
That’s why it’s the right place to check tokens. It does it once, at the door, for every service behind it, and picks up your provider’s new keys by itself. Your services just read who is calling:
app.use((req, res, next) => {
req.userId = req.headers['x-abz-user-id'];
next();
});Bad, expired and wrongly signed tokens never reach this code. When your provider rotates its keys, APIblaze picks up the new ones.
Your login in front of your API, step by step
- 1
Copy three values from your provider
Pick your provider to see what to copy, and watch where it goes in your project’s Authentication tab. Paste a sample token and APIblaze fills the values in for you.
What to copy from your provider
- Issuer
- https://YOUR_DOMAIN/
- Audience
- Your API’s identifier in Auth0
- JWKS URL
- https://YOUR_DOMAIN/.well-known/jwks.json
Keep the trailing slash on the issuer: it must match the token exactly.
Where it goes in APIblaze
dashboard.apiblaze.com · bookings-apiPress Add issuer. Every call is now checked against your login.
Prefer the terminal? Pass them when you create:
npx apiblaze@latest create --target https://api.yourapp.com --oauth '{"iss":"https://YOUR_DOMAIN/","aud":"bookings-api","jwks":"https://YOUR_DOMAIN/.well-known/jwks.json"}' - 2
Every token gets all four checks
Three tokens arrive at your booking API: one from a signed-in user, one that expired yesterday, and one issued for a different app.
A signed-in user
eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyXzhkMWYi….Qm9va2luZ3Mh…
- iss
- https://login.yourapp.com/
- aud
- bookings-api
- sub
- user_8d1f
- exp
- in 55 minutes
- Signed with your provider’s key
- Not expired
- Issued by your login
- Meant for this API
Sent to your API with:
x-abz-user-id: user_8d1f
Common questions
- Which login providers work?
- Any that signs its tokens with a public key and publishes that key at a JWKS address. That covers Auth0, Clerk and Supabase, and most other OpenID Connect providers, including your own.
- What is an API proxy?
- A service that sits in front of your API. Every request goes through it first, so it can check the caller’s token before your code runs. APIblaze is an API proxy.
- What exactly does APIblaze check?
- That the token is signed with your provider’s key, has not expired (with a minute of leeway for clock drift), was issued by your login, and is meant for your API.
- What happens when my provider rotates its keys?
- Nothing for you to do. When a token arrives signed with a key APIblaze has not seen, it fetches your provider’s key list again.
- What does my API receive?
- The request, plus the user’s id in x-abz-user-id and a signed identity header. Callers cannot send these headers themselves: APIblaze removes any they try.
- Can I use something other than sub as the user id?
- Yes. In the issuer’s settings, pick another claim from the token, such as email, and APIblaze uses it to identify the user. Tokens must still carry a sub.
- Does it work with Supabase?
- Yes, with Supabase’s JWT signing keys. Projects still on the legacy shared secret need to switch to signing keys first.
- Does it work with Clerk?
- Yes, once your tokens carry an audience. Add an aud claim in a Clerk JWT template and send that token to your API.
- What stops someone calling my backend directly?
- Have APIblaze send your backend a secret header on every call, and have your backend refuse calls without it. One command sets it up.
Prices are on the pricing page.
Protect your API with your own login today
One command, no sign-up. Every token checked before your API runs.
$ npx apiblaze@latest create --target https://ninopizzas.com/openapi.yaml