New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Login through Azure AD account. #1342

Open
alborozd opened this Issue Feb 19, 2018 · 28 comments

Comments

@alborozd
Copy link

alborozd commented Feb 19, 2018

Current behavior:

I use Azure AD to authenticate my users.

My logic:

When user opens the browser and get 401 status code from my api then application redirects to https://login.microsoftonline.com/.... page to input login and password and then microsoft redirects back with access token for my application.

Now when I open my page cy.visit("http://localhost:8080") I just get 401 status code from my application and it doesn't redirect me to input login and password.

I tried to open login page directly to check login and password typing: cy.visit("https://login.microsoftonline.com");

but cypress can't load this page and fails with timeout.

In dev tools I see that it tries to load page content all the time.

My cypress,json file:

{
	"chromeWebSecurity": false
}

Selenium works fine with this case.

Desired behavior:

Cypress can redirect me to https://login.microsoftonline.com to input login and password and redirect back to http://localhost:8080.

https://login.microsoftonline.com should be open correctly.

How to reproduce:

  1. cy.visit("https://login.microsoftonline.com");
  2. Try to get 401 status code from your application and redirect to https://login.microsoftonline.com

Test code:

describe("Integration tests", function() {
	it("login", function() {
              cy.visit("http://localhost:8080");
              //cy.visit("https://login.microsoftonline.com");
	});
});

Additional Info (images, stack traces, etc)

CypressError: Timed out after waiting '60000ms' for your remote page to load.
Your page did not fire its 'load' event within '60000ms'.
You can try increasing the 'pageLoadTimeout' value in 'cypress.json' to wait longer.
Browsers will not fire the 'load' event until all stylesheets and scripts are done downloading.
When this 'load' event occurs, Cypress will continue running commands.
  • Operating System:
    Windows 10 pro
  • Cypress Version:
    2.0.2
  • Browser Version:
    Chrome 63
@brian-mann

This comment has been minimized.

Copy link
Member

brian-mann commented Feb 19, 2018

You're trying to test SSO - and we have recipes showing you exactly how to do this.

Also best practice is never to visit or test 3rd party sites not under your control. You don't control microsoftonline, so there's no reason to use the UI to test this. You can programmatically test the integration between it and your app with cy.request - which is far faster, more reliable, and still gives you 100% confidence.

Look at this recipe - it employs the exact principles you can use to test the integration between the two.

Click the giant "Single Sign On" link and you'll be taken the recipes code.

@pieterdv

This comment has been minimized.

Copy link

pieterdv commented Feb 27, 2018

Hey @brian-mann, I looked at the recipes, but unless i'm mistaken, Microsoft is not allowing Resource Owner Password Credentials grant for web applications. So testing with cy.request and sending the username / password seems not to work. Do you have alternative ideas? Also looking forward to extra documentation for this!

@blex18

This comment has been minimized.

Copy link

blex18 commented Apr 24, 2018

@brian-mann I totally agree with the best practices and the reasons behind them make perfect sense. The problem is microsoftonline is not only SSO - it is an ActiveDirectory. The tokens issued on login are then being used to access other resources like MS Graph API from within the application. Is there any recipe to test a web application that so massively rely on 3rd-party API that there is no single page that doesn't make at least 1 request to external resources?

@pieterdv

This comment has been minimized.

Copy link

pieterdv commented Apr 24, 2018

@blex18 as a work around for now I use puppeteer to do the login in microsoftonline, then I set the variables that are in puppeteer localstorage in cypress localstorage and from there on i can continue. Not an ideal workflow but it works

@brian-mann

This comment has been minimized.

Copy link
Member

brian-mann commented Apr 24, 2018

@pieterdv and @blex18 you can just use cy.request().

@pieterdv I have no idea what your comment meant in regards to microsoft not allowing ROPC grant for web applications. A cy.request acts as if it comes from the browser but is not bound to any CORS restrictions. There is no difference between making a cy.request and using the browser - zero. As long as you provide to cy.request what the browser would have sent it will work the same and avoid using the UI.

I can say with 100% guarantee it will work, but you just have to figure out what microsoft is accepting. Try reading their docs to see what API's they expose to programmatically log in. Or instead - capture all of the network traffic at the browser level and inspect it to see what the browser sent.

@brian-mann

This comment has been minimized.

Copy link
Member

brian-mann commented Apr 24, 2018

@blex18 as for recipes - yes, we specifically show a SSO example in our recipes that interact with a 3rd party server. There is no difference between that example and any other 3rd party services - whether its oauth, sso, saml, etc. It's all the same, and you interact with them with cy.request which is nothing more than a programatic http request, the same ones that come from your browser.

@pieterdv

This comment has been minimized.

Copy link

pieterdv commented Apr 24, 2018

@brian-mann I'm using the msal library provided by microsoft to do the authentication in the app. The request from the library is just a redirect with an identifier to the azure ad login page. That is possible to simulate with cy.request, but that won't help, because you only get a page where you have to enter your credentials, and sending a request with your credentials in body is not allowed by microsoft as i stated before. This is mainly a restriction by Microsoft and not something Cypress can do much about at this moment in my opinion.

@blex18

This comment has been minimized.

Copy link

blex18 commented Apr 24, 2018

@brian-mann, thanks for the prompt response. Believe me, I went through the docs linked in the original response. The problem with the SSO recipe is it assumes the login form is an html form.

In case of login.microsoftonline.com it is a webpacked knokoutjs app which does much more than just rendering a login form. Form submission requires not only login and password, but a bunch of other fields populated by microsoft runtime. In fact there are 2 consecutive forms - first login, then password. So basically I am stuck with this "providing to cy.request what the browser would have sent".

I did capture the traffic from the browser. Reverse engineering would be quite expensive and extremely brittle exactly because of the The 3rd party site may have changed or updated its content reason, so I was wondering if there is any example of how to capture dynamic parameters to actually build cy.request.

Azure provides quite rich API to log in programmatically, and even maintain multiple versions of it. The problem is they distinguish programmatic logins from interactive ones, but I believe @pieterdv already mentioned it. I am using these API, and there is no need for a browser to test this scenarios. Plain request is more than enough. It does not cover cases with interactive user login though, and that's exactly where I hoped your reliable testing of anything that runs in a browser could help.

@beanian

This comment has been minimized.

Copy link

beanian commented May 22, 2018

@pieterdv do you have any sample code you can share on how to use puppeteer to login to AAD and subsequently set these cookies in cypress?

@blex18

This comment has been minimized.

Copy link

blex18 commented May 22, 2018

@beanian setCookie is pretty well documented, and it's not the best place to ask puppeteer questions. I am sure you will have better chances on Q&A sites like stackoverflow.

@saschwarz

This comment has been minimized.

Copy link

saschwarz commented May 22, 2018

@blex18 I think the question is relevant to the OP's question. When using AD for authentication, they use a multi-step login wizard and there don't appear to be any endpoints to which you can POST user credentials and get back an auth token for use in subsequent Cypress tests (as recommended in the Cypress docs/examples). Consequently, any code showing a working solution would be extremely helpful for supporting fully automated testing. For now we are manually logging in, copying the auth token, pasting it into our Cypress test config and using it until it expires.

@blex18

This comment has been minimized.

Copy link

blex18 commented May 22, 2018

@saschwarz I know. I've been there =) Setting the session cookie is trivial. Retrieving the cookie requires a 3rd-party tool. I am not trying to moderate, just pointing out there are better resources to ask for help.

@brian-mann

This comment has been minimized.

Copy link
Member

brian-mann commented May 22, 2018

Microsoft should really expose a programmatic API to accomplish this. All of the standard oAuth API's from 3rd party providers do this. I really feel like this must exist in some manner on Microsoft's end. It makes no sense that the only way to authenticate is with a browser's UI.

With that said - you could utilize another tool to login with the UI, extract the login token and then pass that off to Cypress (and effectively then run all the tests). It's cumbersome but would technically work. Albeit, it seems you'll inherit most of the problems related to 3rd party content updating and breaking your tests.

There's not going to be an easier way unless the 3rd party makes it easy.

@rogeralsing

This comment has been minimized.

Copy link

rogeralsing commented May 30, 2018

Microsoft should really expose a programmatic API to accomplish this

Yes, but they have not yet.
So question remains, what is the currently best way to test an Azure AD authenticated site using Cypress?

@pieterdv

This comment has been minimized.

Copy link

pieterdv commented May 30, 2018

I created a quick gist of how we are using puppeteer to get the auth tokens, you can find it here: https://gist.github.com/pieterdv/82773fbe036719479d76ab0a4985dc3b

probably the script can be improved but for now it works fine enough

@saschwarz

This comment has been minimized.

Copy link

saschwarz commented May 30, 2018

@beanian

This comment has been minimized.

Copy link

beanian commented May 30, 2018

@pieterdv Thanks you! How do you handle the quick redirect to AAD and back again to validate the token when doing a cy.visit with the token set? There seems to be no rhyme or reason to it but because it redirects away from the AUT it breaks cypress.

@saschwarz

This comment has been minimized.

Copy link

saschwarz commented Jun 6, 2018

So I bailed on that other project and found @pieterdv had an excellent solution.

I only had to change from localStorage to sessionStorage. I put some wrapper code around his function and created a simple node script to prompt for required values via the command line. I store the values in the json file created so you can re-run it before your tests via --no-prompt option. I added an example of a Cypress custom command for logging in a user and another for making subsequent API calls with the token in the Authorization header:

https://gist.github.com/saschwarz/b7f115ab6a7765ff5e45b9b9461cf895

Suggestions/comments appreciated.

@johnnyreilly

This comment has been minimized.

Copy link

johnnyreilly commented Jul 10, 2018

Hey guys!

I was mulling this a related problem this weekend. (Auth0 rather than Azure AD) I came up with a solution which seems solid. I've blogged about it here:

https://blog.johnnyreilly.com/2018/07/cypress-and-auth0.html

I'm still very much a Cypress novice and so it's possible my advice here might not be 100% perfect. Please do feel free to set me straight if there's any bad advice in my post. Happy to improve it; I want to spread good advice, not bad.

@grimunit

This comment has been minimized.

Copy link

grimunit commented Aug 14, 2018

For anyone that comes across this issue and is just trying to get past the authentication to start using cypress and isn't yet trying to actually test their login flow, I finally found out I can just set the tokens with the onBeforeLoad hook of the visit command.

cy.visit('/', {
	onBeforeLoad: (win) => {
		console.log('onBeforeLoad')
		// call any methods you want
		win.localStorage.setItem('AUTH0_ACCESS_TOKEN', 'access-token-here');
		win.localStorage.setItem('AUTH0_ID_TOKEN', 'id-token-here');
		win.localStorage.setItem('AUTH0_EXPIRES_AT', '1534238901425');
	},
});
@thomassuckow

This comment has been minimized.

Copy link

thomassuckow commented Jan 10, 2019

Wondering if something like https://docs.microsoft.com/en-us/rest/api/apimanagement/user/generatessourl is what we are looking for.

@jkosters

This comment has been minimized.

Copy link

jkosters commented Jan 12, 2019

I updated @saschwarz 's script to make it work for our situation (mostly some selectors).
https://gist.github.com/jkosters/63726a89f8e2be396c4c1675eca90837

So I bailed on that other project and found @pieterdv had an excellent solution.

I only had to change from localStorage to sessionStorage. I put some wrapper code around his function and created a simple node script to prompt for required values via the command line. I store the values in the json file created so you can re-run it before your tests via --no-prompt option. I added an example of a Cypress custom command for logging in a user and another for making subsequent API calls with the token in the Authorization header:

https://gist.github.com/saschwarz/b7f115ab6a7765ff5e45b9b9461cf895

Suggestions/comments appreciated.

@calvinballing

This comment has been minimized.

Copy link

calvinballing commented Jan 16, 2019

This issue appears to be related to this closed issue: #834

@saschwarz

This comment has been minimized.

Copy link

saschwarz commented Jan 16, 2019

@calvinballing Unfortunately, the approaches described in the Cypress best practices documentation/YT videos don't appear to be applicable to the Azure AD login process.

Azure AD doesn't seem to have an API that can be called with a login/password to login a user. They redirect to their website to accept the username/password, which isn't currently supported by Cypress. If anyone has an API-call API for Azure AD please provide your solution!

@jkosters

This comment has been minimized.

Copy link

jkosters commented Jan 17, 2019

They do have two JavaScript libraries to support said login I believe. This is one. https://github.com/AzureAD/azure-activedirectory-library-for-js

For me the puppeteer solution actually works and we have bigger issues than not logging into sad during testing in the correct way :). But if we manage to carry on like we do this week, I might have time next week to have a look (it's been a while)

@abu-wizata

This comment has been minimized.

Copy link

abu-wizata commented Feb 7, 2019

Unfortunately none of the puppeteer solutions presented have been able to work for us. Even when I'm able to get the login to succeed, as soon as Cypress opens our page it redirects to the login area as if we need to re-authenticate.

A nice unified solution or short term fix to this would be awesome.

@beanian

This comment has been minimized.

Copy link

beanian commented Feb 7, 2019

@abu-wizata Are you setting the cookies in cypress based on the cookies that were set during the login process in puppeteer?
We do this in puppeteer at the end of the login process to our Asp.Net Core web application.
console.log(await (page.cookies()));
This passes the result to stdout. At that point we have this code in a cypress command

Cypress.Commands.add('populateCookies', function (cookies) {
  cy.clearCookies();
  const puppetCookies = new Function('return ' + cookies)();
  puppetCookies.forEach(cookie => {
    cy.setCookie(cookie.name, cookie.value);
    Cypress.Cookies.preserveOnce(cookie.name)
  });
});

Might be a better way to do all this but this works for us.

@abu-wizata

This comment has been minimized.

Copy link

abu-wizata commented Feb 8, 2019

@beanian In one example (saschwarz) there is a .nav-link selector that is broken and removing it or replacing it with a wildcard selectors prevents the await function that follows from succeeding altogether and I don't know why.

In the other example (jkosters) there is a broken xpath selector, that even when replaced with the wildcard (or anything really) does not work. Essentially it will silently run everything, and then when I load up our app page it still requests authentication. It doesn't appear to be receiving or storing auth info into cookies even though the login page visibly works in Puppeteer.

The examples given are unfortunately quite far from plug-and-play. A barebones example with a little more detail would be fantastic, but given that puppeteer puts you at the whims of microsoft changing the login page it's a regrettable situation altogether.

Also is the puppeteer solution meant to open the Microsoft login page directly, or to open your apps page, click the login button, and then operate on the tab that is subsequently opened? I've tried both ways but it's not clear what is meant in the example. (Also I'm not a puppeteer expert so please forgive me for that.)

EDIT - I've managed to remove the errors, and logs the Cookies correctly into Cypress but it STILL redirects me to the log in page when trying to access the site. . .

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