When I make requests to a server with Postman(an api service), chrome automatically makes a cookie. However, when I make a request with my nodejs server, the cookie is not being made even thought the request is successful.
//Headers
var options = {
method: 'GET'
};
options.headers = {};
options.headers.Authorization = auth;
options.url = urlm;
console.log(options);
request(options, function(error,response,body) {
res.status(200).send(response.headers);
});
The response header is
{"date":"Tue, 23 Feb 2016 20:06:57 GMT","server":"Jetty(9.2.1.v20140609)","x-csrf-header":"X-CSRF-TOKEN","expires":"Thu, 01 Jan 1970 00:00:00 GMT","x-csrf-token":"xxxxxxxxxxx","cache-control":"no-store","content-type":"audio/mpeg","set-cookie":["JSESSIONID=uiqwnksadbohqjkq675d;Path=/;HttpOnly"],"connection":"close","transfer-encoding":"chunked"}
Pass { jar: true } in your request options.
From the documentation:
jar - If true, remember cookies for future use (or define your custom cookie jar; see examples section)
Related
The Setup / Environment
Client: (React.js, vs code, axios)
POST request to backend server to set auth cookie.
On every refresh Ill verify the cookie by a GET request to the auth backend server.
Every axios call is done with the "withCredentials:true" property set.
Backend (.net 6 - miminal API app written in c# and visual studio 2022.)
Set the cookie to "httpOnly", "sameSite=none" and "secure".
What Works
I have tested the flow on Windows 10, 11 + Mac computer and here everythink works fine. The cookie is set and I can login to my app.
The setCookie header is present here.
The problem
When I try to login from my iPhone with the latest IOS 15.4 it is not working (though it should be supported according to this https://caniuse.com/same-site-cookie-attribute).
The cookie is not set and the "getcookie" request returns null and Ill return Unauthorized.
Code:
Frontend (React js):
//run "npx create-react-app iphone-cors-test-app" and add a useEffect hook in App component //like this.
useEffect(() => {
var urlToBackendService= "https://829f-217-211-155-130.eu.ngrok.io";
axios({
url: baseURL + '/setcookie',
method: 'post',
withCredentials: true,
data: {
Email: 'ineedhelp#please.com',
Password: 'loveu'
}
}).then(() => {
axios({
url: baseURL + '/getcookie',
method: 'get',
withCredentials: true
}).then((resp) => {
var cookieValue = resp.data;
console.clear();
console.log(`cookie value: ${cookieValue}`);
alert(`cookie value: ${cookieValue}`);
})
});
Backend (c# .net 6, visual studio 2022):
//.net core web api template > minimal api (no controllers) enable https.
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.WithOrigins("https://nameofmyreactapp.azurewebsites.net")
.WithHeaders("accept", "content-type", "origin")
.WithMethods("GET", "POST", "OPTIONS")
.AllowCredentials();
});
});
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseCors();
app.MapPost("/setcookie", async ([FromServices] IHttpContextAccessor httpContextAccessor, LogonRequest logonRequest) =>
{
return await Task.Run<IResult>(() =>
{
//login user and get an accesstoken. set accesstoken to a httpOnly cookie.
var accessToken = "newly generated jwt access token";
httpContextAccessor.HttpContext!.Response.Cookies.Append(key: "accesstoken", accessToken, new CookieOptions
{
HttpOnly = true,
/*This should work with an iPhone with ios 15.4 (https://caniuse.com/same-site-cookie-attribute).*/
SameSite = SameSiteMode.None,
Secure = true
});
return Results.Ok();
});
});
app.MapGet("/getcookie", async ([FromServices] IHttpContextAccessor httpContextAccessor) =>
{
return await Task.Run<IResult>(() =>
{
var accesstoken = httpContextAccessor.HttpContext!.Request.Cookies["accesstoken"];
return string.IsNullOrEmpty(accesstoken)
? Results.Unauthorized()
: Results.Ok(accesstoken);
}
);
});
app.Run();
public record LogonRequest(string Username, string Password);
Screenshots:
setCookie request.
getCookie request.
Please help me.
UPDATE
If you want to test this with your phone I use ngrok. Sign up and follow directions. Download ngrok.exe and go to that folder in your terminal. Then start your backend localhost and type "ngrok http + your localhost address".
Example:
"ngrok http https://localhost:7200"
Then hit enter and you will get a public url to your localhost.
Replace the client axios url (urlToBackendService) with this new public url and publish your react app to to cloud (or create another ngrok user and do the same think for the client) or if you have browserstack account or if you have another idé how to test this.
I just want to clarify the solution here.
(Solution 2 is the best practice version here if you just want the short version)
Solution 1
I knew that it probably should work if my two sites where hosted on the same domain but since I was in early stages in development and I didnt want to create custom domains just yet, and I also had read documentation that interpreted that is should work anyways I continued with this solution.
So my first solution (which is not idéal since localstorage is not as safe as a secure httponly cookie) was to change my auth server to be able to receive accesstoken via headers and cookies, I also made sure to return tokens in the response so I could store the tokens in localstorage. Flow example:
login with username & password and send form to auth server.
Get tokens from auth server response and store in a local storage variable.
Send a request to auth server with accesstoken header provided by localstorage variable.
Solution 2 (Best practice version)
Cred to my fellow user #AndrewLeonardi and the original post from #RossyBergg which could confirmed what I expected, that it will work if I just put the two services on the same domain. I ended up with this solution:
AuthService url: https://auth.domain.se
Client url: https://domain.se
The httpOnly secure cookies was now working properly and I was able to get, set & remove the cookie in the auth server. The header & localstorage implementation from "Solution 1" could be removed.
From this example I've use this code:
chrome.webRequest.onHeadersReceived.addListener(
function (details) {
details.responseHeaders.forEach(function(responseHeader){
console.log(responseHeader.name + "===" + responseHeader.value);
if (responseHeader.name.toLowerCase() === "set-cookie") {
responseHeader.value = processSetCookieStr(responseHeader.value);
}
});
return {
responseHeaders: details.responseHeaders
};
}, {
urls: ["*://*/*"]
}, ['blocking','responseHeaders']
);
But I'm not able to see any cookie in the headers.
For main_frame type I've printed the values but cookie isn't there:
Cache-Control===private
Content-Type===text/html; charset=utf-8
Server===Microsoft-IIS/7.5
X-AspNet-Version===4.0.30319
X-Powered-By===ASP.NET
Date===Sat, 29 Apr 2017 08:51:45 GMT
Content-Length===29880
Though I'm able to fetch the desired cookie using chrome.cookies.get
Why am I unable to access cookie info in the onHeadersReceived?
You should add "extraHeaders" to the third parameter of the webRequest listener and it should be ['blocking','responseHeaders', 'extraHeaders'] for your example.
Starting from Chrome 72, the Set-Cookie response header is not provided and cannot be modified or removed without specifying 'extraHeaders' in opt_extraInfoSpec.
Refer https://developer.chrome.com/docs/extensions/reference/webRequest/
I'm trying to make a request using Node behind a corporate web proxy which requires NTLM authentication. I've tried using a couple libraries such as the proxying-agent but am having little success.
Here's a simplified version of my code using the request library and ntlm.js similar to the proxying-agent. I'd expect to receive a successful response after the last request call but for some reason am still getting a 407 -
var ntlmRequest = function(req) {
var ntlmOptions = {};
ntlmOptions.ntlm = {};
ntlmOptions.method = 'GET';
ntlmOptions.path = 'http://www.jsonip.co.uk';
ntlmOptions.ntlm.username = 'USERNAME';
ntlmOptions.ntlm.password = 'Pa$$word';
ntlmOptions.ntlm.workstation = 'PC-NAME';
ntlmOptions.ntlm.domain = 'DOMAIN';
var type1message = ntlm.createType1Message(ntlmOptions.ntlm);
var requestOptions = {
url: 'http://www.jsonip.co.uk',
proxy: 'http://webproxy.domain.com:8080',
headers: req.headers
};
requestOptions.headers['Proxy-Authorization'] = type1message;
requestOptions.headers['Proxy-Connection'] = 'Keep-Alive'
request(requestOptions, function(err,res){
var type2message = ntlm.parseType2Message(res.headers['proxy-authenticate']);
var type3message = ntlm.createType3Message(type2message, ntlmOptions.ntlm);
requestOptions.headers['Proxy-Authorization'] = type3message;
request(requestOptions, function(err,res){
console.log(res.statusCode);
});
});
};
I've tried comparing some packet captures and on a working NTLM request (using curl) I can see this during the type 1 and type 3 requests -
[HTTP request 1/2]
[HTTP request 2/2]
My requests only show 1/1.
I'm thinking maybe in other implementations of NTLM the browsers are spanning the requests across multiple packets. Not sure if this is the reason why it isn't working, or maybe just a different way of doing things.
Thanks in advance.
I am not entirely sure if this is a bad thing or not, but i was monitoring (logging) what incoming http request i get with this piece of code (note that running an NodeJS application on the scalable OpenShift platform):
function onRequest(request, response)
{
var date = new Date();
console.log(date.toUTCString() + " A request was made with url: " + request.url + " and header: " + JSON.stringify(request.headers));
// continue handling the request
}
The results i get are the following (every 2 seconds):
Fri, 07 Mar 2014 09:43:59 GMT A request was made with url: / and header: {}
Fri, 07 Mar 2014 09:44:01 GMT A request was made with url: / and header: {}
Fri, 07 Mar 2014 09:44:03 GMT A request was made with url: / and header: {}
So i am wondering if this is normal behaviour for a scalable NodeJS app (with a MongoDB database gear attached) in openshift, or is this something that could cause problems?
Sincerely,
Hylke Bron
If you are running a scaled application, then that is haproxy making sure that your application is up so that it can forward requests to it. You can change haproxy settings in your haproxy/haproxy.cfg file on your main gear.
I am using this method to inform OPENSHIFT that the application is alive
app.head("/", function(req, res, next){
res.status(200).end();
});
Since I didn't want to mess with haproxy.cfg, I did this and it seems to work. In your main app, add a middleware function to abort on the ping
function ignoreHeartbeat(except) {
except = except || 0;
var count = except;
return function(req, res, next) {
if (req.headers["x-forwarded-for"])
return next();
if (except > 0) {
if (--count <= 0) {
count = except;
return next();
}
}
res.end();
}
}
app.use(ignoreHeartbeat(1000));
Place that code before the call to setup the logger (Express 3 example shown below)
app.use(express.logger(...))
This logs out every 1000th ping. Set except to 0 or -1 to ignore all the pings.
I have built a API using node.js and express.
But i need to be able to proxy some requests on a specific route to a external server and show the response from the external server to the clint doing the request.
But i also need to forward the basic auth that the client is send along with the request.
I have tried using the request module like:
app.get('/c/users/', function(req,res) {
//modify the url in any way you want
var newurl = 'https://www.external.com'
request(newurl).pipe(res),
})
But it seems to not send the basic auth header because i get "403 Forbidden" back form the external server(www.external.com)
The request im making is looking like:
GET http://example.se:4000/c/users/ HTTP/1.1
Accept-Encoding: gzip,deflate
X-version: 1
Authorization: Basic bmR4ZHpzNWZweWFpdjdxfG1vcmV1c2*******=
Accept: application/json
Host: example.se:4000
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
And it works if i do the exact same request but against www.external.com directly so there is some issue when doing the proxy in node.
The request module is totally unaware of anything that you don't pass explicitly to it. To set the request headers and copy the response headers as well do the following:
// copy headers, except host header
var headers = {}
for (var key in req.headers) {
if (req.headers.hasOwnProperty(key)) {
headers[key] = req.get(key)
}
}
headers['host'] = 'final-host'
var newurl = 'http://final-host/...'
request.get({url:newurl, headers: headers }, function (error, response, body) {
// debug response headers
console.log(response.headers)
// debug response body
console.log(body)
// copy response headers
for (var key in response.headers) {
if (response.headers.hasOwnProperty(key)) {
res.setHeader(key, response.headers[key])
}
}
res.send(response.statusCode, body)
})
Try explicitly passing in the auth details like so
request(newurl).auth(username, password).pipe(res);
https://github.com/mikeal/request#http-authentication