Tech

Securing NodeJS Microservices with Oauth

In this article, we aim to implement a well-known security standard called Oauth which will help us secure our endpoints anywhere.

Oauth is a standard that you can implement on your own by using a variety of libraries. It is also a consistent and easy way to:

  • secure communication between our services without reinventing the wheel.
  • give access to a resource from another service without providing your credentials.

Explaining Oauth is out of scope for this tutorial but you can find more information here.

Now let's get to the point and see what are the steps to implement Oauth with a NodeJS microservice.

Oauth Architecture

In order to understand better, let's start by reviewing its architecture.

As part of the Oauth architecture, we have an authorization server that acts as middleman in handling all the permissions very clearly. Instead of providing credentials to another application to access your resources, with Oauth we’ll provide a key that this application will use to retrieve a token with a very specific set of permissions called scopes. The scopes are a representation of our resources in the resource server.

This is an example of how the Oauth flow works, as you can see in the following diagram.

The application that needs access to a specific resource server makes a call to the Oauth Authorization Server first, with the client id and secret previously shared with the application by the authorization server. The scope in this case should represent the service that the application wants to access.

If all goes well, the Oauth Authorization Server will push back an access token, valid only for a specific timeframe and only to access the resource specified in the scope.

The application is now able to call the resource server (our services) by including the token in the authorization header of each call.

Oauth tokens

Oauth security is based on access tokens for authorization, so let's talk about the different types of tokens that are used by this standard and some particularities about them.

Access token

The access tokens are the mechanism that the applications will use to access an API or service on behalf of a user. This token represents the authorization of a specific application to access an specific part of information on the service and they are short-lived.

The access token is very sensitive information and we should keep it in a very secure way, so it should only be accessed via the resource server, the authorization server and the application itself.

All the tokens are usually represented with the JWT standard (JSON Web Tokens).

JWT
A JWT (pronounced 'jot') is a secure and trustworthy standard for token authentication. JWTs allow you to digitally sign information (referred to as claims) with a signature and can be verified at a later time with a secret signing key.

Refresh token

This one can live more time, as in days, months, or even years. It can be used to get new tokens. To get a refresh token, applications typically require confidential clients with authentication.

OpenIdConnect

Oauth can be combined with another standard called SAML,  OpenID Connect (OIDC) extends OAuth 2.0 with a new signed id_token for the client to be able to get more information about the users directly from the token.

How can we implement Oauth?

So this is great, but how can we implement this standard?, well Oauth can be implemented in two ways: on your own, or via third-parties.

Implementing on your own is maybe the hardest option, as you will need to  create and maintain the authorization server from your end. However, it also has some advantages as you will have more control over the authorization piece and we can take advantage of libraries that are available in any programming language.

For NodeJS we have oauth2-server - open source, simple, and easy to integrate with your Node apps (even if they’ve already been running for a while).

In the docs, you will find the official Model Specification that describes how your JS code must override the default OAuth2 functions to provide your customized auth experience.

With the OAuth2Server object, you can override the default OAuth2 provider by the Express server. Then, we can easily provide your own auth experience.

Another way to use Oauth is by entrusting a third-party with providing the desired level of security. Oauth is based on access tokens for authentication and authorization, so - given the fact that this is just an example - I’ll use Okta as Oauth provider.

Lets see all of this in an example specifically to secure a nodeJS service.

Practical example

All the code is available here and you will also need to have nodejs installed.

First of all, let's create a very simple nodeJS application: create a folder called 'node-oauth-test', then open a terminal in that folder and execute the following command:

npm init

After providing all the required information, a package.json file will be created for you. Then we need to add a very simple code for our nodejs 'hello word' example.

To do this, you have to create a new file, index.js, and add the content provided below.

const express = require('express')
const bodyParser = require('body-parser')
const { promisify } = require('util')
const app = express()

app.use(bodyParser.json())

app.get('/', (req, res) => {
    res.status(200).send('Hello, world!').end();
});

const startServer = async () => {
    const port = process.env.SERVER_PORT || 8080
    await promisify(app.listen).bind(app)(port)
    console.log(`Listening on port ${port}`)
}

startServer()

The code is self-explanatory but in this case we are using a library called ExpressJS to create our first hello world path.

To install express, type

npm install express util

The next step is to run your NodeJS application for the first time, by executing the following:

`node.
`C:\Users\pablo.portillo\Documents\VSworkspace\node-oauth-test>node 
Listening on port 8080` 

Now we should see our application running in port 8080 directly in our browsers.

Now that we have our application up and running, it is time to secure it: the first step for this is to setup Okta CLI, a utility that will help us make this process very smooth.

First step is installing a package manager (I used Chocolatey), open a powershell with admin permissions and execute the following command:

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

Now its time to Install Okta CLI, then in the same powershell run this command:

choco install okta --version 0.7.1

If all goes well, let's setup our Okta organization for testing proposes by executing the following command:

C:\Users\pablo.portillo>okta register
An existing Okta Organization (https://dev-4446414.okta.com) was found in C:\Users\pablo.portillo\.okta\okta.yaml
Overwrite configuration file? [Y/n]Y
Configuration file backed: C:\Users\pablo.portillo\.okta\okta.yaml.20210520T1144
First name: Pablo
Last name: Portillo
Email address: [email protected]
Company: xtiyo
Creating new Okta Organization, this may take a minute:
-

After a few seconds, Okta will send you an email with a confirmation code, then you just need to copy the details of your testing organization.

C:\Users\pablo.portillo>okta register
An existing Okta Organization (https://dev-4446414.okta.com) was found in C:\Users\pablo.portillo\.okta\okta.yaml
Overwrite configuration file? [Y/n]Y
Configuration file backed: C:\Users\pablo.portillo\.okta\okta.yaml.20210520T1144
First name: Pablo
Last name: Portillo
Email address: [email protected]
Company: xtiyo
Creating new Okta Organization, this may take a minute:
OrgUrl: https://dev-6501327.okta.com
An email has been sent to you with a verification code.

Check your email
Verification code: 692374
New Okta Account created!
Your Okta Domain: https://dev-6501327.okta.com
To set your password open this link:
https://dev-6501327.okta.com/welcome/drpP3KxvcL6DMLjJOXfQ

Now is important to go to the same folder of your nodejs project in order to create our testing application in Okta.

Let's run the following command, which will create a new file inside your project folder called '.okta.env': okta apps create service.

C:\Users\pablo.portillo\Documents\VSworkspace\node-oauth-test>okta apps create service
Application name [node-oauth-test]:
Configuring a new OIDC Application, almost done:
Created OIDC application, client-id: 0oas9qvg9xhxzqxRc5d6
Okta application configuration has been written to: C:\Users\pablo.portillo\Documents\VSworkspace\node-oauth-test\.okta.env

The next step is to create the environment variables for your project. It's important for you to ignore the .env and .okta.env from your source code control to avoid security bridges.

Create a new file called '.env', which will include the following variables:

ISSUER=https://{yourOktaDomain}/oauth2/default
SCOPE={scope}
CLIENT_ID={yourClientId}
CLIENT_SECRET={yourClientSecret}

NOTE: You will need to replace the content later.

For now, let's create a new scope in the okta web interface. Run okta login and open the resulting URL in your browser. Sign in the Okta Admin Console, then go to Security > API and select your default authorization server.

You need to navigate to the Scopes tab and click on the 'Add Scope' button and create a scope for your REST API. This time will be testing_scope - remember to replace the scope in your .env file.

Next, using express, we create an interceptor in nodejs - this will help our application to verify the JWT token to see if its valid.

We create a new file called 'auth.js' and paste this content - this is only to verify our tokens against okta endpoints.

const OktaJwtVerifier = require('@okta/jwt-verifier')

const oktaJwtVerifier = new OktaJwtVerifier({
    issuer: process.env.ISSUER,
    clientId: process.env.CLIENT_ID
})

module.exports = async (req, res, next) => {
    try {
        const { authorization } = req.headers
        if (!authorization) throw new Error('You must send an Authorization header')
        
        const [authType, token] = authorization.trim().split(' ')
        if (authType !== 'Bearer') throw new Error('Expected a Bearer token')

        const { claims } = await oktaJwtVerifier.verifyAccessToken(token, 'api://default')
        if (!claims.scp.includes(process.env.SCOPE)) {
            throw new Error('Could not verify the proper scope')
}
        next()
} catch (error) {
        next(error.message)
    }
}

Install dotenv to load environment variables and jwt verifier from okta, using

npm install [email protected] @okta/[email protected]

Finally, we need to add some changes to our index.js: basically, our simple service is now using an interceptor defined in the ./auth.js file to verify the token before the request reach our endpoints.

const express = require('express')
const bodyParser = require('body-parser')
const { promisify } = require('util')
require('dotenv').config()

const authMiddleware = require('./auth')
const app = express()

app.use(bodyParser.json())
app.use(authMiddleware)

app.get('/', (req, res) => {
    res.status(200).send('Hello, world!').end();
});

const startServer = async () => {
    const port = process.env.SERVER_PORT || 8080
    await promisify(app.listen).bind(app)(port)
    console.log(`Listening on port ${port}`)
}

startServer()

If you run our code again, you will see an error if you access directly from your browser.

What do? Let's call the service with a bad bearer token, using postman.

Now we have an endpoint that is only accessible with an access_token provided by Okta, a third-party that will guarantee the security of our application. In order to call our service, we need to ask Okta for a new token and then pass it to our service.

In postman, create a new POST request and complete the following details with your own information:

  • URL: it's your organization URL plus '/v1/token path'
  • Username and password: your client id and client secret.

In the body, don't forget to add the grant_type as client_credentials and the scope as test_scope following with our example.

You should get your own access_token, which is what we will use to call our new service; you just need to add the access token in the bearer token option.

Note: Postman collections are also in the source repository.

Closing Thoughts

Oauth is an amazing way to secure our endpoints. Now that you know there are two ways to implement it, choose the option that better fits your needs.

From my perspective, this decision depends on the layout of your team and how many services you would like to secure. NodeJS, with the help of express, has built-in support for Oauth, which makes the implementation quite easy.

Using Okta as an Oauth is easy too and allows you to focus on the business logic instead of creating and maintaining the Oauth architecture on your own.

Author image

by Pablo Portillo

Google Cloud Professional Certified Architect and Solutions Architect with more than 6 years of experience with cloud technologies. Pablo worked with companies in Aeronautics, Geolocation, or BPOs.
  • Santa Ana, El Salvador

Have an app idea? It’s in good hands with us.

Contact us
Contact us