Welcome folks today in this blog post we will be implementing passwordless
auth to send magic link
to user’s email address in node.js and express using the nodemailer
library. All the full source code of the application is shown below.
Get Started
In order to get started you need to make a new node.js
project using the below command
npm init -y
npm i express
npm i uuid
npm i nodemailer
And now you need to make an index.js
file and copy paste the following code
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
const express = require("express"); const nodemailer = require("nodemailer"); const uuid = require("uuid"); // Set up Nodemailer transport const transporter = nodemailer.createTransport({ service: "gmail", auth: { type: "OAuth2", user: "sharmagautam1997dob@gmail.com", accessToken:"##youraccesstoken##", }, }); // User database const users = [ { id: 1, email: "geekygautam1997@gmail.com", magicCode: null }, { id: 2, email: "nehajoshi1982dob@gmail.com", magicCode: null }, ]; const app = express(); app.use(express.json()); // Serve HTML file app.get("/", (req, res) => { res.sendFile(__dirname + "/index.html"); }); app.listen(3000, () => { console.log("Server listening on port 3000"); }); |
As you can see we are making a simple express
app and also we are importing the nodemailer library and configuring it by passing the email address
from which we will be sending out the magic links
and here we need to pass the access_token
which you can easily create using the google oauth2 playground
as shown below
And now we need to make the index.html
file and copy paste the below code
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<!DOCTYPE html> <html> <head> <title>Magic Link Auth</title> </head> <body> <h1>Magic Link Auth</h1> <form> <label for="email">Email:</label> <input type="email" id="email" name="email"> <button type="button" onclick="sendMagicLink()">Send Magic Link</button> </form> <script> async function sendMagicLink() { const email = document.getElementById('email').value; const response = await fetch('/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }) }); const result = await response.text(); alert(result); } </script> </body> </html> |
As you can see we are having the input
field where we allow the user to enter the email address
and then we make the post
request to the /login
route in the backend and here we are passing the email which is entered by the user.
Now we need to make the /login
post request inside the index.js file as shown below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// Login endpoint app.post("/login", async (req, res) => { const { email } = req.body; const user = users.find((u) => u.email === email); if (!user) { return res.send("User not found"); } const magicCode = uuid.v4().substr(0, 8); user.magicCode = magicCode; const mailOptions = { from: "sharmagautam1997dob@gmail.com", to: email, subject: "Magic Link Login", html: ` <p>Click the link below to log in:</p> <a href="http://localhost:3000/dashboard?email=${encodeURIComponent( email )}&code=${encodeURIComponent(magicCode)}">Log in</a> `, }; try { await transporter.sendMail(mailOptions); res.send("Magic link sent to your email"); } catch (err) { console.error(err); res.send("Error sending email"); } }); |
As you can see in the above route we are sending the email
using the nodemailer
. And now we need to implement the request
whenever someone clicks that magic
link that we sent what happens. Now we need to check the code if that matches the code
that we sent as shown below
1 2 3 4 5 6 7 8 9 10 11 |
// Dashboard endpoint app.get("/dashboard", (req, res) => { const { email, code } = req.query; const user = users.find((u) => u.email === email && u.magicCode === code); if (!user) { return; } user.magicCode = null; res.send("Welcome to the dashboard!"); }); |
As you can see in the above /dashboard
route we are extracting the email
and the magic code and then we are finding the email
if it’s exists inside the array of users. And also we are checking the magic code
if it matches the code that we sent. If it’s matches then we show the message to the user that welcome
to the dashboard as shown below
FULL SOURCE CODE
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
const express = require("express"); const nodemailer = require("nodemailer"); const uuid = require("uuid"); // Set up OAuth2 credentials // Set up Nodemailer transport const transporter = nodemailer.createTransport({ service: "gmail", auth: { type: "OAuth2", user: "sharmagautam1997dob@gmail.com", accessToken:"##youraccesstoken##", }, }); // User database const users = [ { id: 1, email: "geekygautam1997@gmail.com", magicCode: null }, { id: 2, email: "nehajoshi1982dob@gmail.com", magicCode: null }, ]; const app = express(); app.use(express.json()); // Serve HTML file app.get("/", (req, res) => { res.sendFile(__dirname + "/index.html"); }); // Login endpoint app.post("/login", async (req, res) => { const { email } = req.body; const user = users.find((u) => u.email === email); if (!user) { return res.send("User not found"); } const magicCode = uuid.v4().substr(0, 8); user.magicCode = magicCode; const mailOptions = { from: "sharmagautam1997dob@gmail.com", to: email, subject: "Magic Link Login", html: ` <p>Click the link below to log in:</p> <a href="http://localhost:3000/dashboard?email=${encodeURIComponent( email )}&code=${encodeURIComponent(magicCode)}">Log in</a> `, }; try { await transporter.sendMail(mailOptions); res.send("Magic link sent to your email"); } catch (err) { console.error(err); res.send("Error sending email"); } }); // Dashboard endpoint app.get("/dashboard", (req, res) => { const { email, code } = req.query; const user = users.find((u) => u.email === email && u.magicCode === code); if (!user) { return; } user.magicCode = null; res.send("Welcome to the dashboard!"); }); app.listen(3000, () => { console.log("Server listening on port 3000"); }); |