Building a React and Node app with Aserto Authorization

Overview

Before we get started, let’s discuss two of Aserto’s major components: the Authorizer and the Control Plane.

The Authorizer is where authorization decisions get made. It is an open source authorization engine which uses Open Policy Agent (OPA) to compute a decision based on policy, user context and resource data. In this tutorial we’re going to use the hosted version of this authorizer.

The Control Plane manages the lifecycle of policies, user context, and resource data that are used by the authorizer. The control plane makes it easy to manage these artifacts centrally, and takes care of the details of synchronizing them to the Authorizer instance(s) deployed at the edge. More specifically, it manages:

  • Connections to external systems such as identity providers and source control systems
  • References to registered authorization policies
  • A user directory built from the identity providers its connected to
  • A centralized log of aggregated decisions made by the Authorizer

The Policy

We define the access control rules we want to enforce in our policy — as opposed to our application code. This is what’s known as the “Policy-as-Code” approach, where authorization logic is decoupled from application logic.

Policies are treated just like application code or infrastructure-as-code — they are stored and versioned in a git repository. We’re going to define and see the policy in action later in this tutorial.

What to expect

When you’ve completed this tutorial you’ll have learned how to:

  1. Create a React application with authentication using oidc-react
  2. Set up a simple Express.js application with authentication middleware and define a protected route
  3. Create and modify a very simple authorization policy
  4. Integrate the Aserto Authorization Express.js SDK to enable fine grained access control
  5. Conditionally render UI elements based on user access

It should take about 30–45 minutes to complete this tutorial.

Prerequisites

  1. Node.JS installed on your machine
  2. Aserto account and credentials (if you don’t have one, sign up here!)
  3. Your favorite code editor

To get started, let’s add users to your Aserto directory. We’ll need these users to test our application and authorization policy.

Add Users to the Aserto Directory

Log in to your Aserto account. To add the Acmecorp identity provider, go to the Connections tab, and click “Add connection”.

From the dropdown, select “acmecorp”:

Name the provider (you can choose whatever name you want) and give it a description. Then, click “Add connection” to complete the process.

Review your Users

You’ll see the following JSON object (shortened here for brevity):

Users in this identity provider have properties and roles associated with them. In this case, among other roles, Euan has the role of a viewer. If you search the Aserto directory for the user Kris Johnson and inspect her associated JSON object, you’ll see she has the role of admin. Later in this tutorial we will leverage these roles to allow the authorizer to make a decision as to which user will have access to a piece of sensitive information.

But first, we’ll set up our React application. Let’s get started!

React Application setup

yarn create react-app aserto-react-demo

You can now cd into the newly created folder and start the app:

cd aserto-react-demo
yarn start

The familiar React logo should appear, indicating that the app is ready to go.

Adding OIDC dependencies

In your terminal, execute the following command:

yarn add oidc-react

The following environment variables are used to point your application to Aserto’s demo IDP, so that you don’t have to set one yourself. Create a file called .env and add the following:

REACT_APP_OIDC_DOMAIN=acmecorp.demo.aserto.com
REACT_APP_OIDC_CLIENT_ID=acmecorp-app
REACT_APP_OIDC_AUDIENCE=acmecorp-app
REACT_APP_API_ORIGIN=http://localhost:8080

Note: Make sure the .env file is added to the .gitignore file so that it is not checked in.

Open the file src/index.js and add the dependency:

import { AuthProvider } from "oidc-react";

Add the following configuration object:

Next, we’ll wrap the top level React Application component with the AuthProvider, and pass it the required configuration we created.

Note: When developing locally, make sure your application is running on port 3000 — other ports are not registered with the identify provider and will not work.

If your application is still running, you should see the following login window:

Use the following user credentials to log in:

  • Email address: euang@acmecorp.com
  • Password: V@erySecre#t123!

After logging in, you should see the React logo again.

Add a stylesheet

<link rel="stylesheet" href="https://aserto-remote-css.netlify.app/react-and-node-quickstart.css"/>

Next, we’ll build the app itself. Open the App.js file, and replace it’s contents with:

Test the application

yarn start

If you haven’t already, log in, using the following credentials:

  • Email address: euang@acmecorp.com
  • Password: V@erySecre#t123!

If everything works as expected, the following should be displayed.

We can make sure that the application’s authentication flow works by logging out and then logging back in.

Great! Our application authenticates with the Acmecorp IDP, and so we have our user’s identity in hand. Next, we’ll create the Express.js service which will host our protected resource and will communicate with the Aserto hosted authorizer to determine whether or not a logged in user has the permissions to access the protected resource based on the user’s identity.

Service Setup

yarn init -y
yarn add express express-jwt jwks-rsa cors express-jwt-aserto dotenv

To the .env file we created previously, we'll add the following:

JWKS_URI=https://acmecorp.demo.aserto.com/dex/keys
ISSUER=https://acmecorp.demo.aserto.com/dex
AUDIENCE=acmecorp-app

In the service folder, Create a file called api.js - that will be our server. To this file, we'll add the following dependencies:

In the next section we define the middleware function which will call our identity provider to verify the validity of the JWT (and also enable CORS): Express.js will pass the call to the checkJwt middleware which will determine whether the JWT sent to it is valid or not. If it is not valid, Express.js will return a 403 (Forbidden) response.

Lastly, we set up a protected route which will use the checkJwt middleware:

Awesome! our service will be listening on port 8080 and we set up a protected endpoint. In the next section we’ll test this endpoint by updating our application to send a JWT token.

Update the application

At the top of the file, modify the line:

import React, { useEffect } from "react";

to:

import React, { useEffect, useCallback, useState } from "react";

Then, find the following code block:

And add the following code right after the definition for the isAuthenticated variable:

In this portion of the code we create a callback (which will be triggered by a button). The callback will first get our JWT token from the identity provider, using the auth object that is obtained from the useAuth hook. Then we perform the call to our service sending the authorization token as part of our request's headers (fetch).

Finally, we parse the JSON response from the server and set the state of the message variable: if the service returns a 403 Forbidden or a 401 Unauthorized errors, and the message “No access to sensitive information” will be shown. If no error is returned from the service, the user has access to the protected resource and the message will be shown.

Next we’ll update the main section of the app (in the div with the className main) to include the button that will trigger accessSensitiveInformation and an area to show the message. Replace the existing div with the class main section with the following:

Test the application

yarn add npm-run-all

Then, update the package.json in the root folder, and add the following to the scripts section:

"scripts": {
...
"start:server": "node service/api.js",
"start:all": "npm-run-all --parallel start start:server"
},

First, stop the application by hitting ctrl+c in the terminal where you previously started the application. To start both the application and the server, you can now run:

yarn start:all

Let’s test our application by first logging out, then logging in again with the email euan@acmecrop.com and the password V@erySecre#t123!.

If everything works as expected, we should see the following:

We can further test this by intentionally sending a malformed header and making sure the sensitive information isn’t shown. One way to do this is to append so rogue characters to the access token like so:

In this case we’d expect the “No access to sensitive information” message to be shown.

Checkpoint

Creating a Role Based Access Control Authorization Model

As we saw before in the Aserto Directory, the user Euan (euang@acmecorp.com) has the viewer role, and the user Kris (krisj@acmecorp.com) has the admin role. Right now, if you log in as Euan user you’ll see the following:

We want to ensure that if we’re logged in as Euan (a viewer), the application won't allow access to our protected resource. Since we didn't add any way to authorize users based on their role - all users will have access to the protected resource. Let's fix that by first creating a simple Aserto policy to allow access only to users with the admin role. We'll then use this policy in our application.

Create an Aserto Policy

In console.aserto.com, go to the Policies tab and click "Add Policy"

If you haven’t already added a source code connection, select “Add a new source code connection”. You can choose either adding a GitHub connection using an OAuth2 flow, or add a GitHub connection using a GitHub PAT.

Once you’ve added the connection, select the organization you’d like to use for the repository, and select “New (using template)”.

Then, from the template dropdown, select “aserto-dev/policy-template”. Name repo “policy-aserto-react”, and click “Create repo”.

Finally, name your policy “aserto-react” and click “Add Policy”.

Aserto will generate a new repository in your GitHub account the will include the necessary policy files. Head to GitHub to retrieve the URL for the repository that was just created, and clone it.

git clone git@github.com:<YOUR ORGANIZATION>/hello-aserto-react.git

Now that we have a local copy of the policy, let’s start modifying it:

We’ll start by updating the .manifest file under src, which currently will only point to the root of our policy. We'll change it from:

{
"roots": ["policies"]
}

to:

{
"roots": ["asertodemo"]
}

Rename the file hello.rego to protected.rego. We'll open the file and change the package name to match the path of our Express API endpoint. The basic structure of the package name is:

[policy-root].[VERB].[path]

Where the path is separated by dots instead of slashes. And so in our case, the Express.js path

app.get('/api/protected'...

Is referenced in the package as:

package asertodemo.GET.api.protected

We’re also going to define the policy such that the only allowed user is one with an admin role. Aserto attaches this user object to the input object. Below is the finished policy:

By default, the allowed decision is going to be false - this follows the principle of a “closed” system where access is disallowed unless specific conditions are satisfied.

At runtime, the application will send the JWT associated with the logged in user. The Express.js service will relay the JWT along with the request path as the identity and resource contexts respectively to the authorizer.

The some index and ...roles[index] expressions indicate the authorizer will iterate over all the elements in the roles array under the attributes property in the user object. The authorizer will check if the iterated role is equal to the string admin. If it is, the allowed decision will evaluate to true.

Updating the Policy Repository

Commit, tag and push the changes you made:

git add .
git commit -m "updated policy"
git push
git tag v0.0.1
git push --tags

Open the Aserto console, and navigate to the Policies tab. Then, open the policy “policy-aserto-react” and review the changes. You should see the following:

Checkpoint

Update the Express service to use the Aserto Express.js middleware

Copy the following values to the .env file:

POLICY_ID={Your Policy ID}
AUTHORIZER_API_KEY={Your Authorizer API Key}
TENANT_ID={Your tenant ID}
POLICY_ROOT=asertodemo
AUTHORIZER_SERVICE_URL=https://authorizer.prod.aserto.com

Add the following dependency reference in service/api.js (after the const jwt = require("express-jwt"); line):

const { jwtAuthz } = require("express-jwt-aserto")

Continue by creating the configuration object for the Aserto middleware. Add the following section after the const app = express(); line:

We’ll define a function for the Aserto middleware, and pass it the configuration object.

Lastly, add the checkAuthz middleware to our protected route: Add the reference to the checkAuthz middleware right after the checkJwt middleware reference. You endpoint definition should look like this:

The checkAuthz middleware is going to pass the request context - which consists of the policy reference (based on the request route), the identity context (based on the JWT token passed) and resource context (based on the request parameters) - to the authorizer, which given the policy will determine what the allowed decision would be.

Test the Application

When we log in with the user krisj@acmecorp.com who has the role of an admin we will still be able to see the following:

If we log out and log in again as euang@acmecorp.com we will see the following:

Euan doesn’t have the role of admin, and the route /api/protected will be disallowed.

Summary

But you’re journey doesn’t have to end here! You can learn how to expand your policy to include more roles, as well as how to use Aserto’s React SDK for conditional UI rendering.

The finished application code is available in here.

--

--

Welcome to modern authorization.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store