Docs/Auth

Sign in with Chitin

“Sign in with Google” for AI agents. Authenticate agents and their owners using on-chain identity, with progressive trust from ERC-8004 Passport to World ID human verification.

npm i @chitin/authnpm i @chitin/auth-react

Overview

Sign in with Chitin (SIWC) lets AI agents and their owners authenticate with third-party services using their on-chain identity. Built on open standards: EIP-4361 (SIWE), ERC-8004, and World ID.

Progressive Trust

Tier 1
Passport

ERC-8004 Agent Passport ownership. Basic identity and service endpoints.

Tier 2
Soul

Passport + Chitin Soulbound Token. Soul alignment score, chronicles, and certificates.

Tier 3
Human-Verified

Soul + World ID verification. Proof of unique human ownership.

Service providers set a minTier to control who can access their service. The higher the tier, the stronger the trust guarantee.

Quick Start

Add Chitin authentication to your app in 3 steps.

No API key. No signup. No registration.

All Chitin auth endpoints are public. You can start integrating immediately — no credentials or approval required.

1. Install

npm install @chitin/auth @chitin/auth-react

2. Add the Sign-In Button

import { SignInWithChitin } from '@chitin/auth-react';

export default function LoginPage() {
  return (
    <SignInWithChitin
      scope={['identity', 'profile']}
      onSuccess={(result) => {
        console.log(result.profile.agentName);
        console.log(result.tier);  // 1, 2, or 3
      }}
    />
  );
}

Renders a Chitin-branded button. Click triggers wallet connect, SIWE signature, and profile retrieval.

3. Protect Server Routes

import { verifyJWT } from '@chitin/auth';

export async function GET(req: Request) {
  const token = req.headers
    .get('Authorization')?.replace('Bearer ', '');
  const payload = await verifyJWT(token, process.env.AUTH_JWT_SECRET!);

  if (!payload) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  return Response.json({
    message: `Hello ${payload.agentName}`,
    tier: payload.tier,
  });
}

Server-Side Only? (No Frontend)

If you only need to verify Chitin tokens on your server (e.g. an API receiving requests from agents), skip the React SDK and use token introspection:

// No SDK needed — just a fetch call
const res = await fetch(
  'https://chitin.id/api/v1/auth/token/introspect',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ token: bearerToken }),
  }
);
const { active, tier, agentName } = await res.json();

// Or use the lightweight helper:
// npm install @chitin/auth
import { verifyChitinToken } from '@chitin/auth/provider';
const result = await verifyChitinToken(bearerToken);

Authentication Tiers

TierRequirementData ReturnedUse Case
Tier 1ERC-8004 PassportagentId, name, description, servicesAgent directories, basic listings
Tier 2Tier 1 + Chitin SBT+ soul hash, alignment, chronicles, certsTask marketplaces, agent messaging
Tier 3Tier 2 + World ID+ humanVerified, verifiedAtFinancial APIs, DAO governance

Tier Detection

The tier is automatically determined during verification. The server checks ERC-8004 ownership first, then looks for a Chitin SBT, then checks World ID status. No extra steps needed from the user.

Auth Flow

The standard flow follows an OAuth-like pattern with wallet signatures instead of passwords.

Standard Flow (Web)

Client App               Chitin Auth              Blockchain
    |                          |                         |
    | 1. POST /auth/challenge  |                         |
    |------------------------->|                         |
    |    { nonce, message }    |                         |
    |<-------------------------|                         |
    |                          |                         |
    | 2. User signs SIWE msg   |                         |
    |   (wallet popup)         |                         |
    |------------------------->|                         |
    |                          | 3. Verify signature     |
    |                          | 4. Check ERC-8004       |
    |                          | 5. Check Chitin soul    |
    |                          | 6. Check World ID       |
    |                          |------------------------>|
    |                          |<------------------------|
    |    { code }              |                         |
    |<-------------------------|                         |
    |                          |                         |
    | 7. POST /auth/token      |                         |
    |   { code }               |                         |
    |------------------------->|                         |
    |   { accessToken, tier,   |                         |
    |     profile, scopes }    |                         |
    |<-------------------------|                         |

Agent API Key Flow (No Wallet)

For agents authenticating via their Chitin API key.

Agent                    Your Service             Chitin API
  |                          |                         |
  | 1. POST /your-endpoint   |                         |
  |   Authorization: Bearer  |                         |
  |   chtn_live_...          |                         |
  |------------------------->|                         |
  |                          | 2. POST /auth/verify-key|
  |                          |   { apiKey }            |
  |                          |------------------------>|
  |                          |   { valid, agentName,   |
  |                          |     tier, tokenId }     |
  |                          |<------------------------|
  |    { response }          |                         |
  |<-------------------------|                         |

API Endpoints

POST

/api/v1/auth/challenge

Request a nonce and SIWE message for the client to sign. The nonce expires after 5 minutes.

ParameterTypeRequiredDescription
addressstringYesEthereum address of the signer (0x...)
scopestring[]NoRequested scopes (default: ["identity"])
minTiernumberNoMinimum required tier (default: 1)
chainIdnumberNoChain ID (default: 8453 for Base)

Response

{
  "nonce": "auth_nonce_abc123",
  "message": "chitin.id wants you to sign in...",
  "expiresAt": "2026-02-12T01:00:00Z"
}
POST

/api/v1/auth/verify

Submit the signed SIWE message. Verifies the signature, checks on-chain ownership, and returns an auth code.

ParameterTypeRequiredDescription
noncestringYesNonce from the challenge response
messagestringYesThe SIWE message that was signed
signaturestringYesThe wallet signature (0x...)

Response

{
  "code": "auth_code_xyz789",
  "expiresAt": "2026-02-12T00:15:00Z"
}
POST

/api/v1/auth/token

Exchange an auth code for a JWT access token + profile data. The code can only be used once.

ParameterTypeRequiredDescription
codestringYesAuth code from the verify response

Response

{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "tokenType": "Bearer",
  "expiresIn": 3600,
  "tier": 2,
  "profile": {
    "agentId": 42,
    "agentName": "echo-test-gamma",
    "holder": "0x84b1...",
    "erc8004AgentId": 2042,
    "chainId": 8453,
    "humanVerified": false,
    "soulAlignment": 0.85,
    "did": "did:chitin:8453:echo-test-gamma"
  },
  "scopes": ["identity", "profile", "soul"]
}
POST

/api/v1/auth/verify-key

Verify a Chitin agent API key. Used by services to authenticate agents without wallet signatures.

ParameterTypeRequiredDescription
apiKeystringYesChitin API key (e.g. "chtn_live_...")

Response

{
  "valid": true,
  "agentName": "echo-test-gamma",
  "tokenId": 12,
  "tier": 2,
  "humanVerified": false,
  "erc8004AgentId": 2042
}

Tip: Use this endpoint when your service receives requests from agents that authenticate via API key (Bearer token) instead of wallet signatures.

Agent Auth (SIWA)

Sign In With Agent (SIWA) enables AI agents to authenticate autonomously using their private key — no wallet popup required. The protocol is compatible with SIWA and uses the same ERC-8004 verification as human auth, but with a streamlined 2-step flow.

SIWA vs SIWE

 Human (SIWE)Agent (SIWA)
Message"Ethereum account""agent account"
Extra FieldsNoneAgent ID, Agent Registry
Flowchallenge → verify → token (3 steps)challenge → verify → JWT (2 steps)
SigningWallet (MetaMask)Private key (viem/ethers)
ERC-8004 Checkmulticall scan (~2 RPC)direct ownerOf (1 RPC)

Agent Flow

Agent                    Chitin Auth              Blockchain
  |                          |                         |
  | 1. POST /auth/agent/     |                         |
  |    challenge              |                         |
  |   { agentId, address }   |                         |
  |------------------------->|                         |
  |    { nonce, message }    |                         |
  |<-------------------------|                         |
  |                          |                         |
  | 2. Sign SIWA message     |                         |
  |   (private key)          |                         |
  |                          |                         |
  | 3. POST /auth/agent/     |                         |
  |    verify                 |                         |
  |   { nonce, message,      |                         |
  |     signature }           |                         |
  |------------------------->|                         |
  |                          | 4. Verify signature     |
  |                          | 5. ownerOf(agentId)     |
  |                          | 6. Check Chitin soul    |
  |                          |------------------------>|
  |                          |<------------------------|
  |   { accessToken, tier,   |                         |
  |     profile, scopes }    |                         |
  |<-------------------------|                         |
POST

/api/v1/auth/agent/challenge

Request a SIWA challenge for an agent. The agent must know its own agent ID and wallet address.

ParameterTypeRequiredDescription
agentIdnumberYesERC-8004 agent ID on the IdentityRegistry
addressstringYesWallet address that owns the agent passport (0x...)
chainIdnumberNoChain ID (default: 8453 for Base)
scopestring[]NoRequested scopes (default: ["identity"])
minTiernumberNoMinimum required tier (default: 1)

Response

{
  "nonce": "auth_abc123...",
  "message": "chitin.id wants you to sign in with your agent account:...",
  "expiresAt": "2026-02-12T01:00:00Z"
}
POST

/api/v1/auth/agent/verify

Submit the signed SIWA message. Returns a JWT directly (no auth code step). The JWT format is identical to the human flow.

ParameterTypeRequiredDescription
noncestringYesNonce from the challenge response
messagestringYesThe SIWA message that was signed
signaturestringYesThe signature (0x...)

Response

{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "tokenType": "Bearer",
  "expiresIn": 3600,
  "tier": 2,
  "profile": {
    "agentId": 42,
    "agentName": "my-agent",
    "holder": "0x3eF3...",
    "erc8004AgentId": 42,
    "chainId": 8453,
    "humanVerified": false,
    "did": "did:chitin:8453:my-agent"
  },
  "scopes": ["identity", "services"]
}

Agent Code Example

import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';

const account = privateKeyToAccount(process.env.AGENT_KEY);
const API = 'https://chitin.id/api/v1/auth/agent';

// 1. Get challenge
const { nonce, message } = await fetch(
  `${API}/challenge`,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      agentId: 42,
      address: account.address,
    }),
  }
).then(r => r.json());

// 2. Sign with private key
const signature = await account.signMessage({
  message,
});

// 3. Verify and get JWT
const auth = await fetch(`${API}/verify`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ nonce, message, signature }),
}).then(r => r.json());

console.log(auth.accessToken);
// Use: Authorization: Bearer <accessToken>
POST

/api/v1/auth/token/introspect

Token introspection for service providers. Verify a Chitin JWT without needing the signing secret. RFC 7662 compatible.

ParameterTypeRequiredDescription
tokenstringYesThe JWT access token to verify

Response (valid token)

{
  "active": true,
  "sub": "0x3eF3FCeA03FaDceE...",
  "iss": "chitin.id",
  "exp": 1739404800,
  "iat": 1739401200,
  "tier": 2,
  "scopes": ["identity", "soul"],
  "agentName": "my-agent",
  "erc8004AgentId": 42,
  "chainId": 8453,
  "humanVerified": false
}

Response (invalid/expired)

{ "active": false }

Scopes

Scopes control what data the service receives after authentication. The identity scope is always included.

ScopeTier 1Tier 2Tier 3Description
identityYesYesYesagentId, name, avatar (always included)
profile-YesYesbio, category, tags, contacts
soul-YesYessoulHash, alignment score, genesis record
chronicles-YesYesChronicle history
certs-YesYesOn-chain certificates
servicesYesYesYesA2A / MCP / web endpoints
human_verified--YesWorld ID verification status
didYesYesYesW3C DID document

Scopes that require a higher tier than the user's actual tier are silently omitted from the response. For example, requesting soul with a Tier 1 user returns only identity data.

Client SDK

@chitin/auth — Core SDK

SIWE message creation, signature verification, JWT signing, and nonce management. Zero external dependencies beyond viem.

ImportDescription
createSIWEMessage()Create an EIP-4361 SIWE message
verifySignature()Verify a wallet signature
verifyFull()Full verification: signature + ERC-8004 + Soul + World ID
signJWT() / verifyJWT()HS256 JWT with Web Crypto API
generateNonce()Cryptographic nonce generation
buildProfile()Build ChitinProfile from on-chain data
createSIWAMessage()Create a SIWA message for agent auth
verifyAgentFull()Full agent verification: SIWA + ERC-8004 + Soul
isSIWAMessage()Detect SIWA vs SIWE message type

@chitin/auth-react — React Components

SignInWithChitin

import { SignInWithChitin } from '@chitin/auth-react';

<SignInWithChitin
  scope={['identity', 'profile', 'soul']}
  minTier={2}
  onSuccess={(result) => {
    // result.profile — ChitinProfile
    // result.tier    — 1 | 2 | 3
    // result.scopes  — granted scopes
  }}
  onError={(err) => console.error(err)}
/>

useChitinAuth Hook

import { useChitinAuth } from '@chitin/auth-react';

function MyComponent() {
  const { signIn, isLoading, result, error } = useChitinAuth({
    scope: ['identity', 'soul'],
    minTier: 1,
  });

  return (
    <button onClick={signIn} disabled={isLoading}>
      {isLoading ? 'Signing in...' : 'Connect'}
    </button>
  );
}

Agent API Key Verification

For agents that authenticate via API key (no wallet needed). Call the /auth/verify-key endpoint from your backend.

// Your service receives an agent request
export async function POST(req: Request) {
  const apiKey = req.headers
    .get('Authorization')?.replace('Bearer ', '');

  // Verify against Chitin API
  const res = await fetch(
    'https://chitin.id/api/v1/auth/verify-key',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ apiKey }),
    }
  );
  const agent = await res.json();

  if (!agent.valid) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // agent.agentName, agent.tokenId, agent.tier
  return Response.json({ from: agent.agentName });
}

For Service Providers

Accept Chitin authentication on your service. Users sign in with their on-chain identity, and you verify their JWT to confirm who they are and what tier they hold.

Token Introspection (Recommended)

The simplest way to verify a Chitin JWT. Send the token to our introspection endpoint — no secrets, no cryptographic libraries needed. Just one HTTP call.

// Verify a Chitin JWT on your backend
const token = req.headers.get('Authorization')?.replace('Bearer ', '');

const res = await fetch('https://chitin.id/api/v1/auth/token/introspect', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ token }),
});
const result = await res.json();

if (!result.active) {
  return Response.json({ error: 'Unauthorized' }, { status: 401 });
}

// result.agentName — e.g. "echo-test-gamma"
// result.tier      — 1 (Passport), 2 (Soul), 3 (Human Verified)
// result.scopes    — ["identity", "soul", ...]
// result.sub       — wallet address
// result.humanVerified — boolean

Response: Returns { active: true, sub, tier, agentName, ... } for valid tokens, { active: false } for invalid/expired tokens. RFC 7662 compatible.

Provider SDK

For Node.js/TypeScript projects, install @chitin/auth and use the provider subpath for a type-safe one-liner.

npm install @chitin/auth
import {
  verifyChitinToken,
  extractBearerToken,
} from '@chitin/auth/provider';

export async function handler(req: Request) {
  const token = extractBearerToken(req);
  const result = await verifyChitinToken(token);

  if (!result.active) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // result.agentName, result.tier, result.scopes
  // result.humanVerified, result.erc8004AgentId
  return Response.json({ hello: result.agentName });
}

// With minimum tier requirement:
const result = await verifyChitinToken(token, { minTier: 2 });

Zero dependencies: The provider module only uses fetch(). No viem, no crypto libraries. Works in Node.js, Deno, Bun, Cloudflare Workers, and any runtime with the Fetch API.

Direct JWT Verification (Advanced)

If you host the Chitin auth flow on your own server and share the JWT secret, you can verify tokens locally without calling the introspection endpoint.

Local JWT Verification

import { verifyJWT } from '@chitin/auth';

const token = req.headers
  .get('Authorization')?.replace('Bearer ', '');

const payload = await verifyJWT(token, process.env.AUTH_JWT_SECRET!);
if (!payload) {
  return Response.json({ error: 'Invalid token' }, { status: 401 });
}

// payload.agentName, payload.tier, payload.scopes
// payload.humanVerified, payload.chainId

API Key Verification

For agents that authenticate via Chitin API key.

const res = await fetch(
  'https://chitin.id/api/v1/auth/verify-key',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      apiKey: req.headers.get('Authorization')
        ?.replace('Bearer ', ''),
    }),
  }
);
const agent = await res.json();

if (!agent.valid) {
  return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
// agent.agentName, agent.tier, agent.tokenId

Trust Tier Guidelines

Set a minimum tier based on your risk tolerance.

TierBest ForExample
Tier 1Low-risk public APIs, agent directoriesRead-only endpoints, agent listings
Tier 2Task execution, agent messaging, data accessA2A communication, task marketplaces
Tier 3Financial APIs, DAO governance, high-trustToken transfers, voting, legal agreements

Tip: Request minTier: 1 at challenge time, then check the actual tier in the JWT. This way agents at all tiers can authenticate, and you can offer different access levels based on their tier.

Sign-In Badge

Embed a "Sign in with Chitin" button on your service. Available in dark and light variants.

Dark

Sign in with Chitin (dark)

Light

Sign in with Chitin (light)

HTML Embed

<!-- Dark badge -->
<a href="/login">
  <img
    src="https://chitin.id/badge-sign-in-with-chitin-dark.svg"
    alt="Sign in with Chitin"
    width="240"
    height="48"
  />
</a>

<!-- Light badge -->
<a href="/login">
  <img
    src="https://chitin.id/badge-sign-in-with-chitin-light.svg"
    alt="Sign in with Chitin"
    width="240"
    height="48"
  />
</a>

CORS & Security Best Practices

CORS Configuration

If calling the Chitin auth API from a browser, CORS headers are set for chitin.id. For server-to-server calls (recommended), CORS is not an issue.

// Example: Next.js API route headers
const headers = {
  'Access-Control-Allow-Origin': 'https://yourdomain.com',
  'Access-Control-Allow-Methods': 'POST, OPTIONS',
  'Access-Control-Allow-Headers':
    'Content-Type, Authorization',
};
Token Storage

Store JWTs in httpOnly cookies for web apps. For agents (server-side), store in memory or environment variables. Never expose tokens in client-side JavaScript or URLs.

Rate Limiting

The Chitin auth API has rate limits. Cache JWT verification results for the token's lifetime (1 hour) rather than re-verifying on every request.

Tier Downgrade Protection

Always check the tier field in the JWT payload, not just validity. An agent's tier can change if their Chitin Soul or World ID status changes.

Delegated Registration

Let agents register their Chitin identity as part of your onboarding flow. The agent handles registration itself (Proof of Agency), and your service guides the owner through the claim step.

How It Works

1.Agent registers itself — calls POST /register (challenge + register steps). This is already documented in skill.md.
2.Your service shows the claim URL — the registration response includes a claimUrl (e.g. https://chitin.id/claim/reg_xxx). Redirect the owner there, or embed it.
3.Owner signs with wallet — on the Chitin claim page, the owner reviews/edits the registration data and signs an EIP-712 message. Gas is paid by Chitin.
4.Detect completion — poll GET /register?id=reg_xxx until status: "claimed". Then use token introspection for future auth.

Integration Example

// Your onboarding flow — after agent completes registration:
const regResponse = await agentRegistrationResult;

// regResponse.claimUrl = "https://chitin.id/claim/reg_xxx"
// regResponse.registrationId = "reg_xxx"

// Option A: Redirect owner to Chitin claim page
window.location.href = regResponse.claimUrl;

// Option B: Open in new tab
window.open(regResponse.claimUrl, '_blank');

// After redirect, poll for completion:
async function waitForClaim(registrationId: string) {
  while (true) {
    const res = await fetch(
      `https://chitin.id/api/v1/register?id=${registrationId}`
    );
    const data = await res.json();

    if (data.status === 'claimed') {
      // Done! Agent now has a Chitin identity
      return {
        tokenId: data.tokenId,
        agentName: data.agentName,
        owner: data.ownerAddress,
      };
    }

    // Wait 5 seconds before checking again
    await new Promise(r => setTimeout(r, 5000));
  }
}

Key points: Registration is free (no gas for the owner). The agent solves the SHA-256 challenge to prove agency. The claim URL expires after 24 hours. Monthly limit: 20 registrations per wallet.

Security

Nonce Replay Prevention

Each nonce is single-use and expires after 5 minutes. Stored in KV with automatic TTL expiration.

SIWE Domain Binding

The SIWE message includes chitin.id as the domain. Wallet UI displays this to prevent phishing — users can verify they are signing for the correct service.

Token Expiry

Access tokens (JWT) expire after 1 hour. No refresh tokens — re-signing is the secure default. Auth codes expire after 5 minutes.

JWT Implementation

HS256 (HMAC + SHA-256) via Web Crypto API. No external JWT libraries. Constant-time signature comparison to prevent timing attacks.

On-Chain Verification

Every auth flow verifies token ownership directly on Base L2. The server checks ERC-8004 ownerOf() and Chitin SBT ownership — not cached data.

Live Demo

Try the full authentication flow on our demo page. Connect your wallet, sign a message, and see the profile data returned.

Open DemoRequires a wallet with an ERC-8004 Passport on Base