Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Protect an API

This guide explains how to verify Aero2 access tokens in your backend API to protect your endpoints.

Overview

When a user signs in through Aero2, your frontend receives an access token. Your backend API should verify this token on every request to ensure the user is authenticated and authorized.

Steps

Fetch the JWKS

Aero2 publishes a JSON Web Key Set (JWKS) at a well-known URL. Start by discovering it from the OpenID Connect discovery endpoint:

GET https://your-app.aero2.dev/.well-known/openid-configuration

The response includes a jwks_uri field pointing to the JWKS endpoint (typically https://your-app.aero2.dev/oauth2/jwks.json).

The JWKS contains the public keys used to verify token signatures. Most libraries cache these keys automatically.

Verify the token

Use the jose library to verify tokens. It handles JWKS fetching, caching, and key rotation automatically.

npm install jose
import * as jose from 'jose';
 
const JWKS = jose.createRemoteJWKSet(
  new URL('https://your-app.aero2.dev/oauth2/jwks.json')
);
 
async function verifyToken(token: string) {
  const { payload } = await jose.jwtVerify(token, JWKS, {
    issuer: 'https://your-app.aero2.dev',
    audience: 'your-client-id',
  });
 
  return payload;
}

Extract user info

The verified payload contains the user's claims:

const payload = await verifyToken(accessToken);
 
const userId = payload.sub;       // User ID
const email = payload.email;      // Email address
const roles = payload.roles;      // Assigned roles

Express Middleware Example

Here is a complete middleware pattern for Express/Node.js:

import * as jose from 'jose';
import { Request, Response, NextFunction } from 'express';
 
const JWKS = jose.createRemoteJWKSet(
  new URL('https://your-app.aero2.dev/oauth2/jwks.json')
);
 
async function authMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
) {
  const authHeader = req.headers.authorization;
 
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing or invalid Authorization header' });
  }
 
  const token = authHeader.slice(7);
 
  try {
    const { payload } = await jose.jwtVerify(token, JWKS, {
      issuer: 'https://your-app.aero2.dev',
      audience: 'your-client-id',
    });
 
    // Attach user info to the request
    req.user = {
      id: payload.sub,
      email: payload.email as string,
      roles: payload.roles as string[],
    };
 
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
}
 
// Use the middleware on protected routes
app.get('/api/protected', authMiddleware, (req, res) => {
  res.json({ message: `Hello, ${req.user.email}` });
});

Role-Based Authorization

You can extend the middleware to check for specific roles:

function requireRole(role: string) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user?.roles?.includes(role)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    next();
  };
}
 
// Only admins can access this route
app.delete('/api/users/:id', authMiddleware, requireRole('admin'), handler);

Security Checklist

  • Always verify the token signature using the JWKS endpoint
  • Check the iss (issuer) claim matches your Aero2 application URL
  • Check the aud (audience) claim matches your client ID
  • Check the exp (expiration) claim to ensure the token has not expired
  • Use the JWKS endpoint for key discovery — never hardcode public keys
  • Handle token verification errors gracefully and return appropriate HTTP status codes