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
Createservices/wallet-auth.ts:
3. Authentication route
Createroutes/auth.ts:
4. Database schema (Prisma)
Add to yourschema.prisma:
5. JWT authentication middleware
Createmiddleware/auth.ts:
6. Protected route example
Client implementation
1. Dependencies
2. API client
Createlib/api.ts:
3. React authentication context
Createcontext/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
Using RS256 (recommended)
Generate RSA key pair:.env:
Using HS256 (simpler but less secure)
If you choose to use symmetric signing (HS256), storeJWT_SECRET in environment variables:
.env:
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