developers

The Complete Guide to Node.js User Authentication with Auth0

Learn how to add user authentication to Node.js web apps built with Express using middleware

Nov 6, 20201 min read

Look for the 🛠️️ emoji if you'd like to skim through the content while focusing on the build steps.

This tutorial demonstrates how to secure a Node.js web application built with the Express framework by implementing user authentication. You'll enhance a starter Node.js project to practice the following security concepts:

  • Add user login and logout.
  • Retrieve user information.
  • Protect application routes.
  • Call protected endpoints from an API.

This guide uses the Auth0 Express OpenID Connect library to secure Express web applications. This library provides Node.js developers with an alternative to Passport.js. Express OpenID Connect lets you add user authentication to Express applications using security best practices while writing less code.

⚠️ If you still need to use Passport.js to secure your Express application, please refer to the Node.js and Express Authentication Using Passport tutorial.

Auth0 Express sample app

How does Auth0 work?

With the help of Auth0, you don't need to be an expert on identity protocols, such as OAuth 2.0 or OpenID Connect, to understand how to secure your web application stack. You first integrate your application with Auth0. Your application will then redirect users to an Auth0 customizable login page when they need to log in. Once your users log in successfully, Auth0 takes them back to your app, returning JSON Web Tokens (JWTs) with their authentication and user information.

⏰⚡️ If you are short of time, check out the Auth0 Express Quickstart to get up and running with user authentication for Express in just a few minutes.

Get the Starter Application

We have created a starter project to help you learn Node.js security concepts through hands-on practice. The starter application uses Bootstrap with a custom theme to take care of the styling and layout of your application. You can focus on building Express components to secure your application.

🛠 As such, clone the

auth0-express-pug-sample
repository on its
starter
branch to get started:

git clone -b starter git@github.com:auth0-blog/auth0-express-pug-sample.git

🛠 Once you clone the repo, make

auth0-express-pug-sample
your current directory:

cd auth0-express-pug-sample

🛠 Install the Node.js project dependencies:

npm install

🛠 Create a

.env
file under the project directory:

touch .env

🛠 Populate

.env
as follows:

DEV_PORT=4041
PROD_PORT=4040

To streamline your Node.js development workflow, this project uses

nodemon
to restart the server and
browser-sync
to reload the browser
whenever relevant source files change.

🛠 Execute the following command to run the Node.js server:

npm run dev

You can emulate the live reload behavior of front-end frameworks such as React and Angular in Express templates using Browsersync. The browser will refresh automatically whenever the source code changes: for example, when you modify a CSS rule or change the return value of a function.

🛠 Open a separate terminal window and execute the following command to serve the user interface of your Express app:

npm run ui

Browsersync automatically opens a new window presenting your application user interface. If it didn't, open

http://localhost:4040/
.

Connect Express with Auth0

The best part of the Auth0 platform is how streamlined it is to get started by following these steps:

Sign up and create an Auth0 Application

If you haven't already,

sign up for a free Auth0 account →

A free account offers you:

During the sign-up process, you create something called an Auth0 Tenant, representing the product or service to which you are adding authentication.

🛠 Once you sign in, Auth0 takes you to the Dashboard. In the left sidebar menu, click on "Applications".

🛠 Then, click the "Create Application" button. A modal opens up with a form to provide a name for the application and choose its type.

  • Name:
Auth0 Express Sample
  • Application Type: Regular Web Applications

🛠 Click the "Create" button to complete the process. Your Auth0 application page loads up.

In the next step, you'll learn how to help Express and Auth0 communicate.

What's the relationship between Auth0 Tenants and Auth0 Applications?

Let's say that you have a photo-sharing Express app called "Noddit". You then would create an Auth0 tenant called

noddit
.

Now, say that Noddit is available on three platforms: web as a single-page application and a native mobile app for Android and iOS. If each platform needs authentication, you need to create three Auth0 applications to provide the product with everything it needs to authenticate users through that platform.

Create a communication bridge between Express and Auth0

When you use Auth0, you don't have to build login forms. Auth0 offers a Universal Login page to reduce the overhead of adding and managing authentication.

How does Universal Login work?

Your Express application will redirect users to Auth0 whenever they trigger an authentication request. Auth0 will present them with a login page. Once they log in, Auth0 will redirect them back to your Express application. For that redirecting to happen securely, you must specify in your Auth0 Application Settings the URLs to which Auth0 can redirect users once it authenticates them.

🛠 As such, click on the "Settings" tab of your Auth0 Application page and fill in the following values:

🛠 Allowed Callback URLs

http://localhost:4040/callback

The above value is the URL that Auth0 can use to redirect your users after they successfully log in.

🛠 Allowed Logout URLs

http://localhost:4040

The above value is the URL that Auth0 can use to redirect your users after they log out.

🛠 Scroll down and click the "Save Changes" button.

🛠 Do not close this page yet. You'll need some of its information in the next section.

Add the Auth0 configuration variables to Express

From the Auth0 Application Settings page, you need the Auth0 Domain and Client ID values to allow your Express application to use the communication bridge you created.

What exactly is an Auth0 Domain and an Auth0 Client ID?

Domain

When you created a new Auth0 account, Auth0 asked to pick a name for your Tenant. This name, appended with

auth0.com
, is your Auth0 Domain. It's the base URL that you will use to access the Auth0 APIs and the URL where you'll redirect users to log in.

You can also use custom domains to allow Auth0 to do the authentication heavy lifting for you without compromising your branding experience.

Client ID

Each application is assigned a Client ID upon creation, which is an alphanumeric string, and it's the unique identifier for your application (such as

q8fij2iug0CmgPLfTfG1tZGdTQyGaTUA
). You cannot modify the Client ID. You will use the Client ID to identify the Auth0 Application to which the Auth0 Express SDK needs to connect.

Warning: Another critical piece of information present in the "Settings" is the Client Secret. This secret protects your resources by only granting tokens to requestors if they're authorized. Think of it as your application's password, which must be kept confidential at all times. If anyone gains access to your Client Secret, they can impersonate your application and access protected resources.

🛠 Open the

.env
file from your
auth0-express-pug-sample
project directory and update it as follows:

DEV_PORT=4041
PROD_PORT=4040
AUTH0_ISSUER_BASE_URL=https://<AUTH0_DOMAIN>/
AUTH0_CLIENT_ID=

🛠 For the

AUTH0_ISSUER_BASE_URL
value,
<AUTH0_DOMAIN>
is your Domain value from the "Settings". Ensure that you keep the trailing slash for this value.

🛠

AUTH0_CLIENT_ID
is your Client ID from the "Settings".

Auth0 Application Settings from the Auth0 Dashboard

These variables let your Express application identify itself as an authorized party to interact with the Auth0 authentication server.

Auth0 and Express connection set

You have completed setting up an authentication service that your Express application can consume. All that is left is for you to continue building up the starter project throughout this guide by implementing components to trigger and manage the authentication flow.

Feel free to dive deeper into the Auth0 Documentation to learn more about how Auth0 helps you save time on implementing and managing identity.

Set Up Express OpenID Connect

🛠 You need to follow these steps to integrate the Express OpenID Connect library with your Express application.

Install Express OpenID Connect

🛠 Execute the following command:

npm install express-openid-connect

Configure Express OpenID Connect

🛠 Open the

.env
file once again and add a
BASE_URL
and
SESSION_SECRET
value to it:

DEV_PORT=4041
PROD_PORT=4040
AUTH0_ISSUER_BASE_URL=<...>
AUTH0_CLIENT_ID=<...>
BASE_URL=http://localhost:4040
SESSION_SECRET=

The

BASE_URL
value is the URL where your application is served.

The

SESSION_SECRET
value is the secret used to sign the session ID cookie, which can be either a string for a single secret or an array of multiple secrets.

🛠️ Execute the following command to generate a suitable string for the session secret:

node -e "console.log(crypto.randomBytes(32).toString('hex'))"

🛠️ Copy and paste the output of the command above as the value for

SESSION_SECRET
in
.env
.

🛠️ For your application to recognize these new environment variables, you need to restart the Node.js server. Locate the terminal window where you executed

npm run dev
earlier, stop it, and run it again.

User authentication is a mechanism to monitor who is accessing your application and control what they can do. For example, you can prevent users who have not logged in from accessing parts of your application. In that scenario, Auth0 can act as your application bouncer.

A bouncer is a person employed by a nightclub or similar establishment to prevent troublemakers from entering or to eject them from the premises. Express security is not too different from nightclub security.

If users want to enter a protected route from your application, Auth0 will stop them and ask them to present their credentials. If Auth0 can verify who they are and that they are supposed to go in there, Auth0 will let them in. Otherwise, Auth0 will take them back to a public application route.

Now, it's important to reiterate that the authentication process won't happen within your application layer. Your Express application will redirect your users to the Auth0 Universal Login page, where Auth0 asks for credentials and redirects the user back to your application with the result of the authentication process.

The Express OpenID Connect library provides the

auth
router to attach authentication routes to your application. You won't have to implement
/login
or
/logout
controllers, Express OpenID Connect takes does that for you.

You now need to initialize, configure, and integrate

express-openid-connect
with your Express application.

🛠 Open

src/index.js
and update the
Required External Modules
section to import
auth
:

// src/index.js

/**
 * Required External Modules
 */

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

🛠 Then, update the

App Configuration
section to initialize and use
auth
as an Express middleware function:

// src/index.js

/**
 *  App Configuration
 */

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(express.static(path.join(__dirname, '..', 'public')));
app.use(
  auth({
    issuerBaseURL: process.env.AUTH0_ISSUER_BASE_URL,
    baseURL: process.env.BASE_URL,
    clientID: process.env.AUTH0_CLIENT_ID,
    secret: process.env.SESSION_SECRET,
    authRequired: false,
    auth0Logout: true,
  }),
);

You are adding two additional properties,

authRequired
and
auth0Logout
. What are these properties doing?

authRequired
is a boolean property that configures Express OpenID Connect to require authentication for all routes when you set it to
true
. For this project, you'll have a mix of public and protected routes. As such, you set this property to
false
.

auth0Logout
is another boolean value that enables the Auth0 logout feature, which lets you log out a user of the Auth0 session. When implementing logout functionality in an application, there are typically three sessions layers you need to consider:

  • Application Session Layer
  • Auth0 Session Layer
  • Identity Provider Session Layer

For example, if one of your users logged in using Google, you can configure your Auth0 authentication service to log out the user from the application, from the Auth0 session, or from Google itself. Check out the "Logout" document to learn more details about the architecture of user logout.

For this application, you'll log out users from the Auth0 session layer.

The Express OpenID Connect library is all set up. You are ready to implement user authentication in the next section.

Add User Authentication

Throughout this guide, you'll use Pug mixins to implement the application's user interface (UI) following a component-based architecture. Each mixin will act as a UI component, becoming a reusable piece that you can create and maintain in isolation.

You need to create UI components for your users to trigger authentication events: login, logout, and sign up.

Express OpenID Connect creates an

oidc
namespace in your application's
req
object. In this namespace, the library stores authentication methods and data, such as a
user
object to hold user profile information and a
login
method to customize the user login experience. You'll explore the
oidc
object in the next sections.

Create a login button

🛠 Create a

login-button.pug
file under the
src/components/
directory:

touch src/components/login-button.pug

🛠 Create a

mixin
to represent a login button component in
src/components/login-button.pug
like so:

mixin login-button()
  button(
    class="btn btn-primary btn-block",
    onclick="window.location='/login'"
  ) Log In

Under the hood, Express OpenID Connect created a

/login
route for your Express application. When the user clicks on this button, your Express application will prompt the user to authenticate and provide consent for your Express application to access certain data on behalf of that user. In your current architecture, this means that your Express application redirects the user to the Auth0 Universal Login page to carry out the authentication process. You'll see this in action in the next sections.

You can customize the login experience further by using the

req.oidc.login()
method in an Express controller. For example, you can pass that method options to redirect users to an Auth0 Universal Login page optimized for signing up for your Express application.

Create a sign-up button

You can make users land directly on a sign-up page instead of a login page by creating a

/sign-up
route controller.

🛠 To start, open the

src/index.js
file. Locate the
Routes Definitions
section. Under this section, there are different subsections that define routes for each feature of your Expres web application. Locate the
> Authentication
subsection, and update it as follows:

// src/index.js

/**
 * Routes Definitions
 */

// > Other route subsections...

// > Authentication

app.get('/sign-up', (req, res) => {
  res.oidc.login({
    authorizationParams: {
      screen_hint: 'signup',
    },
  });
});

You create a

/sign-up
route controller, where you access the
res.oidc.login()
method. This method takes some
LoginOptions
to customize the behavior of the user login experience.

Here, you override the default

authorizationParams
, which are URL parameters that Express OpenID Connect uses when redirecting users to Auth0 to log in.

You can pass new values to change what the Auth0 authorization server returns depending on your use case. In your

/sign-up
controller, you specify the
screen_hint=signup
property as an authorization parameter to take the users to a sign-up form.

{
  authorizationParams: {
    screen_hint: "signup",
  },
}

Now, create a sign-up button to trigger this event by requesting the

/sign-up
route.

🛠 Create a

signup-button.pug
file under the
src/components/
directory:

touch src/components/signup-button.pug

🛠 Populate

src/components/signup-button.pug
like so to define a
signup-button
mixin:

mixin signup-button()
  button(
    class="btn btn-primary btn-block",
    onclick="window.location='/sign-up'"
  ) Sign Up

Using the Signup feature requires you to enable the Auth0 New Universal Login Experience in your tenant.

🛠 Open the Universal Login section of the Auth0 Dashboard and choose the "New" option under the "Experience" subsection.

Auth0 Universal Login Experience options

🛠 Scroll down and click on the "Save Changes" button.

The difference between the log-in and sign-up user experience will be more evident once you integrate those components with your Express application and see them in action. You'll do that in the next sections.

Create a logout button

🛠 Create a

logout-button.pug
file under the
src/components/
directory:

touch src/components/logout-button.pug

🛠 Populate

src/components/logout-button.pug
like so:

mixin logout-button()
  button(
    class="btn btn-danger btn-block",
    onclick="window.location='/logout'"
  ) Log Out

The

/logout
route created by Express OpenID Connect calls the
req.oidc.logout()
method
under the hood. This method clears the application session and redirects to the Auth0
/v2/logout
endpoint to clear the Auth0 session. As with the login method, you can pass
LogoutOptions
to
req.oidc.logout()
to customize its behavior.

Here, you pass the

returnTo
option to specify the URL where Auth0 should redirect your users after they logout. Right now, you are working locally, and your Auth0 application's "Allowed Logout URLs" point to
http://localhost:4040
.

However, if you were to deploy your Express application to production, you need to add the production logout URL to the "Allowed Logout URLs" list and ensure that Auth0 redirects your users to that production URL and not

localhost
.

Read more about how Logout works at Auth0.

Integrate the login and logout buttons

Let's wrap the

login-button
and
logout-button
mixins into a mixin called
authentication-button
.

🛠 Create an

authentication-button.pug
file under the
src/components/
directory:

touch src/components/authentication-button.pug

🛠 Populate

src/components/authentication-button.pug
with the following code:

include ./login-button
include ./logout-button

mixin authentication-button(isAuthenticated)
  if isAuthenticated
    +logout-button
  else
    +login-button

isAuthenticated
is a boolean value exposed by the
req.oidc
object. Its value is
true
when Auth0 has authenticated the user and
false
when it hasn't.

There are some advantages to using this

authentication-button
mixin wrapper:

You can build flexible interfaces.

authentication-button
serves as a "log in/log out" switch that you can put anywhere you need that switch functionality. However, you still have separate
login-button
and
logout-button
mixins for cases when you need their functionality in isolation. For example, you may have a logout button on a page that only authenticated users can see.

You can build extensible interfaces. You can easily swap the

login-button
mixin with the
signup-button
mixin in
authentication-button
to create a "sign up/log out" switch. You could also wrap the "sign up/log out" switch in a
new-authentication-button
mixin.

You can build declarative interfaces. Using

authentication-button
, you can add login and logout functionality to a navigation bar component, for example, without thinking about the implementation details of how the authentication switch works.

🛠 With that in mind, create an

auth-nav.pug
file under the
src/components/
directory:

touch src/components/auth-nav.pug

🛠 Populate

src/components/auth-nav.pug
like so:

include ./authentication-button

mixin auth-nav(isAuthenticated)
  div(class="navbar-nav ml-auto")
    +authentication-button(isAuthenticated)

isAuthenticated
makes another appearance. Where is that value coming from? You'll find out soon!

🛠 Finally, open

nav-bar.pug
under the
src/components/
directory and update it like so:

include ./main-nav
include ./auth-nav

mixin nav-bar(activeRoute)
  div(class="nav-container mb-3")
    nav(class="navbar navbar-expand-md navbar-light bg-light")
      div(class="container")
        div(class="navbar-brand logo")
        +main-nav(activeRoute)
        +auth-nav(isAuthenticated)

By having different types of navigation bar subcomponents, you can extend each as you need without reopening and modifying the main

nav-bar
component.

Once again,

isAuthenticated
shows up. The Express OpenID Connect library defines this value in the
req.oidc.isAuthenticated()
method. Its return value is essential for authentication to work correctly in your Express app.

How do you pass data from a controller to a template in Express?

In Express web applications, you have access to a one-way data flow from route controller to template. As such, each route controller that renders a template that depends on

isAuthenticated
must pass down this value.

However, passing a value manually to a template on each route controller is not only tedious but error-prone. What you can do instead is to make the value available as a local value to all Pug templates.

🛠 Update the

App Configuration
section in
src/index.js
as follows:

// src/index.js

/**
 *  App Configuration
 */

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(express.static(path.join(__dirname, '..', 'public')));

app.use(
  auth({
    issuerBaseURL: process.env.AUTH0_ISSUER_BASE_URL,
    baseURL: process.env.BASE_URL,
    clientID: process.env.AUTH0_CLIENT_ID,
    secret: process.env.SESSION_SECRET,
    authRequired: false,
    auth0Logout: true,
  }),
);

app.use((req, res, next) => {
  res.locals.isAuthenticated = req.oidc.isAuthenticated();
  next();
});

You define

res.locals
within a middleware function handler. This object lets you pass data around your Express application. All of your Pug templates can access its properties (such as
isAuthenticated
) directly. Since
req.oidc.isAuthenticated()
is a method, you have to execute it to get its value.

There is one caveat about using

res.locals
: these values only live within an individual request. As soon as the request-response cycle is complete, the values are gone. This isn't a problem for your application since each time a user requests a route from the browser, the request-response cycle starts all over again.

Read more details about

res.locals
from StackOverflow.

🛠 Go ahead and try to log in. Your Express application redirects you to the Auth0 Universal Login page. You can use a form to log in with a username and password or a social identity provider like Google. Notice that this login page also gives you the option to sign up.

New Auth0 Universal Login Experience Form

Experiment: Use the

signup-button
mixin

Swap the

login-button
mixin with the
signup-button
mixin in the conditional defined in the
authentication-button
mixin:

include ./signup-button
include ./logout-button

mixin authentication-button(isAuthenticated)
  if isAuthenticated
    +logout-button
  else
    +signup-button

When you click the "Sign Up" button, you'll land on a page with language optimized to encourage you to sign up for your Express application.

Try this out!

New Auth0 Universal Login Experience Signup Page

Once you complete this experiment, swap back

signup-button
with
login-button
to continue with the rest of this guide:

include ./login-button
include ./logout-button

mixin authentication-button(isAuthenticated)
  if isAuthenticated
    +logout-button
  else
    +login-button

You can customize the appearance of New Universal Login pages. You can also override any text in the New Experience using the Text Customization API.

🛠 Click on the Profile tab and then click on the "Log Out" button.

Notice that Express logged you out, but instead of taking you back to the

/profile
route, it took you back to the
/
route, the home page.

🛠 Click on the Profile tab and now click on the "Log In" button.

This time, Express takes you again back to the home page,

/
after you log in. As a user, you would expect to access the
/profile
page after you log in, right? After all, your authentication system is acting like a bouncer. The bouncer doesn't take people back to the nightclub entrance after they present their ID. The bouncer lets them pass through and access the VIP lounge or whatever room the bouncer is protecting.

Why is this home page redirect happening?

This Express web app is using static routes to render its user interface. You map a user interface action with a server endpoint or controller. This mapping is hard-coded so far.

The

/login
route controller that the Express OpenID Connect library created for you has a hard-coded value for the path to which Auth0 should return users after they log in. That default value is the root of your server URL,
/
.

However, what if the

/profile
page had both public and protected resources? In a scenario like that, anyone can visit the
/profile
page to read its public information. Then, users can log in to read protected information. The best user experience is for the users to return to the
/profile
page after they log in, not the home page.

One way to fix this home-page redirect is to create a dedicated route for each type of login and logout:

GET /sign-up-profile

GET /sign-up-external-api

GET /login-profile

GET /login-external-api

GET /logout-profile

GET /logout-external-api

However, this method won't scale well at all. Imagine if you had more than 10 different pages that require that smooth login experience.

It would be better to create a single dynamic route that can handle different "types" of login and logout actions:

GET /sign-up/:page

GET /login/:page

GET /logout/:page

Since the

/sign-up
route behaves very much like the
/login
route, you are enhancing that route too — in case that you want to use the "Sign Up" button in your application.

Then you could take that

:page
parameter and use it to tell Auth0 to return users to
serverUrl/page
after they log in or log out.

To implement these new authentication route controllers easily, you can leverage the

activeRoute
property that you are passing from your route controllers to your templates. Take a look at the
/profile
route controller, for example:

app.get('/profile', (req, res) => {
  res.render('profile', { activeRoute: req.originalUrl });
});

You pass down this

{ activeRoute: req.originalUrl }
object in all route controllers. You recently learned how to leverage
res.locals
to make the authentication status of the user available to your whole Express application. You can do the same for the
activeRoute
property.

🛠 The steps that follow will show you how to create dynamic authentication routes. However, if you want Express to redirect all users who log out to the home page, you can leave your application as it is.

🛠 Update the

App Configuration
section in
src/index.js
as follows:

// src/index.js

/**
 *  App Configuration
 */

app.set("views", path.join(__dirname, "views"));
app.set("view engine", "pug");

app.use(express.static(...));

app.use(
  auth({...})
);

app.use((req, res, next) => {
  res.locals.isAuthenticated = req.oidc.isAuthenticated();
  res.locals.activeRoute = req.originalUrl;
  next();
});

You now define another local variable that all Pug templates can access:

res.locals.activeRoute
. You no longer need to pass
activeRoute
down to your templates from each controller.

🛠 Locate the

Routes Definitions
section in
src/index.js
. Update the
> Home
,
> Profile
, and
> External API
subsections as follows:

// src/index.js

/**
 * Routes Definitions
 */

// > Home

app.get('/', (req, res) => {
  res.render('home');
});

// > Profile

app.get('/profile', (req, res) => {
  res.render('profile');
});

// > External API

app.get('/external-api', (req, res) => {
  res.render('external-api');
});

🛠 Now, you need to create the

sign-up/:page
,
login/:page
and
logout/:page
route controllers. Head back to the
Routes Definitions
section in
src/index.js
. Update the
> Authentication
subsection as follows:

// src/index.js

/**
 * Routes Definitions
 */

// > Authentication

app.get('/sign-up/:page', (req, res) => {
  const { page } = req.params;

  res.oidc.login({
    returnTo: page,
    authorizationParams: {
      screen_hint: 'signup',
    },
  });
});

app.get('/login/:page', (req, res) => {
  const { page } = req.params;

  res.oidc.login({
    returnTo: page,
  });
});

app.get('/logout/:page', (req, res) => {
  const { page } = req.params;

  res.oidc.logout({
    returnTo: page,
  });
});

Now, you need to update your login and logout buttons to use these custom route controllers instead of the default ones created by Express OpenID Connect.

🛠 Update

src/components/signup-button.pug
as follows:

mixin signup-button()
  button(
    class="btn btn-primary btn-block",
    onclick=`window.location='/sign-up/${activeRoute}'`
  ) Sign Up

🛠 Update

src/components/login-button.pug
as follows:

mixin login-button()
  button(
    class="btn btn-primary btn-block",
    onclick=`window.location='/login/${activeRoute}'`
  ) Log In

🛠 Update

src/components/logout-button.pug
as follows:

mixin logout-button()
  button(
    class="btn btn-danger btn-block",
    onclick=`window.location='/logout/${activeRoute}'`
  ) Log Out

activeRoute
is available in any template without the need to pass it down from mixin to mixin.

🛠 Visit the "Profile" page and try to log in or log out.

If you log in, notice that you come back to the "Profile" page after you log in using the Auth0 Universal Login Page.

If you log out, you get an error!

Auth0 logout error

Why are you getting an error when logging out from the

/profile
page?

If you click on the "See details for this error" link from the error page, you'll learn that the error relates to an

invalid_request
: The "returnTo" querystring parameter "http://localhost:4040/profile" is not defined as a valid URL in "Allowed Logout URLs".

During the Auth0 setup section of this guide, you defined "Allowed Logout URLs" for the Auth0 application that represents your Express web application in the Auth0 platform:

Allowed Logout URLs

http://localhost:4040

Auth0 can only redirect your users after they log out to the URLs listed in that field. As such, you need to add the

/profile
and
/external-api
paths to it.

🛠 Head back to the "Applications" section from the Auth0 Dashboard. Select your "Auth0 Express Sample" application and then click on the "Settings" tab.

🛠 Locate Allowed Logout URLs and update like so:

http://localhost:4040,
http://localhost:4040/profile,
http://localhost:4040/external-api

🛠 Scroll down and click on "Save Changes".

🛠 Head back to your application user interface in the browser. Visit either the "Profile" or "External API" page. Refresh the page and try to log in and log out. You should now stay in the same page after either action completes without any errors.

In this section, you have learned how to use the built-in

/login
and
/logout
route controllers exposed by the Express OpenID Connect library. You also learned how to create custom authentication controllers to improve the user experience of your application and to accommodate to different use cases.

In the next section, you'll learn how to retrieve and display user profile information in your user interface.

Retrieving User Information

You can use profile data stored in your Auth0 user database to personalize the user interface of your Express application. The Express OpenID Connect library exposes that profile data in the

req.oidc.user
object. Some of the user information available includes the name, nickname, picture, and email of the logged-in user.

How can you use

req.oidc.user
to create a profile page for your users?

🛠 Update the

/profile
route controller under the
Routes Definitions > Profile
section in
src/index.js
as follows:

// src/index.js

/**
 * Routes Definitions
 */

// > Profile

app.get('/profile', (req, res) => {
  res.render('profile', {
    user: req.oidc.user,
  });
});

🛠 Next, update the

/profile
template defined in
src/views/profile.pug
as follows:

extends ../components/layout

block content
  if user
    div
      div(class="row align-items-center profile-header")
        div(class="col-md-2 mb-3")
          img(
            class="rounded-circle img-fluid profile-picture mb-3 mb-md-0"
            src=user.picture
            alt="Profile"
          )
        div(class="col-md text-center text-md-left")
          h2 #{user.name}
          p(class="lead text-muted") #{user.email}
      div(class="row")
        pre(class="col-12 text-light bg-dark p-4")
          | #{JSON.stringify(user, null, 2)}

What's happening within the

profile
template?

  • You obtain the user

    name
    ,
    picture
    , and
    email
    from the
    user
    object that you passed to the template from the
    /profile
    route controller.

  • You then display these three properties in the user interface. You only render the content of the

    profile
    template if the
    user
    object is defined.

  • Finally, you display the full content of the decoded ID token within a code box. You can now see all the other properties available for you to use.

What is an ID token?

After a user successfully logs in, Auth0 sends an ID token to your application. Authentication systems, such as Auth0, use ID Tokens in token-based authentication to cache user profile information and provide it to an application. The caching of ID tokens can contribute to improvements in performance and responsiveness for your application.

You can use the data from the ID token to personalize the user interface of your application. The Express OpenID Connect library decodes the ID token and attaches its information to the

user
property of the
req.oidc
namespace. Some of the ID token information includes the name, nickname, picture, and email of the logged-in user.

The

profile
template renders user information that you could consider protected. Additionally, the
user
property is
null
if there is no logged-in user. So either way, this component should only render if Auth0 has authenticated the user.

As such, you should protect the route that renders this template,

http://localhost:4040/profile
. You'll learn how to do just that in the next section.

Protecting Routes

The Express OpenID Connect library exposes a

requiresAuth()
middleware function that you can use to require users to login in order to access a specific route. Express will redirect to the Auth0 Universal Login page any users who have not logged in and try to access the route.

As a reminder: for

requiresAuth()
to work, you must set
authRequired
to
false
when you initialize Express OpenID Connect using the
auth
middleware function.

🛠 Open

src/index.js
and update the
Required External Modules
section as follows:

// src/index.js

/**
 * Required External Modules
 */

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

You can add the

requiresAuth()
middleware function on as part of the request-response cycle of each controller that you want to protect — in this case,
/profile
and
/external-api
.

🛠 Update the

Routes Definitions > Profile
subsection of
src/index.js
as follows:

/**
 * Routes Definitions
 */

// > Profile

app.get('/profile', requiresAuth(), (req, res) => {
  res.render('profile', {
    user: req.oidc.user,
  });
});

Now, when users who have not logged in visit a protected route, your Express application will redirect that user to the login page. After the user logs in, Auth0 will redirect the user to the page they intended to access before login.

🛠 You can now test that

/profile
requires users to log in before they can access it. Log out and try to access the Profile page again. If it works, Express redirects you to log in with Auth0.

Calling an API

This section focuses on showing you how to get an access token in your Express application and how to use it to make API calls to protected API endpoints.

When you use Auth0, you delegate the authentication process to a centralized service. Auth0 provides you with functionality to log in and log out users from your Express application. However, your application may need to access protected resources from an external API, such as contacts, pictures, or purchase history.

You can also protect an API with Auth0. There are multiple API quickstarts to help you integrate Auth0 with your backend platform.

When you use Auth0 to protect your API, you also delegate the authorization process to a centralized service that ensures only approved client applications can access protected resources on behalf of a user.

How can you make secure calls to an external API from Express?

Your Express application authenticates the user and receives an access token from Auth0. The application can then pass that access token to your external API as a credential. In turn, your external API can use Auth0 libraries to verify the access token it receives from the calling application and issue a response with the desired data.

Instead of creating an API from scratch to test the authentication and authorization flows between the client and the server, you'll use a demo Express API that I've prepared for you.

Get the Express API demo

🛠 Open a new terminal window and clone the

auth0-express-js-sample
repo somewhere in your system. Ensure that you clone it outside your Express project directory.

git clone git@github.com:auth0-blog/auth0-express-js-sample.git

🛠 Once you clone this repo, make the

auth0-express-js-sample
directory your current directory:

cd auth0-express-js-sample

🛠 Install the Node.js project dependencies:

npm install

Connect the Express API with Auth0

Create a communication bridge between Express and Auth0

This process is similar to how you connected Express with Auth0.

🛠 Head to the APIs section in the Auth0 Dashboard, and click the "Create API" button.

🛠 Then, in the form that Auth0 shows:

  • Add a Name to your API:
Auth0 Express Sample
  • Set its Identifier value:
https://express.sample
  • Leave the signing algorithm as
    RS256
    as it's the best option from a security standpoint.

Auth0 Dashboard new API form

Identifiers are unique strings that help Auth0 differentiate between your different APIs. We recommend using URLs to facilitate creating unique identifiers predictably; however, Auth0 never calls these URLs.

🛠 With these values in place, hit the "Create" button. Keep this page open as you'll need some of its values in the next section.

Add the Auth0 configuration variables to Express

🛠 Create a

.env
file for the API Server under the
auth0-express-js-sample
directory:

touch .env

🛠 Populate this

auth0-express-js-sample/.env
file as follows:

SERVER_PORT=6060
CLIENT_ORIGIN_URL=http://localhost:4040
AUTH0_AUDIENCE=
AUTH0_DOMAIN=

🛠 Head back to your Auth0 API page, and follow these steps to get the Auth0 Audience:

Get the Auth0 Audience to configure an API

  1. 🛠 Click on the "Settings" tab.

  2. 🛠 Locate the "Identifier" field and copy its value.

  3. 🛠 Paste the "Identifier" value as the value of

    AUTH0_AUDIENCE
    in
    .env
    .

Now, follow these steps to get the Auth0 Domain value:

Get the Auth0 Domain to configure an API

  1. 🛠 Click on the "Test" tab.
  2. 🛠 Locate the section called "Asking Auth0 for tokens from my application".
  3. 🛠 Click on the cURL tab to show a mock
    POST
    request.
  4. 🛠 Copy your Auth0 domain, which is part of the
    --url
    parameter value:
    tenant-name.region.auth0.com
    .
  5. 🛠 Paste the Auth0 domain value as the value of
    AUTH0_DOMAIN
    in
    .env
    .
Tips to get the Auth0 Domain
  • The Auth0 Domain is the substring between the protocol,

    https://
    and the path
    /oauth/token
    .

  • The Auth0 Domain follows this pattern:

    tenant-name.region.auth0.com
    .

  • The

    region
    subdomain (
    au
    ,
    us
    , or
    eu
    ) is optional. Some Auth0 Domains don't have it.

  • Click on the image above, please, if you have any doubt on how to get the Auth0 Domain value.

🛠 With the

.env
configuration values set, run the API server by issuing the following command:

npm start

Configure your Express app to consume the Express Demo API

Your Express application needs to pass an access token when it calls a target API to access protected resources. You can request an access token from the Auth0 authorization server by configuring your

auth()
middleware to include the API audience and a
code
response type. Take this
auth()
configuration as example:

app.use(
  auth({
    authorizationParams: {
      response_type: 'code',
      audience: 'https://api.example.com/products',
    },
  }),
);

Now, whenever you configure

auth()
to get a
code
as your
response_type
, you need to include the Client Secret of your Auth0 application.

🛠 Head back to the "Applications" section from the Auth0 Dashboard. Select your "Auth0 Express Sample" application and click on its "Settings" tab. Locate the "Client Secret" field.

🛠 Head back to the

auth0-express-pug-sample
project directory that stores your Express application.

🛠 Locate the

auth0-express-pug-sample/.env
file and add your
AUTH0_AUDIENCE
,
SERVER_URL
, and
CLIENT_SECRET
values to it:

DEV_PORT=4041
PROD_PORT=4040
AUTH0_ISSUER_BASE_URL=https://<AUTH0-DOMAIN>/
AUTH0_CLIENT_ID=<...>
BASE_URL=http://localhost:4040
SESSION_SECRET=<...>
AUTH0_AUDIENCE=https://express.sample
SERVER_URL=http://localhost:6060
CLIENT_SECRET=

🛠 The value of

AUTH0_AUDIENCE
is the same for both the Express web app and the Express API.

SERVER_URL
is the URL where the Express API server is running.

🛠 Use the value of Client Secret from the Auth0 application settings as the value of

CLIENT_SECRET
.

The Auth0 Client Secret is a critical value as it protects your resources by only granting authentication-related credentials in the form of tokens to requestors if they're authorized. Think of it as your application's password, which must be kept confidential at all times. If anyone gains access to your Client Secret, they can impersonate your application and access protected resources.

Together, these variables let your application identify itself as an authorized party to interact with the Auth0 authentication server.

🛠️ You need to restart the Node.js server for your application to recognize these new environment variables. Locate the terminal window where you executed

npm run dev
earlier, stop it, and run it again.

You can request an access token in a format that the API can verify by passing an

audience
and
response_type
props to the
auth()
initializer.

🛠 Open

src/index.js
, locate the
App Configuration
section, and update the initialization of
auth()
as follows:

// src/index.js

/**
 *  App Configuration
 */

app.set("views", path.join(__dirname, "views"));
app.set("view engine", "pug");

app.use(express.static(path.join(__dirname, "..", "public")));

// 👇 Update the mounting and initialization of auth()
app.use(
  auth({
    issuerBaseURL: process.env.AUTH0_ISSUER_BASE_URL,
    baseURL: process.env.BASE_URL,
    clientID: process.env.AUTH0_CLIENT_ID,
    secret: process.env.SESSION_SECRET,
    authRequired: false,
    auth0Logout: true,
    clientSecret: process.env.CLIENT_SECRET,
    authorizationParams: {
      response_type: "code",
      audience: process.env.AUTH0_AUDIENCE,
    },
  })
);

app.use((req, res, next) => {...});

clientSecret
goes at the same level as the other properties. However, when Express OpenID Connect makes requests to the Auth0 authorization server, it uses
response_type
and
audience
as request parameters. As such, you need to specify these two values as properties of the
authorizationParams
object.

Why is the Auth0 Audience value the same for both apps? Auth0 uses the value of the

audience
prop to determine which resource server (API) the user is authorizing your Express application to access. It's like a phone number. You want to ensure that your Express application "texts the right API".

The actions that your Express application can perform on the API depend on the scopes that your access token contains.

Remember that screen you saw when you first logged in with Auth0 asking you for permission to access your profile information? Your Express application will request authorization from the user to access the requested scopes, and the user will approve or deny the request. You may have seen something similar when sharing your contacts or photos from a social media platform with a third-party application.

When you don't add a

scope
property to the
authorizationParams
object used to configure
auth()
, the Express OpenID Connect library defaults to the OpenID Connect Scopes:
openid profile email
.

  • openid
    : This scope informs the Auth0 authorization server that the client is making an OpenID Connect (OIDC) request to verify the user's identity. OpenID Connect is an authentication protocol.

  • profile
    : This scope value requests access to the user's default profile information, such as
    name
    ,
    nickname
    , and
    picture
    .

  • email
    : This scope value requests access to the
    email
    and
    email_verified
    information.

The details of the OpenID Connect Scopes go into the ID Token. However, you can define custom API scopes to implement access control. You'll identify those custom scopes in the calls that your client applications make to that API. Auth0 includes API scopes in the access token as the

scope
claim value.

The concepts about API scopes or permissions are better covered in an Auth0 API tutorial such as "Use TypeScript to Create a Secure API with Node.js and Express: Role-Based Access Control".

🛠 Visit the "External API" page and notice that it has two buttons for you to request resources from the Express Demo API:

  • "Get Public Message" =>

    GET /messages/public-message

  • "Get Protected Message" =>

    GET /messages/protected-message

What's the plan to implement these API calls in your Express web application? Create two more routes.

First, you'll need a package to make HTTP requests from your Express route controllers. Don't worry... I

got
you.

🛠 Install

got
in your
auth0-express-pug-sample
project:

npm install got

got
labels itself as a human-friendly and powerful HTTP request library for Node.js applications.

🛠 Open

src/index.js
and update the
Required External Modules
section as follows:

// src/index.js

/**
 * Required External Modules
 */

const express = require('express');
const path = require('path');
const { auth, requiresAuth } = require('express-openid-connect');
const got = require('got');

🛠 Next, update the

Routes Definitions > External API
subsection in
src/index.js
to add two routes to handle retrieving messages from the Express API:

// src/index.js

/**
 * Routes Definitions
 */

// > External API

app.get('/external-api', (req, res) => {
  res.render('external-api');
});

app.get('/external-api/public-message', async (req, res) => {
  let message;

  try {
    const body = await got(
      `${process.env.SERVER_URL}/api/messages/public-message`,
    ).json();

    message = body.message;
  } catch (e) {
    message = 'Unable to retrieve message.';
  }

  res.render('external-api', { message });
});

app.get('/external-api/protected-message', requiresAuth(), async (req, res) => {
  const { token_type, access_token } = req.oidc.accessToken;
  let message;

  try {
    const body = await got(
      `${process.env.SERVER_URL}/api/messages/protected-message`,
      {
        headers: {
          Authorization: `${token_type} ${access_token}`,
        },
      },
    ).json();

    message = body.message;
  } catch (e) {
    message = 'Unable to retrieve message.';
  }

  res.render('external-api', { message });
});

What's happening in the External API route controllers?

  • Nothing changes for

    /external-api
    . It remains the landing page for that path.

  • /external-api/public-message
    requests message data from a public API endpoint,
    /api/messages/public-message
    .

    • It stores the response from that request in the

      message
      variable, which it passes down to the
      external-api
      template.

    • This route doesn't require user login to access the page or make the API call.

  • /external-api/protected-message
    requests message data from a protected API endpoint,
    /api/messages/protected-message
    .

    • It obtains the access token and its type from

      req.oidc.accessToken
      .

    • It uses the access token in the authorization header of the protected API call.

    • It stores the response from that request in the

      message
      variable, which it passes down to the
      external-api
      template.

    • Since this route requires a valid access token to make the protected API request, it uses

      requiresAuth()
      to request the user to log in.

The

.json()
chain method from
got
lets you obtain the server responses in JSON format.

🛠 Now, update

src/views/external-api.pug
as follows:

extends ../components/layout

block content
  div
    h1 External API
    p
      | Use these buttons to call an external API. The protected API call has an access token in its authorization header. The API server will validate the access token using the Auth0 Audience value.
    div(
      class="btn-group mt-5",
      role="group",
      aria-label="External API Requests Examples"
    )
      button(
        type="button",
        class="btn btn-primary"
        onclick="window.location='/external-api/public-message'"
      ) Get Public Message
      button(
        type="button",
        class="btn btn-primary"
        onclick="window.location='/external-api/protected-message'"
      ) Get Protected Message

    if message
      div(class="mt-5")
        h6(class="muted") Result
        div(class="container-fluid")
          div(class="row")
            code(class="col-12 text-light bg-dark p-4")
              | #{JSON.stringify(message, null, 2)}

What is happening now within the

external-api
template?

You trigger calls to the

/external-api/public-message
and
/external-api/protected-message
route controllers from the button group. If
message
is defined, you render the server response.

🛠 Log out and log back in to get a new access token from Auth0 that includes the audience information.

🛠 Visit

http://localhost:4040/external-api
and click any of the buttons on the External API page to test the responses. You may be prompted to log in depending on the action you choose to execute.

Get Public Message:

The API doesn't require an access token to share this message.

Get Protected Message:

The API successfully validated your access token.

There's a problem now... Visit

http://localhost:4040/external-api/protected-message
. After you log in, you'll see the result box. Now try to log out...

You get an error message:

Cannot GET /logout/external-api/protected-message
.

If you were to try to login from

http://localhost:4040/external-api/public-message
, you'll get a similar error:

Cannot GET /login/external-api/public-message

There is no route controller to handle the following requests:

GET /sign-up/:page/:section

GET /login/:page/:section

GET /logout/:page/:section

You could create a route controller to handle those specific paths, but there's no need. You can add

section
as an optional parameter for the existing authentication route controllers.

🛠 Open

src/index.js
and locate the
Routes Definitions > Authentication
subsection. Update this section as follows:

// src/index.js

/**
 * Routes Definitions
 */

// > Authentication

app.get('/sign-up/:page/:section?', (req, res) => {
  const { page, section } = req.params;

  res.oidc.login({
    returnTo: section ? `${page}/${section}` : page,
    authorizationParams: {
      screen_hint: 'signup',
    },
  });
});

app.get('/login/:page/:section?', (req, res) => {
  const { page, section } = req.params;

  res.oidc.login({
    returnTo: section ? `${page}/${section}` : page,
  });
});

app.get('/logout/:page/:section?', (req, res) => {
  const { page } = req.params;

  res.oidc.logout({
    returnTo: page,
  });
});

Let's test this fix.

🛠 Visit

http://localhost:4040/external-api/protected-message
and log out from there. You should be taken to the "External API" page.

🛠 Visit

http://localhost:4040/external-api/public-message
and log in from there. You should have remained in that same page.

Conclusion

You have implemented user authentication in Express to identify your users, get user profile information, and control the content that your users can access by protecting routes and API resources.

This tutorial covered the most common authentication use case for an Express web application: simple sign-up, login and logout and calling protected APIs.

However, Auth0 is an extensible and flexible platform that can help you achieve even more. If you have a more complex use case, check out the Auth0 Architecture Scenarios to learn more about the typical architecture scenarios we have identified when working with customers on implementing Auth0.

In a follow-up guide, we'll cover advanced authentication patterns and tooling, such as using a pop-up instead of a redirect to log in users, adding permission information to the

oidc
namespace, using metadata to enhance user profiles, and much more.

Let me know in the comments below what you thought of this tutorial. Thank you for reading and stay tuned, please.