Skip to main content

Protect your backend

Securing your backend is even more important than securing your frontend since the backend usually holds sensitive and important information that we never want to return to a user that is either not logged in or not the rightful recipient of that information.

In this guide we'll be showing you how easy it is to secure your application backend with Nblocks. You will apply protection by using the tokens obtained from the logged in user. Below we will Protect an endpoint with role based access control, so that only users with the right privilege can query it.

After you're done with this guide you will have an application backend that is secure and ready for production.

The Nblocks team provides, maintains, and adds code examples for popular languages continuously. However, if you have a specific need not covered by this quickstart yet, you'll find documentation on how to use Nblocks with any language in our API reference API reference.

Prerequisites
  1. You have completed the Quickstart and have access to the tokens obtained from the logged in user.

Short info about the access token

When you completed the Quickstart you got hold of a couple of tokens that was signed securely by Nblocks. One of them, the access token holds the users access information and this information can be used to protect your app. The token is encoded in a format called JWT for greater portability and security. If we decode the access token it looks like this:


{
"aid": "63d2ab029e23db0afb07a5a7",
"tid": "63d2b5c18892e10022e08399",
"scope": "AUTHENTICATED USER_READ USER_WRITE TENANT_READ TENANT_WRITE",
"role": "OWNER",
"plan": "FREE",
"iat": 1685648418,
"exp": 1685652018,
"aud": [
"63d2ab029e23db0afb07a5a7",
"https://app.nblocks.cloud"
],
"iss": "https://auth.nblocks.cloud",
"sub": "63d2b5c18892e10022e083a2"
}

Decoding and verifying JWTs

JWTs is a well known concept in security. That means there are plenty of libraries for many software stacks to decode and verify them into readable JSON. Here's a extensive list of different libraries. We'll be using one of these in our code examples.

In the structure above we highlighted the parts that we'll be focusing on in this guide.

  • The role property tells the role of the user.
  • The scope property tells what privileges the user obtained when logging in as a that role. This can be used for more fine grained protection and role based access control on individual features.
  • The sub property shows the id of the user.
  • The tid property shows the id of the tenant

How will the user provide the token to the backend on each request?

  • If your developing an application with both a backend and a frontend it is best practise to let the frontend attach the access token as an authorization header on each request to the backend.
  • When developing a backend only application you could either still resort to clients providing the authorization header in each request or store the accessToken in a session cookie once you obtained it in the quickstart. Clients will provide this cookie automatically on every future request.

Step 1. Protect an endpoint with role based access control

Imagine you have declared two endpoints, /dashboardData and /analyticsData in your express app like this.

const app = express();

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

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

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

We want both of the endpoints to require the user to be logged in before rendering a result. The /analyticsData endpoint should only be accessable for users with the privilege ANALYTICS_READ. All this can easily be done with Express middlewares:

Create a new file in which we declare two middlewares.

import { jwtVerify, createRemoteJWKSet } from 'jose';

// Verifies and decodes the access token from the authorization header
const jwtMiddleware = async (req, res, next) => {
if (req.headers['authorization']) {
try {
// Get the public key to verify the token signed by Nblocks
const JWKS = createRemoteJWKSet(
new URL('https://auth.nblocks.cloud/.well-known/jwks.json')
);

// Deconstruct user from the verified token and store the decoded information in a variable that can be used by other middlewares
const { payload: user } = await jwtVerify(
req.headers['authorization'], JWKS, { issuer: 'https://auth.nblocks.cloud' }
);
req.user = user;
next();
} catch (e) {
next(e);
}
}
};

// Grants or denies access based on the decoded token from jwtMiddleware
const protectedRoute = ({ roles, privileges }) => {
return (req, res, next) => {
if (req.user) {
if (
roles
? roles.includes(req.user.role)
: false || privileges
? privileges.some((scope) => req.user.scope.includes(scope))
: false
) {
next();
} else {
res.status(403).send({ message: 'Forbidden' });
}
} else {
res.status(401).send({ message: 'Unauthorized' });
}
};
};

export { jwtMiddleware, protectedRoute }
Handling users not logged in

If you're building a backend only app, you might want to redirect users to /auth/login instead of rendering a 401 Unauthorized error in case they're unauthorized for a better user experience.

Heading back to the initial express app file we can now make use of these middleware to start protecting the endpoints!

import { jwtMiddleware, protectedRoute } from './middlewares'

const app = express();

// First middleware that decodes tokens
app.use(jwtMiddleware);

// Restrict all endpoints globally with protectedRoute to require authenticated users
app.use(protectedRoute({privileges: ['AUTHENTICATED']}));

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

// Restrict /analyticsData with protectedRoute to require users with ANALYTICS_READ privilege
app.get('/analyticsData', protectedRoute({privileges: ['ANALYTICS_READ']}), (req, res) => {
...
});

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
More examples

Our protectedRoute middleware can be activated in other ways, like this:

// Protect all endpoints
app.use(protectedRoute({privileges: ['AUTHENTICATED']}));

// Protect /analyticsData
app.use("/analyticsData", protectedRoute({privileges: ['AUTHENTICATED']}));

// Protect using wildcards
app.use("/secret/*", protectedRoute({roles: ['ADMIN']}));

Next steps

  • Protect your frontend with user tokens.
  • Add Nblocks hosted user management to your app.
  • Add SSO alternatives to your login experience.

If you haven't already, join our Discord