Node.js authentication, simplified


Authentication is one of those things that just always seems to take a lot more effort than we want it to.

To set up auth, you have to re-research topics you haven't thought about since the last time you did authentication, and the fast-paced nature of the space means things have often changed in the meantime. New threats, new options, and new updates may have kept you guessing and digging through docs in your past projects.

In this tutorial, we lay out a different approach to authentication (and access control, SSO, and more) in Node.js applications.

Rather than add a static library that you have to keep up to date or re-research each time you want to implement auth, we'll use a service that stays up to date automatically and is a simpler alternative to Auth0, Okta, and others.

Node.js authentication

When writing authentication in Node.js, our client-side app makes a request to our authentication server, which then returns an access token. That token is saved in the browser and can be used in subsequent requests to your Node.js server (or other servers, if needed).

We'll set up a Node.js server to handle authentication & authorization on the backend.

  1. Receive requests from the frontend
  2. Verify the user's JWT access token for each request
  3. Determine what the user is allowed to do

JWTs (JSON Web Tokens)

JSON Web Tokens (JWTs) are compact, URL-safe tokens that can be used for authentication and access control in Node.js applications.

Each JWT has a simple JSON-object as its "payload" and is signed such that your server can verify that the payload is authentic.

It's important to note that this payload is readable by anyone with the JWT, including your frontend application.


A JWT access token looks like this:


The payload for the JWT is the middle section (separated by periods):


The JWT payload can be decoded from base64 to yield the JSON object:

JWT access tokens

With modern RSA cryptography, an authentication server can generate secure JWT access tokens, and any other machine can read and verify those JWT access tokens using a public key.

Your backend server can verify a JWT access token as authentic by checking it against the public key.

This allows your backend server to reject any JWT access tokens that were not created by the authentication server (or that have expired).

  1. Your frontend requests a JWT access token whenever the user wants to sign on.
  2. The authentication server generates a JWT access token using a private key and then sends the JWT access token back to your frontend.
  3. Your frontend sends this JWT access token to your Node.js server whenever your user makes a request.
  4. Your Node.js server verifies the JWT access token using a public key and then reads the payload to determine which user is making the request.

Each of these steps is simple to write down, but each step has its own pitfalls when you actually want to implement it and keep it secure.

Especially over time, as new threat vectors emerge and new platforms need to be patched or supported, the security overhead can add up quickly.

Userfront removes auth complexity in Node.js apps

Userfront is a framework that abstracts away auth complexity. It is a fully-featured solution that stays up to date automatically.

This makes it much easier for you to work with authentication in a Node application and, perhaps most importantly, keeps all the auth protocols updated for you over time.

Setting up authentication in Node.js

Now we will go through building all the main aspects of authentication in a Node.js application.

We will use Express.js for routing, along with the jsonwebtoken library for verifying JWT access tokens.

JWT access token flow

We will use a JWT access token generated by Userfront that contains the user's identity and roles.

1. User logs in

Your signup or login form sends a request to Userfront, which returns the user's JWT access token.

2. Client sends JWT access token to your server

Your frontend code sends the JWT access token with each request, and your Node.js server verifies the JWT access token before responding.

Node.js authentication diagram


Our Node.js server

Routes

Our Node.js server will have 3 GET routes: /public, /protected, and /admin.


We'll use Express.js for the routing, but other frameworks (Nest.js, Hapi.js, Feathers.js, etc.) all work in the same manner.

RouteDescriptionResponse data
/publicThis route is accessible by anyone, whether they are logged in or notPublic data
/protectedThis route is accessible by any user who is logged inUser-specific data
/adminThis route is only accessible by users with an admin roleAdmin-only data

Public route

To build a route that anyone can view, we do not need authentication or access control.

Thus, our server code does not need to check the JWT access token, and we can return a response directly.


Protected route

To build a route that only logged-in users can view, the frontend should include the user's JWT access token in the authorization header for each request.

Our server can then read the authorization header and reject any JWT access tokens that are not valid.

Client (frontend)

The frontend should include the user's JWT access token in the authorization header of the request:

Server (backend)

Our Node.js server reads the authorization header of each request and rejects any request without a valid JWT access token.

If the JWT access token is valid, we return information about the user.

If the JWT access token is invalid or expired, we throw an error and return Unauthorized.


Admin route

To build a route that only admin users can view, we need the frontend to include the user's JWT access token in the authorization header for the request.

Our server can then read the header and reject any JWT access tokens that don't have the admin role.

Client (frontend)

The client should include the user's JWT access token in the authorization header of the request:

Server (backend)

To restrict the route to admins only, we need to check that the JWT access token has the admin role.

So we want to check that the payload.authorization[tenantId].roles array contains the admin role.

As with the protected route, our server should read the authorization header for the request, then verify the JWT access token with the workspace's public key before responding.

If the JWT access token is valid and has the admin role, we return information restricted to admins.

If the JWT access token is invalid, expired, or missing the admin role, we throw an error and return Unauthorized.


Node.js Single Sign-On

From here, we can add social identity providers like GitHub, Google, Facebook, and LinkedIn to our application.

No additional code is needed to implement SSO: we can add or remove providers without having to update the way we handle JWT access tokens in our server application code.

The process for setting up SSO is similar for most providers:

  1. Create an SSO "Client" or "App" for the provider
  2. Set the authorized domains
  3. Record the credentials
  4. Enter the credentials into Userfront

Single Sign-On

Configure the SSO provider

First, we'll need to visit the provider's site, create an account, and configure SSO options there. Some providers refer to your setup as an SSO "Client", while others refer to it as an SSO "App".

Depending on the provider, they may request information to verify that your application does what you claim it does. Provide the details needed to set up your SSO provider.

Set your authorized domains

SSO providers will typically limit their operation to only the domains that you provide. In this step, you need to add Userfront to your list of allowed domains.

By default, Userfront will redirect the SSO flow to the URL below. You should authorize your application to allow this URL:

Verified domains

Some SSO providers may ask you to verify each of your domains with a DNS record. In this case, you can configure your own subdomain to use instead of the default api.userfront.com, so that you can add the DNS record required by your provider.

Find your SSO credentials

The SSO provider will have 2 pieces of necessary information:

  1. Client ID (sometimes called App ID)
  2. Client secret (sometimes called App Secret)

Locate these 2 pieces of information so that we can add them to Userfront.

Locate SSO Credentials


The "Client secret" is a Key created and downloaded in Apple Developer.

Also add your Key ID (identifier of your Key above) and Team ID in the Userfront dashboard.

By default, Userfront is configured for multi-tenant Azure applications.

For a single-tenant Azure application, add your Azure tenant ID as well.

Enter your credentials

Select the provider you want to use on the authentication page in the Userfront dashboard.

Enter your credentials in the fields provided.

Enter SSO credentials


You can use different SSO credentials for live mode and test mode. Be sure to add the credentials while in the mode you want to use.

Using SSO

Once you've finished setting up your SSO provider and have added your credentials, you can signin via SSO in various ways:

The following libraries each implement all of the core JS methods:

JWT access token flow revisited

We can use the same JWT access token flow as we used above with our protected route on the Node.js server when using SSO:

1. User logs in

Your signup or login form sends a request to Userfront, which returns the user's JWT access token and saves it as a cookie.

2. Client sends JWT access token to your server

Your frontend code should send the JWT access token with each request, and your server should verify the JWT access token before responding.

Node.js authentication diagram

Turning SSO on & off

You can control whether the SSO provider will allow signin by toggling the switch next to the provider in the authentication page within the "First factors" section.

With the Toolkit

The Toolkit is the path of least resistance for adding SSO to your site. It's a set of pre-built forms that you can add to your site with a small amount of code.

Displaying the SSO button with Toolkit

When you toggle the provider "On" or "Off", the Toolkit will automatically add or remove the SSO button in the signup and login forms.

With core JS

If you're using not using one of our frontend framework libraries, we recommend using core JS to call either Userfront.signup() or Userfront.login() using the provider name.


The signup() & login() methods are equivalent when using SSO; there is no need to detect when a user is signing up or logging in.

See the core JS login() docs for more information.

Final notes

Adding authentication and access control to your Node.js application doesn't have to be a hassle. Both the setup step and, more importantly, the maintenance over time, are handled with modern platforms like Userfront.

JSON Web Tokens allow you to cleanly separate your auth token generation layer from the rest of your application, making it easier to reason about and more modular for future needs. This architecture also allows you to focus your efforts on your core application, where you are likely to create much more value for yourself or your clients.

For more details on adding auth to your Node application, visit the Userfront guide, which covers everything from setting up your auth forms to API documentation, example repositories, working with different languages and frameworks, and more.