Securing Nodejs(Express) REST API with Role-based access control(RBAC) using Keycloak

Saurav Samantray
5 min readMay 29, 2024

--

REST API could contain sensitive data(user information, financial details, or proprietary business data) and needs to be protected from unauthorized access. In this article, we will explore steps to secure a Nodejs(Express) REST API using RBAC(Role-based access control) setup in Keycloak.

Pre-requisite

Spin up Keycloak server — If you have Docker setup in your local system, you can use one of the existing GitHub repositories I created to spin up the Keycloak server — https://github.com/saurav-samantray/custom-auth-service

Setup Keycloak

Step1: Access the Local Keycloak Server

Access the local Keycloak server setup in the previous setup at http://localhost:8080

Step2: Create a new client

Step3: Create Realm Roles

  • Navigate to the Realm roles in the left navigation menu and click on Create role
  • Create user role — express-user
  • Repeat the steps to create an admin role — express-admin

Step4: Create Users

Navigate to the Users section from the left navigation menu. Fill out the new user form and click on Create.

  • Navigate to the details of the newly created user — user@nerdcore.com, go to the Credentials tab and Click on Set Password
  • Fill out the Set password form with the appropriate password. Make sure to mark the Temporary toggle button Off
  • Create another user admin@nerdcode.com

Step5: Assign roles to users

Navigate to the Role Mapping Tab on the user details page and click on the Assign Role button

  • Select express-user and click on the Assign button
  • Similarly, assign the express-admin role to user admin@nerdcore.com

Basic Express JS application to serve REST endpoints

Initialize a Node JS application

run the below set of commands to initialize a NodeJS application as our express server will reside inside the Node application.

mkdir secure-express-service
cd secure-express-service
npm init

The npm init command will initiate a series of prompts to create the base package.json. Sample input below

package name: (secure-express-service)
version: (1.0.0)
description: Express JS based REST APIs secured using Keycloak auth server
entry point: (index.js) app.js
test command:
git repository:
keywords:
author: Saurav Samantray
license: (ISC)

Install dependencies

npm install express express-session keycloak-connect nodemon

Define main execution file — app.js

Add a file named app.js at the root level of your secure-express-service project.

Define imports and express app

// file - app.js

const express = require('express');
const session = require("express-session");
const Keycloak = require("keycloak-connect");

const app = express();
const PORT = 3000;

...

Setup Keycloak Middleware

// file - app.js

...

const USER_ROLE = process.env.USER_ROLE || 'express-user';
const ADMIN_ROLE = process.env.ADMIN_ROLE || 'express-admin';

const kcConfig = {
clientId: process.env.AUTH_CLIENT_ID || 'secure-express-service',
bearerOnly: true,
serverUrl: process.env.AUTH_SERVER || 'http://localhost:8080',
realm: process.env.AUTH_REALM || 'master'
};

const memoryStore = new session.MemoryStore();

Keycloak.prototype.accessDenied = function (request, response) {
response.status(401)
response.setHeader('Content-Type', 'application/json')
response.end(JSON.stringify({ status: 401, message: 'Unauthorized/Forbidden', result: { errorCode: 'ERR-401', errorMessage: 'Unauthorized/Forbidden' } }))
}

const keycloak = new Keycloak({ store: memoryStore }, kcConfig);

function adminOnly(token, request) {
return token.hasRole(`realm:${ADMIN_ROLE}`);
}

function isAuthenticated(token, request) {
return token.hasRole(`realm:${ADMIN_ROLE}`) || token.hasRole(`realm:${USER_ROLE}`);
}


app.use(session({
secret: process.env.APP_SECRET || 'BV&%R*BD66JH',
resave: false,
saveUninitialized: true,
store: memoryStore
}));

app.use( keycloak.middleware() );

...

Setup REST endpoint and server

app.get('/public', (req, res) => {
res.status(200).send({
'message': "This is a public enpoint which can be accessed by anonymous users",
});
})

app.get('/secured', [keycloak.protect(isAuthenticated)], (req, res) => {
res.status(200).send({
'message': "This is a secured enpoint which can be accessed by any authenticated user",
});
})

app.get('/secured-admin', [keycloak.protect(adminOnly)], (req, res) => {
res.status(200).send({
'message': "This is a secured enpoint which can be accessed only by any authenticated user with role admin",
});
})

app.listen(PORT, (error) => {
if (!error) {
console.log("Server is Successfully Running, and App is listening on port " + PORT)
}
else {
console.log("Error occurred, server can't start", error);
}
}
);

Update package.json scripts to run server

  • Update the content in the scripts section of package.json as below. It will help you run your express server in both development and production mode
  "scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
},
  • Start your express server in development mode(auto-reload on file change) using the below command
npm run dev

Testing your REST APIs

Public Endpoint— http://localhost:3000/public

  • Even without an authentication token, this endpoint will fetch a response.

Secured Endpoint — http://localhost:3000/secured

  • Without a token, you should get an authentication error
  • Let’s set up the Authorization tab to generate a token using the Configure New Token section. You can use the values below.
Token Name - secure-express-service
Grant type - Password Credentials
Access Token URL - http://localhost:8080/realms/master/protocol/openid-connect/token
Client ID - secure-express-service
Username - user@nerdcore.com
Password -
Client Authentication - Send as basic auth header
  • Click on Get New Access Token, then Proceed, then Use Token
  • Now fire the /secured rest endpoint again, and you will get a successful response.

Secured Admin Endpoint — http://localhost:3000/secured-admin

  • If you try to access the endpoint without any token — Auth error
  • If you try to access the endpoint with the token from user@nerdcore.com — Auth error
  • If you try to access the endpoint with the token from admin@nerdcore.com — Success

You can find the GitHub repository here for reference.

Happy learning and happy coding!

--

--