Code Monkey home page Code Monkey logo

passport-ethereum's Introduction

passport-ethereum-siwe

Passport strategy for authenticating with Sign-In with Ethereum.

This module lets you authenticate using Sign-In with Ethereum in your Node.js applications. By plugging into Passport, Ethereum authentication can be easily and unobtrusively integrated into any application or framework that supports Connect-style middleware including Express.

Install

$ npm install passport-ethereum-siwe

Usage

The Ethereum authentication strategy authenticates users using an Ethereum wallet.

The strategy takes a verify function as an argument, which accepts address as an argument. address is the user's Ethereum address. When authenticating a user, this strategy obtains this information from a message signed by the user's wallet.

The verify function is responsible for determining the user to which the address belongs. In cases where the account is logging in for the first time, a new user record is typically created automatically. On subsequent logins, the existing user record will be found via its relation to the address.

Because the verify function is supplied by the application, the app is free to use any database of its choosing. The example below illustrates usage of a SQL database.

var EthereumStrategy = require('passport-ethereum-siwe');
var SessionNonceStore = require('passport-ethereum-siwe').SessionNonceStore;

var store = new SessionChallengeStore();

passport.use(new EthereumStrategy({ store: store },
  function verify(address, cb) {
    db.get('SELECT * FROM blockchain_credentials WHERE chain = ? AND address = ?', [
      'eip155:1',
      address
    ], function(err, row) {
      if (err) { return cb(err); }
      if (!row) {
        db.run('INSERT INTO users (username) VALUES (?)', [
          address
        ], function(err) {
          if (err) { return cb(err); }
          var id = this.lastID;
          db.run('INSERT INTO blockchain_credentials (user_id, chain, address) VALUES (?, ?, ?)', [
            id,
            'eip155:1',
            address
          ], function(err) {
            if (err) { return cb(err); }
            var user = {
              id: id,
              username: address
            };
            return cb(null, user);
          });
        });
      } else {
        db.get('SELECT rowid AS id, * FROM users WHERE rowid = ?', [ row.user_id ], function(err, row) {
          if (err) { return cb(err); }
          if (!row) { return cb(null, false); }
          return cb(null, row);
        });
      }
    });
  }
));

Define Routes

Two routes are needed in order to allow users to log in with their Ethereum wallet.

The first route generates a randomized nonce, saves it in the NonceStore, and sends it to the client-side JavaScript for it to be included in the signed message. This is necessary in order to protect against replay attacks.

router.post('/login/ethereum/challenge', function(req, res, next) {
  store.challenge(req, function(err, nonce) {
    if (err) { return next(err); }
    res.json({ nonce: nonce });
  });
});

The second route authenticates the signed message and logs the user in.

router.post('/login/ethereum',
  passport.authenticate('ethereum', { failWithError: true }),
  function(req, res, next) {
    res.json({ ok: true });
  },
  function(err, req, res, next) {
    res.json({ ok: false });
  });

Examples

License

The MIT License

Copyright (c) 2022 Jared Hanson <https://www.jaredhanson.me/>

passport-ethereum's People

Contributors

jaredhanson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

passport-ethereum's Issues

Usage of host header for domain validation leads to metamask warning

The problem:

In strategy.js, there's a validation for URI and Domain:

  var origin = utils.originalOrigin(req);
  if (origin !== siweMessage.uri) {
    return self.fail({ message: 'URI mismatch.' }, 403);
  }
  
  var domain = url.parse(origin).host;
  if (domain !== siweMessage.domain) {
    return self.fail({ message: 'Domain mismatch.' }, 403);
  }

It uses the Host header, which is the backend's domain (which is often different from the client's domain).
Earlier it was fine and I just used backend's domain for the message. But recently I noticed that MetaMask shows a warning to the user:
image
So for example, when my client is example.com, and the backend is api.example.com, this strategy allows me to use api.example.com, whereas MetaMask requires me to use example.com.

Possible solution:

In my personal project, I just made my own passport strategy and modified the validation to check the domain of origin:

const domain = url.parse(req.get("origin")).host;
if (siwe.domain !== domain)
  throw new Error({ message: "Domain does not match." });

Not sure if this strategy is still maintained, but would be nice if someone fixed it (or created another).

Works fine on localhost and http, but doesn't work with HTTPS

First, let me thank you @jaredhanson for all the great work you've done getting Passport to be such a useful set of modules (apologies if I'm minimizing the library of tools you've built).

I love that you built something for Ethereum, and given that it's only been about 2 weeks since release, there probably haven't been enough people working on it to run into these issues.

Heads up: This took me 12+ hrs to figure out, and to be honest, I'm not sure what the proper solution should be. But the source of the problem is in how tls is defined in the utils.js file.

This works fine in localhost and http. But when trying to access from https, I was constantly receiving a 403 Authorization Error, URI Mismatch. I thought this was something I had done, so I read all the documentation (like 5 times), tested all my code, and still couldn't identify the source. At first, I didn't even realize that http worked - I learned that by accident.

When I started playing with the module (around the 10th hour), that's when things started to click.

It's the logic for how tls is defined that seems to continue to default my Heroku server with SSL (when I'm hitting the https URL) to http which creates the URI Mismatch since https != http.

This is what I did to fix it on my end (had to bring this out from being a module and instead make it my own directory).

I hope that this is useful. Please let me know when you've resolved this. I don't know much about the technical details of http vs https, so I'm not sure if my solution is fully secure.

Thanks again for what you've built!

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.