I'm trying to authenticate at a different domain as part of a Cypress test using cy.request. The authentication request needs to contain the value of the XSRF-TOKEN cookie as a header. This is easily solved when on the same baseURL as the authentication domain: visit the domain, read the cookie via cy.getCookie, then make the request. Since cypress does not allow visiting multiple domains in a single test a different workflow is needed.
My solution right now is to replace the initial cy.visit with a cy.reqeuest (this sets the cookie as verified by looking at the request headers of the second request). However, I can't figure out how to read this cookie before I make the second authentication request. cy.getCookies() is empty, and document.cookie is empty. The response of the cy.request only contains a "set-cookie" header the first time, and I can't figure out how to read the default headers of the cy.request since cy.intercept does not work with cy.request.
Sketch of my attempt (where c.value is null):
cy.request({url: "https://notbaseurl/login"}).then(res => {
cy.getCookie("XSRF-TOKEN").then(c => {
cy.request({url: "https://notbaseurl/auth", method: "POST", headers: {"X-XSRF-TOKEN": c.value}})
})
})
This is not currently possible. See https://github.com/cypress-io/cypress/issues/8956
Related
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.
I’ve setup the react UI in Cloudflare Pages with custom domain https://ui.example.com. There is a login page that allows users to key in user name and password. Once they’ve supplied the info, it will use axios to make a call to an API that is hosted in Cloudflare Workers https://apis.example.com and from there I’ve written sample codes to return Set-Cookies header with the “secret-token” value.
Since Pages and Workers reside in different domain, so I’ve configured Workers to supplied the following values to the API response, in addition to Set-Cookies header-values:
"Access-Control-Allow-Origin": "*"
"Access-Control-Allow-Methods": "*"
"Access-Control-Allow-Headers": "*"
"Access-Control-Expose-Headers": "Set-Cookie"
But when I check the response headers by using script (console.log) written in my login.jsx, it only contains content-length and content-type. But when I check the response from Chrome (Developer Tools’ Network) in my case, those header-values above exist.
How can I get the response’s header values from the axios call in this case?
You will need to use a different header name.
Set-Cookie is a forbidden response header, which means JavaScript is not permitted to read this header. The browser handles this header (adding the cookie to cookie storage) and then removes it before JavaScript sees the response at all.
Instead, try inventing your own header name, like X-Secret-Token.
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.
I have the following rest endpoint that I would like to send a cookie along with my ResponseEntity. However after succesfully sending the response, the cookie is nowhere to be found.
#RequestMapping(value = "myPath", method = RequestMethod.POST)
public ResponseEntity<?> createToken(HttpServletResponse response)
final String token = "a1b2c3d4e";
Cookie cookie = new Cookie("token", token);
response.addCookie(cookie);
// Return the token
return ResponseEntity.ok(new MyCustomResponse(token));
}
MyCustomResponse
class MyCustomResponse {
private final String token;
public MyCustomResponse(String token) {
this.token = token;
}
}
I have also tried creating the ResponseEntity manually and setting the cookie in the headers with the "Set-Cookie" header but same thing, no cookie.
edit: I have confirmed that the Set-Cookie header is in fact present in the response, however it is not actually being stored in the browser. I am using a static web-app running in WebStorm to access my rest endpoint running on a different port. This web app is just using JQuery's $ajax method to call the REST endpoint, nothing fancy. When I run the web app in WebStorm I am able to see the cookie it creates in my browser, so that confirms my browser is allowing cookie storage.
I figured it out. My web application I am using to access my rest api is running on a different local port than my rest api. This causes the AJAX request to fail CORS requirements, thus the cookie doesnt actually get set.
I found the solution here What can cause a cookie not to be set on the client?
edit: I should add that it was adding the xhrFields snippet to JQuery's $ajax method that fixed it for me, the other parts weren't necessary.
(posting the answer below in case it gets deleted)
I think I found the solution. Since during development, my server is at "localhost:30002" and my web app at "localhost:8003", they are considered different hosts regarding CORS. Therefore, all my requests to the server are covered by CORS security rules, especially Requests with credentials. "Credentials" include cookies as noted on that link, so the returned cookie was not accepted because I did not pass
xhrFields: {
withCredentials: true
}
to jQuery's $.ajax function. I also have to pass that option to subsequent CORS requests in order to send the cookie.
I added the header Access-Control-Allow-Credentials: true on the server side and changed the Access-Control-Allow-Origin header from wildcard to http://localhost:8003 (port number is significant!). That solution now works for me and the cookie gets stored.
I'm trying to write a simple script that fetches text from a webpage and processes that string. But, that website requires me to be logged in. I was successful in logging in to that website. This is how I logged in:
var payload = {"name1":"val1","name2":val2"};
var opt ={"payload":payload,"method":"post"};
var respose = UrlFetchApp.fetch("http://website.com/login",opt);
After logging in, the website places me in http://website.com/home. I checked response.getContentText() and I can confirm that I have been logged in successfully as it contains the text from http://website.com/home.
Now I need to get the contents of http://website.com/page and process it.
I first assumed the script can handle cookies by itself and proceeded with
var pagedata = UrlFetchApp.fetch("http://website.com/page);//Did not work
That obviously didnt work and pagedata.getContentText() says me to login first, which indicates cookies were not successfully passed..
I then tried to extract cookies which the server responded during login and to send it along with this request.
var cookie = response.getAllHeaders()['Set-Cookie'];
// variable cookie now contains a legitimate cookie.
// It contains 'JSESSIONID=blabla;Path=/' and
// it is the ONLY cookie that server responds.
I tried to send that cookie in my page request.
var header = {'Cookie':cookie};
var opt2 = {"header":header};
var pagedata = UrlFetchApp.fetch("http://website.com/page",opt2);
I think even now cookies were not properly sent, as the content again says me to login.
Am I passing cookies correctly? I need help regarding the correct method of sending cookies in a request.
Here you can find cookies specification:
http://www.w3.org/Protocols/rfc2109/rfc2109
You have a potential issue in your code:
response.getAllHeaders()['Set-Cookie'] can return either a string or a table of string if multiple 'set-cookie' attributes are sent back from the server.
Eric is right, you cannot return the cookie without digesting it.
Second error in your code:
var opt2 = {"header":header};
should be
var opt2 = {"headers":header};
Be aware also that GAS uses Google IPs. It can happen that two consecutive fetch use different IPs.
The server your are connecting to may be session-IP dependant.
Are you sure the server only send you back one cookie after an authentification ?
It looks like you are setting the headers correctly in UrlFetchApp.fetch().
I believe that the data in the Set-Cookie header is in a different format than the data that is expected in Cookie header. For example, Set-Cookie contains information about expiration, etc.