Skip to content

API Authentication

Secure your API requests with authentication.

Methods

Cloudflare Access JWT

When Cloudflare Access is configured:

bash
curl https://your-domain.com/api/agents \
  -H "CF-Access-JWT-Assertion: <jwt-token>"

No Authentication

For development or public endpoints:

bash
curl https://your-domain.com/api/agents

Cloudflare Access Setup

1. Create Access Application

  1. Go to Cloudflare Zero Trust
  2. Access > Applications
  3. Add an application
  4. Configure self-hosted application

2. Configure Identity Provider

Supported providers:

  • Google
  • Microsoft Azure AD
  • Okta
  • GitHub
  • OneLogin
  • SAML
  • OpenID Connect

3. Set Audience Tag

Get the audience tag from Access application settings:

toml
# wrangler.toml
[vars]
CF_ACCESS_AUD = "your-audience-tag-here"

4. Create Access Policy

Define who can access:

  • Email domains
  • Email addresses
  • Identity provider groups
  • IP ranges

JWT Validation

The worker validates JWTs automatically:

typescript
import { getUser } from './lib/auth'

export async function onRequest(context) {
  const user = await getUser(context.request, context.env)

  if (!user) {
    return new Response('Unauthorized', { status: 401 })
  }

  // User is authenticated
  console.log(user.email)
}

JWT Payload

Cloudflare Access JWTs contain:

json
{
  "aud": ["audience-tag"],
  "email": "user@example.com",
  "exp": 1702648800,
  "iat": 1702645200,
  "iss": "https://your-team.cloudflareaccess.com",
  "sub": "user-id"
}

Request Headers

JWT Header

CF-Access-JWT-Assertion: eyJhbGciOiJSUzI1NiIsInR5cCI...

Client Info Headers

CF-Access-Authenticated-User-Email: user@example.com
CF-Access-Client-Id: client-id

Agent Access Control

After authentication, check agent permissions:

typescript
async function checkAccess(db, agentId, email) {
  // Owner or admin?
  const admin = await db
    .prepare('SELECT role FROM agent_admins WHERE agent_id = ? AND email = ?')
    .bind(agentId, email)
    .first()

  return admin !== null
}

Roles

RolePermissions
ownerFull access, delete agent, manage admins
adminFull access, cannot delete
viewerRead-only access

Public Endpoints

Some endpoints are public:

  • GET /api/agents (lists public agents)
  • Default agent access (when auth disabled)

Error Responses

401 Unauthorized

No valid authentication:

json
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication required"
  }
}

403 Forbidden

Authenticated but no access:

json
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Access denied to this agent"
  }
}

Testing Authentication

With Access

bash
# Get token from browser or Access service token
TOKEN="your-jwt-token"

curl https://your-domain.com/api/agents \
  -H "CF-Access-JWT-Assertion: $TOKEN"

Service Tokens

For automated systems:

  1. Create Service Token in Zero Trust
  2. Use Client ID and Secret
  3. Exchange for JWT
bash
# Get service token JWT
curl -X POST "https://your-team.cloudflareaccess.com/cdn-cgi/access/get-identity" \
  -H "CF-Access-Client-Id: $CLIENT_ID" \
  -H "CF-Access-Client-Secret: $CLIENT_SECRET"

Best Practices

1. Always Use HTTPS

All API calls should use HTTPS:

https://your-domain.com/api/...

2. Validate on Every Request

Check authentication for each request:

typescript
const user = await getUser(request, env)
if (!user) return unauthorized()

3. Use Service Tokens for Automation

For CI/CD and scripts:

  • Create dedicated service tokens
  • Rotate regularly
  • Limit permissions

4. Log Access

Track authentication events:

typescript
await logAccess({
  user: user.email,
  action: 'api_request',
  resource: request.url
})

5. Handle Token Expiration

JWTs expire. Handle gracefully:

typescript
if (isExpired(token)) {
  return new Response('Token expired', { status: 401 })
}

Released under the MIT License.