Secure React Routes & Component with Keycloak

Chanuka Asanka
7 min readMay 21, 2020

--

There are several authentication services out there such as Firebase Authentication, Auth0, etc. In sum project, you might need more control over your authorization and user management with advanced features.

That’s where Keycloak is an excellent choice, It provides lots of features out of the box. Keycloak has built-in support for OpenID Connect and SAML 2.0 as well as a number of social networks such as Google, GitHub, Facebook, and Twitter.

I wanted to write this because I couldn't find good examples when I’m doing this implementation to restrict react routes/components/function with keycloak with minimal 3rd party libraries.

In this short guide, we will walk through the following steps

  • Install Keycloack — Get Started with Keycloack Docker
  • Setup Realm, Client Id in Keycloak
  • Create React App
  • Install keycloak-js & @react-keycloak/web
  • Setup Keycloak instance as needed
  • Wrap Basic app with KeycloakProvider
  • Routes restrict based on Keycloak Authentication & Authorization,
  • Component/Function level restriction based on Keycloak Authentication & Authorization

Example code:

https://github.com/cagline/react-router-component-keycloak-example

Let’s get started with the integration

First, we need to install the Keycloak server. There are a few ways of installing Keycloak like Docker, OpenJDK, Openshift, Podman, Kubernetes. I prefer to install Keycloak using Docker since I’m familiar with Docker.

Then we need to Create a Realm, Client ID, and a User on Keycloack side to start with the rest of the implementation. I’m not going to explain more detail regarding keycloak configuration. But you’ll find good resources regarding Keycloak configuration at the end of the article.

Running Keycloak docker instance in port 8080
Running Keycloak docker instance in port 8080

Then you need to login to the master console with admin/admin default credentials to Setup Realm, Client Id & a user in Keycloak side.

Create realm called Demo in Keycloak
Create a realm called Demo in Keycloak
Create realm called Demo in Keycloak
Create a realm called Demo in Keycloak

Create Client Id for your react app in your project.

Most importantly Access Type needs to be selected as public. we can’t use Authorisation features that come with the confidential Access Type in a client-side app. Read more

https://www.keycloak.org/docs/6.0/server_admin/#oidc-clients

Web origins, Valid Redirect URLs, Root URL configuration needs to carefully filed out.

Create Client ID called react-app in Keycloack
Create Client ID called react-app in Keycloak

Let’s start with create a fresh react app with create-react-app

// npm uninstall -g create-react-app to ensure that npx always uses the latest version.
npm install -g create-react-app
npx create-react-app my-keycloak-app
cd my-keycloak-app
npm start

Install @react-keycloak/web library

Pre requires:
keycloak-js 9.0.2 or later

npm install --save keycloak-jsnpm install --save @react-keycloak/web

Create a keycloak.js file in the src folder of your project with the following content to set up a Keycloak instance as needed.

Set up a Keycloak instance

Then Wrap your App inside KeycloakProvider and pass the keycloak instance as prop

Then Wrap your app inside KeycloakProvider

There are two ways of accessing Keycloak in a component.

  1. Wrap it inside the withKeycloak HOC (Higher-Order Components).
  2. Use the useKeycloak Hook (React >=16.8 required).

I prefer using useKeycloak hook 🙃, but I use both ways in this to demonstrate🙄. You will find useKeycloak hook is used in AppRouter & HomePagecomponents and withKeycloak HOC used in Menu components.

Let's create following files with the following content.

  1. src/routes/index.js (Routes of homepage & protected page)
  2. src/page/HomePage.js (Public page with keycloak JSON)
  3. src/page/Menu.js (Login & Logout buttons)

src/routes/index.js

As you see in AppRouter component useKeycloak is being used without 1st param because we don't need that keycloak instance object to be used. But 2nd initialized parameter is being used because PrivateRoute is using keycloak instance, and keycloak object need to be available on PrivateRoute initialization, that means until keycloak initialized Routes cannot be returned.

Menus & Routes wrap with AppRouter component with useKeycloack hook.

src/page/HomePage.js

The HomePage will show the initialized Keycloak object as a formatted JSON string to get more understanding.

Display Keycloak object as a JSON string in the HomePage component with useKeycloak hook.

src/page/Menu.js

The Menu is implemented with HOC (Higher-Order Components) just to demonstrate 🙄. It renders the login & logout button according to the value of keycloak.authenticated attribute.

keycloak login & logout option with Menu component implemented with withKeycloak HOC

Let’s jump into the most wanted part which is restricting Routes & Components.

We will work through:

  1. Route restriction
  2. Component restriction (with a utility function)
  3. Component restriction (with wrapper component).

Routes restrict based on Keycloak Authorization

In the Keycloak there are two ways of assigning roles to the user. A role can assign to Realm or Client. In this example, I create a role called ReamAdmin under Demo realm and ClientAdmin under react-app client.

In that case, You have to consider checking authorization using both hasRealmRole()& hasResourceRole() methods available in Keycloak.

Create a PrivateRoute.js file in src/utilities folder of your project with the following content.

Authorized routes wrapper restrict routes based on Keycloak with hasRealmRole() & hasResourceRole()

Then change routes/index.js the file as below

If the login user doesn’t have a role called RealmAdmin, it gets redirected to the homepage.

The user gets redirected back to the home page when the user tries to access the Protected page since user “chanuka” only has ClientAdmin roles in the above scenario. ( ClentAdmin role assign to react-app client — as you see it’s included in resource_access attribute.

In the below scenario the user can access the Protected page since user “Asanka” has a role called RealmAdmin .

Here user able to access the protected page because of user “asanka” has RealmAdmin role, and its assign to the current realm — as you see in its included in realm_access attribute.

Let’s see how to restrict Components/Functions with a Keycloak.

Create a AuthorizedFunction.js file in src/utilities folder of your project with the following content.

Then change src/components/Menu.js the file as below

The above solution is ideal for functional restriction and restricts rendering elements.

Also, there is another way of restrict with anther wrapper component as below example. (with wrapper component)

Create a AuthorizedElement.js file in src/components folder of your project with the following content.

Also, AuthorizedFunction utility function can be used to remove isAuthorized method duplication from AuthorizedElement & PrivateRoute as bellow example.

Then change src/pages/HopePage.js the file as below

Above solution is ideal for restrict UI components as below

This is the look of the homepage before login with keycloak credentials. There are two buttons supposed to render based on the user role. Let’s see how it’s work after the user logged in.

The homepage looks like below when the user “chanuka” logged in as a ClientAdmin

The homepage looks like below when the user “Asanka” logged in as a RealmAdmin

I hope this Route/Component/Function restriction with roles is help full for your development.

  1. Route restriction
  2. Component restriction (with a utility function)
  3. Component restriction (with wrapper component).

Resources:

https://www.youtube.com/watch?v=XJYy6Aq-PJ8&t=639s

Still If you feel lazy to write your own utilities , Here is a library which wrap these utilities as a library for you.

--

--

Chanuka Asanka
Chanuka Asanka

Written by Chanuka Asanka

Full Stack Developer | JavaScript Enthusiastic | Open Source Contributor

Responses (6)