w3resource

Enhance Web Security in Cypress Testing: Comprehensive Guide


In this tutorial we will show you how to manage the security of your application when testing with Cypress. We will look at the proper way to access subdomains and how to build your application and test in order not to expose yourself to serious security issues.

By default, all browsers adhere to a strict same-origin policy. This implies that the browser will restrict access between <iframes> when their origin policies do not match.

Since Cypress works from within the browsr, Cypress has to be able to communicate directly with your remote applications at all times. However, browsers will try to prevent Cypress from doing this by default.

To overcome this restriction, Cypress implements some strategies that involves JavaScript code, the browser’s internal APIs, and the network proxying to play by the rules of same-origin policy. Cypress aims at fully automating your application without the need for you modify your application’s code.

Here are some examples of what Cypress does under the hood:

  • Inject document.domain into text/html pages.
  • Proxy all HTTP / HTTPS traffic.
  • Change the hosted URL to match that of the application under test.
  • Use the browser’s internal APIs for network level traffic.

On initial load of Cypress, the internal Cypress web application will be hosted on a random port: similar to http://localhost:65874/_/

When you issue the first cy.visit() command in a test, Cypress will change its URL to match the origin of our remote application, thus solving the first major hurdle of same-origin policy. Now your application’s code will execute the same way it executes outside of Cypress, and everything will work as expected.

How does Cypress support HTTPS?

In order for you to be able to test HTTPS sites, Cypress does a lot of work under the hood. It enables you to control and stub network level. Thus, Cypress has to assign and mange browser certificates in order to be able to modify the traffic in real time.

You will notice that Chrome displays a warning that the ‘SSL certificate does not match’. This is a normal and correct warning. This warning happens because, under the hood, Cypress acts its own CA authority and then issues a certificate dynamically so as to intercept requests that were otherwise impossible to access. This is only done for the superdomain currently under test and bypasses other traffic.

Limitations

Although the Cypress team does their best to ensure that your application functions normally inside of Cypress, there are some still some limitations that you have to be aware of.

A. Same superdomain per test

Since Cypress changes its own host URL to match thatof our application, it is required that the URLS navigated to in our test have the same superdomain for the entirely pf a single test.

Cypress will throw an error, if you try to visit two different superdomains. It is okay to visit superdomains, but you have to visit different superdomains in different tests and not in the same test.

'''it('navigates', () => {
  cy.visit('https://www.cypress.io')
  cy.visit('https://docs.cypress.io') // yup all good
})
it('navigates', () => {
  cy.visit('https://testdocs.com')
  cy.visit('https://sampleapp.com')      // this will error
})

it('navigates', () => {
  cy.visit('https://testdocs.com')
})

// splits visiting different origin in another test
it('navigates to new origin', () => {
  cy.visit('https://sampleapp.com')      // yup all good
})'''

Although Cypress will try to enforce this limitation, it is possible for your application to bypass the ability of Cypress to detect this.

Here are some examples of test cases that will throw an error due to superdomain limitations:

  1. .click() an <a> with an href to another superdomain.
  2. .submit() a <form> that causes your web server to redirect to you another superdomain.
  3. Issue a JavaScript redirect in your application, such as window.location.href = '...', to another superdomain.

In each of these cases, Cypress loses the ability to automate your application and will throw an error immediately.

B. Cross-origin iframes

If you embed an <iframe> that is a cross-origin frame, Cypress won’t be able to automate or communicate with the <iframe>

Here are some sample use cases for a cross-origin iframe:

  • When you want to embed a Vimeo or YouTube video.
  • When you want to display a credit card form from Braintree or Stripe.
  • When you need an embedded login form from Auth0.
  • When you need to show comments from Disqus.

It is possible for Cypress to accommodate these situations the same way that Selenium does, however, you will not have native access to the iframes from inside of Cypress.

A workaround is to attempt to use window.postMessage to communicate directly with these iframes and control them.

C. Insecure Content

By default, if you are testing an HTTPS site in Cypress, Cypress will throw an error anytime you attempt to navigate back to an HTTP site. This behavior helps to highlight a serious security flow of your application.

Here is an example of accessing an insecure content-

Say you have a test code that is as shown below:

'''cy.visit('https://sampleapp.org')'''

and in your application code, you set the cookies and store a session on the browser. Now let us imagine that there is a single insecure link (or JavaScript redirect) in our application code.

'''
<html>
  <a href="http://sampleapp.org/page3">Page 3</a>
</html>
The following code will fail in Cypress:
// Test code
cy.visit(?https://sampleapp.org?)
cy.get('a').click()               // will fail'''

By default, Browsers will refuse to display insecure content on a secure page. And because Cypress initially changed its URL to match https://sampleapp.org when the browser followed the href to http://sampleapp.org/page3, the browser refuses to display the contents.

This behavior is not a flaw of Cypress, rather it is a security flaw of your application which Cypress is exposing to you. cookies that does not have their secure flag set to true will be sent as clear text to the insecure URL and leave your application vulnerable to session hijacking. You might think forcing a 301 redirect back to the HTTPS site will solve the problem, but no,

the original HTTP request was still made once, thus exposing insecure session information.

To solve this problem, you will need to update your HTML and JavaScript code not to navigate to an insecure HTTP page, instead they should only use HTTPS. Additionally, you should ensure that cookies have their secure flag set to true.

If you don't have control over the code or you cannot work around this, you can by this Cypress restriction by disabling web security.

Same port per test

It is required in Cypress that the URLs navigated to have the same port for the entirety of a single test. This behavior matches the behavior of the browser’s normal same-origin policy.

Common Workarounds

Let us investigate how you might encounter cross-origin errors in your test code and break down how to work around them in Cypress. You can make a cy.request() directly to it.

External Navigation

You most commonly might encounter this error is when you click on an <a> that navigates to another superdomain.

'''
<html>
  <a href="https://google.com">Google</a>
</html>
// Test code
cy.visit('http://localhost:5000') // where your web server + HTML is 
cy.get('a').click()               // hosted browser will attempt to load google.com, Cypress errors
'''

It is not recommended to visit a superdomain that you don?t control in your tests.

Rather, what you can test is that the href property is correct!

// this test will verify the behavior and will run considerably faster
cy.visit('http://localhost:5000')
cy.get('a').should('have.attr', 'href', 'https://google.com') // no page load!

Okay but let assume say you are worried about google.com serving up the right HTML content. How would you test that? you can make a cy.request() directly to it. cy.request() is NOT bound to CORS or same-origin policy.

'''cy.visit('http://localhost:5000')
cy.get('a').then(($a) => {
  // pull off the fully qualified href from the <a>
  const url = $a.prop('href')

  // make a cy.request to it
  cy.request(url).its('body').should('include', '</html>')
})'''

If you still require visiting a different origin URL then you should consider disabling web security.

Form Submission Redirects

Whenevr you submit a regular HTML form, the browser follows the HTTP(s) request.


<html>
  <form method="POST" action="/submit">
    <input type="text" name="email" />
    <input type="submit" value="Submit" />
  </form>
</html>
cy.visit('http://localhost:5000')
cy.get('form').submit()           // submits the form!

If your back end server that is handling the /submit route does a 30x redirect to a different superdomain, you will receive a cross-origin error.

'''// assume this is some node / express code on
// your localhost:5000 server

app.post('/submit', (req, res) => {
  // redirect the browser to google.com
  res.redirect('https://google.com')
})'''

A popular use case for this is Single sign-on (SSO). In this case you may POST to a different server and you are redirected elsewhere (typically with the session token in the URL).

If that is the case, you can still test this behavior using cy.request(). cy.request() is NOT bound to CORS or same-origin policy.

Cypress may likely bypass the initial visit altogether and POST directly to your SSO server.

'''cy.request('POST', 'https://samplesso.com/auth', { username: 'foo', password: 'bar' })
  .then((response) => {
    // pulls out the location redirect
    const loc = response.headers['Location']

    // this will parse out the token from the url (assuming its in there)
    const token = parseOutMyToken(loc)

    //you can do something with the token that your web application expects
    // the exaxt behavior as what your SSO does under the hood,
    // assuming that it handles query string tokens like this
    cy.visit('http://localhost:5000?token=' + token)

    // if you do not need to work with the token you can sometimes visit the
    // location header directly
    cy.visit(loc)
  })'''

In the case where you still want to be able to be redirected to your SSO server, you should consider disabling web security.

JavaScript Redirects

An example of JavaScript redirect is as shown below

'''window.location.href = 'http://sample.superdomain.com''''

This is probably the hardest situation to test because it is usually happening due to another cause. You will have to figure out why your JavaScript code is redirecting. Perhaps you are not logged in, and you have to handle that setup elsewhere. Perhaps you are using a Single sign-on (SSO) server and in that case, you can read the previous section for the work around.

You should consider disabling web security if you want to continue using the code to navigate to a different superdomain.

Disabling Web Security

In the case where you cannot work around any of the issues using the suggested workarounds above, you can consider disabling web security.

Chrome only

Disabling of web security is only supported in Chrome-based browsers. Settings in chromeWebSecurity has no effect in other browsers. Cypress will log a warning in this case.

Set chromeWebSecurity to false

Setting chromeWebSecurity to false in Chrome-based browsers enables you to do the following:

  • Display an insecure content
  • Navigate to any superdomain without getting cross-origin errors
  • Access the cross-origin iframes that are embedded in your application

To disable web security, you will need to set chromeWebSecurity to false in your configuration file (cypress.json by default)`

'''{
  "chromeWebSecurity": false
}'''


Become a Patron!

Follow us on Facebook and Twitter for latest update.

It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.

https://www.w3resource.com/cypress/web-security.php