-
Notifications
You must be signed in to change notification settings - Fork 63
Open
Labels
help wantedExtra attention is neededExtra attention is needed
Description
Describe the bug
Hello, the documentation lacks an example of a SIWE server. I was wondering if you can kindly help me debug my implementation.
When SIWE is enabled, the wallet successfully makes a request to /auth/v1/nonce and I can see a reply, but I never see a call to /auth/v1/authenticate
To Reproduce
Steps to reproduce the behavior:
git clone https://github.com/rhamnett/reown_flutter.gitflutter run --dart-define="PROJECT_ID=3de10c688399aa49889ff67453c20ae4" --dart-define="AUTH_SERVICE_URL=https://2264vhbgqg.execute-api.eu-west-1.amazonaws.com"- try to log in with siweAuthValue set to true -
final siweAuthValue = prefs.getBool('appkit_siwe_auth') ?? true; - Fails to sign
Error log:
flutter: 2024-11-21 11:18:54.937725 🐛 [SiweService] getNonce() called
flutter: [SIWEConfig] getNonce()
flutter: 2024-11-21 11:18:55.084693 📝 [AnalyticsService] send event 202: {"eventId":"7e64ea11-3854-4da0-ac93-b42936abbaf2","bundleId":"com.web3modal.flutterExample3","timestamp":1732187934936,"props":{"type":"track","event":"CLICK_SIGN_SIWE_MESSAGE","properties":{"network":"1"}}}
flutter: [SIWESERVICE] getNonce() => {"nonce":"73643","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjczNjQzIiwiaWF0IjoxNzMyMTg3OTM2LCJleHAiOjE3MzIxODgyMzZ9.e_y4AOzlinaFQIY5Voo55CGpRLseszNwtFHuOJJWPN0"}
flutter: [SIWEConfig] getMessageParams()
flutter: 2024-11-21 11:18:56.292061 🐛 [SiweService] createMessage() called
flutter: [SIWEConfig] createMessage()
flutter: {chainId: eip155:1, domain: appkit-lab.reown.com, nonce: 73643, uri: https://appkit-lab.reown.com/login, address: eip155:1:0x4B0321761aCfc2bdE49ece923647433B4F04Dd3E, version: 1, type: {t: eip4361}, nbf: null, exp: null, statement: Welcome to AppKit 1.0.4 for Flutter., requestId: null, resources: null, expiry: null, iat: 2024-11-21T11:18:56.291Z}
flutter: 2024-11-21 11:18:56.293713 🐛 [SiweService] formatMessage() called
flutter: 2024-11-21 11:18:56.296369 🐛 [SiweService] signMessageRequest() called
flutter: 2024-11-21 11:18:56.302327 🐛 [MagicService] postMessage({"type":"@w3m-app/RPC_REQUEST","payload":{"method":"personal_sign","params":["0x6170706b69742d6c61622e72656f776e2e636f6d2077616e747320796f7520746f207369676e20696e207769746820796f757220457468657265756d206163636f756e743a0a3078344230333231373631614366633262644534396563653932333634373433334234463034446433450a0a57656c636f6d6520746f204170704b697420312e302e3420666f7220466c75747465722e0a0a5552493a2068747470733a2f2f6170706b69742d6c61622e72656f776e2e636f6d2f6c6f67696e0a56657273696f6e3a20310a436861696e2049443a20310a4e6f6e63653a2037333634330a4973737565642041743a20323032342d31312d32315431313a31383a35362e3239315a","0x4B0321761aCfc2bdE49ece923647433B4F04Dd3E"]}})
My attempt at a SIWE server:
import express, { Request, Response } from 'express';
import { SiweMessage } from 'siwe';
import jwt from 'jsonwebtoken';
import serverlessExpress from '@vendia/serverless-express';
import dotenv from 'dotenv';
import bodyParser from 'body-parser';
// Load environment variables
dotenv.config();
// Ensure JWT_SECRET is loaded
if (!process.env.JWT_SECRET) {
throw new Error('JWT_SECRET is not defined in the environment variables.');
}
const app = express();
app.use(bodyParser.json()); // Replaced with body-parser for compatibility
const JWT_SECRET = process.env.JWT_SECRET;
const nonces: Record<string, boolean> = {}; // Temporary in-memory nonce storage
// Generate a new nonce and a preliminary token
app.get('/auth/v1/nonce', (req: Request, res: Response): void => {
console.log('[Nonce] Received request for new nonce');
const nonce = Math.floor(Math.random() * 1e6).toString();
nonces[nonce] = true;
console.log(`[Nonce] Generated nonce: ${nonce}`);
// Generate a temporary JWT token that includes the nonce
const tempToken = jwt.sign(
{ nonce },
JWT_SECRET,
{ expiresIn: '5m' } // Token valid for 5 minutes
);
console.log('[Nonce] Generated temporary token for nonce');
res.json({ nonce, token: tempToken });
});
// Authenticate using SIWE
app.post('/auth/v1/authenticate', async (req: Request, res: Response): Promise<void> => {
console.log('[Auth] Received authentication request');
try {
const { message, signature } = req.body;
console.log(`[Auth] Message: ${message}`);
console.log(`[Auth] Signature: ${signature}`);
if (!message || !signature) {
console.log('[Auth] Missing message or signature in request body');
res.status(400).json({ error: 'Message and signature are required.' });
return;
}
const siweMessage = new SiweMessage(message);
const fields = await siweMessage.validate(signature);
console.log(`[Auth] SIWE message validated. Fields: ${JSON.stringify(fields)}`);
if (!nonces[fields.nonce]) {
console.log(`[Auth] Invalid or expired nonce: ${fields.nonce}`);
res.status(400).json({ error: 'Invalid or expired nonce.' });
return;
}
delete nonces[fields.nonce];
console.log(`[Auth] Nonce ${fields.nonce} deleted from storage`);
// Generate the main authentication JWT
const authToken = jwt.sign(
{
address: fields.address,
domain: fields.domain,
issuedAt: fields.issuedAt,
},
JWT_SECRET,
{ expiresIn: '1h' } // Token valid for 1 hour
);
console.log(`[Auth] Generated auth token for address: ${fields.address}`);
res.json({
token: authToken,
address: fields.address,
message: 'Authentication successful.',
});
console.log('[Auth] Authentication successful');
} catch (error) {
console.error(`[Auth] Authentication error: ${(error as Error).message}`);
res.status(400).json({ error: (error as Error).message || 'An unknown error occurred.' });
}
});
// Retrieve user details
app.get('/auth/v1/me', (req: Request, res: Response): void => {
console.log('[User] Received request to retrieve user details');
const authHeader = req.headers.authorization;
if (!authHeader) {
console.log('[User] Authorization header is missing');
res.status(401).json({ error: 'Authorization header is missing.' });
return;
}
const token = authHeader.split(' ')[1];
console.log(`[User] Extracted token: ${token}`);
try {
const decoded = jwt.verify(token, JWT_SECRET) as jwt.JwtPayload;
console.log(`[User] Token decoded successfully: ${JSON.stringify(decoded)}`);
res.json({ address: decoded.address, domain: decoded.domain });
} catch (error) {
console.error(`[User] Token verification failed: ${(error as Error).message}`);
res.status(401).json({ error: (error as Error).message || 'Invalid token.' });
}
});
// Update user details
app.post('/auth/v1/update-user', (req: Request, res: Response): void => {
console.log('[User] Received request to update user');
const authHeader = req.headers.authorization;
if (!authHeader) {
console.log('[User] Authorization header is missing');
res.status(401).json({ error: 'Authorization header is missing.' });
return;
}
const token = authHeader.split(' ')[1];
console.log(`[User] Extracted token: ${token}`);
try {
const decoded = jwt.verify(token, JWT_SECRET) as jwt.JwtPayload;
const userAddress = decoded.address;
console.log(`[User] Token decoded successfully. User address: ${userAddress}`);
// Here you would update the user in your database.
const { metadata } = req.body;
console.log(`[User] Received metadata for update: ${JSON.stringify(metadata)}`);
// Update user metadata in the database associated with userAddress
res.status(200).json({ message: 'User updated successfully.' });
console.log('[User] User updated successfully');
} catch (error) {
console.error(`[User] Token verification failed: ${(error as Error).message}`);
res.status(401).json({ error: (error as Error).message || 'Invalid token.' });
}
});
// Sign out
app.post('/auth/v1/sign-out', (req: Request, res: Response): void => {
console.log('[Auth] Received sign-out request');
// Implement any necessary sign-out logic here
res.status(200).json({ message: 'Signed out successfully.' });
console.log('[Auth] User signed out successfully');
});
Expected behavior
/auth/v1/authenticate endpoint gets called
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
help wantedExtra attention is neededExtra attention is needed