Authentication
Every POST request from the client to a snap server MUST be authenticated with JSON Farcaster Signatures (JFS).
How It Works
When a user taps a post button, the Farcaster client:
- Collects all input values from the current page
- Builds a payload with the user's identity (user.fid), timestamp, audience, and other payload fields that are unrelated to authentication.
- Signs the payload using the user's Farcaster signer key
- Sends the signed JFS compact string as the POST body
The snap server then:
- Verifies the JFS signature cryptographically
- Checks the signing key against hub state for the claimed FID
- Validates that
audiencematches the server's origin - Validates
timestampfor replay protection - Processes the request and returns a new page
JFS Payload Shape
The decoded JFS payload (signed inside JFS, not sent as bare JSON):
{
"fid": 12345,
"inputs": {
"guess": "CLASS",
"vote": "Tabs"
},
"timestamp": 1710864000,
"audience": "https://snap.example.com",
"user": {
"fid": 12345
},
"surface": {
"type": "standalone"
}
}
Audience
The payload MUST contain an audience field set to the origin of the snap server the
request is intended for. Origin = scheme + host + port (port only needed if it is not
the default port for that scheme)
Servers MUST reject requests where audience does not match the server's own origin.
This prevents a signed payload meant for one snap from being replayed against a
different snap server.
Replay protection
The payload MUST contain a timestamp field (Unix seconds).
Servers MUST reject requests with timestamps outside an allowed skew (default 5 minutes).
If strict replay protection is needed beyond that, clients should add a nonce field
(unique per request) to the signed payload.
Requirements
- The client MUST send a valid JFS for every authenticated POST
- The client MUST include the user's FID,
audience(server origin) andtimestamp(Unix seconds) in every payload - The server MUST verify the JFS cryptographically and MUST verify the signing key against hub (or equivalent) state for the FID
- The server MUST verify that
audiencematches its own origin - The server MUST enforce timestamp skew checks
JSON Farcaster Signatures (JFS) Format
JFS is a standardized way for Farcaster identities to sign arbitrary payloads. It consists of three components:
- Header — metadata (FID, key type, key)
- Payload — the content being signed
- Signature — the cryptographic signature
Compact Serialization
JFS uses a dot-separated format similar to JWT:
BASE64URL(header) . BASE64URL(payload) . BASE64URL(signature)
The signing input is constructed as:
ASCII(BASE64URL(UTF8(Header)) || '.' || BASE64URL(Payload))
Key Types
JFS supports three key types:
| Type | Signature Method | Description |
|---|---|---|
custody | ERC-191 | Signature from the FID's custody address |
auth | ERC-191 | Signature from a registered auth address |
app_key | EdDSA | Signature from a registered App Key |
For snaps, the client typically uses app_key (EdDSA signature from the user's
registered signer key).
Verification
To verify a JFS:
- Decode the header and extract the
fid,type, andkey - Verify the FID is registered and the key is active for that FID
- Verify the signature matches the signing input using the declared key
- Query a Farcaster Hub to confirm the key is currently associated with the FID
Reference Implementation
The official JFS Node.js package is
@farcaster/jfs.
Server-Side Verification with @farcaster/snap-hono
The @farcaster/snap-hono package handles JFS verification automatically:
import { registerSnapHandler } from "@farcaster/snap-hono";
registerSnapHandler(
app,
async (ctx) => {
// ctx.action.fid is verified — the JFS signature was checked
// ctx.action.inputs contains the user's input values
// Distinguish buttons via distinct submit target URLs on each button (ctx.request.url)
},
{
skipJFSVerification: false, // set to `true` for local dev
},
);
Set SKIP_JFS_VERIFICATION=1 in your environment to skip JFS verification for local
development.