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-reactOverview
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
ERC-8004 Agent Passport ownership. Basic identity and service endpoints.
Passport + Chitin Soulbound Token. Soul alignment score, chronicles, and certificates.
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
| Tier | Requirement | Data Returned | Use Case |
|---|---|---|---|
| Tier 1 | ERC-8004 Passport | agentId, name, description, services | Agent directories, basic listings |
| Tier 2 | Tier 1 + Chitin SBT | + soul hash, alignment, chronicles, certs | Task marketplaces, agent messaging |
| Tier 3 | Tier 2 + World ID | + humanVerified, verifiedAt | Financial 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
/api/v1/auth/challenge
Request a nonce and SIWE message for the client to sign. The nonce expires after 5 minutes.
| Parameter | Type | Required | Description |
|---|---|---|---|
| address | string | Yes | Ethereum address of the signer (0x...) |
| scope | string[] | No | Requested scopes (default: ["identity"]) |
| minTier | number | No | Minimum required tier (default: 1) |
| chainId | number | No | Chain ID (default: 8453 for Base) |
Response
{
"nonce": "auth_nonce_abc123",
"message": "chitin.id wants you to sign in...",
"expiresAt": "2026-02-12T01:00:00Z"
}/api/v1/auth/verify
Submit the signed SIWE message. Verifies the signature, checks on-chain ownership, and returns an auth code.
| Parameter | Type | Required | Description |
|---|---|---|---|
| nonce | string | Yes | Nonce from the challenge response |
| message | string | Yes | The SIWE message that was signed |
| signature | string | Yes | The wallet signature (0x...) |
Response
{
"code": "auth_code_xyz789",
"expiresAt": "2026-02-12T00:15:00Z"
}/api/v1/auth/token
Exchange an auth code for a JWT access token + profile data. The code can only be used once.
| Parameter | Type | Required | Description |
|---|---|---|---|
| code | string | Yes | Auth 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"]
}/api/v1/auth/verify-key
Verify a Chitin agent API key. Used by services to authenticate agents without wallet signatures.
| Parameter | Type | Required | Description |
|---|---|---|---|
| apiKey | string | Yes | Chitin 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 Fields | None | Agent ID, Agent Registry |
| Flow | challenge → verify → token (3 steps) | challenge → verify → JWT (2 steps) |
| Signing | Wallet (MetaMask) | Private key (viem/ethers) |
| ERC-8004 Check | multicall 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 } | |
|<-------------------------| |/api/v1/auth/agent/challenge
Request a SIWA challenge for an agent. The agent must know its own agent ID and wallet address.
| Parameter | Type | Required | Description |
|---|---|---|---|
| agentId | number | Yes | ERC-8004 agent ID on the IdentityRegistry |
| address | string | Yes | Wallet address that owns the agent passport (0x...) |
| chainId | number | No | Chain ID (default: 8453 for Base) |
| scope | string[] | No | Requested scopes (default: ["identity"]) |
| minTier | number | No | Minimum 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"
}/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| nonce | string | Yes | Nonce from the challenge response |
| message | string | Yes | The SIWA message that was signed |
| signature | string | Yes | The 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>/api/v1/auth/token/introspect
Token introspection for service providers. Verify a Chitin JWT without needing the signing secret. RFC 7662 compatible.
| Parameter | Type | Required | Description |
|---|---|---|---|
| token | string | Yes | The 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.
| Scope | Tier 1 | Tier 2 | Tier 3 | Description |
|---|---|---|---|---|
| identity | Yes | Yes | Yes | agentId, name, avatar (always included) |
| profile | - | Yes | Yes | bio, category, tags, contacts |
| soul | - | Yes | Yes | soulHash, alignment score, genesis record |
| chronicles | - | Yes | Yes | Chronicle history |
| certs | - | Yes | Yes | On-chain certificates |
| services | Yes | Yes | Yes | A2A / MCP / web endpoints |
| human_verified | - | - | Yes | World ID verification status |
| did | Yes | Yes | Yes | W3C 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.
| Import | Description |
|---|---|
| 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 — booleanResponse: 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.chainIdAPI 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.tokenIdTrust Tier Guidelines
Set a minimum tier based on your risk tolerance.
| Tier | Best For | Example |
|---|---|---|
| Tier 1 | Low-risk public APIs, agent directories | Read-only endpoints, agent listings |
| Tier 2 | Task execution, agent messaging, data access | A2A communication, task marketplaces |
| Tier 3 | Financial APIs, DAO governance, high-trust | Token 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
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
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',
};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.
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.
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
POST /register (challenge + register steps). This is already documented in skill.md.claimUrl (e.g. https://chitin.id/claim/reg_xxx). Redirect the owner there, or embed it.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
Each nonce is single-use and expires after 5 minutes. Stored in KV with automatic TTL expiration.
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.
Access tokens (JWT) expire after 1 hour. No refresh tokens — re-signing is the secure default. Auth codes expire after 5 minutes.
HS256 (HMAC + SHA-256) via Web Crypto API. No external JWT libraries. Constant-time signature comparison to prevent timing attacks.
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.