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

Mailchain tutorial #108

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions books/tutorials/mailchain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Introduction

Mailchain is the communication layer for Web3 offering private, end-to-end encrypted email using
blockchain wallet addresses and web3 identities (e.g. an Ethereum address, ENS name, Unstoppable
Domains name, etc.). Instead of users needing to sign data with a blockchain wallet, _passwordless
authentication with magic links for Passport.js_ improves the safety of users.

Key feature include:

- Users can signup and login to your application without passwords or needing to sign data with their wallets.
- Mailchain supports magic links sent to ENS names, Ethereum addresses, Mailchain accounts and more.
- Passport handles secure token generation, expiration and confirmation.

In this tutorial, we will build a todo list app, complete with functionality
that allows users to sign in with Mailchain. By following along with this tutorial, you will
learn how to use Passport for passwordless authentication using Mailchain to send magic links.

If you want to see where we are headed, here's an example of the final result:
[https://github.com/passport/todos-express-mailchain](https://github.com/passport/todos-express-mailchain)

Before we dive in, you'll need:

1. A working development environment with [Node.js](https://nodejs.org/)
and [Git](https://git-scm.com/)
1. An editor and terminal of your
choosing.
1. A [Mailchain](https://mailchain.com/) account. If you don't already have one, it's free to sign up.

Take a moment to set these up if you have not already done so.

Let's get started!

We are going to start with a starter app, which has all the scaffolding needed
to build a todo list. Let's clone the app:

```sh
$ git clone https://github.com/passport/todos-express-starter.git mailchain-tutorial
```

You now have a directory named `'mailchain-tutorial'`. Let's `cd` into it:

```sh
$ cd mailchain-tutorial
```

Take a moment browse through the files in the starter app. As we work through
this tutorial, we'll be using [Express](https://expressjs.com/) as our web
framework, along with [EJS](https://ejs.co/) as our template engine and [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS)
for styling. We will use [SQLite](https://github.com/mapbox/node-sqlite3) as
our database for storing data. Don't worry if you are not familiar with these
technologies -- the necessary code will be provided at each step.

Now, let's install the dependencies:

```sh
$ npm install
```

And start the server:

```sh
$ npm start
```

Let's check to see if its working. Open [http://localhost:3000](http://localhost:3000)
in your browser. You should be greeted with a page explaining how todos help
you get things done.

Next, we will [add a login page](prompt/) to the app.
10 changes: 10 additions & 0 deletions books/tutorials/mailchain/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Summary

* [Introduction](README.md)
* [Login Prompt](prompt.md)
* [Set Up Mailchain](setup.md)
* [Configure Strategy](configure.md)
* [Send Mailchain Message](send.md)
* [Verify Mailchain Address](verify.md)
* [Establish Session](session.md)
* [Log Out](logout.md)
4 changes: 4 additions & 0 deletions books/tutorials/mailchain/book.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "Mailchain Magic Link Tutorial",
"description": "In this tutorial you will build an Express app that lets users log in using their Mailchain or web3 email address by clicking a magic link."
}
96 changes: 96 additions & 0 deletions books/tutorials/mailchain/configure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Configure Strategy

Now that we've set up Mailchain, we are ready to configure Passport and the
`passport-magic-link` strategy.

Install the necessary dependencies:

```sh
$ npm install passport
$ npm install passport-magic-link
$ npm install @mailchain/sdk
```

Open `'routes/auth.js'` and `require` the newly installed packages at line 2,
below where `express` is `require`'d:

```js
var passport = require("passport");
var MagicLinkStrategy = require("passport-magic-link").Strategy;
var Mailchain = require("@mailchain/sdk").Mailchain;
var db = require("../db");
```

The app's database is also `require`'d.

Add the following code at line 8 to configure the `MagicLinkStrategy`.

```js
var mailchain = Mailchain.fromSecretRecoveryPhrase(process.env.SECRET_RECOVERY_PHRASE);
var fromAddress = process.env['FROM_ADDRESS'] || mailchain.user().address;
let createMailchainAddress = function(address) {
switch (address) {
case address.match(/^[\d\w\-\_]*@mailchain\.com$/)?.input: // Mailchain address:
return address
case address.match(/^0x[a-fA-F0-9]{40}$/)?.input: // Ethereum address:
return address + '@ethereum.mailchain.com'
case address.match(/^.*\.eth$/)?.input: // ENS address:
return address + '@ens.mailchain.com'
case address.match(/^.*\.*@mailchain$/)?.input: // Mailchain address without .com:
return address + '.com'
default:
console.error("Invalid address");
}
}
passport.use(new MagicLinkStrategy({
secret: 'keyboard cat', // change this to something secret
userFields: [ 'mailchain_address' ],
tokenField: 'token',
verifyUserAfterToken: true
}, async function send(user, token) {
var link = 'http://localhost:3000/login/mailchain/verify?token=' + token;

var msg = {
to: [ createMailchainAddress(user.mailchain_address) ],
from: fromAddress,
subject: 'Sign in to Todos',
content: {
text: 'Hello! Click the link below to finish signing in to Todos.\r\n\r\n' + link,
html: '<h3>Hello!</h3><p>Click the link below to finish signing in to Todos.</p><p><a href="' + link + '">Sign in</a></p>',
}
};
return await mailchain.sendMail(msg);
}, function verify(user) {
return new Promise(function(resolve, reject) {
db.get('SELECT * FROM users WHERE mailchain_address = ?', [
user.mailchain_address
], function(err, row) {
if (err) { return reject(err); }
if (!row) {
db.run('INSERT INTO users (mailchain_address, mailchain_address_verified) VALUES (?, ?)', [
user.mailchain_address,
1
], function(err) {
if (err) { return reject(err); }
var id = this.lastID;
var obj = {
id: id,
mailchain_address: user.mailchain_address
};
return resolve(obj);
});
} else {
return resolve(row);
}
});
});
}));
```

This configures the `MagicLinkStrategy` to sanitize the input address, then send
mails containing a magic link using Mailchain. When the user clicks on the magic
link, the user record associated with the Mailchain address will be found. If a
user record does not exist, one is created the first time someone signs in.

The strategy is now configured. Next we need to
[send the user a magic link](../send/) when they click "Sign in with Mailchain"
19 changes: 19 additions & 0 deletions books/tutorials/mailchain/logout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Log Out

Now that users can sign in, they'll need a way to sign out.

Open `'routes/auth.js'` and add this route at line 84, below the
`'/login/mailchain/verify'` route:

```js
router.post('/logout', function(req, res, next) {
req.logout(function(err) {
if (err) { return next(err); }
res.redirect('/');
});
});
```

Return to the app, where you should already be signed in, and click "Sign out."

We've now got a working app where users can sign in and sign out!
61 changes: 61 additions & 0 deletions books/tutorials/mailchain/prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Login Prompt

We want to let users sign in with their Mailchain account or any valid Mailchain
address. For that, we need a login page that prompts the user to enter their
Mailchain address. Let's add that now.

Let's create a file that will contain authentication-related routes:

```sh
$ touch routes/auth.js
```

Add the following code to that file, which creates a login route that will
render the login page.

```js
var express = require('express');

var router = express.Router();

router.get('/login', function(req, res, next) {
res.render('login');
});

module.exports = router;
```

Next, we need to add this route to the app. Open `'app.js'` and `require` the
newly created auth route at line 10, below where `'routes/index'` is
`require`'d:

```js
var authRouter = require('./routes/auth');
```

Continuing within `'app.js'`, use the newly `require`'d `authRouter` at line 27,
below where `indexRouter` is `use`'d.

```js
app.use('/', authRouter);
```

Now we will update the login page so the use can enter a Mailchain address.

Open `'views/login.ejs'` and add the form at line 15, below `<h1>Sign in</h1>`.
heading:

```html
<form action="/login/mailchain" method="post">
<section>
<label for="mailchain_address">Mailchain Address or ENS Name</label>
<input id="mailchain_address" name="mailchain_address" type="text" autocomplete="username"
placeholder="...@mailchain.com or ensname.eth" required autofocus>
</section>
<button type="submit">Sign in with Mailchain</button>
</form>
```

Refresh the page. We've now got a login page that prompts the user to sign in
with Mailchain. Next, we will [set up Mailchain](../setup/), in preparation for
sending the user a magic link.
33 changes: 33 additions & 0 deletions books/tutorials/mailchain/send.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Send Web3 Email

Now that we are prompting the user for their Mailchain address, and have the
strategy configured, the next step is to send the user a Mailchain message when
they click "Sign in with Mailchain."

Open `'routes/auth.js'`, add this route at line 74, below the `'/login'` route:

```js
router.post('/login/mailchain', passport.authenticate('magiclink', {
action: 'requestToken',
failureRedirect: '/login'
}), function(req, res, next) {
res.redirect('/login/mailchain/check');
});
```

This route will process the form on the login page and send a Mailchain message to
the user.

Continuing within `'routes/auth.js'`, add this route at line 63, below the newly
added `'/login/mailchain'` route:

```js
router.get('/login/mailchain/check', function(req, res, next) {
res.render('login/mailchain/check');
});
```

This route will render a page instructing the user to check their Mailchain account
and click the link.

Next, we will [verify the Mailchain address](../verify/) when the user clicks that link.
69 changes: 69 additions & 0 deletions books/tutorials/mailchain/session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Establish Session

Once we've verified the user's Mailchain address, we need a login session to
remember the fact that the user has authenticated as they navigate the app.

To do that, we'll add session support. Begin by installing the necessary
dependencies:

```sh
$ npm install express-session
$ npm install connect-sqlite3
```

Open `'app.js'` and `require` the additional dependencies at line 8, below
where `'morgan'` is `require`'d:

```js
var passport = require('passport');
var session = require('express-session');

var SQLiteStore = require('connect-sqlite3')(session);
```

Add the following code at line 29, after `express.static` middleware, to
maintain and authenticate the session.

```js
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false,
store: new SQLiteStore({ db: 'sessions.db', dir: './var/db' })
}));
app.use(passport.authenticate('session'));
```

Finally, we need to configure Passport to manage the login session. Open
`'routes/auth.js'` and add the following code at line 70, below our Passport strategy:

```js
passport.serializeUser(function(user, cb) {
process.nextTick(function() {
cb(null, { id: user.id, mailchain_address: user.mailchain_address });
});
});

passport.deserializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, user);
});
});
```

Now, let's retry signing in. Start the server:

```sh
$ npm start
```

Open [http://localhost:3000](http://localhost:3000),
click "Sign in", enter your Mailchain address and click "Sign in with Mailchain".

Now, check your Mailchain Inbox and click the link.

We are logged in! Go ahead and enter some tasks you've been needing to get
done.

At this point, users can sign in with Mailchain! Next, we will add the ability to
[sign out](../logout/).
Loading