Available

Protect an API

Require a valid access token issued for one API, write rules for who may call each route, and look up how route rules are matched.

Zero-Code checks every call to an API on the node, before the request reaches your code. A caller without a valid access token for this API is denied, and your service does not verify tokens, fetch keys or parse claims.

Use this guide for a service that programs call. For a service people open in a browser, see Protect a web app.

Before you begin

You need:

Open the workload

In the AuthFI console, open Zero-Code > Kubernetes and select the cluster. On the Workloads tab, find the workload and choose Classify on its row.

Classify it as an App that serves an API

  1. The step asks what the workload is. Choose App.
  2. Under What does it serve?, turn on API.
  3. Choose Continue to Protect.

The console confirms that the workload is classified and opens the Protect step, which asks who may reach the workload.

Turn on the API door

Turn on API · software calls it. Every call must then carry a token issued for this service's audience, the name a token is issued for.

Write the route rules

A route rule says who may call a route. With the API switch on, the Protect step shows Route rules.

AuthFI drafts rules from the routes and callers it observed, so you start from what is really called and by whom. Choose Fill in the draft to load them, then correct them.

To add a rule:

  1. Choose the method and enter the path, for example /orders or /admin/*.
  2. Choose Add route.
  3. On the new row, choose Signed in or Anyone.
  4. Choose + Requirement and pick what the caller must hold.

Requirements are chosen from your project's own roles, groups and permissions, not typed in. Each control is described in the route rule reference.

Choose where the service may connect

Under What it may reach, the console lists each destination it saw the service connect to. Tick the destinations the service needs.

To add a destination the service will need, enter its address or network and port, for example 10.0.0.0/8:5432, and choose Allow destination.

Save the draft and copy the audience

Choose Save draft. The console confirms with Draft saved: the rules are recorded, not enforced, until you turn protection on.

Saving gives the service its own API identity, registered as an application in your project. The Protect step now shows the audience. On the workload's page, under How it is reached, the API card has Copy audience.

Grant each caller access to the API

A caller must be granted access to the API before a token for it is issued. On the API card, choose Grant an application access to it. For a machine-to-machine application, the control on the application's page is Allow an API.

A caller then asks for a token by naming the audience in resource.

CallerHow it gets a token for this API
A serviceClient credentials
An AI agentAgent sign-in

A token issued for another API is denied here.

Turn on protection

While the draft is saved and protection is off, every request is forwarded, and each one that would have been denied is recorded with its reason. Read the audit record and correct the rules until it reads right.

Then open the Protect step again and choose Turn on protection…, and confirm with Turn on protection. The button is available after 24 hours of watching.

The console confirms that protection is turned on. It applies on the nodes running the workload within 30 seconds. From then on, a call is denied unless its access token was issued for this API, including callers that work today. The API card shows token required.

Check that it works

Get a token for the API as a granted machine-to-machine application.

curl https://acme.authfi.io/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials \
  -d resource="$API_CLIENT_ID"

$CLIENT_ID and $CLIENT_SECRET belong to the calling application. $API_CLIENT_ID is the audience you copied.

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6Ii4uLiJ9...",
  "token_type": "Bearer",
  "expires_in": 900
}

Call a route that the caller's rule allows, with access_token as a Bearer token. The request reaches your service. Call the same route with no token, and the request is denied with missing_token before it reaches your service.

What is checked on each request

The node makes these checks in order, before your service sees anything.

CheckDenied as
There is a Bearer tokenmissing_token
It is signed by your project, and has not expiredinvalid_token
The AI agent or session behind it has not been revokedinvalid_token
It is an access token, not an ID tokennot_an_access_token
It was issued for this APIaudience_mismatch
The route's rule is metpolicy_denied

Revocation is checked on every request. Revoke an AI agent or end a session, and the next request is denied, without waiting for the token to expire.

What a denied request receives

HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer realm="authfi", error="invalid_token"
X-Authfi-Ref: 7c1f0a2e
Content-Type: application/json

{ "error": "authfi_audience_mismatch", "message": "This credential was issued for a different service.", "ref": "7c1f0a2e" }

ref identifies this denial in the audit record, with the caller, the route and the reason. Give it to whoever asks why they were denied.

To see which rules deny most, open Security > Signals. See What is recorded.

Read who is calling

As for a web app, the request reaches your service with the caller in headers: X-AuthFI-User-ID, X-AuthFI-Groups and X-AuthFI-Roles. A request from an AI agent also carries X-AuthFI-Agent-ID.

Route rule reference

A rule names a method and a path, and what a caller must hold to call it. You edit rules under Route rules in the Protect step.

Controls

ControlMeaning
MethodGET, POST, PUT, PATCH, DELETE, or Any method
PathAn exact path, or a prefix ending in /*. A path starts with /.
Signed in / AnyoneWho may call the route
Permissions · all ofThe caller holds all of these
Groups · any ofThe caller is in any of these
Roles · any ofThe caller holds any of these
Require a recent MFAThe person signed in with multi-factor authentication (MFA) or a passkey

The last four are under + Requirement. A caller needs every permission listed and any one of the groups or roles.

Example

MethodPathRequires
POST/v1/refundsGroup finance, permission refunds.create, a recent MFA
GET/v1/payments/*Group finance or support
Any method/internal/*Role platform-admin
Any method/*Signed in: a valid token for this API

Which rule decides a request

The first rule that matches a request decides it.

More specific rules are tried first: an exact path before a prefix, and a longer prefix before a shorter one. In the example, /v1/refunds is decided by its own rule, not by /*.

When no rule matches

The list of rules ends with a fixed row, everything else. Once the workload is protected, a request that matches no rule above it is denied. An API with rules is closed by default, and you open the routes you mean to open.

Paths that are always let through

These paths are answered without a token, so that health checks and sign-in keep working.

KindPaths
Health/health, /healthz, /livez, /ready, /readyz
Discovery/.well-known/*
Sign-in/login, /register, /signup, /callback, /oauth/callback, /auth/* equivalents, and POST to /token
Browser basics/favicon.ico, /robots.txt, /static/*, /assets/*, any OPTIONS request
These paths are public

Do not serve anything private under these paths. A request to one of them reaches your service without a token.

Hand a service back

To remove protection from a service, open Zero-Code > Kubernetes, select the cluster and open the Services tab. The tab lists what the node agent found listening in the cluster, and whether each service is protected.

Choose Unprotect on the service's row and confirm with Hand it back. Every policy for the service is deactivated, and its port reopens the next time the nodes fetch their rules.

Protect on the same row does the opposite, with no confirmation. The service is denied by default from the next time the nodes fetch their rules: every caller without a grant is dropped in the kernel.

Troubleshooting

What you seeCauseFix
missing_tokenThe request has no Bearer tokenSend the access token as a Bearer token
invalid_tokenThe token is not signed by your project, has expired, or was revokedGet a new token from your project
not_an_access_tokenThe request carries an ID tokenSend the access token
audience_mismatchThe token was issued for another APIAsk for a token with this API's audience in resource
policy_deniedThe caller does not meet the route's ruleCompare the caller's roles, groups and permissions with the rule
invalid_target when asking for a tokenThe caller has no grant on the APIGrant the caller access to the API
"A path starts with /" when adding a routeThe path does not start with /Enter a path such as /orders or /admin/*
Turn on protection… is unavailable, with Watched for under 24 hoursThe workload has been watched for less than 24 hoursSave the draft. It records what it would deny in the meantime.

Next steps