Skip to main content
This guide explains how to implement wallet-based authentication using message signing and JSON Web Tokens (JWTs). Users sign a timestamped message with their Phantom wallet, the server verifies the signature, and then issues a JWT for authenticated requests.

Overview

This authentication approach uses cryptographic message signing instead of passwords. The client signs a message with a timestamp, the backend verifies the signature, checks the timestamp, and returns a JWT. No challenge storage or session state is required.

Key components

  • Message signing: User signs a timestamped message with their Phantom wallet
  • Signature verification: Server verifies the signature using the wallet’s public key
  • User management: Server locates or creates a user record for the wallet
  • JWT issuance: Server returns a token for authenticated API requests
Timestamp reliability: Client-side clocks may drift, especially on mobile devices. For production use, consider fetching timestamps from the server or using the Date header from API responses.

Authentication flow

Backend implementation

1. Dependencies

2. Verification service

Create services/wallet-auth.ts:

3. Authentication route

Create routes/auth.ts:

4. Database schema (Prisma)

Add to your schema.prisma:
Run migration:

5. JWT authentication middleware

Create middleware/auth.ts:

6. Protected route example

Client implementation

1. Dependencies

2. API client

Create lib/api.ts:

3. React authentication context

Create context/AuthContext.tsx:

Security considerations

1. Timestamp validation

Messages include timestamps to prevent replay attacks. The server checks timestamps are recent and rejects older signed messages.

2. Message format validation

The server checks the message matches the expected structure so signatures cannot be reused in different contexts.

3. JWT token security

For production applications, use asymmetric signing algorithms such as PS256 or RS256. These separate public and private keys, improving key management and security.
Generate RSA key pair:
Example .env:
Sign and verify with RS256:

Using HS256 (simpler but less secure)

If you choose to use symmetric signing (HS256), store JWT_SECRET in environment variables:
Example .env:
Sign and verify with HS256:

4. HTTPS required

Always use HTTPS in production environments.

5. Rate limiting

Protect your authentication route from abuse:

6. Input validation

Always validate wallet addresses.

7. Error handling

Avoid exposing internal errors in API responses.

Advantages

Security

  • Prevents replay attacks
  • Ties signatures to specific wallet addresses and timestamps
  • Uses cryptographic verification rather than passwords
  • Requires no server-side sessions

Environment setup

Backend (.env)

Frontend (.env.local)

Testing

Manual tests with cURL

Troubleshooting

”Invalid signature” error

  • Ensure the message format exactly matches what the server expects
  • Verify the wallet address in the message matches the signing wallet
  • Check signature encoding (base58)

“Invalid or expired timestamp” error

  • Ensure the client and server clocks are synchronized
  • Check the timestamp is ISO 8601
  • Check the timestamp falls within a 5-minute window

”No token provided” error

  • Ensure Authorization: Bearer <token> header is included
  • Verify the token has not expired