Available

Give an AI agent an identity

Register an AI agent, sign it in with a key only the agent holds, and receive a 15-minute access token.

In this tutorial you register an AI agent in your project, sign it in, and receive an access token that lasts 15 minutes.

An agent on AuthFI never has a shared secret. It holds a private key, you register the public half, and the agent signs in by signing a short statement with that key. This is the standard private_key_jwt method (RFC 7523) with an Ed25519 key.

Before you begin

You need:

  • An AuthFI project and its issuer, the project's URL. This tutorial uses https://acme.authfi.io.
  • Permission to register agents in the AuthFI console.
  • A group in Identity > Groups whose members answer for the agent.
  • Node.js 18 or later, or Python 3 with the cryptography package.

Create the agent's key

Generate an Ed25519 key pair where the agent runs. The script saves the private key to agent-key.pem and prints the public key, which is what you register.

// create-key.mjs
import { generateKeyPairSync } from "node:crypto";
import { writeFileSync } from "node:fs";

const { publicKey, privateKey } = generateKeyPairSync("ed25519");

// Keep this with the agent, and nowhere else.
writeFileSync("agent-key.pem", privateKey.export({ format: "pem", type: "pkcs8" }), { mode: 0o600 });

// Register this.
console.log(JSON.stringify(publicKey.export({ format: "jwk" })));

The script prints the public key as a JSON Web Key (JWK). You paste it into the console in a later step.

{ "kty": "OKP", "crv": "Ed25519", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" }

Name the agent

In the console, open Identity > AI Agents and select Register agent. Registration is a journey with the steps Name it, Its key, What it may do, Who it acts for, Review and Done.

On Name it, fill in these fields, then select Continue.

FieldWhat to enter
NameHow a person will recognise the agent, for example ticket-helper
What kind of agent is this?Works on its own
Group: who answers for it, and who is askedThe group that answers for the agent

An agent that works on its own is an autonomous agent. The other kind, Acts for a person, is a delegated agent with an owner. How agent sign-in works explains the difference.

Register the agent's key

On Its key, under Where does the key come from?, select The agent already has one. Fill in these fields, then select Continue.

FieldWhat to enter
Public JWKThe JSON the script printed. Paste the public half only.
Key idagent-key-1. The agent puts this in the header of each assertion it signs.
The console can make the key

The other choice, Make the key here, generates the key pair in your browser with Generate key pair. The Done step then shows the private key once, with Copy private key and Download.

Choose what the agent may do

On What it may do, under On this platform, open Allow and choose the permissions the agent needs. Then select Continue.

The console says permission where these docs say capability. A capability can carry limits on the details of an action. An agent holds the permission itself through a role, as a person does. Limit what an agent may do shows how to give the role and set the limits.

Review and register

An agent that works on its own skips Who it acts for. On Review, check the Name, Kind and Key id, then select Register ticket-helper. The button carries the name you gave the agent.

The Done step says the agent is registered and shows its Agent id, which starts with agt_. Select Copy beside it. The agent ID is the agent's client_id.

export AGENT_ID=<your agent ID>

Select Open ticket-helper to go to the agent's page. Its Connect tab lists the same value as Client id, with the Token endpoint and a worked example.

Sign in and get an access token

The agent signs a short statement, called a client assertion, and sends it to the token endpoint. The assertion proves that the caller holds the agent's private key.

POST

/apis/regional/v1/ai/auth/token

The assertion is a JSON Web Token (JWT) signed with the agent's key. It has these parts.

PartValue
Header algEdDSA
Header kidThe key ID you registered
iss and subThe agent ID
audThe token endpoint's full URL
jtiA new random value every time
expAt most 5 minutes after iat

The body of the request is form-encoded.

// sign-in.mjs
import { createPrivateKey, randomUUID, sign } from "node:crypto";
import { readFileSync } from "node:fs";

const ISSUER = "https://acme.authfi.io";
const TOKEN_URL = `${ISSUER}/apis/regional/v1/ai/auth/token`;
const b64 = (v) => Buffer.from(JSON.stringify(v)).toString("base64url");

const privateKey = createPrivateKey(readFileSync("agent-key.pem"));
const agentId = process.env.AGENT_ID; // agt_…
const now = Math.floor(Date.now() / 1000);
const head = b64({ alg: "EdDSA", typ: "JWT", kid: "agent-key-1" });
const body = b64({ iss: agentId, sub: agentId, aud: TOKEN_URL, jti: randomUUID(), iat: now, exp: now + 120 });
const signature = sign(null, Buffer.from(`${head}.${body}`), privateKey).toString("base64url");

const res = await fetch(TOKEN_URL, {
  method: "POST",
  headers: { "content-type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "client_credentials",
    client_id: agentId,
    client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
    client_assertion: `${head}.${body}.${signature}`,
    resource: `${ISSUER}/mcp`,
  }),
});
const token = await res.json();
console.log(token);

Replace https://acme.authfi.io with your project's issuer. AGENT_ID is the agent ID from the console, and agent-key-1 is the key ID you registered.

resource names the service the access token will be used at. Here it is the project's MCP gateway, the endpoint agents call tools through.

The token endpoint returns the access token.

{
  "access_token": "eyJhbGciOiJFZERTQSIs…",
  "token_type": "Bearer",
  "expires_in": 900,
  "agent_id": "agt_7f3a9c21",
  "type": "autonomous",
  "capabilities": ["read:tickets", "reply:tickets"]
}

capabilities lists the names of the permissions you allowed.

Use the access token

Send access_token as a Bearer credential in the Authorization header of each request the agent makes.

The access token lasts 15 minutes. When it expires, sign a new assertion and request another. An agent does not receive a refresh token.

An assertion is accepted once. Build a new one, with a new jti, for every request to the token endpoint.

To give the agent another capability later, open its Permissions tab and select Grant a permission.

Check that it works

Read the claims inside the access token. Add these lines to the end of the sign-in script and run it again.

const claims = JSON.parse(Buffer.from(token.access_token.split(".")[1], "base64url").toString());
console.log(claims.iss, claims.client_id, claims.agent_type);
https://acme.authfi.io agt_7f3a9c21 autonomous

If you see your issuer, your agent ID and autonomous, the agent has an identity and a working access token. A service that receives the access token validates its signature first. See Accept agents at your own API.

Troubleshooting

The token endpoint answers a failure with a standard OAuth error in error.

What you seeCauseFix
invalid_clientThe agent ID is unknown, the agent was revoked, or the assertion did not verifyCheck kid, aud and the clock, and that jti is new
invalid_targetresource names something this agent has not been grantedAsk an administrator to grant the agent that target
unsupported_grant_typegrant_type is missing or not one this endpoint offersUse client_credentials
invalid_requestThe body could not be readSend the body form-encoded

Every error the token endpoint returns is listed in Errors.

Next steps