loading...
Cover image for The Easiest Way to Add Node.js User Authentication

The Easiest Way to Add Node.js User Authentication

jamesqquick profile image James Q Quick Originally published at jamesqquick.com on ・5 min read

Adding authentication in your Node.js applications can be a daunting task. Even if you're using Passport.js to do some of the heavy lifting, it's still tricky to incorporate. In this article, let's see how to use the express-openid-connectlibrary to add authentication to your Node.js/Express application 💪 Trust me, this is by far the easiest way I've found to do this!

Project Setup

We are going to be building a Node.js/Express application that has routes for handling login and logout as well as displaying profile information to the user. Let's start from the very beginning. Create a folder on your computer, and then, in the terminal, run npm init -y to set the project up as a JavaScript project.

Then, we'll need to install some dependencies.

  • express - server framework
  • dotenv - for working with local environment variables
  • express-openid-connect - library that handles authentication
npm install express dotenv express-openid-connect
Enter fullscreen mode Exit fullscreen mode

Then, open the folder up with your favorite text editor. Create an app.js file in the root of your directory. Inside of here, add the code to create an express server.

const express = require('express');
const app = express();

app.get('/', (req, res) => {
    res.send("hello world");
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
    console.log(`listening on port ${port}`);
});

Enter fullscreen mode Exit fullscreen mode

You can then run the server with node app.js or, if you have nodemon installed, nodemon app.js. You should see a log stating the server is running.

Setup Environment Variables

We are going to need 4 different environment variables to configure the express-openid-connect libary.

  1. ISSUER_BASE_URL - the base url of the issuer (from the authorization server)
  2. CLIENT_ID = unique id for the client (from the authorization server)
  3. BASE_URL- the url of the locally running server (http://localhost:3000 in this case)
  4. SECRET - a random string of at least 32 characters

Since we are running this app locally, we are going to store these environment variables inside of a .env file. Create that file in the root of your repository and paste in the following.

ISSUER_BASE_URL=
CLIENT_ID=
BASE_URL=
SECRET=
Enter fullscreen mode Exit fullscreen mode

Auth0 (Or Alternative) Setup

In this demo, we are going to use Auth0, a 3rd party authentication provider, to do the majority of the behind-the-scenes authentication work. It's important to note that you could use any other 3rd party authentication provider that is OpenID Connect compliant. That means you could easily swap out to a different provider by changing your environment variables.

If you are going to use Auth0, you'll need to sign up for a FREE account if you don't have one already. As part of the process, you will create a tenant, which is basically a container for different applications.

Next, you'll need to create an application and choose Regular Web App.

With your application created, you'll need to update two settings, the callback URL and logout URL. We will be leveraging the OpenID Connect protocol for handling authentication which requires the user to be redirected to the authorization and then back to our application. Because of this, we need to tell Auth0 where the user should be redirected back to.

Don't worry, all of the redirect business gets handled by the authorization server. You don't have to write any of that logic.

Be sure to scroll down and hit save.

Lastly, we need to grab two properties from our Auth0 application, the Domain and the Client ID.

Update Environment Variables Appropriately

Remember, you don't have to use Auth0 to make this work, so if you used another provider, just use those credentials. Now, update the .env file with the appropriate values.

ISSUER_BASE_URL=https://<YOUR_DOMAIN>
CLIENT_ID=<YOUR_CLIENT_ID>
BASE_URL=http://localhost:3000
SECRET=<LONG_RANDOM_STRING>

Enter fullscreen mode Exit fullscreen mode

Lastly, for our environment variables to be accessible while running locally, you need to require the dotenv package and call its config() function like so. Make sure to put this at the top of your file.

require('dotenv').config();
Enter fullscreen mode Exit fullscreen mode

Express Open ID Package Configuration

With all of that setup, let's get down to the auth. We need to auth from the express-openid-connection package.

Then, we configure the auth object using the credentials from our environment variables. Lastly, we use this object as middleware in our Express server. Here's what that looks like.

const { auth } = require('express-openid-connect');

app.use(
    auth({
        authRequired: false,
        auth0Logout: true,
        issuerBaseURL: process.env.ISSUER_BASE_URL,
        baseURL: process.env.BASE_URL,
        clientID: process.env.CLIENT_ID,
        secret: process.env.SECRET,
    })
);
Enter fullscreen mode Exit fullscreen mode

With this middleware in place, we can get access to the logged-in user (if they are logged in) inside of the request parameter of a given endpoint. For example, if we wanted to show whether the user is logged in or not, we could define an index route like so.

app.get('/', (req, res) => {
    res.send(req.oidc.isAuthenticated() ? 'Logged in' : 'Logged out');
});
Enter fullscreen mode Exit fullscreen mode

Restart your server and then open your browser to localhost:3000. and you should see "Logged out".

Now, for the magic. Notice, we didn't define any login or logout routes specifically. Well, those are already created for us! Now, you can navigate to localhost:3000/login and follow the login process. After you finish, the home page should now show logged in 🥳

How cool is that?!?!

Creating a Profile Route

Now that you can track a logged-in user, we can create a profile route that will show info about the user. This grabs the information about the logged-in user and returns it as json.

app.get('/profile', (req, res) => {
    res.send(JSON.stringify(req.oidc.user));
});
Enter fullscreen mode Exit fullscreen mode

Restart your server and make sure you are logged in. Then, navigate to localhost:3000/profile.

Protecting Routes

With the profile route, you don't want someone who isn't logged in to be able to access it. Therefore, we need to add some protection to the route to make sure that doesn't happen. Thankfully, the library we are using will help us do just that.

Start by importing the requiresAuthmiddleware from the library.

const { auth, requiresAuth } = require('express-openid-connect');
Enter fullscreen mode Exit fullscreen mode

Then, use this middleware in the profile route like so.

app.get('/profile', requiresAuth(), (req, res) => {
    res.send(JSON.stringify(req.oidc.user));
});
Enter fullscreen mode Exit fullscreen mode

Restart your server and logout by going to localhost:3000/logout. Then try to navigate to the /profile route. You should be redirected to the login page!

Wrap Up

This library did a lot of work for us. Behind the scenes, it created fully functions login and logout routes. It also tracks the user and exposes user info on the req object of each API request. It also provided middleware to be able to easily protect our API routes by forcing a user to login!

Here's the full source code, LESS THAN 30 LINES!

const express = require('express');
const app = express();
require('dotenv').config();
const { auth, requiresAuth } = require('express-openid-connect');

app.use(
    auth({
        authRequired: false,
        auth0Logout: true,
        issuerBaseURL: process.env.ISSUER_BASE_URL,
        baseURL: process.env.BASE_URL,
        clientID: process.env.CLIENT_ID,
        secret: process.env.SECRET,
    })
);

app.get('/', (req, res) => {
    res.send(req.oidc.isAuthenticated() ? 'Logged in' : 'Logged out');
});

app.get('/profile', requiresAuth(), (req, res) => {
    res.send(JSON.stringify(req.oidc.user));
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
    console.log(`listening on port ${port}`);
});

Enter fullscreen mode Exit fullscreen mode

I've tried several different approaches to handle authentication in Node.js and this is by far the easiest one I've found.

Discussion

pic
Editor guide
Collapse
dcsan profile image
dc

Senior Developer Advocate at Auth0

can you redo the guide above to work with just say github or google login? I don't want to start on the slippery slope with a freemium auth0 account.

Collapse
jamesqquick profile image
James Q Quick Author

I mean I'm not going to "redo" the guide for a specific person... if you don't want to use Auth0 and have you have an approach that you prefer, that's totally fine.

Collapse
dcsan profile image
dc

Well, you work for Auth0 right? But your byline doesn't mention that, just "James is a Developer, Speaker, and Teacher ..."
I think you should at least retitle it "How to use Auth0 to..." and even disclose this as paid content marketing. There's a free tier and the article is very solid, so why not own it? Otherwise it just feels deceptive.

Thread Thread
codefinity profile image
Manav Misra

Here's a better idea 💡. Say thanks for the great information that he took to write up (probably outside of his working hours), and if you don't appreciate that there are a lot of other articles on here for you to read.

Thread Thread
jamesqquick profile image
James Q Quick Author

Ok, point taken. For what it's worth, I've updated my tagline to include my role at Auth0. That said, I only put stuff on my personal brand that I believe in. I didn't post this because I'm an employee at Auth0. I posted it because I truly believe this is the easiest way to handle authentication in Node.js. If I started a Node app tomorrow, this is what I would do. In no way was I trying to deceptively drive people to Auth0. I wrote the article and the title specifically because I believe in what it says.

Thread Thread
codefinity profile image
Manav Misra

That's 🆒 of you to do. I wouldn't have bothered personally.😼

Thread Thread
jamesqquick profile image
James Q Quick Author

hahah thanks! I want to be fair to people who invest the time to read and leave feedback. I can see why someone would be skeptical of an Auth0 employee writing Auth0 content as his own...but I think the people that know me better know it's because I genuinely believe in what I share on my personal platforms :)

Collapse
madza profile image
Madza

what terminal/theme, please? 🎨

Collapse
jamesqquick profile image
James Q Quick Author

Cobalt 2 by wes bos. I used his command line power user course to set it all up

Collapse
madza profile image
Madza

thanks