Skip to content

Instantly share code, notes, and snippets.

@ndavis
Created November 7, 2019 03:38
Show Gist options
  • Save ndavis/2c84ab40aaa3c98c3a8062bdb3938232 to your computer and use it in GitHub Desktop.
Save ndavis/2c84ab40aaa3c98c3a8062bdb3938232 to your computer and use it in GitHub Desktop.
Cypress Custom Command for Okta Login
Cypress.Commands.add('loginOkta', () => {
const optionsSessionToken = {
method: 'POST',
url: Cypress.env('session_token_url'),
body: {
username: Cypress.env('username'),
password: Cypress.env('password'),
options: {
warnBeforePasswordExpired: 'true'
}
}
}
cy.request(optionsSessionToken).then(response => {
const sessionToken = response.body.sessionToken;
const qs = {
client_id: Cypress.env('client_id'),
code_challenge: Cypress.env('code_challenge'),
state: Cypress.env('state'),
nonce: Cypress.env('nonce'),
redirect_uri: Cypress.env('redirect_uri'),
code_challenge_method: 'S256',
response_mode: 'fragment',
response_type: 'code',
scope: ['openid', 'profile', 'email'],
sessionToken: sessionToken
}
cy.request({
method: 'GET',
url: Cypress.env('auth_token_url'),
form: true,
followRedirect: false,
qs: qs
}).then(responseWithToken => {
const redirectUrl = responseWithToken.redirectedToUrl;
const accessToken = redirectUrl
.substring(redirectUrl.indexOf('access_token'))
.split('=')[1]
.split('&')[0];
cy.wrap(accessToken).as('accessToken');
cy.visit(redirectUrl).then(() => {
cy.visit('/');
});
});
});
})
@ndavis
Copy link
Author

ndavis commented Nov 7, 2019

Credit to @Postavshik

Import Custom Command

In commands.js import the file like so:

import './oktaLogin'

Setup Environment Variables

Set up a cypress.env.json with variables defined in the commands.js:

{
  "username": "abc",
  ...
}

For session_token_url replace the <...> with your okta domain

  • https://<your-domain.okta.com>/api/v1/authn

For auth_token_url replace the <...> with your okta domain

  • https://<your-domain.okta.com>/oauth2/default/v1/authorize

Get Additional Environment Variable from Browser

If you sign into Okta in your browser and inspect the default/v1/authorize network call you'll be able to find these values:

  • client_id
  • code_challenge
  • nonce
  • state

@bondar-artem
Copy link

Great stuff! Thank you for the refactoring and making the code nicer :)

@cuadllop
Copy link

It works pretty well.

In our case, we had to remove the last cy.visit("/"), since it was not been understood properly by Cypress.

@bondar-artem
Copy link

It because you probably didn't set baseUrl of your app in cypress.json.
https://docs.cypress.io/guides/references/configuration.html#Global

@cuadllop
Copy link

cuadllop commented Dec 3, 2019

Thanks, that's was the issue.
It is working now.

@arthurdayton116
Copy link

awesome! worked for me. Was able to comment out
// code_challenge: Cypress.env('code_challenge'),
// state: Cypress.env('state'),
// nonce: Cypress.env('nonce'),
and it still worked.

@cuadllop
Copy link

cuadllop commented Dec 4, 2019

Yes, code_challenge was not required for me either

@cuadllop
Copy link

cuadllop commented Dec 4, 2019

By the way, I am trying to implement a similar command for the logout function.
It is basically the same code as for login, but changing parameters (according to Okta documentation) and the url.

This is the code I have done so far:


Cypress.Commands.add('logoutOkta', () => {

  var redirect_uri = "" + Cypress.env('testConfig').baseUrl + Cypress.env('redirect_uri');

  cy.log(redirect_uri);
  const sessionToken = Cypress.env('accessToken');
  cy.log(sessionToken);
  const qs = {
    id_token_hint: sessionToken,
    post_logout_redirect_uri: `${Cypress.env('testConfig').baseUrl}/Account/Logout`,
    state: Cypress.env('state')
  }

  cy.request({
    method: 'GET',
    url: Cypress.env('auth_logout_url'),
    form: true,
    followRedirect: false,
    qs: qs
  }).then(responseWithToken => {
    const redirectUrl = responseWithToken.redirectedToUrl;
    
    cy.visit(redirectUrl).then(() => {
      cy.visit('/');
    });
  });
});

It returns 302, but when accessing the url in the browser, a page appears showing the following error message:

400: Bad Request
BAD REQUEST

Your request resulted in an error.
Go to Homepage
Identity Provider:
Error Code: invalid_token
Description: The id token is invalid.

Has anyone tried implementing Logout?

@DaveA-W
Copy link

DaveA-W commented Feb 5, 2020

Does anyone know if someone's adapted this or similar for Auth0 PKCE?

@khitrenovich
Copy link

Does anyone know if someone's adapted this or similar for Auth0 PKCE?

@DaveA-W See if https://auth0.com/blog/end-to-end-testing-with-cypress-and-auth0/ can help you.

@DaveA-W
Copy link

DaveA-W commented Feb 7, 2020

Does anyone know if someone's adapted this or similar for Auth0 PKCE?

@DaveA-W See if https://auth0.com/blog/end-to-end-testing-with-cypress-and-auth0/ can help you.

Thanks @khitrenovich - unfortunately that article demonstrates password grant only.

Only potential workaround I've seen so far is not ideal, per corruptedmonk comments at the bottom of the article:

  1. Disable chrome security in Cypress config.
  2. Use Classic universal login.
    image
  3. Disable click-jacking protection in advanced tenant settings.
    image

Then Cypress may handle the redirects and silent auth okay - but at the expense of compromising security for the whole tenant.

@flora8984461
Copy link

Hello, this code snippet works fine when I was using Okta v2, but now after I upgrade to Okta v3, it causes AuthSdkError: Unable to parse a token from the url . Does anyone know how to handle this in Okta v3? Thanks a lot.

@Kishorekumar2789
Copy link

Kishorekumar2789 commented May 27, 2020

I am facing the same SSO login issue. My application uses OpenID as auth server.

will this approach solve the login issue for OpenID?

@hemantc09
Copy link

hemantc09 commented Aug 7, 2020

Hi ,
Im also facing the "AuthSdkError: Unable to retrieve OAuth redirect params cookie" . any help would really appreciate.
Every time I rerun the test in the same browser it gives me this error. But If I start a fresh test it doesnt give me error. I guess for second test attempt its not able to find the cookie or etc I suspect. But unable to resolve the solution.

Below is my OKTA code.

`Cypress.Commands.add('loginOkta', () => {
const optionsSessionToken = {
method: 'POST',
url: Cypress.env('session_token_url'),
body: {
username: Cypress.env('username'),
password: Cypress.env('password'),
options: {
warnBeforePasswordExpired: 'true'
}
}
}

cy.request(optionsSessionToken).then(response => {
  const sessionToken = response.body.sessionToken;
  const qs = {
    client_id: Cypress.env('client_id'),
    //code_challenge: Cypress.env('code_challenge'),
    //state: Cypress.env('state'),
    //nonce: Cypress.env('nonce'),
    redirect_uri: Cypress.env('redirect_uri'),
    code_challenge_method: 'S256',
    response_mode: 'fragment',
    response_type: 'code',
    scope: ['openid', 'profile', 'email'],
    sessionToken: sessionToken
  }

  cy.request({
    method: 'GET',
    url: Cypress.env('auth_token_url'),
    form: true,
    followRedirect: false,
    qs: qs
  }).then(responseWithToken => {
    const redirectUrl = responseWithToken.redirectedToUrl;

    const accessToken = redirectUrl
    .substring(redirectUrl.indexOf('access_token'))
    .split('=')[1]
    .split('&')[0];

    cy.wrap(accessToken).as('accessToken');

    cy.visit(redirectUrl).then(() => {
      cy.visit('/');
    });
  });
});

});`

below is my test:

before('Open the atmos', () => {
console.log('inside the before', " user name -");
cy.loginOkta();
})

it('login to atmos via okta', () => {
    console.log('inside the beffore', " user name -");
   // cy.loginOkta();
})

it('Click create team', () => {
    cy.get(':nth-child(2) > a > .card > .card-body > .clearcard-title').click()
})`

-Hemant

@hemantc09
Copy link

just an update.
I was able to resolve my issue by following original code
cypress-io/cypress#4416 (comment)

If I re-run the test from the browser, I dont see the error. Atlest my test continues each time.
Not sure why. But i thought this might help anyone.

@hemantc09
Copy link

hemantc09 commented Aug 12, 2020

In my solution chrome is giving me sameSite error. Does anyone faced this issue?
Even though okta login successful Im keep seeing the okta login screen and if I open need tab in same session Im able to access my app.
Its kinda confusing that one tabshows okta login and another tab shows successful login.
It was working earlier chrome version less than 80.

@cuadllop
Copy link

cuadllop commented Aug 19, 2020

Hi there

That workaround also started failing for me since last week. I haven't been able to find a reason why it stopped working.
The flow is slightly different as the solution provided by @ndavis

  1. Get a session token (same as before)
  2. Use the library okta-auth-js (npm i @okta/okta-auth-js) calling the method getWithoutPrompt to get the idToken and set the variable into the Okta authClient object.
var OktaAuth = require('@okta/okta-auth-js');
  
  var authClient = new OktaAuth({      
    url: 'https://YOUR_DOMAIN.okta.com',      
    clientId: Cypress.env('client_id'),      
    redirectUri: Cypress.env('testConfig').baseUrl + Cypress.env('redirect_uri')
  });
  
  // Attempt to retrieve ID Token from Token Manager

  const optionsSessionToken = {
    method: 'POST',
    url: Cypress.env('session_token_url'),
    body: {
      username: username,
      password: password,
      options: {
        warnBeforePasswordExpired: 'true'
      }
    }
  }
  var redirect_uri = "" + Cypress.env('testConfig').baseUrl + Cypress.env('redirect_uri');

  cy.log(redirect_uri);

  cy.request(optionsSessionToken).then(response => {
    const sessionToken = response.body.sessionToken;

    authClient.token.getWithoutPrompt({
      sessionToken: sessionToken,
      scopes: [
        'openid',
        'email',
        'profile'
      ],
      state: Cypress.env('state'),
      nonce: Cypress.env('nonce')
    })
      .then(function (res) {
        authClient.tokenManager.add('idToken', res);
      })
      .catch(function (err) {
        // handle OAuthError or AuthSdkError
      });
  })

  cy.visit('/')

I thought this was going to solve the issue in Firefox as well since it should be crossing domains; however, I am still getting the same error in Firefox as I was getting before.

Hope it helps.

@boda234baran
Copy link

Hi @cuadllop ! Did you decide this problem?

Maybe you may tell me where I get these options ?

  • code_challenge
  • nonce
  • state

Reviewed in all requests in authorization in okta and never came....

@cuadllop
Copy link

cuadllop commented Aug 26, 2020

Hi @boda234baran,

Yes, this solution is working for me and my team so far.

I got those details by analyzing the POST requests when doing a manual login into my app:
image

Bear in mind that each environment you have configured in okta will have its own set of properties.
Then it is configured in my env.json properties file
image

, I don't know the value of code_challenge. It works for me to configure it as empty
"code_challenge": "",

@keaoner
Copy link

keaoner commented Jun 3, 2021

Hi, I am newbie to cypress, how can I integrate this code for okta authentication into my existing tests on Cypress?

Thank you

@iamskok
Copy link

iamskok commented Feb 1, 2022

Here is the solution that worked for me. Two things to point out:

  • Your company might be using custom OKTA_AUTHORIZATION_SERVER_ID and not default that is used in the most doc examples
  • You should be able to use dummy values for state and nonce

Certain values (like scope) might be different for your Okta configuration, but you should be able to identify them by inspecting the network tab.

Cypress.Commands.add('oktaApiLogin', ({ email, password, url }) => {
  const optionsSessionToken = {
    method: 'POST',
    url: `${Cypress.env('OKTA_DOMAIN')}/api/v1/authn`,
    body: {
      username: email,
      password,
      options: {
        warnBeforePasswordExpired: 'true',
      },
    },
  };

  cy.request(optionsSessionToken).then((response) => {
    const { sessionToken } = response.body;
    cy.log(`sessionToken: ${sessionToken}`);

    const qs = {
      response_type: 'code',
      client_id: Cypress.env('OKTA_CLIENT_ID'),
      state: 'test-state',
      nonce: 'test-nonce',
      redirect_uri: Cypress.env('OKTA_REDIRECT_URI'),
      scope: 'openid offline_access email',
      sessionToken,
    };

    cy.request({
      method: 'GET',
      url: `${Cypress.env('OKTA_DOMAIN')}/oauth2/${Cypress.env(
        'OKTA_AUTHORIZATION_SERVER_ID'
      )}/v1/authorize`,
      form: true,
      followRedirect: false,
      qs,
    }).then((responseWithToken) => {
      const redirectUrl = responseWithToken.redirectedToUrl;
      cy.log('responseWithToken:', responseWithToken);
      cy.log('redirectUrl:', redirectUrl);

      cy.request({
        method: 'GET',
        url: redirectUrl,
        followRedirect: false,
      });

      cy.visit(url);
    });
  });
});

@onuralp
Copy link

onuralp commented May 23, 2022

Okta supports hosting a sign-in page under your own domain with Embedded Okta Sign-In Widget. In this way, since you use your domain, cypress work seamlessly.

Okta provides a sign-in page, available at your organization's URL, which allows the user to complete the entire authorization flow, start an SSO (Single Sign-On) session, and set the Okta session cookie in the web browser. You can customize this page with a background image and logo. By default, signing in on this page redirects the user to the Okta user dashboard.

See the GitHub repository for the implementation

PS: Use the embed link, so the login process ends with the application you want to test instead of Okta dashboard.

Edit:
Important update: Cypress has introduced cy.origin() command with version 9.6.0 that allows you to visit multiple origins in a single test. See here for details.

@DHenry7471
Copy link

Hey I'm getting this error when running tests/
cy.request() requires a url. You did not provide a url

Should optionsSessionToken have a url in it to provide?

@piyush12
Copy link

piyush12 commented Jun 22, 2023

@iamskok hi, is there any example to test if there is MFA enable in okta, In my app we have enabled the okta sms MFA.

@mmarteli77
Copy link

Hi, i´m newbie in cypress. how to implement Okta DSSO (Desktop single sign-on) authentication using cypress? with DSSO there is no prompt page to enter user/password for authentication, seems that okta does the authentication in the background when i login into the computer. Since cypress use its own browser to run the automation when it hits the app URL i´m getting an error message saying the i´m not allowed to processed since i´m not authenticated. Please help.
Thanks in advance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment