5

How to Build A Passwordless Authentication with Email and JWT

 3 years ago
source link: https://hackernoon.com/how-to-build-a-passwordless-authentication-with-email-and-jwt-o33w3311
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

How to Build A Passwordless Authentication with Email and JWT

@FlippedCodingMilecia

Software/Hardware Engineer | International tech speaker | Random inventor and slightly mad scientist

Broken authentication is the second highest security risk for web applications. This usually means that session management and authentication aren't handled correctly. This gives attackers several avenues to get access to data they can use maliciously.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

That's why it is important to make sure you get the best practices in place as early in the development process as possible. You can do a few things to make your authentication process more secure and protect your users. We'll go over a few of those things with a quick Node.js app.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

First, let's go over some of the different ways you can handle authentication.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Authentication methods

There are a few different types of authentication methods you can choose from: session-based, token-based, and passwordless. Each of these authentication methods has its pros and cons and we'll go over a few of them.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Session-based authentication

0 reactions
heart.png
light.png
money.png
thumbs-down.png

This is the most common form of authentication. It only requires a username and password that match what's in a database. If a user enters the correct set of login credentials, they will have a session initialized for them with a specific ID. A session is typically ended when a user logs out of the app.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

When sessions are implemented correctly, they will automatically expire after a set amount of time. You'll see this a lot in finance apps, like banking and trading. This gives users an added layer of security in case they've logged into their bank account on a public computer and forgot about that tab.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Token-based authentication

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Instead of using actual credentials to authenticate requests, token-based authentication gives users a temporary token that's stored in the browser. This token is typically a JWT (JSON Web Token) that contains all of the information an endpoint will need to validate a user.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Every request that a user makes will include that token. One of the benefits to using a token is that they can have embedded information about what roles and permissions a user might have without fetching that data from a database. This gives attackers less access to critical information, even if they are able to steal a user's token.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Passwordless authentication

0 reactions
heart.png
light.png
money.png
thumbs-down.png

This form of authentication is completely different from the others. There is no need for credentials to log in. All you need is an email address or phone number associated with an account and you will get a magic link or one-time password each time you want to log in. As soon as you click the link, you'll get redirected to the app and you'll already be logged in. After that, the magic link isn't valid so no one else can use it.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

When the magic link is generated, a JWT is also generated with it. This is how the authentication happens. With this login method, it's a lot harder for attackers to hack their way into your system. There are fewer inputs for them to take advantage of and sending the JWT through the magic link makes them harder to intercept than sending them through a response.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Now that you know about these different authentication methods, let's implement a passwordless authentication model.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Implementing authentication in Node

Passwordless authentication flow

0 reactions
heart.png
light.png
money.png
thumbs-down.png

We'll start by going through the process flow of passwordless authentication.

0 reactions
heart.png
light.png
money.png
thumbs-down.png
  • A user submits their email address or phone number in the web app.
  • They are sent a magic link to log in with.
  • The user clicks the magic link and they are redirected to the app, already logged in.

Now that we have the flow we need to implement, let's start by making a super basic front-end.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Front-end setup

0 reactions
heart.png
light.png
money.png
thumbs-down.png

We don't even need to use a JavaScript framework since the focus is mostly on the back-end. So we'll use some basic HTML and JavaScript to make the front-end.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Here's what the user interface code will be. Just a small HTML file that uses a frontend.js file.

0 reactions
heart.png
light.png
money.png
thumbs-down.png
<!DOCTYPE html>
<html>
    <head>
        <title>Passwordless Authentication</title>
        <script src="./frontend.js"></script>
    </head>
    <body>
        <h1>This is where you'll put your email to get a magic link.</h1>
        <form>
            <div>
                <label for="email_address">Enter your email address</label>
                <input type="email" id="email_address" />
            </div>
            <button type="submit" id="submit_email">Get magic link</button>
        </form>
    </body>
</html>
0 reactions
heart.png
light.png
money.png
thumbs-down.png

This is what the frontend.js file will look like.

0 reactions
heart.png
light.png
money.png
thumbs-down.png
window.onload = () => {
  const submitButton = document.getElementById("submit_email");
  const emailInput = document.getElementById("email_address")
  submitButton.addEventListener("click", handleAuth);
  /** This function submits the request to the server for sending the user a magic link.
   * Params: email address
   * Returns: message
   */
  async function handleAuth() {
    const message = await axios.post("http://localhost:4300/login", {
      email: emailInput.value
    });
    return message;
  }
};

The JavaScript file gets the submit button we made in the HTML file and adds a click event listener to it. So when the button is clicked, we'll make a POST request to the server we have running on

http://localhost:4300
at the
login
endpoint with the email address entered. Then, if the POST request is successful, we will get a message back we can show to the user.
0 reactions
heart.png
light.png
money.png
thumbs-down.png

Back-end setup

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Now we're going to start making our Node app. We'll start by making an express app and installing a few packages.

0 reactions
heart.png
light.png
money.png
thumbs-down.png
import cors from "cors";
import express from "express";

const PORT = process.env.PORT || 4000;
const app = express();

// Set up middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

// Login endpoint
app.post("/login", (req, res) => {
  const email = req.body.email;

  if (!email) {
    res.statusCode(403);
    res.send({
      message: "There is no email address that matches this.",
    });
  }

  if (email) {
    res.statusCode(200);
    res.send(email);
  }
});

// Start up the server on the port defined in the environment
const server = app.listen(PORT, () => {
  console.info("Server running on port " + PORT)
})

export default server

With this basic server in place, we can start adding more functionality. Let's go ahead and add the email service we're going to use. First, add nodemailer to your package.json and then import it.

0 reactions
heart.png
light.png
money.png
thumbs-down.png
import nodeMailer from "nodemailer";
0 reactions
heart.png
light.png
money.png
thumbs-down.png

Then below the middleware, we'll make a transporter to send emails. This code configures nodemailer and makes the email template with some simple HTML.

0 reactions
heart.png
light.png
money.png
thumbs-down.png
// Set up email
const transport = nodeMailer.createTransport({
  host: process.env.EMAIL_HOST,
  port: 587,
  auth: {
      user: process.env.EMAIL_USER,
      pass: process.env.EMAIL_PASSWORD
  }
});

// Make email template for magic link
const emailTemplate = ({ username, link }) => `
  <h2>Hey ${username}</h2>
  <p>Here's the login link you just requested:</p>
  <p>${link}</p>
`

Next, we need to make our token that holds the user's info. This is just an example of some of the basic things you might include in a token. You could also include things like, user permissions, special access keys, and other information that might be used in your app.

0 reactions
heart.png
light.png
money.png
thumbs-down.png
// Generate token
const makeToken = (email) => {
  const expirationDate = new Date();
  expirationDate.setHours(new Date().getHours() + 1);
  return jwt.sign({ email, expirationDate }, process.env.JWT_SECRET_KEY);
};

Now we can update the

login
endpoint to send a magic link to registered users and they'll be logged in to the app as soon as they click it.
0 reactions
heart.png
light.png
money.png
thumbs-down.png
// Login endpoint
app.post("/login", (req, res) => {
  const { email } = req.body;
  if (!email) {
    res.status(404);
    res.send({
      message: "You didn't enter a valid email address.",
    });
  }
  const token = makeToken(email);
  const mailOptions = {
    from: "You Know",
    html: emailTemplate({
      email,
      link: `http://localhost:8080/account?token=${token}`,
    }),
    subject: "Your Magic Link",
    to: email,
  };
  return transport.sendMail(mailOptions, (error) => {
    if (error) {
      res.status(404);
      res.send("Can't send email.");
    } else {
      res.status(200);
      res.send(`Magic link sent. : http://localhost:8080/account?token=${token}`);
    }
  });
});

There are only two more things we need to add to the code to get the server finished. Let's add an

account
endpoint. Then we'll add a simple authentication method.
0 reactions
heart.png
light.png
money.png
thumbs-down.png
// Get account information
app.get("/account", (req, res) => {
  isAuthenticated(req, res)
});

This gets the user's token from the front-end and calls the authentication function.

0 reactions
heart.png
light.png
money.png
thumbs-down.png
const isAuthenticated = (req, res) => {  const { token } = req.query
  if (!token) {
    res.status(403)
    res.send("Can't verify user.")
    return
  }
  let decoded
  try {
    decoded = jwt.verify(token, process.env.JWT_SECRET_KEY)
  }
  catch {
    res.status(403)
    res.send("Invalid auth credentials.")
    return
  }
  if (!decoded.hasOwnProperty("email") || !decoded.hasOwnProperty("expirationDate")) {
    res.status(403)
    res.send("Invalid auth credentials.")
    return
  }
  const { expirationDate } = decoded
  if (expirationDate < new Date()) {
    res.status(403)
    res.send("Token has expired.")
    return
  }
  res.status(200)
  res.send("User has been validated.")
}

This authentication check gets the user's token from the URL query and tries to decode it with the secret that was used to create it. If that fails, it returns an error message to the front-end. If the token is successfully decoded, a few more checks occur and then the user is authenticated and has access to the app!

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Best practices for existing authentication systems

Passwordless authentication might not be possible for existing systems, but there are things you can do to make your apps more secure.

0 reactions
heart.png
light.png
money.png
thumbs-down.png
  • Increase the complexity requirements of passwords.
  • Use two-factor authentication.
  • Require passwords to be changed after a certain amount of time.

Conclusion

There are a lot of different ways you can implement an authentication system for your app and passwordless is just one of those. Token-based is another commonly used type of authentication and there are plenty of ways to handle this.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Making your own authentication system might take more work than you have time for. There are a lot of existing libraries and services that you can use to integrate authentication in your app. Some of the most commonly used ones are Passport.js and Auth0.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Previously published at https://dev.to/flippedcoding/implementing-passwordless-authentication-in-node-js-43m0

0 reactions
heart.png
light.png
money.png
thumbs-down.png
8
heart.pngheart.pngheart.pngheart.png
light.pnglight.pnglight.pnglight.png
boat.pngboat.pngboat.pngboat.png
money.pngmoney.pngmoney.pngmoney.png
by Milecia @FlippedCoding. Software/Hardware Engineer | International tech speaker | Random inventor and slightly mad scientistCheck out my site!
Join Hacker Noon

Create your free account to unlock your custom reading experience.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK