Cypress.io + OneLogin: using API to authenticate before testing

Jonathan Machado
5 min readAug 23, 2020

--

Cypress is one of hottest NodeJS based automated testing framework at the moment, and its founder, Brian Mann, spares no words talking about how you should never use your login page to authenticate a user before testing.

If your application uses OneLogin, however, that’s easier said then done. As far as Oauth solutions go, OneLogin is one of the (unnecessarily) hardest ones you could ask to tackle during automated testing. This is exactly what I had to do and, after many days of try and error, I finally did it.

Plus, I was very frustrated for not finding any detailed documentation specific for this subject. There are plenty of examples even within Cypress itself for how to handle social login and other generic Oauth solutions, but I wanted something tailored-made for OneLogin. So here I am writing my own on a Sunday afternoon. Hopefully this will help you also.

Goal

What we are trying to achieve here is simple: using http requests to communicate with OneLogin and with your own target app to bypass the login page completely and start interacting with the UI after that.

We also want to encapsulate the login process so it can be called easily during a test inside a before hook.

Macro steps

Overall, what you need to do during the login process itself is:

  • Generate a OneLogin token using a clientId and clientSecrect (More on those two latter on)
  • Generate a user login token using the token from before, plus user name and password.
  • Call your app to generate an authenticated session using the user login token from previous step. After this, you can use cy.visit() into any of your app’s page and you’ll be logged in.

Those three steps are explained in three different functions bellow.

Cypress comes with an http request solution built-in with the method cy.request(), so you can make all of your GETs, POSTs out-of-the-box. It also handles redirects and cookies gracefully, which comes very handy in this case.

Note: if your app has two factor authentication enabled, it is better to have a specific user that can login without 2FA for you to use during tests. In my opinion, things are already hard enough as is, so don’t even start without having that sorted.

Coding time

I’m assuming you are already inside a Cypress project at this point. The only dependency needed is Cypress itself installed from a npm install cypress command inside your NodeJS project.

Before starting, you’ll need to get a OneLogin developer Client ID and Client Secret. There is a detailed step-by-step on how to do this in here. Just keep in mind that only an admin of your project in OneLogin can achieve that.

One Login instructions on how to obtain a Client Secret and Client ID

With those at hand, we can make our first call to OneLogin to generate an access token.

The resulting function is:

You’ll notice I finished this function by calling another function: generateSessionToken(token), using the token from the response as argument.

And this is how I now use this token to generate a Session Token:

Remember that the username and password used above is for an user that does not require two factor authentication.

The value to be replaced in yourAppDomain is simply the domain in the URL when you try to access your app without being logged in, minus onelogin.com.

Again I finished by calling the third function using the userSessionToken I got from response, and here is how that function is written:

The big difference here is that we are using a form to send the data, not a standard JSON.

The way OneLogin handle user sessions is through Cookies. After this last step, your browser session will be able to receive the cookies from OneLogin and then it automatically redirects to whichever new page you visit using cy.visit.

After this point you can use cy.visit() to open a private page inside your app and you will be logged in.

This is also true for making API calls into your app using cy.request()

Sometimes this will not be enough though, specially if you need to do a POST before anything else, because of how sending data and simple GETs behave during redirects (response code 302).

A workaround is using cy.request() for casting a GET into an endpoint that is visible to any user in your app before anything else.

Organizing things

After all that, I encapsulated the three functions, one after the other, in a sessionFactory() function, inside a sessionFactory.js file. This function then needs to be exposed through return.

The end result will end up similar to this:

export default function sessionFactory (){
function getOneLoginToken() {
// continue with all 3 functions ...
}
return {
getOneLoginToken
}}

This sessionFactory.js file can then be placed anywhere, but I recommend placing it inside the support folder, in cypress folder along with index.js. Index.js is where you can define custom cypress commands that can then be used from anywhere in your project, the perfect place to create a cy.login() of your own!

The end result in ./cypress/support/index.js will be like this:

import './commands'
import sessionFactory from "./sessionFactory"
const authGen = sessionFactory()
Cypress.Commands.add("login", () => {
authGen.getOneLoginToken();
})

That’s it! Now, during tests, you can invoke the command cy.login() to perform the whole bunch of stuff. This can be used from any test file, without the need of any additional imports too.

I hope this article will help you finally using Cypress to log in into your application that is behind a OneLogin wall, using only API calls.

Actually… if you try to login using Cypress to fill up login and password fields in a traditional login process, using the UI, you will notice a hell lot of problems because how Cypress handles cross-domain URL calls, which is the case when OneLogin comes into play. So this is pretty much the only good way I found to test a web app behind OneLogin at all.

PS. I’m quite sure that there is a lot of things I’m not doing the best way through my explanation, so feel free to point them out for me!

--

--