...

/

Custom Authentication: The Good, the Bad, and the Ugly

Custom Authentication: The Good, the Bad, and the Ugly

Learn to implement custom authentication strategies and how to utilize established providers for secure authentication.

Custom authentication

Let’s make this clear from the outset: when possible, we should avoid implementing custom authentication strategies. There are several great providers (including Auth0, Firebase, AWS Cognito, and Magic.link, just to name a few) that are putting a lot of effort into making authentication secure, reliable, and optimized for many different situations.

When investigating authentication strategies for a web app, we highly recommend looking into a well-established service provider because this is possibly one of the most critical aspects of a dynamic web application.

In this lesson, we’re looking into creating a custom authentication mechanism for a simple reason: we just want to understand at a high level how authentication works, how to make it as secure as possible, and what the critical factors of a custom auth system are.

As we’ll find out during this lesson, there will be several limitations when implementing custom authentication mechanisms. For instance, we highly discourage implementing client-side authentication on statically generated websites because it forces us to authenticate users on the client side exclusively, possibly exposing sensitive data to the network.

For that reason, we’ll create a new Next.js web application that will use the API routes for communicating with a data source (typically a database) and retrieving the user data.

Write a login API

We can start writing the login API. Please keep in mind that the following code is not meant to go to production; we’re just taking a simplified, high-level overview of how authentication works.

Let’s start by creating a /pages/api/login.js file by exporting the following function:

export default (req, res) => {}

This is where we’ll handle the user input and authenticate it. The first thing we can do is to take the user input and filter the request method to accept POST requests only:

Press + to interact
export default (req, res) => {
const { method } = req;
const { email, password } = req.body;
if (method !== 'POST') {
return res.status(404).end();
}
}

Validate email and password

We can now validate the user input. When validating an email and password, for example, we could check that the passed email is in a valid format and that the password is following a particular policy. That way, if any of the given data isn’t valid, we can just reply with a 401 status code (unauthorized) because we won’t find any occurrence in the database for that email and password combination. That would also help us to avoid useless database calls.

Right now, we don’t have a database, and we’ll rely on hardcoded values because we only want to understand authentication at a high level. That said, we’ll only check whether the request body contains an email and a password, so we can keep it simple:

Press + to interact
export default (req, res) => {
const { method } = req;
const { email, password } = req.body;
if (method !== 'POST') {
return res.status(404).end();
}
if (!email || !password) {
return res.status(400).json({
error: 'Missing required params',
});
}
}

If the email or password doesn’t exist in the request body, we’ll return a 400 status code (bad request) with an error message explaining why the request failed.

If the request is sent using the HTTP POST method and provides both an email and password, we can process them using any authentication ...