Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Logging into a Django server with Cypress.io programmatically (without using UI)

Tags:

django

cypress

Must be missing something obvious but I am very much stuck on logins into Django due to its CSRF protection.

I looked Check out our example recipes using cy.getCookie() to test logging in using HTML web forms but that really doesn't help that much if the first thing it recommends is disabling CSRF.

What Django wants:

This is what a normal, CSRF-protected, Django login view is expecting in its incoming POST data:

csrfmiddlewaretoken=Y5WscShtwZn3e1eCyahdqPURbfHczLyXfyPRsEOWacdUcGNYUn2EK6pWyicTLSXT
username=guest
password=password
next

It is not looking for the CSRF in the request headers and it is not setting x-csrf-token on the response headers.

enter image description here

And, with my code, I am never passing in the csrf token which gets Django to return a 403 error.

Cypress.Commands.add("login", (username, password) => {
    var login_url = Cypress.env("login_url");

    cy.visit(login_url)

    var hidden_token = cy.get("input[name='csrfmiddlewaretoken']").value;
    console.log(`hidden_token:${hidden_token}:`)

    console.log(`visited:${login_url}`)
    var cookie = cy.getCookie('csrftoken');
    // debugger;

    var csrftoken = cy.getCookie('csrftoken').value;
    console.log(`csrftoken:${csrftoken}:`) 
    console.log(`request.POST`)

    cy.request({
        method: 'POST',
        form: true,
        url: login_url,
        // body: {'username': 'guest', 'password': 'password', 'csrfmiddlewaretoken': cy.getCookie('csrftoken').value}
        body: {'username': 'guest', 'password': 'password', 'csrfmiddlewaretoken': hidden_token}
    })
})

Cypress's error:

enter image description here

I suspect that the POST data has something to do with the token being undefined via the both the hidden form input or the cookie acquisition approach, as shown by the console.log for either.

Now, I've already started looking at https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/logging-in__csrf-tokens and I think I should be able to adjust strategy #1: parse token from HTML to pick up $("input[name='csrfmiddlewaretoken']").value but I was hoping someone had done this before.

One other idea I have is to conditionally add a request Middleware to Django that would grab the csrftoken from the request headers and inject into the POST form data when it's missing. Provided I insert it to fire before the CSRF stuff, would that work?

Last, I was planning to exclude the sessionid token from getting reset so that I can run multiple tests after logging in just once.

env: Django 1.10, cypress 1.4.2, now upgraded to 2.0.0, same issue.

like image 443
JL Peyret Avatar asked Feb 16 '18 21:02

JL Peyret


1 Answers

You can get the first CSRF token required for login using a HEAD request and looking at the cookies (no need to parse the page).

Also you can have your custom cy.login() return the token (asynchronously, so you need to use .then()) instead of having to call cy.getCookie('csrftoken') again if you need a token afterwards for POST requests and such:

Cypress.Commands.add('login', (username, password) => {

  return cy.request({
    url: '/login/',
    method: 'HEAD' // cookies are in the HTTP headers, so HEAD suffices
  }).then(() => {

    cy.getCookie('sessionid').should('not.exist')
    cy.getCookie('csrftoken').its('value').then((token) => {
      let oldToken = token
      cy.request({
        url: '/login/',
        method: 'POST',
        form: true,
        followRedirect: false, // no need to retrieve the page after login
        body: {
          username: username,
          password: password,
          csrfmiddlewaretoken: token
        }
      }).then(() => {

        cy.getCookie('sessionid').should('exist')
        return cy.getCookie('csrftoken').its('value')

      })
    })
  })

})

Note: The token changes after login, therefore two cy.getCookie('csrftoken') calls.

Afterwards you can just use it in the following way in your tests (see https://docs.djangoproject.com/en/dev/ref/csrf/ for why the header is needed):

cy.login().then((csrfToken) => {

  cy.request({
    method: 'POST',
    url: '/api/baz/',
    body: { 'foo': 'bar' },
    headers: { 'X-CSRFToken': csrfToken }
  })

})
like image 80
phk Avatar answered Sep 30 '22 08:09

phk