How to push request a CSRF Token in k6 performance testing tool - ReferenceError - request

I am quite new to k6 and load testing and I could not figure out why I am not able to push a token from one request to another, to test the login process in a Laravel-built web app. I hope somebody can help me out with this issue.
So my script looks like the following:
First request from where I want the token:
import { parseHTML } from 'k6/html';
import { sleep, group, check } from "k6";
import http from 'k6/http'
export const options = {}
export default function main() {
let response
group('page_1 - http://localhost:81/login', function () {
response = http.get('http://localhost:81/login', {
headers: {
host: 'localhost:81',
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0',
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'accept-language': 'en-US,en;q=0.5',
'accept-encoding': 'gzip, deflate, br',
connection: 'keep-alive',
cookie:
'XSRF-TOKEN=eyJpdiI6InY5ckZiaGdFTkI4Q0YyRi8rdmtyNUE9PSIsInZhbHVlIjoiT0NjZXlWWVBubTE5Zjh6cXBmNmZFWTdZKzBjVXlEOGhheGR0aVUybURSSGRZbEFmQ0N2RW5BQ3pOYzBQUXgweXhUaGNpRDhrcTV5SHBJUkEvU0FYTmN3eCswYTFsVnhQdk8wL1dkeHMvOTNXRTU4dnk2WjJ0QWFCSWdyQzEwQkwiLCJtYWMiOiIyODI1YmFkMDI1MzlkOGY4ODEyMDg4YWU5M2I5MWE3NmI3Yjg2ODczYTBkMzhhNmZiZTU5ODNlZDBjOGViNWIzIn0%3D; dev_session=eyJpdiI6ImNGalhPQW9GTWlYLzdsaEg1Qk0zdnc9PSIsInZhbHVlIjoiVU5jQ21OZmkyUDVnUmd2WUxUc3Z5dWhRbzBJTm1HWFhmQ1RuNzdFaEpRb1IzdVlIa1VhUkNXYTBlc2IxMHRMajl6UTAzYmFVTHZheEdTV2RrYU84d3pmdEUxYUlkaVFFT3J5YUVWSE1wVklRektqemVmbjhmK3hLWHo2ZmlMYlgiLCJtYWMiOiI3MTQ2ODg0Yjk4YjhhNjg2Yzg1YjllZjdmMWMyNzVkY2ZmNGM1NjAzYWUyN2NlMmE0ZjAwOTAyNWMwNGI2YmM2In0%3D',
'upgrade-insecure-requests': '1',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'sec-fetch-user': '?1',
'sec-gpc': '1',
},
})
// Query the HTML for an input field named "_token".
const elem = response.html().find('input[name=_token]');
// Get the value of the attribute "value" and save it to a variable
const token = Elem.attr('value');
// Now you can concatenate this extracted value in subsequent requests that require it.
// console.log() works when executing k6 scripts locally and is handy for debugging purposes
console.log('The value of the hidden field is: ' + token);
check(response, {
list_OK: (r) => r.status === 200,
});
})
Second request - I want to use the token from the previous request to log in:
group('page_2 - http://localhost:81/customlogin', function () {
const url = 'http://localhost:81/customlogin';
const payload = JSON.stringify({
_token: `${token}`,
email: 'user',
password: '1234',
});
const params = {
headers: {
'Content-Type': 'application/json',
host: 'localhost:81',
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0',
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'accept-language': 'en-US,en;q=0.5',
'accept-encoding': 'gzip, deflate, br',
'content-type': 'application/x-www-form-urlencoded',
origin: 'http://localhost:81',
connection: 'keep-alive',
referer: 'http://localhost:81/login',
cookie:
'XSRF-TOKEN=eyJpdiI6ImlnZFZCUGF1b1FYUlJOdTJHNDd2Vnc9PSIsInZhbHVlIjoiTXhhdnZyQzlPamRFQ21rajdQVEZXcThzWittZndqU2d1L0hyN1BmRTA2a2RBbEpYZUhIUlRpWjh1RWJoQ1Y5dWJoTWVnaXEzZ1NVTjBndG1tenUyN2phY1lMdkIxSzBGek5aYndlSmRxaEhVTGY4WkNCcE1UY3N6YmowUnkrTkciLCJtYWMiOiJlNDIxNjhkYTc1NjYxNTVkNWZhOWViZDYwMGU1ODRkNmQ2ZGU0NjgyMjU5NjIxMzQ0MjYyYzRjMmJkYTVmNjUwIn0%3D; dev_session=eyJpdiI6IndxWXpobW9BUm1GSHNVZkorN0N0OGc9PSIsInZhbHVlIjoiSE82by9aRnBXQjFkNG5JMHFkVzUzc3kraUZOYUdIdjNlUGN6a3c2SjBSZy9TaVNxNmRsWnQzMTltMGt0MGQvWUoxQndyQXFvd2theWViNU94Z2FXaXlGTkc4ZVdERGY2KzRpUUZDZDIxNG85UFhhanRiajBCWElmcmthMWE0R3IiLCJtYWMiOiJjMDllMmRmNGJjNDRlMjM2MmZmZTViOWEwZmUzNWQ3MzNjZDI1NWQwYmU3MjE4OTZiMTRhN2U0NWNkMTcxMDAzIn0%3D',
'upgrade-insecure-requests': '1',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'same-origin',
'sec-fetch-user': '?1',
'sec-gpc': '1',
},
};
response = http.post(url, payload, params);
console.log(response)
check(response, {
list_OK: (r) => r.status === 200,
});
})
After running the script I get an error message saying:
> ReferenceError: token is not defined
Thank you for your passionate help!

In this case you are defining a variable in the first call to group (const token ....` and then try to use it in the second call.
This has nothing to do with CSRF or k6, but with javascript ... and arguably scoping in most other languages.
But in order for both functions to see the same variable you will need to define it earlier - so for example next to your let response in the start of the default function.
Also remove const from the current definition as otherwise it won't work ;)

Related

Symfony React Cors issue

I use Symfony 5 and React, with docker.
Both container are on different docker-compose but on the same newtork, so they can see and ping each other.
Now I want to POST something on one of my api route, here is my request:
const handleSubmit = async function (e) {
setError(null)
setLoading(true)
e.preventDefault()
const data = new FormData(e.target)
console.warn(data)
try {
const response = await fetch('https://localhost:8443/authentication_token', {
credentials: 'include',
headers: {
Accept: 'application/json',
},
method: 'POST',
body: data,
})
console.warn(response)
const responseData = await response.json()
console.warn(responseData)
onConnect(responseData)
} catch (e) {
setError(e)
setLoading(false)
}
}
Then I'm getting the following error:
Access to fetch at 'https://localhost:8443/authentication_token' from origin 'http://localhost:3001' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
So after some search I installed nelmio package to deal with cors, here is my nelmio_cors.yml file:
nelmio_cors:
defaults:
allow_credentials: true
allow_origin: ['*']
allow_methods: [ 'GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE' ]
allow_headers: [ '*' ]
expose_headers: [ 'Authorization']
max_age: 0
hosts: []
origin_regex: true
forced_allow_origin_value: ~
paths:
'^/':
origin_regex: true
allow_origin: ['*']
allow_headers: [ '*' ]
allow_methods: [ 'POST', 'PUT', 'PATCH', 'GET', 'DELETE' ]
expose_headers: [ 'Link' ]
max_age: 3600
And added the below line in my .env:
###> nelmio/cors-bundle ###
CORS_ALLOW_ORIGIN=(.*)
###< nelmio/cors-bundle ###
And yet I get the same error and didn't manage to find an answer online.
Does anyone see what I've done wrong?
UPDATE
I've changed my nelmio_cors.yml config to the one above
And still got issues with cors but a different error message now:
Response {type: "cors", url: "https://localhost:8443/authentication_token", redirected: false, status: 404, ok: false, …}
It seems react don't manage to find the route. Any idea why ?
UPDATE 2
My nelmio_cors.yaml file now looks like that :
nelmio_cors:
paths:
'^/':
origin_regex: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_headers: ['*']
allow_methods: ['POST', 'PUT', 'PATCH', 'GET', 'DELETE']
max_age: 3600
.env contains:
CORS_ALLOW_ORIGIN=(.*)
But on the route call I receive the following error :
Access to fetch at 'https://localhost:8443/authentication_token' from origin 'http://localhost:3001' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'.
My response headers
access-control-allow-origin: http://localhost:3001
cache-control: no-cache, private
content-encoding: gzip
content-length: 451
content-type: application/json
date: Thu, 22 Oct 2020 08:12:11 GMT
server: openresty/1.17.8.2
status: 404
vary: Accept-Encoding
vary: Accept
x-debug-exception: Unable%20to%20find%20the%20controller%20for%20path%20%22%2Fauthentication_token%22.%20The%20route%20is%20wrongly%20configured.
x-debug-exception-file: %2Fsrv%2Fapi%2Fvendor%2Fsymfony%2Fhttp-kernel%2FHttpKernel.php:141
x-debug-token: 9b2891
x-debug-token-link: https://localhost:8443/_profiler/9b2891
x-powered-by: PHP/7.4.11
x-previous-debug-token: 486328
x-robots-tag: noindex
request header:
:authority: localhost:8443
:method: POST
:path: /authentication_token
:scheme: https
accept: application/json
accept-encoding: gzip, deflate, br
accept-language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
content-length: 248
content-type: multipart/form-data; boundary=----WebKitFormBoundarywFASHmBarrNFyL7Y
cookie: pma_lang=fr; phpMyAdmin=a18f394d8e7daaefba20691da57c4b58; io=Jh7QElIlIXLDeAYaAAAA
origin: http://localhost:3001
referer: http://localhost:3001/
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.99 Safari/537.36
I would suggest to have a different environmental variable value for local development and for your production or non-production environments.
nelmio_cors:
paths:
'^/':
origin_regex: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_headers: ['*']
allow_methods: ['POST', 'PUT', 'PATCH', 'GET', 'DELETE']
max_age: 3600
For local development you may have the following env.
CORS_ALLOW_ORIGIN=(.*)
For prod/non-prod you may want to list your web applications domains explicitly in order to be on a safe side.
CORS_ALLOW_ORIGIN=^https://(www\.)?(site.domain.com|site2.domain.com)$
For those who have the same issue, the problem came from the frontend
The data I recovered and place in body :
const response = await fetch(
'https://localhost:8443/authentication_token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: data
})
were not well formated, and for some reason the backend sent back weird errors.
So I hardcoded them and this final result work :
const data = "{\"email\":\"test#test.com\",\"password\":\"test\"}";
const response = await fetch(
'https://localhost:8443/authentication_token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: data
})

React request is being sent without an Authorization header

I am trying to pass a bearer token, to the fastify http server, in my headers.
I set the headers inside my request as:
...
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json'
}
const token = localStorage.getItem('token')
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
const newOptions = {
...options,
mode: 'no-cors',
headers
}
console.log('options:', newOptions)
return fetch(url, newOptions)
My console.log prints:
options: {
mode: "no-cors",
headers: {
Accept: "application/json",
Content-Type: "application/json",
Authorization: "Bearer NQ9xQLmYtq92aT8JHHRd7DGZJ..."
}
}
From the Chrome network tab, I look at the headers, and Authorization is just not present there. My route handler function is below:
async function user(server, options) {
server.route({
method: 'GET',
url: '/user/:email',
handler: async (req, res) => {
const username = req.params.email
console.log('user email:', username)
console.log('headers:', req.headers)
res.send({
type: 'promoter'
})
}
})
}
When I print headers on the server, it also does not have Authorization, showing:
headers: { host: 'localhost:5000',
connection: 'keep-alive',
pragma: 'no-cache',
'cache-control': 'no-cache',
accept: 'application/json',
'sec-fetch-dest': 'empty',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36',
'sec-fetch-site': 'same-site',
'sec-fetch-mode': 'no-cors',
referer: 'http://localhost:3000/admin',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9,ru;q=0.8' }
What am I missing?
Another interesting issue is that when I run the request from Postman it shows 200 response code, and fastify prints 200 to the log. However, running from the saga/request with:
return fetch(url, newOptions)
.then(checkStatus)
.then(parseJSON)
I get response.status of 0 instead of 200 inside the request method, while the server log still shows "res":{"statusCode":200}.
I figured out what was the problem.
Apparently, my version of Chrome - Version 80.0.3987.106 (Official Build) (64-bit) and possibly other browsers and older versions of Chrome, as well, strip the authorization header, when it is used in conjunction with the no-cors mode. Enabling CORS and setting the appropriate headers solves the problem.
Try adding withCredentials: true and credentials: 'include' to your options:
options: {
mode: "no-cors",
withCredentials: true, // <-- ADD THIS
credentials: 'include', // <-- AND THIS
headers: {
Accept: "application/json",
Content-Type: "application/json",
Authorization: "Bearer NQ9xQLmYtq92aT8JHHRd7DGZJ..."
}
}
Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials

Making an axios POST request with multipart/form-data, via React Native Debugger

I am trying to upload a file from React Native to my server. However I am unable to set the 'Content-Type' header to multipart/form-data.
Here's my simple request:
axios({
uri: 'http://localhost:3000',
method: 'POST',
data: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data'
}
});
I am using the React Native Debugger to monitor Network requests.
When I see my Network request inside the Debugger, I see this:
'Content-Type': 'text/plain;charset=UTF-8'
and the Request Payload is simply [object Object]
User Agent:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) ReactNativeDebugger/0.7.13 Chrome/58.0.3029.110 Electron/1.7.9 Safari/537.36
If I am unable to do this with the React Native Debugger, can anyone suggest any steps to testing via Expo.
Using the latest version of Axios (0.17.1), you make an HTTP request with 'Content-Type': 'multipart/form-data' in the header as follows:
const formData = new FormData();
formData.append('action', 'ADD');
formData.append('param', 0);
formData.append('secondParam', 0);
formData.append('file', new Blob(['test payload'], {
type: 'text/csv',
}));
axios({
url: 'http://localhost:5000/api/hello',
method: 'POST',
data: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
},
});
Two things: I am using url, not uri. Also, please insert your own form data and url. Inspecting the request, which has been replied successfully, this is what I get for the request header:
After hours trying to make it work, I realized that a multipart/form-data needs a boundary that is generated dynamically depending on the amount of data that is being sent.
Here is the code that works for me:
const data = new FormData();
data.append('field_name', 'field_pictures');
data.append('files[file]', fs.createReadStream(filepath), filename);
const headers = {
'Content-Type': 'multipart/form-data',
'Authorization': 'here you can set your headers',
...data.getHeaders() // this line is the key
// you need to mix your headers with those generated by the form data
}
return axios.post(url, data, { headers });
You can do simply:
axios.defaults.headers.common['Content-Type'] = 'multipart/form-data; boundary=someArbitraryUniqueString';
It will set your headers for sure. However, I have been struggling with form data in React Native for a day and without success. After some experimental aproaches, I found out that everything works without debugger. I don't know why but with debugger, I have been sending empty body to the server.
What's more I didn't need to set headers as it is your question in this post.
According to #JMA answer,
import axios from 'axios';
export async function somefunction(data) {
const formData = new FormData(); // No imports needed
for(let key in userData) {
formData.append(key.toString(), data[key].toString())
}
return axios.post(`${ROUTE}`, formData, {
'Content-Type': 'multipart/form-data',
});
}
where: data is your payload to what you want to send. somefunction is function that send POST request to ROUTE.

jwt authorization token missing from response header

I'm trying to setup a simple Angularjs app with Hapi, using JWT authentication.
I send an email to newly registered user with a jwt token link to verify if the email exists. The link looks like this:
http://127.0.0.1:3000/verifyEmail/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6InJpY2tAaWNvZGU0dS5ubCIsInNjb3BlIjpbIkN1c3RvbWVyIl0sImZpcnN0TmFtZSI6IlJpY2siLCJsYXN0TmFtZSI6IkdvbW1lcnMiLCJpYXQiOjE0NDkxNDc5MzR9.6lWxcsSIC7DgAiGC0hcp7bdAhyl40Nbcqid3VgVtM6c
This is how I generate the token:
handler: function(request, reply) {
request.payload.password = Common.encrypt(request.payload.password);
request.payload.scope = "Customer";
User.saveUser(request.payload, function(err, user) {
if (!err) {
var tokenData = {
userName: user.userName, //email address
scope: [user.scope],
firstName: user.firstName,
lastName: user.lastName
};
Common.sentMailVerificationLink(user,Jwt.sign(tokenData, privateKey));
reply("Please confirm your email id by clicking on link in email");
} else {
if (11000 === err.code || 11001 === err.code) {
reply(Boom.forbidden("please provide another user email"));
} else {
console.log(Boom.forbidden(err));
reply(Boom.forbidden(err)); // HTTP 403
}
}
});
}
Now when I click the verification link, the response header looks like this:
{ host: '127.0.0.1:3000',
connection: 'keep-alive',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;
q=0.8',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KH
TML, like Gecko) Chrome/46.0.2490.86 Safari/537.36',
dnt: '1',
'accept-encoding': 'gzip, deflate, sdch',
'accept-language': 'nl-NL,nl;q=0.8,en-US;q=0.6,en;q=0.4,fr;q=0.2,es;q=0.2',
'x-cookiesok': 'I explicitly accept all cookies' }
The Question:
I am missing the Authorization token from the response header. How can I send the Authorization token in the header?
You send the token in the url so it can't return in the headers. You have to get it back from the url.

Angular $resource transformRequest doesn't change Request Header

When I use $resource for a REST login, the transformRequest doesn't add the Authorization header as intended. Using a $.ajax call it does work as intended.
So using:
$scope.login2 = function() {
function setHeader(xhr){xhr.setRequestHeader("Authorization", "Basic " + btoa($scope.gebruikersnaam + ':' + $scope.wachtwoord))}
    $.ajax({type: "POST",  url: "http://localhost:8000/authview/",  beforeSend: setHeader}).
        fail(function(resp){
            console.log('bad credentials.')
        }).
        done(function(resp){
            console.log('welcome ' + resp.email)
        })
}
I get the authorization header added to the request:
Origin: http://localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36
Authorization: Basic YWRtaW46cGFzc3dvcmQ=
But when doing:
$scope.login = function() {
api.auth.login($scope.getCredentials()).
$promise.
then(function(data){
// on good username and password
$scope.gebruikersnaam = data.username;
}).
catch(function(data){
// on incorrect username and password
alert(data.data.detail);
});
};
where "api.auth.login" is defined like:
kmregistratieApp.factory('api', function($resource){
function add_auth_header(data, headersGetter){
var headers = headersGetter();
headers['Authorization'] = ('Basic ' + btoa(data.username + ':' + data.password));
}
return {
auth: $resource('http://localhost:8000/authview/', {}, {
login: {method: 'POST', transformRequest: add_auth_header},
logout: {method: 'DELETE'}
}),
users: $resource('http://localhost:8000/authview/', {}, {
create: {method: 'POST'}
})
};
});
After "headers['Authorization'] = ('Basic ' + ..." (when debugging) I can see it sitting in headersGetter:
headers: Object
Authorization: "Basic YWRtaW46cGFzc3dvcmQ="
accept: "application/json, text/plain, */*"
content-type: "application/json;charset=utf-8"
But it doesn't turn up in the Network tab when inspecting the headers.
So my question is why doesn't the $resource way of working not add the Authorization header?
TransformRequest is not meant to be used to modify headers.
See AngularJS changelog. Scroll a bit downwards and you will see this:
transformRequest functions can no longer modify request headers.
HTTP headers can only be specified when using $http. Example:
$http.post('/someUrl', data, { headers: { 'Authorization': 'Basic'+key } });

Resources