Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assistance with SIWE #41

Open
rhamnett opened this issue Nov 21, 2024 · 19 comments
Open

Assistance with SIWE #41

rhamnett opened this issue Nov 21, 2024 · 19 comments
Assignees
Labels
help wanted Extra attention is needed

Comments

@rhamnett
Copy link

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:

  1. git clone https://github.com/rhamnett/reown_flutter.git
  2. flutter run --dart-define="PROJECT_ID=3de10c688399aa49889ff67453c20ae4" --dart-define="AUTH_SERVICE_URL=https://2264vhbgqg.execute-api.eu-west-1.amazonaws.com"
  3. try to log in with siweAuthValue set to true - final siweAuthValue = prefs.getBool('appkit_siwe_auth') ?? true;
  4. 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

@rhamnett rhamnett changed the title Aisstance with SIWE Assistance with SIWE Nov 21, 2024
@quetool
Copy link
Member

quetool commented Nov 21, 2024

Hello @rhamnett, here you can see how we constructed our SIWE service example https://github.com/reown-com/reown_flutter/blob/develop/packages/reown_appkit/example/modal/lib/services/siwe_service.dart

/auth/v1/authenticate endpoint is being called during siweService.verifyMessage(); which is being called here https://github.com/reown-com/reown_flutter/blob/develop/packages/reown_appkit/example/modal/lib/home_page.dart#L125

But this is just how we constructed it for explanatory purposes, you don't necessarily need to follow our way. Essentially the SIWEConfig we provide it's just "glue" between AppKit and your backend service but your backend service can be whatever you want

@rhamnett
Copy link
Author

Thanks so much again for your response.

I have copied the entire siwe service and I can see a successful request for a nonce but I never get a call to the verify/auth. I must be missing something.

@quetool
Copy link
Member

quetool commented Nov 21, 2024

Because you are missing a bunch of --dart-define variable that we run on our side. Again, the purpose of that SIWE service is just explanatory.

@rhamnett
Copy link
Author

OK thanks I'll figure it out.

@rhamnett rhamnett closed this as not planned Won't fix, can't repro, duplicate, stale Nov 21, 2024
@rhamnett
Copy link
Author

@quetool in the original post I did mention that I provide the --dart-define="AUTH_SERVICE_URL=https://2264vhbgqg.execute-api.eu-west-1.amazonaws.com" in my flutter run command and I can see that I'm hitting the nonce generation server side, and correctly receiving the nonce and token in the SIWE Service.....just the auth never gets called.

I can't see any other defines that I might be missing, so just curious as to any pointers?

@quetool
Copy link
Member

quetool commented Nov 21, 2024

Any chance you can share your project so I can clone and run?

@rhamnett
Copy link
Author

Any chance you can share your project so I can clone and run?

Yes it was in the original post instructions:

https://github.com/rhamnett/reown_flutter

@quetool
Copy link
Member

quetool commented Nov 21, 2024

Yes, sorry, allow me some time

@rhamnett
Copy link
Author

No problem at all, please take your time - appreciate any support.

@quetool
Copy link
Member

quetool commented Nov 21, 2024

Hello @rhamnett, I do see verifyMessage() (therefor /auth/v1/authenticate endpoint) getting called:

Screenshot 2024-11-21 at 16 06 38

Replace your _initializeService() with this one and try again

void _initializeService(_) async {
  ReownAppKitModalNetworks.removeTestNetworks();
  ReownAppKitModalNetworks.removeSupportedNetworks('solana');

  // Add this network as the first entry
  final etherlink = ReownAppKitModalNetworkInfo(
    name: 'Etherlink',
    chainId: '42793',
    currency: 'XTZ',
    rpcUrl: 'https://node.mainnet.etherlink.com',
    explorerUrl: 'https://etherlink.io',
    chainIcon: 'https://cryptologos.cc/logos/tezos-xtz-logo.png',
    isTestNetwork: false,
  );

  ReownAppKitModalNetworks.addSupportedNetworks('eip155', [etherlink]);

  try {
    _appKitModal = ReownAppKitModal(
      context: context,
      projectId: DartDefines.projectId,
      logLevel: LogLevel.all,
      metadata: _pairingMetadata(),
      siweConfig: _siweConfig(true),
      enableAnalytics: true, // OPTIONAL - null by default
      includedWalletIds: {},
      featuredWalletIds: {
        'f71e9b2c658264f7c6dfe938bbf9d2a025acc7ba4245eea2356e2995b1fd24d3', // m1nty
        'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96', // Metamask
      },
    );

    overlay = OverlayController(
      const Duration(milliseconds: 200),
      appKitModal: _appKitModal,
    );

    _toggleOverlay();

    setState(() => _initialized = true);
  } on ReownAppKitModalException catch (e) {
    debugPrint('⛔️ ${e.message}');
    return;
  }
  // modal specific subscriptions
  _appKitModal.onModalConnect.subscribe(_onModalConnect);
  _appKitModal.onModalUpdate.subscribe(_onModalUpdate);
  _appKitModal.onModalNetworkChange.subscribe(_onModalNetworkChange);
  _appKitModal.onModalDisconnect.subscribe(_onModalDisconnect);
  _appKitModal.onModalError.subscribe(_onModalError);
  // session related subscriptions
  _appKitModal.onSessionExpireEvent.subscribe(_onSessionExpired);
  _appKitModal.onSessionUpdateEvent.subscribe(_onSessionUpdate);
  _appKitModal.onSessionEventEvent.subscribe(_onSessionEvent);

  // relayClient subscriptions
  _appKitModal.appKit!.core.relayClient.onRelayClientConnect.subscribe(
    _onRelayClientConnect,
  );
  _appKitModal.appKit!.core.relayClient.onRelayClientError.subscribe(
    _onRelayClientError,
  );
  _appKitModal.appKit!.core.relayClient.onRelayClientDisconnect.subscribe(
    _onRelayClientDisconnect,
  );
  // _appKitModal.appKit!.core.addLogListener(_logListener);

  //
  await _appKitModal.init();

  DeepLinkHandler.init(_appKitModal);
  DeepLinkHandler.checkInitialLink();

  setState(() {});
}

@rhamnett
Copy link
Author

@quetool Thanks again - I can see your verify attempts in my backend logs.

The issue appears to be when I am using social logins, can you try with apple signin? i dont see any verify request in the backend....it hangs.

@quetool
Copy link
Member

quetool commented Nov 21, 2024

Is social features working for you or do you see any errors when loading appkit?

@rhamnett
Copy link
Author

if i turn off swe then i can log in fine with Apple,

when turning on SWIE i get it hanging after it's successfully got the nonce from the server, pls see my original post for the error log :)

@quetool
Copy link
Member

quetool commented Nov 21, 2024

Hello @rhamnett! Can you add this somewhere in your widget tree?

AppKitModalAccountButton(appKitModal: appKit, custom: const SizedBox.shrink()),

@rhamnett
Copy link
Author

Hello @rhamnett! Can you add this somewhere in your widget tree?

AppKitModalAccountButton(appKitModal: appKit, custom: const SizedBox.shrink()),

Sure. Do you want me to replace the existing or add this as well as?

@quetool
Copy link
Member

quetool commented Nov 21, 2024

Add this in a part of the widget tree that doesn't get disposed

@rhamnett
Copy link
Author

@quetool that works, thanks again. Is this something I have simply done wrong or is there a fix required?

@quetool
Copy link
Member

quetool commented Nov 25, 2024

Probably something that we can do better on our side. We'll take a look in the coming days and let you know.

@rhamnett
Copy link
Author

Thanks! No rush here, no blockers.

Will re-open to keep it on the list.

@rhamnett rhamnett reopened this Nov 25, 2024
@quetool quetool self-assigned this Dec 3, 2024
@quetool quetool added the help wanted Extra attention is needed label Dec 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants