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.
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.
- Receive requests from the frontend
- Verify the user's JWT access token for each request
- 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).
- Your frontend requests a JWT access token whenever the user wants to sign on.
- The authentication server generates a JWT access token using a private key and then sends the JWT access token back to your frontend.
- Your frontend sends this JWT access token to your Node.js server whenever your user makes a request.
- 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.
Our Node.js server
Our Node.js server will have 3
We'll use Express.js for the routing, but other frameworks (Nest.js, Hapi.js, Feathers.js, etc.) all work in the same manner.
|This route is accessible by anyone, whether they are logged in or not||Public data|
|This route is accessible by any user who is logged in||User-specific data|
|This route is only accessible by users with an ||Admin-only data|
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.
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.
The frontend should include the user's JWT access token in the
authorization header of the request:
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
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
The client should include the user's JWT access token in the
authorization header of the request:
To restrict the route to admins only, we need to check that the JWT access token has the
So we want to check that the
payload.authorization[tenantId].roles array contains the
As with the protected route, our server should read the
authorization header for the request, then verify the JWT access token with the account'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
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:
- Create an SSO "Client" or "App" for the provider
- Set the authorized domains
- Record the credentials
- Enter the credentials into Userfront
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:
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:
- Client ID (sometimes called App ID)
- Client secret (sometimes called App Secret)
Locate these 2 pieces of information so that we can add them to Userfront.
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.
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.
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.
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
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.
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.