set-cookie header not setting cookie in Edge - http

I'm testing an app locally and am making an authorization request to http://localhost:3000/api/auth/login. A set-cookie header is returned in the response with a JWT auth token. The JWT looks like:
JWT-TOKEN=[really long alphanumeric string];Version=1;Comment=;Domain=;Path=/;Max-Age=3600;;HttpOnly
Afterwards, I'm making another request to http://localhost:3000/api/other/resource and am getting an unauthorized error as it's expecting a cookie with the JWT token in the request.
The Cookie is being set in Firefox, Safari and Chrome but not Edge. Nothing in the Edge dev tools console that anything went wrong. Any ideas why the cookie is not being set in Edge?

I found an answer that worked in my case.
We were using fetch on the client. In some older browsers, the native fetch implementation would default to credentials: "omit", whereas newer browsers default to credentials: "same-origin".
As such, adding this option seemed to allow Edge to receive cookies in fetch requests, such as
fetch('/users', {
credentials: 'same-origin'
})
https://github.com/github/fetch#sending-cookies for reference. Despite the name of the heading, "omit" will disable both sending AND receiving cookies.

Related

Cannot set cookie in client from a Go API

I have a backend written in Go, hosted on Heroku, let's call it, https://foo.herokuapp.com. I have a frontend hosted on a different domain, let's call it, https://ui.example.com.
The backend API has an endpoint /api/user/login which sends back a JSON Web Token in the form of cookie shown as below:
http.SetCookie(w, &http.Cookie{
Name: "token",
Value: token, // the JWT
HttpOnly: false, // for testing, set to true later once I fix this
MaxAge: int(time.Hour * 24 * 3),
Expires: time.Now().UTC().Add(time.Hour * 24 * 3),
Path: "/",
Secure: true,
SameSite: http.SameSiteNoneMode,
})
These are my CORS settings on the server.
crossOrigin := cors.New(cors.Options{
AllowedOrigins: []string{allowedOrigin},
AllowCredentials: true,
AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut},
})
The frontend makes a request to the backend as given below.
const endpoint = "/api/user/login/"
fetch(host + endpoint, {
method: "POST",
credentials: 'include',
body: JSON.stringify({
email,
password
})
}).then((response) => console.log(response))
.catch((err) => console.log(err));
PROBLEM:
Now this cookie is actually visible in my browser's Network tab.
But the cookie does not exist in the application tab (or Storage tab in firefox where cookies exist). The browser is not saving the cookie, which is causing the subsequent requests to fail as the token in the cookie is verified and decoded before processing the actual request.
In another somewhat related thread, I got to know that Heroku terminates SSL before reaching my app. And, thus secure cookies cannot be set for non-SSL traffic. The solution there suggests trusting the scheme found in X-Forwarded-For. I enabled that using using the https://github.com/gorilla/handlers package as follows.
// srv is my actual handler
// crossOrigin is the CORS middleware
// finally wrapped by ProxyHeaders middleware
handlers.ProxyHeaders(crossOrigin.Handler(srv))
Yet this is not working.
I read many threads/blogs. Nothing has worked so far. What am I doing wrong?
Cookies are saved by the browser for the domain which set the cookie in the first place. Only becasue you can't see the cookie in the Application tab, does not mean that the cookie wasn't saved.
If your frontend https://ui.example.com makes an XHR call to https://foo.herokuapp.com and that call returns a Set-Cookie header, then the browser saves that cookie under foo.herokuapp.com domain. You will not see it in the ui.example.com's Application tab. Still, when you make another XHR call to foo.herokuapp.com then the browser will send the cookies that you've set earlier.
You can make this experiment: After logging in, open a new tab and navigate to https://foo.herokuapp.com. Now open the Application tab and you should see your cookies there.
That said, remember that the browser will treat these cookies as 3rd party cookies, and browser vendors will eventually drop support for 3rd party cookies. Eventually you should make sure that your frontend and backend are served from the same parent domain.
As for the other problem - Heroku's termination of SSL between their gateway and your app is not a problem. The secure flag on a cookie is an information for the browser - the browser will not accept or send a cookie with this flag over a non-SSL connection. The connection between your browser and the heroku server is SSL, so cookies will be accepted/sent. In your backend, cookies are just HTTP headers, and the backend does not really care neither about the cookies' flags nor by the connection type.

Express module cookie-session not including SameSite and Secure in Response Set-Cookie

Seen this before here, but I've seen no real resolution. The server's Node Express express-session module OR cookie-session module sends back a Session Cookie, but as I had not coded in the SameSite/Secure attributes, they are not there and do the client on a subsequent POST to the server fails as Not Logged In, with a 403. As expected.
First, my client logs in to the server successfully:
Here is the corresponding server code, using express-session:
Which produced a Session Cookie via the Set-Cookie. NOTICE the SameSite='none' and Secure=true attributes were not included, and as expected, not there.
Now, I have added the sameSite and secure attributes to the session object and run the Login again.
Lets look at the Response Headers returned from this Login, with the attributes added to the session object. Not only do we not see the attributes on the Set-Cookie Response Header, but there is NO cookie returned!
It appears that when these 2 attributes are added to the session object in either express-session or cookie-session that the result is no cookie is returned. The meaning being that a subsequent POST to the server will return a 403, User Not Logged In.
I'm really stumped. I've spent a LOT of time on this! Thank you for ideas and help.
You seem to be misusing the cookie-session middleware. The cookieSession function takes an JavaScript object but the documentation doesn't mention any cookie field in that object.
Anything specified in a cookie field is ignored by the middleware and has no effect on the resulting cookie; the only reason your cookie ended up being flagged HttpOnly is that it's the middleware's default behaviour.
Instead, all the cookie attributes should be specified in a "flat" object, like so:
app.use(cookieSession({
name: 'session',
secret: secret,
domain: 'chicagomegashop.com',
sameSite: 'none',
secure: true,
httpOnly: true
}));
However, you have another issue. If I'm interpreting your screenshots correctly, you seem to attempt to set a cookie with a Domain attribute of chicagomegashop.com in a response from https://paylivepmt.com. That cannot work; browsers will ignore such a Set-Cookie response header:
The user agent will reject cookies unless the Domain attribute specifies a scope for the cookie that would include the origin server.

Cookies sent from browser not found in HttpServletRequest

Not all the cookies shown by Chrome Dev Tools as part of the Request Header are found in the HttpServletRequest object.
Here is a screenprint from Chrome Dev Tools, showing the request headers for the GET HTTP request.
You can see that the "cookie" includes the "_fbp" cookie:
cookie request header
From the Chrome Dev Tools application tab, you can see that the "_fbp" cookie is from my same Domain and has not expired and is for Path / so it should be included (which it was):
cookie details
We're running a Java Spring MVC backend and when the request comes to the controller endpoint, we have the "HttpServletRequest request" parameter.
Examining that "request" object, I find that it has some of the cookies which were observed as part of the request, but not all of them... and specifically not the "_fbp" cookie I'm looking for.
Tried examining the cookies in all the following manners:
Object rhc = request.getHeader("Cookie");
Cookie wuc = org.springframework.web.util.WebUtils.getCookie(request, "_fbp");
parameter #CookieValue(name="_fbp", required=false) String fbpCookie
but no matter how I inspect that request, the "_fbp" cookie is not there. I noticed that most of the other cookies that are not found all have names beginning with underscores so I suspected maybe that is a naming convention that is ignored by Spring MVC but couldn't find any such rule.
Can anyone help explain the missing cookies? How can I get that "_fbp" cookie value for server-side processing? Appreciate any help.

fetch API: Can't add Authorization on Request Header with Chrome

Chrome version: 57.0.2987
Actually, in older Chrome version I also have this problem.
I added Authorization on Request Header with my access token,
fetch('https://example.com/endpoint', {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + accesstoken
}
})
I always get Access-Control-Allow-Headers:authorization on Response Header in Chrome
Besides, My fetch is always Request Method:OPTIONS (not display GET), then Status Code is 200 OK in Chrome
But if I run the same fetch code in Firefox (ver 52.0.1 ), everything works great. I can add Authorization on Request Header correctly. It won't display Access-Control-Allow-Headers:authorization on Response Header in Firefox. It will display Authorization: Bearer accesstoken on Request header.
The server side already processed CORS for my request header..
This is a Chrome bug or my code fault? How should I do to make Authorization on Request Header correctly in Chrome?
Below image is the detail Network in Chrome dev tool:
Below image is the detail Network in Firefox dev tool:
As #stackdave said, browser send OPTIONS request before GET request when cross-domain ajax. Then browser will wait server response. My situation that the server didn't response, so browser just stop OPTIONS status. Server need to handle this issue, it's still CORS issue, not fetch api bug or issue.
browser will send before a OPTIONS request, without the authorisation token, and then will send the real request
http://www.w3.org/TR/cors/ See http://metajack.im/2010/01/19/crossdomain-ajax-for-xmpp-http-binding-made-easy/ for a bit more info

Suppress NTLM dialog box after unauthorized request

In a recent sharepoint project, I implemented an authentication webpart which should replace the NTLM authentication dialog box. It works fine as long as the user provides valid credentials. Whenever the user provides invalid credentials, the NTLM dialog box pops up in Internet Explorer.
My Javascript code which does the authentication via XmlHttpRequest looks like this:
function Login() {
var request = GetRequest(); // retrieves XmlHttpRequest
request.onreadystatechange = function() {
if (this.status == 401) { // unauthorized request -> invalid credentials
// do something to suppress NTLM dialog box...
// already tried location.reload(); and window.location = <url to authentication form>;
}
}
request.open("GET", "http://myServer", false, "domain\\username", "password");
request.send(null);
}
I don't want the NTLM dialog box to be displayed when the user provides invalid credentials. Instead the postback by the login button in the authentication form should be executed. In other words, the browser should not find out about my unauthorized request.
Is there any way to do this via Javascript?
Mark's comment is correct; The NTLM auth prompt is triggered by a 401 response code and the presence of NTLM as the first mechanism offered in the WWW-Authenticate header (Ref: The NTLM Authentication Protocol).
I'm not sure if I understand the question description correctly, but I think you are trying to wrap the NTLM authentication for SharePoint, which means you don't have control over the server-side authentication protocol, correct? If you're not able to manipulate the server side to avoid sending a 401 response on failed credentials, then you will not be able to avoid this problem, because it's part of the (client-side) spec:
The XMLHttpRequest Object
If the UA supports HTTP Authentication [RFC2617] it SHOULD consider requests
originating from this object to be part of the protection space that includes the
accessed URIs and send Authorization headers and handle 401 Unauthorised requests
appropriately. if authentication fails, UAs should prompt the users for credentials.
So the spec actually calls for the browser to prompt the user accordingly if any 401 response is received in an XMLHttpRequest, just as if the user had accessed the URL directly. As far as I can tell the only way to really avoid this would be for you to have control over the server side and cause 401 Unauthorized responses to be avoided, as Mark mentioned.
One last thought is that you may be able to get around this using a proxy, such a separate server side script on another webserver. That script then takes a user and pass parameter and checks the authentication, so that the user's browser isn't what's making the original HTTP request and therefore isn't receiving the 401 response that's causing the prompt. If you do it this way you can find out from your "proxy" script if it failed, and if so then prompt the user again until it succeeds. On a successful authentication event, you can simply fetch the HTTP request as you are now, since everything works if the credentials are correctly specified.
IIRC, the browser pops the auth dialog when the following comes back in the request stream:
Http status of 401
WWW-Authenticate header
I would guess that you'd need to suppress one or both of those. The easy way to do that is to have a login method that'll take a Base64 username and password (you are using HTTPS, right?) and return 200 with a valid/invalid status. Once the password has been validated, you can use it with XHR.
I was able to get this working for all browsers except firefox. See my blog post below from a few years ago. My post is aimed at IE only but with some small code changes it should work in Chrome and safari.
http://steve.thelineberrys.com/ntlm-login-with-anonymous-fallback-2/
EDIT:
The gist of my post is wrapping your JS xml call in a try catch statement. In IE, Chrome, and Safari, this will suppress the NTLM dialog box. It does not seem to work as expected in firefox.

Resources