I have an application based FastAPI Which serves as the backend for a website And currently deployed on a server with an external IP. The frontend is situated at another developer, temporarily in local hosting.
At the beginning of the work we encountered a CORS problem, which was solved by using the following code I found on the Internet:
from fastapi.middleware.cors import CORSMiddleware
...
app.add_middleware(
CORSMiddleware,
allow_origins=['http://localhost:3000'],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
The addition allowed Frontend to make requests properly, but for some reason, cookies that are set to send (and work properly in the Swagger UI) are not set in Frontend.
The client side look like:
axios({
method: 'POST',
baseURL: 'http://urlbase.com:8000',
url: '/login',
params: {
mail: 'zzz#zzz.com',
password: 'xxxxxx'
},
withCredentials: true
}).then( res => console.log(res.data) )
.catch( err => console.log(err))
Setting and reading cookies in FastAPI can be done through the use of the Request class:
Setting the cookie refresh_token
from fastapi import Response
#app.get('/set')
async def setting(response: Response):
response.set_cookie(key='refresh_token', value='helloworld', httponly=True)
return True
Setting httponly=True makes sure the cookie can't be accessed by JS. This is great for sensitive data such as a refresh token. But if your data isn't that sensitive then you can just omit it.
Reading the cookie
from fastapi import Cookie
#app.get('/read')
async def reading(refresh_token: Optional[str] = Cookie(None)):
return refresh_token
You can find more information on using cookies as parameters on the FastAPI docs here.
Remove the wildcards since wildcarding is not allowed with allow_credentials=True :
app.add_middleware(
CORSMiddleware,
allow_origins=['http://localhost:3000'],
allow_credentials=True,
allow_methods=["GET", "POST", "OPTIONS"], # include additional methods as per the application demand
allow_headers=["Content-Type","Set-Cookie"], # include additional headers as per the application demand
)
Set samesite to none while setting the cookie:
# `secure=True` is optional and used for secure https connections
response.set_cookie(key='token_name', value='token_value', httponly=True, secure=True, samesite='none')
If client side is using Safari, disable Prevent cros-site tracking in Preferences. That's It!
For Cross-Origin Situation Cookie Setting, Check things below 👇🏻
Pre-requirements
Your FE, BE servers need to talk each other with https protocol. (Set SSL Certificates using let's encrypt or other services)
Make sure your domain doesn't include port
Backend
Server Setup
Add FE Domain to allow_origins
Set allow_credentials True
allowed_methods should not be a wildcard("*")
allowed_headers should not be a wildcard("*")
Cookie Setup
secure = True
httponly = True
samesite = 'none'
List item
Fastapi Example
# main.py
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["GET", "POST", "HEAD", "OPTIONS"],
allow_headers=["Access-Control-Allow-Headers", 'Content-Type', 'Authorization', 'Access-Control-Allow-Origin'],
)
# cookie
response = JSONResponse(content={"message": "OK"})
expires = datetime.datetime.utcnow() + datetime.timedelta(days=30)
response.set_cookie(
key="access_token", value=token["access_token"], secure=True, httponly=True, samesite='none', expires=expires.strftime("%a, %d %b %Y %H:%M:%S GMT"), domain='.<YOUR DOMAIN>'
)
Frontend
Include header "Access-Control-Allow-Origin": ""
Set withCredentials: true
Axios Example
// post request that sets cookie
const response = await axios.post(
"https://<Target Backend API>",
{
param1: "123",
param2: "456",
},
{
headers: {
"Access-Control-Allow-Origin": "https://<FE DOMAIN>",
},
withCredentials: true,
},
);
Reverse Proxy Server (If you have)
Allow "OPTIONS" method (this is need when browser check server options in preflight request)
Check if any middlewares blocks your preflight requests. (e.g. Nginx Basic HTTP Authentications can block your request)
IMPORTANT
if your FE use subdomain like dev.exmaple.com and your BE also use subdomain like api.example.com, you should set cookie domain to .example.com so the subdomain services can access root domain cookie!!
In FastAPI you can set cookies via response.set_cookie,
from fastapi import FastAPI, Response
app = FastAPI()
#app.post("/cookie-and-object/")
def create_cookie(response: Response):
response.set_cookie(key="fakesession", value="fake-cookie-session-value")
return {"message": "Come to the dark side, we have cookies"}
It should be noted though that these are NOT SECURE SESSIONS you should use something like itsdangerous to create encrypted sessions.
In response to the requests not seeming to not be sent; You should make sure the option for which urls the cookies are valid for are being set.
By default they are typically / which means everything however your system might be setting them to a specific case with CORS setup.
Related
I have 2 servers. One hosting a next.js application on localhost:5555 and another hosting an express server for the api on localhost:4444.
The authentication api returns a cookie however this is not being set in the browser running on localhost:5555.
res.cookie('cokkieName', 'bob', {
domain: '127.0.0.1:5555',
maxAge: 900000,
httpOnly: true,
});
res.status(200).json({
session: jwtSigned,
role: 'default',
});
My cors setup is:
const options: cors.CorsOptions = {
allowedHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'X-Access-Token', 'Authorization'],
credentials: true,
methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
origin: 'http://localhost:5555',
preflightContinue: false,
};
I'd prefer to set the cookie with the api rather than on the via next.js.
I've tried alternating the cors settings but have had no success. The client call uses axios and has withCredentials set.
As mentioned in the other answer, you cannot set cookie on a different domain, as it can be a security risk.
But from your question...
Since the API server is running the authentication logic, the cookie should be set on the API server domain, NOT on client domain
So in your API server, you can change code to
res.cookie('cokkieName', 'bob', {
domain: '127.0.0.1:4444',
maxAge: 900000,
httpOnly: true,
});
or just remove the domain as it defaults to server domain.
res.cookie('cokkieName', 'bob', {
maxAge: 900000,
httpOnly: true,
});
As you have a client running on localhost:5555 and API server running on localhost:4444, it's a cross domain call and you need to pass withCredentials option to axios to pass the cookie. You might not be able to see the cookie in browser, as it can be a security risk, but can echo the cookie value on server console.
axios.get("url",{
withCredentials: true
}).then(res => {
console.log(res);
});
Note that credentials option work only if Access-Control-Allow-Origin header is not a wildcard like *
I have a created a server to mimic the code you posted. The server basically echoes the request cookie back.
It can be accessed through this stackblitz. You can toggle the withCredentials flag to see the difference in response
You can not set a cookie for other domains, Because that would be an enormous security risk.
But if you are looking for a way to share a cookie between your applications, the only way you have is to share a single domain between these applications, either separate them by url prefix or create one of them as a subdomain of other.
domain.com and api.domain.com. This way by specifying domain.com in your cookie both domains have access to it
If you concern about developing environment of yours, you can use Nginx and proxy_pass these two applications in single domain
What you are trying to do is definitely possible, the problem is that cookies are not port specific: Are HTTP cookies port specific? so you should specify a domain name but no port.
If you set a Cookie on its own domain (or if you don't specify it at all), all of the subsequent requests will carry the cookie even if initiated by a different port (only when withCredentials set to true, though). Also, note that, if set, the domain of the Cookie must match exactly the current domain, or parent domain (localhost is considered as different than 127.0.0.1)
Note: doing so shows the following warning in Chrome:
A cookie associated with a cross-site resource at http://example.com/
was set without the SameSite attribute. A future release of Chrome
will only deliver cookies with cross-site requests if they are set
with SameSite=None and Secure. You can review cookies in developer
tools under Application>Storage>Cookies and see more details at
https://www.chromestatus.com/feature/5088147346030592 and
https://www.chromestatus.com/feature/5633521622188032.
So, prepare your code for the future web standards!
I had a similar problem with nuxt.js. To set cookies in your browser using express, I suggest using the library Cookies
In my case I set cookies in express like this:
setResponseCookie(req, res, token, strategyName) {
const isHttps = req.protocol === 'https';
let { cookie: { maxAge } } = config;
const cookieSettings = isHttps => ({
maxAge,
httpOnly: true,
secure: isHttps,
});
const cookies = new Cookies(req, res);
cookies.set('auth.strategy', strategyName, cookieSettings(isHttps))
cookies.set('auth.token', token, cookieSettings(isHttps))
}
and then in express route you can add to this like:
let token = sign(user);
this.setResponseCookie(req, res, token, 'local-login')
res.json({ token, status: 'success' });
or
let token = sign(req.user);
this.setResponseCookie(req, res, token, 'google')
res.redirect(config.client.url);
I managed to fix this issue of the server not setting the browser cookies by setting my res.cookie like the following
res.cookie("Test", "If you see this then it works", {httpOnly: false, secure: true, sameSite: "none"}).json({message: "Finished"});
I checked to see if the cookie was set by opening up the chrome dev tools, going to the application, then cookies, then the site URL.
There are a few things I would like to preference about the above code. This method only works on a URL starting with https, this means it will not fix the problem if on a URL like http://localhost:3000. I don't know if it matters if httpOnly is true or false. Also, What is necessary when setting the cookie this way is to pass in the properties secure: true, sameSite: "none". Passing these values into res.cookie was what finial fixed the issue for me. I have not seen this solution posted anywhere and decided I out to let you know.
My code that fetches the data from the API and triggers the server to set the browser cookie looks like this for anyone who is interested.
fetch(domain.com/testSetCookie', {
method: 'get',
mode: "cors",
credentials: 'include' //<-- important
})
.then((res) => {
//console.log(res);
return; //res.json();
})
.then((data) => {
console.log(data);));
})
.catch((err) => {
console.log("an error happeoned");
console.log(err);
});
I have a Koa and GraphQL API with an authentication resolver which adds sets an auth token to the response cookies.
My API runs on http://localhost:3000 and my front end runs on http://localhost:3001. In order to make this work, I needed to add withCredentials flag to my API request as well as configure my Koa/Cors options with { origin: false, credentials: true }. Without these settings I would get a CORS error which I find confusing because they are both on localhost so technically the same domain...?
There will be multiple front ends using this API and will need to authenticate and get the auth token set in the cookie. When I spoof my domain name as http://app1.dev & http://app2.dev, both calling the API on http://localhost:3000, the auth token gets set in the response cookies but the browser does not store the cookie like it did when everything was localhost.
On my API, I am using #koa/cors for my configuring my cors and then set the following:
app.use(cors({ origin: false, credentials: true }))
On my front end react app, I have a request function which I use for all my GraphQL queries:
const request = query => {
const request = axios.create({
baseURL: apiUrl,
headers: {'X-apikey': apiKey},
withCredentials: true,
});
return request.post('/graphql', { query })
.then(res => res.data)
.catch(console.error)
}
Why are the cookies not getting set in the browser?
EDIT: Updated my axios request to create instance of axios and attempting to set withCredentials. I am still not seeing the credentials param in my request in dev tools though. I suspect this could be the issue?
I am sending requests from the client to my Express.js server using Axios.
I set a cookie on the client and I want to read that cookie from all Axios requests without adding them manually to request by hand.
This is my clientside request example:
axios.get(`some api url`).then(response => ...
I tried to access headers or cookies by using these properties in my Express.js server:
req.headers
req.cookies
Neither of them contained any cookies. I am using cookie parser middleware:
app.use(cookieParser())
How do I make Axios send cookies in requests automatically?
Edit:
I set cookie on the client like this:
import cookieClient from 'react-cookie'
...
let cookie = cookieClient.load('cookie-name')
if(cookie === undefined){
axios.get('path/to/my/cookie/api').then(response => {
if(response.status == 200){
cookieClient.save('cookie-name', response.data, {path:'/'})
}
})
}
...
While it's also using Axios, it is not relevant to the question. I simply want to embed cookies into all my requests once a cookie is set.
You can use withCredentials property.
XMLHttpRequest from a different domain cannot set cookie values for their own domain unless withCredentials is set to true before making the request.
axios.get(BASE_URL + '/todos', { withCredentials: true });
Also its possible to force credentials to every Axios requests
axios.defaults.withCredentials = true
Or using credentials for some of the Axios requests as the following code
const instance = axios.create({
withCredentials: true,
baseURL: BASE_URL
})
instance.get('/todos')
TL;DR:
{ withCredentials: true } or axios.defaults.withCredentials = true
From the axios documentation
withCredentials: false, // default
withCredentials indicates whether or not cross-site Access-Control requests should be made using credentials
If you pass { withCredentials: true } with your request it should work.
A better way would be setting withCredentials as true in axios.defaults
axios.defaults.withCredentials = true
It's also important to set the necessary headers in the express response. These are those which worked for me:
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', yourExactHostname);
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
I am not familiar with Axios, but as far as I know in javascript and ajax there is an option
withCredentials: true
This will automatically send the cookie to the client-side. As an example, this scenario is also generated with passportjs, which sets a cookie on the server
So I had this exact same issue and lost about 6 hours of my life searching, I had the
withCredentials: true
But the browser still didn't save the cookie until for some weird reason I had the idea to shuffle the configuration setting:
Axios.post(GlobalVariables.API_URL + 'api/login', {
email,
password,
honeyPot
}, {
withCredentials: true,
headers: {'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json'
}});
Seems like you should always send the 'withCredentials' Key first.
You can use withCredentials property to pass cookies in the request.
axios.get(`api_url`, { withCredentials: true })
By setting { withCredentials: true } you may encounter cross origin issue. To solve that
you need to use
expressApp.use(cors({ credentials: true, origin: "http://localhost:8080" }));
Here you can read about withCredentials
What worked for me:
Client Side:
import axios from 'axios';
const url = 'http://127.0.0.1:5000/api/v1';
export default {
login(credentials) {
return axios
.post(`${url}/users/login/`, credentials, {
withCredentials: true,
credentials: 'include',
})
.then((response) => response.data);
},
};
Note: Credentials will be the body of the post request, in this case the user login information (Normally obtained from the login form):
{
"email": "user#email.com",
"password": "userpassword"
}
Server Side:
const express = require('express');
const cors = require('cors');
const app = express();
const port = process.env.PORT || 5000;
app.use(
cors({
origin: [`http://localhost:${port}`, `https://localhost:${port}`],
credentials: 'true',
})
);
Fatih's answer is still valid and great in 2022.
Also axios.defaults.withCredentials = true will do the trick.
It seems passing { withCredentials: true } to individual axios calls is deprecated.
How do I make Axios send cookies in requests automatically?
set axios.defaults.withCredentials = true;
or for some specific request you can use axios.get(url,{withCredentials:true})
this will give CORS error if your 'Access-Control-Allow-Origin' is set to
wildcard(*).
Therefore make sure to specify the url of origin of your request
for ex: if your front-end which makes the request runs on localhost:3000 , then set the response header as
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
also set
res.setHeader('Access-Control-Allow-Credentials',true);
for people still not able to solve it, this answer helped me.
stackoverflow answer: 34558264
TLDR;
one needs to set {withCredentials: true} in both GET request as well the POST request (getting the cookie) for both axios as well as fetch.
Another solution is to use this library:
https://github.com/3846masa/axios-cookiejar-support
which integrates "Tough Cookie" support in to Axios. Note that this approach still requires the withCredentials flag.
After trying for 2 days long and after trying out from the suggestions here this is what worked for me.
express:
cors: cors({ origin: "http:127.0.0.1:3000", credentials: true, })
Cookie : Make sure your cookie has secure: true, sameSite: "None"
Frontend(React)
axios.defaults.withCredentials = true;
(withCredentials : true did not work for me) to the places where you request the cookie as well as to the place where you send the cookie (GET/POST)
Hope this helps others as well.
You are getting the two thinks mixed.
You have "react-cookie" and "axios"
react-cookie => is for handling the cookie on the client side
axios => is for sending ajax requests to the server
With that info, if you want the cookies from the client side to be communicated in the backend side as well, you will need to connect them together.
Note from "react-cookie" Readme:
Isomorphic cookies!
To be able to access user cookies while doing server-rendering, you
can use plugToRequest or setRawCookie.
link to readme
If this is what you need, great.
If not, please comment so I could elaborate more.
For anyone where none of these solutions are working, make sure that your request origin equals your request target, see this github issue.
I short, if you visit your website on 127.0.0.1:8000, then make sure that the requests you send are targeting your server on 127.0.0.1:8001 and not localhost:8001, although it might be the same target theoretically.
This worked for me:
First, I had to make a new instance of axios with a custom config
Then, I used that axios instance to make a post request
See code below:
const ax = axios.create({
baseURL: 'yourbaseUrl',
withCredentials: true,
});
const loginUser = () => { const body ={username:state.values.email, password:state.values.password};
ax.post('/login',body).then(function(response){
return response}).then().catch(error => console.log(error));}
source:
https://www.npmjs.com/package/axios#creating-an-instance
This won't apply to everyone, but I was using a React frontend with Vite and it was serving the localhost as 127.0.0.1:5173, which is what I put as the CORS allowable domain. As soon as I both to localhost everything worked as expected!
// use this while creating axios instance
const API = axios.create({
baseURL: "http://localhost:4000", // API URL
withCredentials: true,
});
// USE THIS MIDDLEWARE in app.js of backend
first, install cors npm i cors
var cors = require("cors"); // This should be at the end of all middlewares
const corsOptions = {
origin: "http://localhost:3000",
credentials: true, //access-control-allow-credentials:true
optionSuccessStatus: 200,
};
app.use(cors(corsOptions));
In my case, the problem was with the cookie, not with Axios; although I was receiving and sending the cookie from / to the same domain / subdomain / host, I was expecting it to work with different resources in different paths - but my coookie was acting like I had set it to a single Path, even though I omitted that attribute. Explicitly setting Path=/; in the cookie solved the issue.
Set the proxy in package.json(Frontend) and restart the server again (problem solved)
I am trying to make a POST request to the server (Which is a REST service)via javascript,and in my request i want to send a cookie.My below code is not working ,as I am not able to receive cookie at the server side.Below are my client side and server side code.
Client side :
var client = new XMLHttpRequest();
var request_data=JSON.stringify(data);
var endPoint="http://localhost:8080/pcap";
var cookie="session=abc";
client.open("POST", endPoint, false);//This Post will become put
client.setRequestHeader("Accept", "application/json");
client.setRequestHeader("Content-Type","application/json");
client.setRequestHeader("Set-Cookie","session=abc");
client.setRequestHeader("Cookie",cookie);
client.send(request_data);
Server Side:
public #ResponseBody ResponseEntity getPcap(HttpServletRequest request,#RequestBody PcapParameters pcap_params ){
Cookie cookies[]=request.getCookies();//Its coming as NULL
String cook=request.getHeader("Cookie");//Its coming as NULL
}
See the documentation:
Terminate these steps if header is a case-insensitive match for one of the following headers … Cookie
You cannot explicitly set a Cookie header using XHR.
It looks like you are making a cross origin request (you are using an absolute URI).
You can set withCredentials to include cookies.
True when user credentials are to be included in a cross-origin request. False when they are to be excluded in a cross-origin request and when cookies are to be ignored in its response. Initially false.
Such:
client.withCredentials = true;
This will only work if http://localhost:8080 has set a cookie using one of the supported methods (such as in an HTTP Set-Cookie response header).
Failing that, you will have to encode the data you wanted to put in the cookie somewhere else.
This can also be done with the more modern fetch
fetch(url, {
method: 'POST',
credentials: 'include'
//other options
}).then(response => console.log("Response status: ", response.status));
This is the story of a bird who wants to work for the post but fails during his preflight test...
App built with Laravel being used as a RESTful API and AngularJS/ionic.
My API calls were working fine until...for an unknown reason it stopped.
Although I set the withCredentials for the angularJS side of the call, the preflight OPTIONS are not sending a cookie but I am receiving one back from Laravel. How can we disable OPTIONS to return a cookie laravel_session?
It messes up the CORS as it sets a new session which will obviously be different on every POST.
For Laravel side I use the package Laravel/CORS from #barryvdh with the following configuration:
'*' => array(
'supportsCredentials' => true,
'allowedOrigins' => array('*'),
'allowedHeaders' => array('*'),
'allowedMethods' => array('POST', 'PUT', 'GET', 'PATCH', 'OPTIONS', 'DELETE'),
'maxAge' => 36000,
'hosts' => array('api.*'),
)
On the Angular side I have the following:
$http({
method: 'POST',
url: 'http://api.blabla.local/banana',
data: data,
withCredentials: true
})
My GET calls work fine and I have one running at start of the app to fetch the CSRF from laravel that I send back when needed.
Right now the following happens:
1. Preflight OPTIONS > request has no cookies for the session. Reponse = 200 with a different session cookie which will cause the CSRF to cause all the time. [thoughts: the withCredentials does not work with the OPTIONS call]
2. POST > fails with 500, in the headers I see no response but it did send the cookie/session [thoughts: credentials are passed to it but they are also the wrong ones since they have changed on server side because of the preflight option]. Error message says it is not authorized origin.
What's going on? I've been trying for hours now and checked a lot of other posts but nothing seems to help! Can I get rid of the preflight, how? Or is the problem somewhere else (server side I'm using Laravel Homestead)?
I feel that the real issue is that the OPTIONS returns a session cookie or simply that the request does include one!
Thanks for your help, I've been stuck for hours and I'm going crazzy on that...
In the filters.php under L4.2 I ended up using this:
The problem is old so not sure it's the only thing I did but looks like it:
App::before(function($request)
{
//
// Enable CORS
// In production, replace * with http://yourdomain.com
header("Access-Control-Allow-Origin: http://mydomain.local");
header('Access-Control-Allow-Credentials: true'); //optional
if (Request::getMethod() == "OPTIONS") {
// The client-side application can set only headers allowed in Access-Control-Allow-Headers
$headers = [
'Access-Control-Allow-Methods'=> 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers'=> 'Content-Type'
];
return Response::make('You are connected to the API', 200, $headers);
}
});
App::after(function($request, $response)
{
//
});
JWT could be good for ionic and angular..
Check http://packalyst.com/packages/package/tymon/jwt-auth
also https://www.youtube.com/watch?v=vIGZxeQUUFU