Use Keycloak to add authentication to your React Router 6 application using the new data apis
#react
React Router 6 stopped being “just a router”. The new data apis not only change the way we think about routing in React applications, but also about how we handle data fetching and mutations. In this post we will go over a simple example on how to use Keycloak to add authentication to your React Router 6 app using the new data apis.
All the exploratory code I wrote for this post is on GitHub, including a simple Keycloak setup if you need one. Just follow the instructions in the README to get it running.
Application overview
I don’t want to go into a lot of detail about the application itself as this post is intended to help you add it to your application.
Let’s say we have an application that has some pages that the user can access without being logged in, and some that require authentication. In a traditional React Router 5 application we would create a custom component that checks the auth state via an auth provider and redirects to the login page if the user is not logged in:
We would create an AuthContext that provides the auth state to the rest of the application.
We would wrap the application with the AuthContext.Provider and pass the auth state to it.
We would create a useAuth hook that returns the auth state from the AuthContext.
We would use the ProtectedRoute component and the render prop in the Route component from React Router.
We would use the useAuth hook to check the auth state and redirect the user to the login page if they are not authenticated.
Let’s see how we can do this with the data router.
Using the data router
The first thing we need to do is transform that router into a data router:
Because in React Router 6 there’s no Redirect component, we will have to replace it with Navigate:
This works… but, we can do better. Let’s use the data api to remove the AuthProvider and useAuth hook.
Initializing Keycloak
The first step is to have keycloak-js installed with any package manager you want. We will be working on a series of abstractions to handle auth cases in a case-by-case basis.
An important thing to note is that we can only initialize keycloak-js once. I do this trick to save the instance in a variable and return it if it’s already initialized:
With this function we can create a couple of helper functions to handle authentication for us. For example, we can create a getProfile function that returns the user’s profile if they are authenticated:
Because we set onLoad: "check-sso" on the init function, we can be confident that the instance can effectively determine if the user is already logged in.
Protecting routes
We will be protecting routes by using a loader function for out protected route. But first, I will make a helper function to require authentication and redirect the user to the login page if they are not logged in:
Now, let’s make our new protected route and add it to our router config:
Now if you try to access /protected without being logged in, you will be redirected to Keycloak’s login page. If you are logged in, you will see your username.
Logging users out
We will be using a React Router action for this. But first, let’s make a helper function:
Now let’s build our action
Now if you click on the logout button, you will be logged out. Because React Router will call all loaders again after submitting a form, we will be kicked to the login page once more.
Bonus: app-wide auth protection
In most scenarios, especially if you are building an SPA, there’s no use-case on having pages that are public. With Keycloak you would be using their hosted UI and leveraging redirects, anyway. A single change is needed to make the app-wide auth protection work:
That’s it! Ideally, you would call this as soon as you can in your code. I would put this on src/main.tsx, let’s refactor the code a little:
You can check the full diff in the example in this PR.
Conclusion
Leveraging the data api for React Router can simplify a lot of the code. Since we don’t have to participate in React’s lifecycle, using libraries like keycloak-js becomes a lot easier. Not only that, but we can also leverage the action function to handle things like logging out in just a couple lines.
I love React, but sometimes having to rely on React integrations for libraries or building your own the “React way” can be quite cumbersome. I am a big fan of React Router 6’s data api, I believe that this is a huge DX improvement over having to build custom components and hooks.