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.
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:
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.
Cypress automatically sends the cookie named "csrftoken" with the request, but Django expects the csrf token to be called "csrfmiddlewaretoken". Therefore, I had to get the token and pass it by hand as follows:
cy.getCookie('csrftoken')
.then((csrftoken) => {
cy.request({
method: 'POST',
url: your_url_here,
// "form: true" is required here for the submitted information to be accessible via request.POST in Django (even though the docs make it sound like a bare 'POST' request can be made without the "form: true")
form: true,
body: {
csrfmiddlewaretoken: csrftoken.value,
testing: true,
obj_model: 'Customer',
field_name: 'name',
field_value: 'Customer - Testing'
}
})
.then((result) => {
expect(result.body.success).to.equal(true)
})
.then(() => {
//additional processing here if needed
})
})
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) => {
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/2.2/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 }
})
})
To use Cypress to login programmatically with Django (i.e., without using the UI), the simplest solution is to change two words in the CSRF testing recipe that Cypress provides.
The two changes that I made in the below compared to the Cypress recipe at https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/logging-in__csrf-tokens/cypress/integration/logging-in-csrf-tokens-spec.js are:
- Changing
_csrftocsrfmiddlewaretoken; and - Changing
$html.find("input[name=_csrf]").val()to$html.find("input[name=csrfmiddlewaretoken]").val()
Recipe updated for Django 2.2:
// This recipe expands on the previous 'Logging in' examples
// and shows you how to use cy.request when your backend
// validates POSTs against a CSRF token
//
describe('Logging In - CSRF Tokens', function(){
const username = 'cypress'
const password = 'password123'
Cypress.Commands.add('loginByCSRF', (csrfToken) => {
cy.request({
method: 'POST',
url: '/login',
failOnStatusCode: false, // dont fail so we can make assertions
form: true, // we are submitting a regular form body
body: {
username,
password,
csrfmiddlewaretoken: csrfToken // insert this as part of form body
}
})
})
it('strategy #1: parse token from HTML', function(){
cy.request('/login')
.its('body')
.then((body) => {
const $html = Cypress.$(body)
const csrf = $html.find("input[name=csrfmiddlewaretoken]").val()
cy.loginByCSRF(csrf)
.then((resp) => {
expect(resp.status).to.eq(200)
})
})
})
You are correct, Cypress is not sending the token in the body because it is undefined, because of the way you are using .get() on the input to get the token.
You are using .get() as a synchronous call, but it's actually async. This is because Cypress will intelligently retry finding the DOM element, and that takes an indeterminate amount of time. This is a core concept of Cypress that enables built-in tests. The Cypress documentation details this better than I can, so check that out here: https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Default-Assertions
How you access a property on an element in the DOM should be put in a callback, in your case:
cy.get("input[name='csrfmiddlewaretoken']").then($input=>{
const hidden_token = $input.val()
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}
})
})
...
Pro-tip: using Cypress's doc search will usually lend you what you need

来源:https://stackoverflow.com/questions/48835058/logging-into-a-django-server-with-cypress-io-programmatically-without-using-ui

