How to mock an SSO Keycloak React app for Cypress testing
Initial idea
It’s been a long time since we started working on the creation of an app for our clients that will give them a huge improvement on some of the management systems we use in Stuart.
Our frontend team was in the front line of all these changes and they decided to start a new project creating a specific repository for this development. They focused their efforts on creating an application based on React that was capable of managing a Single Sign On (SSO) system of login giving the users access to multiple tools inside. In our case, the SSO system was a Keycloak instance. Each of these tools has its own different permissions based on the client. So, depending on the permissions a user has on the SSO system, they will be able to see some tools or others. And this concept is key to understanding the problem we faced.
Since different users had access to different tools inside the app, the traditional approach of having one token to authenticate against the API was a liability as this would give the users access to graphQL queries and mutations that they should not be able to access. To go around this, each tool was set up to be an OAuth client requiring users to generate a new token as they navigated between tools. The Frontend server handled this by matching the current path the user was on to a tool and then redirecting to Keycloak with the appropriate client ID. Keycloak redirected the user back to the application with an authentication code which was then used in combination with the client secret to request access and refresh token.
The idea to start using Cypress was discussed for a long time between the QA and FE teams. This new application required mocking graphQL API calls and converting with E2E tests a lot of dynamic changes on the interface that seemed perfect to introduce it.
The problems we found
The first important step after the installation was related to our SSO login system. Any test on the application should bypass this. We were interested in testing the login in isolation, but we don’t want to perform the login on each test as it will be very time-consuming. After some research, we found a possible valid solution by using a combination of approaches.
We found a third-party library created specifically for mocking Keycloak called cypress-keycloak-commands. It’s a simple and easy-to-use tool that perfectly does what it was made for. We also needed to define a cookie with our client id and secret credentials and we set it directly using Cypress due to our technical requirements.
It was nice to run the first test after implementing this approach. It worked! The login system was bypassed and we entered the main menu of our project. But… Here we faced THE PROBLEM.
After logging in with the mock generated by the library and reaching the internal list of available tools, we found that we were unable to mock the second permissions asking the SSO using that library because of what we told about how our app works. Our Cypress server couldn’t access our FE Server which was the one doing the request asking for permissions on the SSO, so there was no possible mock for this. Neither directly in Cypress nor using the external library.
The real solution. Frontend team to the rescue!
It became very clear that we needed to somehow mock the SSO authentication flow on localhost but we also did not want to change any production code to cater for tests. Pointing the application to a local server instead of Keycloak and getting a mock token would not be a problem as all Keycloak URLs were set as environment variables and all data would be mocked during tests.
In the end, the solution was inspired by how our development environment is set up. During development mode there are two express servers running, one is the same server that runs on production and the other one handles hot reloading and serving assets.
After some brainstorming, it was decided to create a similar script to start up two express servers for cypress. One server handles server-side rendering and serves the app the same as production and the other one has two routes added to it. One route handled redirecting back to the main server with a mock authentication code and the other route responded with a mock access and refresh token.
app.get("/openid-connect/auth", (req, res) => {
const { redirect_uri: redirectUri, state } = req.query;
res.redirect(
`${redirectUri}?session_state=${sessionState}&code=${code}&state=${state}`
);
});
app.post("/openid-connect/token", (req, res) => {
res.status(200).json(tokenPayload);
});
Final test
With those changes in mind, we just needed to adapt our CI scripts on Jenkins so the frontend server app was configured correctly to run Cypress tests as explained. And it all started working like a charm!
After creating some tests, we were able to login into the app and see all the tools listed but we were also capable of accessing any tool and, using the intercept method and some fixtures for mocking, we just got the information that we wanted to test!
Special thanks to Tonio Buttigieg for all the time dedicated to the mocking system and writing this article.