diff --git a/books/tutorials/mailchain/README.md b/books/tutorials/mailchain/README.md new file mode 100644 index 00000000..a61aedd0 --- /dev/null +++ b/books/tutorials/mailchain/README.md @@ -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. diff --git a/books/tutorials/mailchain/SUMMARY.md b/books/tutorials/mailchain/SUMMARY.md new file mode 100644 index 00000000..83a51ce4 --- /dev/null +++ b/books/tutorials/mailchain/SUMMARY.md @@ -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) diff --git a/books/tutorials/mailchain/book.json b/books/tutorials/mailchain/book.json new file mode 100644 index 00000000..1d348e0e --- /dev/null +++ b/books/tutorials/mailchain/book.json @@ -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." +} diff --git a/books/tutorials/mailchain/configure.md b/books/tutorials/mailchain/configure.md new file mode 100644 index 00000000..07890cb5 --- /dev/null +++ b/books/tutorials/mailchain/configure.md @@ -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: '

Hello!

Click the link below to finish signing in to Todos.

Sign in

', + } + }; + 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" diff --git a/books/tutorials/mailchain/logout.md b/books/tutorials/mailchain/logout.md new file mode 100644 index 00000000..56d76576 --- /dev/null +++ b/books/tutorials/mailchain/logout.md @@ -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! diff --git a/books/tutorials/mailchain/prompt.md b/books/tutorials/mailchain/prompt.md new file mode 100644 index 00000000..f4e9eeec --- /dev/null +++ b/books/tutorials/mailchain/prompt.md @@ -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 `

Sign in

`. +heading: + +```html +
+
+ + +
+ +
+``` + +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. diff --git a/books/tutorials/mailchain/send.md b/books/tutorials/mailchain/send.md new file mode 100644 index 00000000..97eecc27 --- /dev/null +++ b/books/tutorials/mailchain/send.md @@ -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. diff --git a/books/tutorials/mailchain/session.md b/books/tutorials/mailchain/session.md new file mode 100644 index 00000000..e37217ca --- /dev/null +++ b/books/tutorials/mailchain/session.md @@ -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/). diff --git a/books/tutorials/mailchain/setup.md b/books/tutorials/mailchain/setup.md new file mode 100644 index 00000000..72448591 --- /dev/null +++ b/books/tutorials/mailchain/setup.md @@ -0,0 +1,55 @@ +# Set Up Mailchain + +Now we need a way to send [Mailchain](https://mailchain.com/) messages from our +app. + +For the purposes of this tutorial, we suggest you create a Mailchain account for +testing. + +Navigate to [Mailchain](https://app.mailchain.com/register) and follow the steps +to register. + +You will be prompted to save your Secret Recovery Phrase, which we will use +later. + +You can also retrieve your Secret Recovery Phrase at any time by navigating to +**Settings > +[Secret Recovery Phrase](https://app.mailchain.com/settings/security/)**. Then +click **View** and enter your password to retrieve your Secret Recovery Phrase. + +Save your Secret Recovery Phrase somewhere safe and private. We will use it +later. + +Now that we have the Secret Recovery Phrase and a Mailchain address, let's +create a `'.env'` file to store them. + +```sh +$ touch .env +``` + +Add your FROM_ADDRESS and API key. The contents of the file should look +something like this: + +```sh +FROM_ADDRESS=user@mailchain.com +SECRET_RECOVERY_PHRASE=__INSERT_SECRET_RECOVERY_PHRASE__ +``` + +We also need to update our database schema to store a user's Mailchain address +and verification status. Open `'db.js'` and insert the following at line 16: + +```js +mailchain_address TEXT UNIQUE, \ +mailchain_address_verified INTEGER, \ +``` + +We will now delete the database and re-create it. NOTE: This will delete any +data you may have added in this tutorial so far. If you are considering adding +this solution to an existing app, you would simply run a DB migration to alter +your `users` table. + +```sh +$ rm ./var/db/todos.db +``` + +Next we will [configure the strategy](../configure/). diff --git a/books/tutorials/mailchain/verify.md b/books/tutorials/mailchain/verify.md new file mode 100644 index 00000000..545bb81b --- /dev/null +++ b/books/tutorials/mailchain/verify.md @@ -0,0 +1,79 @@ +# Verify Mailchain Address + +Now that we've sent the user a Mailchain message with a magic link, the next +step is to verify the Mailchain address when they click the link. + +Open `'routes/auth.js'`, add this route at line 83, below the +`'/login/mailchain/check'` route: + +```js +router.get( + "/login/mailchain/verify", + passport.authenticate("magiclink", { + successReturnToOrRedirect: "/", + failureRedirect: "/login", + }) +); +``` + +This route will verify the Mailchain address when the link is clicked. + +Now create a folder and corresponding view for our route. Run the following +commands: + +``` +$ mkdir views/login/mailchain +$ touch views/login/mailchain/check.ejs +``` + +Open `'views/login/mailchain/check.ejs'`, add the following code: + +```html + + + + + + Express • TodoMVC + + + + + +
+

todos

+

Check your Mailchain Inbox

+

+ We sent a magic link to your Mailchain address. Click the link in that + message to sign in. +

+
+

+ Didn't receive the message? Get another link +

+
+ + + +``` + +Finally, update `'views/index.ejs'` at line 15 to include the +`user.mailchain_address`: + +```html +
  • <%= user.name || user.username || user.email || user.mailchain_address %>
  • +``` + +We have configured the flow for users to click "Sign in", enter a +Mailchain address or ENS name and click "Sign in with Mailchain", which will send a magic link. + +Now we need to [establish a session](../session/) for a user logging in.