Open theNailz opened 3 years ago
Hmm it doesn't seem like colon is a valid character in a cookie name: https://gist.github.com/jeremiahlee/ff7dfb60572211dce306
And k6 is using Go's net/http
library, which follows RFCs somewhat strictly, so I'm not sure there's a lot we can do here...
from https://github.com/golang/go/issues/12698#issuecomment-143214403 we can try something like https://github.com/StalkR/misc/blob/master/net/http/spacecookies/spacecookies.go, but I would consider this low prior as I have seen a lot of projects who have the same problem, so apparently, it is not only not standard but very poorly supported.
Hi @MStoykov
We use Azure AD B2C for authentication which uses cookie names with a colon in them. See Azure AD B2C Cookie Definitions. This means I can't run performance tests against any feature on our website requiring a logged in user. This is a major bummer since I really started to love using K6 for protocol level performance testing.
Would you be able advance this up the priority list?
Have you tried replacing the :
with %3A
?
We cannot control the name of the cookie as it is predefined by Azure AD B2C. Even if I were able to change the name of the cookie somehow, that would break B2C, as that is what the internal mechanisms of Azure AD B2C rely on when looking for the cookie.
I don't access the cookie by name from my script, I am relying on K6 to propagate the cookie on subsequent requests. However, when I debug and look at all cookies available in the cookie jar, all cookies are present accept the one with the colon in the name.
If I switch on --http-debug, I can see the cookie coming back on the first call, but the cookie is not present from my export default function().
This is what my code looks like currently
group('Login', () =>
{
let b2cSettings = this.RequestAccess();
if (b2cSettings.success)
{
console.info('RequestAccess: Success');
let submitCredentialsResult = this.SubmitCredentials(b2cSettings.content!, username, password);
if (submitCredentialsResult.success)
{
console.info('SubmitCredentials: Success');
let selfAssertResult = this.Confirm(b2cSettings.content!);
if (selfAssertResult.success)
{
console.info('Confirm: Success');
let retreiveAuthTokenResult = this.RetreiveAuthToken(b2cSettings.content!);
if (retreiveAuthTokenResult.success)
{
console.info('RetreiveAuthToken: Success');
}
else
{
console.error('ERROR => ' + retreiveAuthTokenResult.error);
}
}
else
{
console.error(selfAssertResult.error);
}
}
else
{
console.error(submitCredentialsResult.error);
}
}
else
{
console.error(b2cSettings.error);
}
});
sleep(1);
This is what I get back in the response by using the --http-debug flag when running:
group="::Login" iter=0 name=Login_RequestAccess request_id=a7974197-1e57-44a5-702a-aedfdd2abef5 scenario=default source=http-debug vu=1
INFO[0005] Response:
HTTP/1.1 200 OK7
Content-Length: 16972-
Allow: OPTIONS,TRACE,GET,HEAD,POST
Cache-Control: no-store, must-revalidate, no-cache
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Thu, 18 Feb 2021 16:21:24 GMT
Expires: -1
Public: OPTIONS,TRACE,GET,HEAD,POST
Set-Cookie: x-ms-cpim-sso:xchangedocsdev.onmicrosoft.com_0=m1.VKws5CBVuLOC+ETX.46S00j2fvvGpdgcn6yBlLg==.0.vB1mkFOMVv+ZtIkM9PKaPAtcF5O5EtpV3p6yXY0ZCStj2rbPlxIwD3KX14UjLsJGidhjkSqT5bDbUk5I9GJggsk+T7PsDBMTUIVMdPhJgse18iNgSTuLoga9EClyqt5VK3SIoDUKvWrVfqQRyaWqM/jOFCa28L1f4Po5f2KgSuKRG/MBAaz1oAU2YbRbW7Whp0VqTdmVdGA7XhRcVv/+KwcAFdlfbvDvc9oAXXLTjREqRiLpa2IyetX3WCFfDwruP5poJBrPejGSkPXwuAIA3y9IGwFx0ufGs25A8ilK3TI=; domain=xchangedocsdev.b2clogin.com; path=/; SameSite=None; secure; HttpOnly
Set-Cookie: x-ms-cpim-csrf=MkZsaUd3SkowUGJ5TFVRZVpic3paTkl2UjJTanNTVjhiTkgxdVRielF0QUdxak9NU04rVHZSWmU4REQzdUVpRGRqS3pTOStlZmc0V3J5VEtuN0tTOWc9PTsyMDIxLTAyLTE4VDE2OjIxOjI0LjI0MTk1MTdaO3ViallkQWFCR2dMZCs1b3JYaXlmZmc9PTt7Ik9yY2hlc3RyYXRpb25TdGVwIjoyfQ==; domain=xchangedocsdev.b2clogin.com; path=/; SameSite=None; secure; HttpOnly
Set-Cookie: x-ms-cpim-cache|uzr_94lw0kc9b7niavtuqg_0=m1.1gihidD0ifnvLU2n.7znIGGgSxCTGIP0ZcqD9NA==.0.ayPj+GhJFduZuYKbqYX5ELiPQ/UbhS4+vxWkDcXE3tsU2RqPL26EOCsxrwN8sadJrYca5SpI/FfgidddtdglFQ77nMrnipdWMCpQibNagck5rsktHmEuhGYlgcGPte8iWrT2yQTllSqSPbpdR3NC90Qc/m5kz9mkemqt7Yc12C6m/8DW+G8i/cx6w+gzzi/JqCACiQbkANQcS3xd4u1+sgN46DIGqAkahlPftuWX+7umLT9vkbYaP87odpGjQeSGEWV58PFEia5/7zIAG9109sWjv7l17NWwnmW2NonOvfK4KaKUeOlceCavRGCE4fFMSmrxxLahQZYPKaDcNscrS7ABPqFZpPajo1lzqoIv9rdreht7y1ZaBjx2s6o0qS3PoS13gNeXJnzhm64WxKZKggJ25ts/2lnwlMxW3bFCqssojMH3IgAkwOPmafVznkmjSub9rZs2XTe+MU5P4KDDQeUBmvKszZPqH16t58kSEu4zec4PKjsnf+wWNDPg0BgxqEJm6oV8KgAD1Fkg0YZFKrx3j/gEUN2afdqbmBWgVopQmZRcakysHtwqO+NR/ynHKDuNxM0Ruxo0AnO5tq5+p3fF3fRtOAsZNN8koqBfKegnS4+Y2yNGStnL; domain=xchangedocsdev.b2clogin.com; path=/; SameSite=None; secure; HttpOnly
Set-Cookie: x-ms-cpim-trans=eyJUX0RJQyI6W3siSSI6ImY3N2Y5NDUxLTU2ODktNDBkMi1iZDZmLWI5Yzg2YWY0ZDQ0MiIsIlQiOiJ4Y2hhbmdlZG9jc2Rldi5vbm1pY3Jvc29mdC5jb20iLCJQIjoiYjJjXzFhX3NpZ251cHNpZ25pbiIsIkMiOiI4NTQ0YzM5OS1lNDZmLTQ2N2QtYTU1Mi05MGY0OTY2ODk2MGEiLCJTIjoxLCJNIjp7fSwiRCI6MH1dLCJDX0lEIjoiZjc3Zjk0NTEtNTY4OS00MGQyLWJkNmYtYjljODZhZjRkNDQyIn0=; domain=xchangedocsdev.b2clogin.com; path=/; SameSite=None; secure; HttpOnly
Strict-Transport-Security: max-age=31536000; includeSubDomains
Vary: Accept-Encoding
X-Build: 1.0.1910.1
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Ms-Gateway-Requestid: 2f143361-6eb7-4711-8869-2b2f486de663
X-Request-Id: f77f9451-5689-40d2-bd6f-b9c86af4d442
X-Ua-Compatible: IE=edge
X-Xss-Protection: 1; mode=block
However, this is the contents of the cookie jar: [Cookie Jar]{"x-ms-cpim-trans":["eyJUX0RJQyI6W3siSSI6ImY3N2Y5NDUxLTU2ODktNDBkMi1iZDZmLWI5Yzg2YWY0ZDQ0MiIsIlQiOiJ4Y2hhbmdlZG9jc2Rldi5vbm1pY3Jvc29mdC5jb20iLCJQIjoiYjJjXzFhX3NpZ251cHNpZ25pbiIsIkMiOiI4NTQ0YzM5OS1lNDZmLTQ2N2QtYTU1Mi05MGY0OTY2ODk2MGEiLCJTIjoxLCJNIjp7fSwiRCI6MH1dLCJDX0lEIjoiZjc3Zjk0NTEtNTY4OS00MGQyLWJkNmYtYjljODZhZjRkNDQyIn0="],"x-ms-cpim-csrf":["MkZsaUd3SkowUGJ5TFVRZVpic3paTkl2UjJTanNTVjhiTkgxdVRielF0QUdxak9NU04rVHZSWmU4REQzdUVpRGRqS3pTOStlZmc0V3J5VEtuN0tTOWc9PTsyMDIxLTAyLTE4VDE2OjIxOjI0LjI0MTk1MTdaO3ViallkQWFCR2dMZCs1b3JYaXlmZmc9PTt7Ik9yY2hlc3RyYXRpb25TdGVwIjoyfQ=="],"x-ms-cpim-cache|uzr_94lw0kc9b7niavtuqg_0":["m1.1gihidD0ifnvLU2n.7znIGGgSxCTGIP0ZcqD9NA==.0.ayPj+GhJFduZuYKbqYX5ELiPQ/UbhS4+vxWkDcXE3tsU2RqPL26EOCsxrwN8sadJrYca5SpI/FfgidddtdglFQ77nMrnipdWMCpQibNagck5rsktHmEuhGYlgcGPte8iWrT2yQTllSqSPbpdR3NC90Qc/m5kz9mkemqt7Yc12C6m/8DW+G8i/cx6w+gzzi/JqCACiQbkANQcS3xd4u1+sgN46DIGqAkahlPftuWX+7umLT9vkbYaP87odpGjQeSGEWV58PFEia5/7zIAG9109sWjv7l17NWwnmW2NonOvfK4KaKUeOlceCavRGCE4fFMSmrxxLahQZYPKaDcNscrS7ABPqFZpPajo1lzqoIv9rdreht7y1ZaBjx2s6o0qS3PoS13gNeXJnzhm64WxKZKggJ25ts/2lnwlMxW3bFCqssojMH3IgAkwOPmafVznkmjSub9rZs2XTe+MU5P4KDDQeUBmvKszZPqH16t58kSEu4zec4PKjsnf+wWNDPg0BgxqEJm6oV8KgAD1Fkg0YZFKrx3j/gEUN2afdqbmBWgVopQmZRcakysHtwqO+NR/ynHKDuNxM0Ruxo0AnO5tq5+p3fF3fRtOAsZNN8koqBfKegnS4+Y2yNGStnL"]}
I find this unlikely to get high enough priority so the core team gets to work on it. It also seems it has the potential to break the handling of cookies, given that the fix seems to be redoing the whole handling of cookies more or less.
We currently mostly use the golang stdlib to handle most of the cookie/headers and starting to handle them ourselves is a big step. One that I don't feel is warranted at this time. So the best case is that https://github.com/golang/go/issues/12698 gets reopened and fixed.
Even if someone decides to make a PR to fix it(which likely will be a lot of work, unless it turns out there is an easy hack) this will have to wait for at least v0.32.0(in 2months+) or 6+ months for the next golang release ;).
I agree that this is not ideal, but the specification is clear, and the fact that Microsoft is (again) not following it and even specifically breaking it is ... reminiscent of something ;)
As a workaround sending the header Cookie
seems to work in k6:
import http from "k6/http";
export default function() {
var res = http.get("https://httpbin.org/get", {headers: {"Cookie": "on:e=two"}})
console.log(JSON.stringify(res, null, " "))
}
Obviously, in your case you will also need to parse the Set-Cookie
header when it comes in. This can probably be done fairly easy with some amount of wrappers and would expect will be fairly non-intrusive as long as that Cookie isn't needed everywhere ... but I haven't tried it so YMMV :man_shrugging:
Thanks @MStoykov , I'll capture the cookie from the header and resubmit on subsequent requests. I suspect it is only needed till I receive the authorization code.
Thanks for the quick assistance.
Unfortunately this workaround didn't work for me. I tried setting the cookie like this:
let response = http.post<'text'>(url, data,
{
headers: { 'Cookie': `x-ms-cpim-sso:xchangedocsdev.onmicrosoft.com_0=${b2cSettings.ssoCookie}`},
tags: { name: 'Login_SubmitCredentials' }
});
and like this:
let response = http.post<'text'>(url, data,
{
cookies: { 'x-ms-cpim-sso:xchangedocsdev.onmicrosoft.com_0': b2cSettings.ssoCookie },
headers: headers,
tags: { name: 'Login_SubmitCredentials' }
});
Even when I use your example of "on:e=two" the cookie is not sent with the request. As soon as I drop the ":" or replace it with a "|", the cookie goes through. But then B2C doesn't recognize the cookie.
I get this B2C error: User does not have an existing session and request prompt parameter has a value of 'None'.
Running against httpbin seems to work ... I just double-checked.
How do you find out the cookie doesn't get to the server? Do you happen to have a firewall or some kind of corporate gateway/proxy in the middle, and does it work with any other tool for example curl
?
I look at the request's cookie after submitting the call:
let response = http.get<'text'>(url,
{
cookies: { 'x-ms-cpim-sso_xchangedocsdev.onmicrosoft.com_0': b2cSettings.ssoCookie },
headers: headers,
redirects: 0,
tags: { name: 'Login_Confirm' }
});
check(response, { 'status was 302': r => r.status === 302 });
Object.keys(response.request.cookies).forEach(key => {
console.log(`[KEY]${key}`);
});
they won't be there, as golang doesn't like the fact that there is :
in them(this whole issue). But the header is sent from at least what I can gather as that doesn't get processed(that much).
That's why using httpbin.org
as I did above is so good as it will return you what headers you sent in the body so you can check that
Looks like you are right. Here is what I get from the response:
"request": {
"method": "GET",
"url": "https://httpbin.org/get",
"headers": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"gzip, deflate, br"
],
"Connection": [
"keep-alive"
],
"Host": [
"xchangedocsdev.b2clogin.com"
],
"Referer": [
"https://xchangedocsdev.b2clogin.com/xchangedocsdev.onmicrosoft.com/b2c_1a_signupsignin/oauth2/v2.0/authorize?response_type=id_token&scope=[REDACTED]%20openid%20profile&client_id=[REDACTED]&redirect_uri=https%3A%2F%2Fxd-dev-webapp-6ami23sdirtd2.azurewebsites.net%2F&prompt=login&response_mode=fragment"
],
"Cookie": [
"x-ms-cpim-sso:xchangedocsdev.onmicrosoft.com_0=m1[REDACTED]="
],
"User-Agent": [
"k6/0.30.0 (https://k6.io/)"
]
},
"body": "",
"cookies":
@JacoKarsten-Korbicom seems fine to me so that seems to work :shrug:
Also, I hope these credentials that you are posting are testing ones and you revoke them before posting? As otherwise, someone can now use those to gain access ;)
I don't know what User does not have an existing session and request prompt parameter has a value of 'None'.
could mean ... but I would argue that you should try using curl
to replicate a request that passes and then try to make it work in k6.
Maybe something else is missing from the request? or it is expecting a specific User-Agent
? no idea
Yep, Just test accounts.
Thanks for your help. Must be something else that cause B2C to return the error then.
Hey @MStoykov
I have found my problem. I was capturing the sso cookie on the first call thinking that I can just pass it to all 3 subsequent calls. I had to discard this sso cookie in favour of a new sso cookie I get back on the third call (function Confirm) to submit with the last (4th) call to get the authorization code back.
I'm so glad I got this working. Your advice helped a lot!
In case anyone is looking for a quick solution, this is the function to extract the cookie.
import http from "k6/http";
export default function() {
var resWithCookies = http.get(url_with_bad_cookies)
var badCookies = parseBadCookie(resWithCookies.headers);
var res = http.get(url_that_now_receives_cookies_correctly, {headers: {"Cookie": badCookies}})
console.log(JSON.stringify(res, null, " "))
}
export function parseBadCookie(setCookieHeader) {
const cookieLines = setCookieHeader.split(",");
// gests all cookies with colons in the name
const cookiesWithColons = cookieLines.filter((cookie) => {
const cookieName = cookie.split("=");
return cookieName[0].indexOf(":") >= 0;
});
// returns comma separated string, ready to be added as a cookie header
return cookiesWithColons.join(",");
}
Btw, Azure AD still has cookies with colons in it, and K6 still ignores them.
Hi again, this issue is quite an important one. I have been working with Jmeter for quite some time and just recently started migrating things to K6. My first big issue was this one, that took a lot of debugging and Fiddler to find out that 1 cookie had a ";" in the name and was ignored.
I understand that we have RFCs, but no modern browser today follows them strictly because technology grows fast and the RFCs don't follow the same pace. The cookie spec is from April 2011 and the spec that defines "token" is from 1999.
I tested this cookie in LoadRunner, JMeter and NeoLoad. All of them just work.
Hi @leandrodotec, thanks for writing in with such a nice example [^1] :bow:.
[^1]: it seems to me there is no typescript syntax in this, so it is 100% javascript just ... I think :shrug:
As mentioned in the links from the old comments this comes from the go stdlib directly. There reasoning for not supporting this is - "it is not RFC compliant and is not as commonly broken as
AFAIK and managed to google - nothing has changed about this. So the go stdlib will not make this any easier for us.
This does mean that at this point this will likely need to be fixed in multiple places multiple times or for us to fix this.
To be honest I am not a fan of the current cookiejar implementation which is direct copy of the go one.
https://github.com/grafana/k6/issues/3026 and https://github.com/grafana/k6/issues/1923 are two issues which make it confusing and hard to use already.
But the current k6/http
is in maintaince mode until we manage to ship a new http API which should try to address all of these issues
It now has a design doc and is planned to be worked on.
I don't think this will be shipped with enough features to be usable for at least 2+ releases (2 months each) and this issue is not in the ones that should be fixed.
I will add it to a list to be discussed, but even if we do add it to the list this will still mean this will not ship soon. Sorry.
At this point my recommendation is to wrap the k6/http
API and do the changes in that - hopefully they will work for you. Posting the wrapper will also be nice and likely useful for other users.
Hopefully this explains why this hasn't been fixed and makes it more clear on when it might be ... if at all.
HI @mstoykov thanks for taking the time to answer me. And yeah, the first version of my example had a lot of typescript, I removed them and I forgot to remove my comment :-).
When a server responds with a Set-Cookie header with a cookie name that contains a colon, the cookie gets ignored and is not stored in the cookie jar.
Environment
Expected Behavior
Cookies with a colon in their name are stored in the cookie jar.
Actual Behavior
Any cookie with a colon (:) in their name are ignored and not stored in the cookie jar.
Steps to Reproduce the Problem
my:cookie=somevalue
my:cookie
is missing from the jar/subsequent requests.I do not have a public working example but I can share my received Set-Cookie headers which were not parsed: (tokens are invalid, don't worry)
In this example the following cookie is ignored:
toegang:Lj6oYyC3oG7sJBZEfHh4R
(Yes, I noticed there are double headers, this is straight from
--http-debug=full
.)