I have a node backend, which should allow cross origin request from my frontend application that is running on localhost:3000. Therefore I've restricted the cors policy to my domain.
import csrf from 'csurf';
app.use(
cors({
origin: 'http://localhost:3000',
credentials: true
})
);
const csrfProtection = csrf({
cookie: {
maxAge: 900,
domain: 'http://localhost:3000'
}
})
router.get('/csrfToken', csrfProtection, async (req, res, next) => {
res.json({ token: req.csrfToken() });
});
When I'm making now a request to my server endpoint (which is running on localhost:5000), it returns me the following error that the cookie cannot be set.
fetch('http://localhost:5000/csrfToken', {
method: 'GET',
credentials: 'include'
})
This has nothing to do with CORS. It is just how cookies work.
The domain in the set-cookie header says http://localhost:3000 but the request is for http://localhost:5000.
That is a different origin and so is an invalid cookie.
It is impossible for a set-cookie header from one origin to set a cookie for a different origin. http://localhost:5000 can only set cookies for http://localhost:5000.
If you really want to set a cookie for :3000 then a work-around would be to provide the data through some other format than a cookie (e.g. in the request body) and then have the client-side JS on http://localhost:3000 set the cookie using the document.cookie API.
If you want to set the cookie for :5000 (which seems more likely), then get the port number right in the set-cookie header.
const csrfProtection = csrf({
cookie: {
maxAge: 900,
domain: 'http://localhost:5000'
}
})
i met this problem today too. i used chrome browser which version higher than 80.
chrome add a new attribute "samesite" to defend csrf which from v51.
chrome v80+ default limit cross-site set cookie, make cookie invalid.
console warning:
A cookie associated with a cross-site resource at http://XXX.XXX.XXX.XXXX/ was set without the SameSite attribute.
It has been blocked, as Chrome now only delivers cookies with cross-site requests if they are set with SameSite=None and Secure.
chrome://flags/
enter image description here
If anyone lands here from Laravel (sanctum), can try this for their local servers:-
//.env file. Try removing :port from SESSION_DOMAIN
SANCTUM_STATEFUL_DOMAINS="127.0.0.1:8000"
SESSION_DOMAIN="127.0.0.1"
Related
Why doesn't FastAPI return the cookie to my frontend, which is a React app?
Here is my code:
#router.post("/login")
def user_login(response: Response,username :str = Form(),password :str = Form(),db: Session = Depends(get_db)):
user = db.query(models.User).filter(models.User.mobile_number==username).first()
if not user:
raise HTTPException(400, detail='wrong phone number or password')
if not verify_password(password, user.password):
raise HTTPException(400, detail='wrong phone number or password')
access_token = create_access_token(data={"sub": user.mobile_number})
response.set_cookie(key="fakesession", value="fake-cookie-session-value") #here I am set cookie
return {"status":"success"}
When I login from Swagger UI autodocs, I can see the cookie in the response headers using DevTools on Chrome browser. However, when I login from my React app, no cookie is returned. I am using axios to send the request like this:
await axios.post(login_url, formdata)
First, make sure there is no error returned when performing the Axios POST request, and that you get a "status": "success" response with 200 status code.
Second, as you mentioned that you are using React in the frontend—which needs to be listening on a different port from the one used for the FastAPI backend, meaning that you are performing CORS requests—you need to set the withCredentials property to true (by default this is set to false), in order to allow receiving/sending credentials, such as cookies and HTTP authentication headers, from/to other origins. Two servers with same domain and protocol, but different ports, e.g., http://localhost:8000 and http://localhost:3000 are considered different origins (see FastAPI documentation on CORS and this answer, which provides details around cookies in general, as well as solutions for setting cross-domain cookies—which you don't actually need in your case, as the domain is the same for both the backend and the frontend, and hence, setting the cookie as usual would work just fine).
Please note that if you are accessing your React frontend at http://localhost:3000 from your browser, then your Axios requests to FastAPI backend should use the localhost domain in the URL, e.g., axios.post('http://localhost:8000',..., not http://127.0.0.1:8000, as localhost and 127.0.0.1 are two different domains, and hence, the cookie would otherwise fail to be created for localhost domain, as it would be created for 127.0.0.1, i.e., the domain used in axios request (and that would be a case for cross-domain cookies, as described in the linked answer above).
Thus, to accept cookies sent by the server, you need to use withCredentials: true in your Axios request; otherwise, the cookies will be ignored in the response (which is the default behaviour, when withCredentials is set to false; hence, preventing different domains from setting cookies for their own domain). The same withCredentials: true property has to be included in every subsequent request to your API, if you would like the cookie to be sent to the server, so that the user can be authenticated and provided access to protected routes.
Hence, an Axios request that includes credentials should look like this:
await axios.post(url, data, {withCredentials: true}))
The equivalent in a fetch() request (i.e., using Fetch API) is credentials: 'include'. The default value for credentials is same-origin. Using credentials: 'include' will cause the browser to include credentials in both same-origin and cross-origin requests, as well as set any cookies sent back in cross-origin responses. For instance:
fetch('https://example.com', {
credentials: 'include'
});
Note
For either the above to work, you would need to explicitly specify the allowed origins, as described in this answer (behind the scenes, that is setting the Access-Control-Allow-Origin response header). For instance:
origins = ['http://localhost:3000', 'http://127.0.0.1:3000',
'https://localhost:3000', 'https://127.0.0.1:3000']
Using the * wildcard instead would mean that all origins are allowed; however, that would also only allow certain types of communication, excluding everything that involves credentials, such as cookies, authorization headers, etc.
Also, make sure to set allow_credentials=True when using the CORSMiddleware (which sets the Access-Control-Allow-Credentials response header to true).
Example (see here):
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
I have a socket.io service that different devices want to connect to that, and because of it, it's very important to me that my socket.io service allows all origins (even when a client doesn't set any origin in the header) to connect to that, this is my io (server-side):
const server = http.createServer(app); // app is express app
const io = require('socket.io')(server,{
allowEIO3: true,
cors: {
origin: '*',
credentials: true
},
});
and this is my client:
const socket = io('http://localhost:4855/', {
timeout: 60 * 1000,
reconnection: true,
forceNew: true,
query: {
authorization: `Bearer ${token}`
},
// extraHeaders: {
// Authorization: "ksi",
// "Access-Control-Allow-Origin": "*",
// "Access-Control-Allow-Credentials": true
// },
// Headers: {
// "Access-Control-Allow-Origin": 'http://localhost',
// },
// headers: {
// "Access-Control-Allow-Origin": "*",
// "Access-Control-Allow-Credentials": true
// }
});
socket.connect();
ERROR MESSAGE:
from origin 'http://localhost' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
If your backend relies on the Bearer authentication scheme (as your code snippet suggests) and not on cookies, you do not need to use credentialed requests or to allow credentials in your CORS configuration. You can keep using the wildcard (*) to specify the allowed origins; you'll also need to explicitly allow the Authorization request header:
cors: {
origin: '*',
allowedHeaders: ["authorization"]
}
This instance of Jake Archibald's playground may be enough to convince you.
The CORS configuration above is secure insofar as cross-origin attackers cannot forge authenticated requests on behalf of their victims.
If, on the other hand, your backend server relies on cookies, you need to use credentialed requests and allow credentials in your CORS configuration. However, the CORS spec forbids the use of the wildcard in conjunction with credentialed requests. It may be tempting to allow all origins with credentials (as recommended by Quentin), but that amounts to voiding the protection provided by the Same-Origin Policy and exposes your users to cross-origin attacks. More details in James Kettle's post on Portswigger's website.
The error is pretty clear.
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
And you said:
origin: '*',
So you have to return the actual origin and not the wildcard.
The documentation says:
All options will be forwarded to the cors package. The complete list of options can be found here.
Which, in turn, says:
origin: Configures the Access-Control-Allow-Origin CORS header.
Possible values:
Boolean - set origin to true to reflect the request origin, as defined by req.header('Origin'), or set it to false to disable CORS.
String - set origin to a specific origin. For example if you set it to "http://example.com" only requests from "http://example.com" will
be allowed.
RegExp - set origin to a regular expression pattern which will be used to test the request origin. If it's a match, the request origin
will be reflected. For example the pattern /example.com$/ will
reflect any request that is coming from an origin ending with
"example.com".
Array - set origin to an array of valid origins. Each origin can be a String or a RegExp. For example ["http://example1.com",
/.example2.com$/] will accept any request from "http://example1.com"
or from a subdomain of "example2.com".
Function - set origin to a function implementing some custom logic. The function takes the request origin as the first parameter and a
callback (called as callback(err, origin), where origin is a
non-function value of the origin option) as the second.
Since you want to allow any origin, you can't use a string or an array.
The other options would all work, but the simplest is the boolean.
cors: {
origin: true,
credentials: true
},
We are currently up against an error with our client/api cookie generation. We are using Angular 12 and NGINX for a frontend server running with SSL on a subdomain of cms.domain.co.uk; the backend is running node.js with pm2 on a subdomain of api.domain.co.uk:3067 with SSL.
Our Backend nodejs file has the following CORS options snippet:
const app = express();
app.use(express.json());
app.use(cookieParser());
app.use(cors({
origin: 'https://cms.domain.cloud',
credentials: true,
methods: 'GET, POST, OPTIONS',
allowedHeaders: 'Origin, Content-Type, X-Auth-Token, Set-Cookie, Authorisation, Accept'
}));
We are setting the cookie in the node.js file as follows:
response.cookie('LoginCookie', sid, {httpOnly: true, secure: true, SameSite: "none"});
We have tried every feasible combination of origins, tags, and policies and we are now officially stumped. Excuse our naivety in CORS as we have come from non-cross origin background.
The responsive header from the server contains the set-cookie tag but the cookie isn't being set in the browser, allow credential controls is set to true in both the POST and OPTIONS as well as having the origin set to avoid CORS errors.
This is the pre-flight OPTIONS header sent:
1
This is the POST header sent with the Set-Cookie tag:
2
By starting another window of chrome with the using chrome.exe --user-data-dir="C:/Chrome dev session" --disable-web-security, the browser acknowledge the existence of the cookie in one section but not in the other:
3
Any feedback or help would be greatly appreciated! Thanks!
An alternative is to retrieve de value of the 'LoginCookie' key Set-Cookie header, and store it to browser by document.cookie.setItem( 'LoginCookie', <data>)
I have a node backend, which should allow cross origin request from my frontend application that is running on localhost:3000. Therefore I've restricted the cors policy to my domain.
import csrf from 'csurf';
app.use(
cors({
origin: 'http://localhost:3000',
credentials: true
})
);
const csrfProtection = csrf({
cookie: {
maxAge: 900,
domain: 'http://localhost:3000'
}
})
router.get('/csrfToken', csrfProtection, async (req, res, next) => {
res.json({ token: req.csrfToken() });
});
When I'm making now a request to my server endpoint (which is running on localhost:5000), it returns me the following error that the cookie cannot be set.
fetch('http://localhost:5000/csrfToken', {
method: 'GET',
credentials: 'include'
})
This has nothing to do with CORS. It is just how cookies work.
The domain in the set-cookie header says http://localhost:3000 but the request is for http://localhost:5000.
That is a different origin and so is an invalid cookie.
It is impossible for a set-cookie header from one origin to set a cookie for a different origin. http://localhost:5000 can only set cookies for http://localhost:5000.
If you really want to set a cookie for :3000 then a work-around would be to provide the data through some other format than a cookie (e.g. in the request body) and then have the client-side JS on http://localhost:3000 set the cookie using the document.cookie API.
If you want to set the cookie for :5000 (which seems more likely), then get the port number right in the set-cookie header.
const csrfProtection = csrf({
cookie: {
maxAge: 900,
domain: 'http://localhost:5000'
}
})
i met this problem today too. i used chrome browser which version higher than 80.
chrome add a new attribute "samesite" to defend csrf which from v51.
chrome v80+ default limit cross-site set cookie, make cookie invalid.
console warning:
A cookie associated with a cross-site resource at http://XXX.XXX.XXX.XXXX/ was set without the SameSite attribute.
It has been blocked, as Chrome now only delivers cookies with cross-site requests if they are set with SameSite=None and Secure.
chrome://flags/
enter image description here
If anyone lands here from Laravel (sanctum), can try this for their local servers:-
//.env file. Try removing :port from SESSION_DOMAIN
SANCTUM_STATEFUL_DOMAINS="127.0.0.1:8000"
SESSION_DOMAIN="127.0.0.1"
There is a node server which on accepting correct credentials of a user, passport js creates and sends a session cookie in request header by name of set-cookie.
But when I do an ajax request from my chrome browser accepts the request it doesn't adds the cookie on the client side . so when a new request generates from client side , the server doesn't authenticates it and throws 401.
I am confused whether it is a browser issue or an I am missing something from AJAX request
Please help.
If you are using 'fetch', you need to add a key
{
headers: req.headers,
credentials: 'include'
}
Thanks for your answers . I was trying it withCredentials thing , but the session cookie was not getting set on my local.
The reason I figured out was the allowed origins. I need to set the allowed origins at the backend.
The XHR by is a secure request if passed with credentials property. So the client side browser only save the cookie if the allowed origin matches request origin.
So the simple fix was to change the host to something which matches to allowed origin .
At node end I need to do origin: 'domain.com'
and at the front end I need to set my server (localhost) to point to test.domain.com. and bingo . It worked.!
I was experiencing this issue using Angular 4 in Chrome (IE was working).
Requests from client on localhost:4200 to WebApi on localhost:24336.
Had all the CORS setup, "Access-Control-Allow-Credentials" value="true" "Access-Control-Allow-Origin" value="http://localhost:4200", etc. and was passing { withCredentials: true } in every request, i.e. like http.post(url, {}, { withCredentials: true }) .
The fix, for me, was to set the default RequestOptions to {withCredentials: true } by following the steps https://angular.io/api/http/BaseRequestOptions and adding the following to providers: in app.module.ts
,{provide: RequestOptions, useClass: MyOptions}
If you are using XHR request then you need set withCredentials to true. It should fix problem if no please provide code