AgentID is the identity layer for AI agents. It lets an AI agent carry a time-limited cryptographic certificate proving its operator has authenticated via BankID. Third-party services verify the certificate offline using standard RS256 JWT verification — no round-trip to AgentID required.
As AI agents become more autonomous, we identified a structural gap in the internet’s architecture: agents can act, but they cannot prove who they represent.
Websites respond by blocking automated traffic because there is no reliable way to distinguish a legitimate personal agent from spam, or large-scale data harvesting.
AgentID is an identity verification layer for AI agents, powered by BankID, which 99.9% of Swedes use in the ages 18-67 (Internetstiftelsen, 2025).
-
MCP Server A one-click install MCP server for personal AI agents, enabling identity binding at the agent level.
-
Framework Integrations NPM packages for major web frameworks (Next.js, Express.js, etc.) that allow services to verify AgentID certificates with minimal integration effort. Next.JS:
, Express.JS:
-
Certificate Issuer Service (this repo) A central issuance service that binds a BankID-verified human identity to an AI agent and issues time-limited cryptographic credentials. https://agentidapp.vercel.app/
- An AI agent calls
POST /api/auth/startto request a new certificate. - AgentID creates a BankID session and returns an
authUrlto display to the user. - The user opens the URL, scans the BankID QR code, and approves.
- The agent polls
GET /api/auth/statusuntilstatus: "complete". - The agent attaches the signed JWT (
X-AgentID-Token) to all subsequent requests. - Any third-party service verifies the JWT signature using the public key at
/api/jwks. - The certificate expires after 1 hour — repeat from step 1.
Initiate a BankID authentication session.
Response
{
"sessionId": "uuid",
"authUrl": "https://agentidapp.vercel.app/auth/<sessionId>",
"expiresAt": "2026-01-01T00:00:00.000Z"
}Poll for the result of an authentication session.
Response
{
"status": "pending | complete | failed",
"jwt": "<signed RS256 JWT>"
}jwt is only present when status is "complete".
Public JWK set for offline RS256 JWT verification. Fetch once and cache for 1 hour.
Issued tokens use RS256 and contain:
{
"iss": "agentid",
"sub": "a3f8c2d1e4b7…",
"auth_method": "bankid",
"iat": 1700000000,
"exp": 1700003600,
"jti": "uuid-v4"
}subis a stable pseudonymous identifier —HMAC-SHA256(secret, personalNumber). Same person always maps to the same value, but the personal number is never exposed.expis alwaysiat + 3600(1 hour).jtiis unique per token.
npm install
npm run devThen open http://localhost:3000.
Create a .env.local file. Generate RSA keys and the HMAC secret with:
node scripts/generate-keys.mjs| Variable | Required | Description |
|---|---|---|
GRANDID_BASE_URL |
Yes | GrandID API base URL |
GRANDID_API_KEY |
Yes | GrandID API key |
GRANDID_SERVICE_KEY |
Yes | GrandID service key |
NEXT_PUBLIC_APP_URL |
Yes | Public base URL of this service |
JWT_PRIVATE_KEY |
Yes | RSA-2048 private key (PEM, base64-encoded) |
JWT_PUBLIC_KEY |
Yes | RSA-2048 public key (PEM, base64-encoded) |
JWT_HMAC_SECRET |
Yes | 32-byte hex secret for pseudonymous sub derivation |
REDIS_URL |
Prod only | Redis connection string for persistent session storage |
- RS256 — asymmetric signing; the private key never leaves the server.
- Pseudonymous identity — personal numbers are never stored or exposed in JWTs.
- Offline verification — consumers fetch the public key from
/api/jwksonce and verify locally. No runtime dependency on AgentID. - Short-lived tokens — 1-hour expiry limits the blast radius of a leaked token.
- Session isolation — each BankID session maps to a single-use AgentID session stored in Redis with a 10-minute TTL for pending sessions.
- Next.js (App Router)
- GrandID / E-identitet — BankID integration
- jose — RS256 JWT signing and JWKS
- Redis — session storage (via Vercel Storage Marketplace)
- Deployed on Vercel

