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.
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.
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}
})
})
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.
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 }
})
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With