As the title says, I'm trying to set cookies/sessions on the browser.
To preface:
Frontend: https://www.example.com
Backend: https://api.example.com
Frontend makes a request to the backend, the backend creates a session, and a cookie is sent back to the frontend in the response.
Problem:
The request is successful and the response shows that there is a session, however it was not set. The error message is as follows:
This attempt to set a cookie via a Set-Cookie header was blocked because its Domain attribute was invalid with regards to the current host url.
Research:
Based off this and this, it appears that:
cookies can be shared is that both domains have the same main-domain
Which is the case for my scenario. The main domain is example.com and the subdomains are www.example.com and api.example.com.
In addition, Mozilla official documentation states that:
if a domain is specified, then subdomains are always included
Implementation
The frontend is developed in React and the backend is developed in Python (Flask). Both are hosted Heroku. Domain registrar is with GoDaddy and I'm using Cloudflare as my DNS to re-route traffic and setting the correct domains.
A sample request looks like this:
const headers = {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
};
...
const handleFormSubmission = async e => {
e.preventDefault();
await axios.post('https://backend.com/add-results', { tokenId: tokenId }, { withCredentials: true, headers: headers })
}
And the backend has the following settings:
...
CORS(app, origins=["https://www.example.com"], expose_headers=["Content-Type", "X-CSRFToken"], supports_credentials=True)
...
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['REMEMBER_COOKIE_SECURE'] = True
app.config['REMEMBER_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_DOMAIN'] = 'example.com'
Was wondering if anyone has experienced a similar problem and found a solution. I got the idea of using subdomains from here, but I have had no luck with my implementation.
Here's a screen shot of what I'm getting
The fix was actually pretty simple:
CORS(app, origins=["https://www.example.com", "https://example.com"], expose_headers=["Content-Type", "X-CSRFToken"], supports_credentials=True)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['REMEMBER_COOKIE_SECURE'] = True
app.config['REMEMBER_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'None'
app.config['SESSION_COOKIE_DOMAIN'] = '.example.com'
app.config['WTF_CSRF_SSL_STRICT'] = False
Main changes were:
app.config['SESSION_COOKIE_SAMESITE'] = 'None'
And:
app.config['WTF_CSRF_SSL_STRICT'] = False
SESSION_COOKIE_SAMESITE needed to be 'None' to avoid the default value of 'Lax' which would prevent the cookie from being received and set on the browser.
WTF_CSRF_SSL_STRICT was set to false because the origins of the request and the server needed to be the same (same origin policy). However, I have configured a whitelist of allowed origins in my CORS configuration. That said, I think that covers that case.
Also, I cleared my caches on my browser and the DNS. In addition, I restarted my servers where the frontend and backend were hosted.
Hopes this helps others, cheers!
Related
Problem
I have a Flask API with two different kinds of routes. I am making requests to my API endpoints from a JavaScript/D3.js application. I need help troubleshooting a CORS error.
Endpoint format
One kind of endpoint follows the format http://127.0.0.1:5000/api/v0.1.0/infrastructure/primary_type/secondary_type
And the other kind follows the format http://127.0.0.1:5000/api/v0.1.0/infrastructure/primary_type/
I am getting a CORS error for the one following the format http://127.0.0.1:5000/api/v0.1.0/infrastructure/primary_type/
CORS Error
Access to fetch at 'http://127.0.0.1:5000/api/v0.1.0/infrastructure/primary_type' from origin 'http://127.0.0.1:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Endpoint Code
Working Endpoint
#bp.route("<primary_type>/<secondary_type>")
def get_infrastructure(primary_type, secondary_type):
infrastructure = ''
if primary_type == 'null':
infrastructure = shotgun_api.db.infrastructure.find({'properties.type.secondary': secondary_type}, projection = {'_id': False})
else:
infrastructure = shotgun_api.db.infrastructure.find({"$and": [{'properties.type.primary': primary_type},{'properties.type.secondary': secondary_type}]}, projection = {"_id": False})
response = jsonify([resource for resource in infrastructure])
response.headers.add("Access-Control-Allow-Origin", "*")
return response
Endpoint triggering errors
#bp.route('<primary_type>/')
def get_by_primary_type(primary_type):
infrastructure = shotgun_api.db.infrastructure.find({'properties.type.primary': primary_type}, projection = {'_id': False})
response = jsonify([resource for resource in infrastructure])
response.headers.add("Access-Control-Allow-Origin", "*")
return response
Expected behavior
I would expect that using the same response.headers.add... code would provide me with a 200 OK status but it only works for the more complex endpoint for some reason.
Client Side JavaScript/D3.js
for (let i = 0, num_draw_props = lyr.draw_props.length; i < num_draw_props; ++i) {
start_loading_layer();
Promise.all(lyr.draw_props[i].src.map(x => lyr.draw_props[i].d3_fetch(x)))
.then(function(files) {
lyr.context.restore();
lyr.context.save();
return files;
}).then(files => {
transform_layer(lyr.context, transform);
return files
}).then(files => {
console.time('draw_layer')
lyr.draw_props[i].draw_layer(lyr.context, files);
console.timeEnd('draw_layer')
});
}
Where d3_fetch is equal to d3.json(). When src is 'http://127.0.0.1:5000/api/v0.1.0/infrastructure/mines/coal' everything works fine, but when src is http://127.0.0.1:5000/api/v0.1.0/infrastructure/railroads I get the previously mentioned CORS error.
What I've tried
I have tried using Flask-CORS but was unable to get it accessible to my application despite installing it with pip. I would prefer to solve this without using Flask-CORS. I am otherwise at a loss for what to do here and any advice is appreciated.
I'm currently developing a Django-React web app and using django-rest-framework-simplejwt and dj-rest-auth for authentication.
At first I was storing JWT in frontend cookies (js-cookie) and sending tokens in the headers to get access for restricted endpoints. Since local client cookies are not HttpOnly and after some research I found out that it was not a safe method to store it on the frontend. So I decided not to store them in the client cookies.
It seems like best solution to use HttpOnly cookies, in django settings I declared cookie name as JWT_AUTH_COOKIE = 'myHttpOnlyCookie', so when I make a request from client with username and password to log-in server responses with the cookie that has the access_token.
For the login part, I didn't write any code since dj-rest-auth handles it well so I use their standard loginserializer and view.(https://github.com/jazzband/dj-rest-auth/blob/master/dj_rest_auth/serializers.py). Well maybe I should modify it.
However the problem is I can't add the token in the header of client requests since I'm not storing the token on the client and it is HttpOnly. Well I really don't know how to authenticate the user if I can't send the token in requests.
Once you make a login request to the server, tokens are added to httponly cookie by default. On consecutive requests cookies are sent by default.
Axios request for login.
axios.post('http://localhost:8080/api/auth/login/',
{'email':'test_email', 'password':'test_password'},
{withCredentials:true},
{headers:{
'Content-Type': 'application/json',
'Accept': 'application/json'
}}
)
"withCredentials" must be always set to "true", this will ensure cookies are added to the every request. Once you login, tokens are stored in httponly coolie. For next requests , refer below pseudo code.
const axiosGetReqConfig = {baseURL: '', withCredentials:true, headers:{'Content-Type':'application/json, 'Accept':'application/json'}}
axiosGetReqConfig.get('test/').then(resp => {console.log(resp)}).catch(err => {console.log(err)})
axiosGetReqConfig.interceptors.response.use(
// If request is succesfull, return response.
(response) => {return response},
// If error occured, refresh token and try again.
(error) => {
const originalRequest = error.config;
console.log(originalRequest)
// Make a call to refresh token and get new access token.
axios('http://localhost:8080/api/auth/token/refresh/', {
method:'post',
withCredentials: true
}).then(resp => {
console.log(resp);
}).catch(err => {
// push user to login page.
console.log(err)
})
// Return original request.
return axios.request(originalRequest)
return Promise.reject(error)
}
)
In the above code, I am creating config object using some basic details and implementing interceptors to refresh token if, access token is expired. If refresh token is expired, user will be re-directed to login page.
Main part with including httponly cookie is the variant that we use in making axios request and "withCredentials". There is an open issue with JWT. Since dj-rest-auth uses JWT, if you need to refresh token, you have to implement middleware in django. Refer below link to implement middleware and add that middleware to settings.
https://github.com/iMerica/dj-rest-auth/issues/97#issuecomment-739942573
For development I am using vuejs which is being served by webpack at my local address: 172.18.0.77:8080 and flask that is run by Werkzeug at address 172.18.0.77:5000. I am trying to set cookie to some GET request by running this code:
response_data = Response(json.dumps(some_json_data, indent=True), status=200, mimetype='application/json')
response_data.set_cookie('user_session_id', value='12345', domain='172.18.0.77:8080')
return response_data
But when I am trying to read this cookie with following code request.cookies.get('user_session_id') I am receiving only None value.
I also tried to set cookie by changing domain to 172.18.0.77 like:
response_data.set_cookie('user_session_id', value='12345', domain='172.18.0.77')
But it also doesn't work
if you use axios in vuejs, i suggest that you can add withCredentials: true
const instance = axios.create({
withCredentials: true,
....
})
And in flask
#app.after_request
def handle_credentials(response):
response.headers["Access-Control-Allow-Credentials"] = True
return response
So I am trying to connect my angular2 app hosted on localhost:4200 to my django api hosted on localhost:8000 , i already have an angular 1,6 app hosted over localhost:800 that manages the login and all the other stuff,
so in my angular2 app i received the token stored in my cookies and i am trying to send a get request using it in the header to the django api .
ps: i already checked my django api and its currently allowing access to all servers with no exception .
fetchUser(){
console.log("Here");
let headers = new Headers({'Accept': 'application/json','X-CSRFToken': this.token});
this.http.get( 'http://localhost:8000/api/orgs', {headers: headers}).subscribe(
(response: Response) => {
const testResponse = response.json();
this.outerLinks.data = testResponse;
this.data =testResponse;
this.dataChange.next(this.data);
}
);
}
So I am receiving this error:
XMLHttpRequest cannot load localhost:8000/api/orgs. Response for preflight is invalid (redirect)
You can try Allow-Controll-Allow-Origin: *
if you use Chrome try this ext
https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi?hl=en
Modify your settings.py by adding the following code:
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = (
'localhost:4200'
'<YOUR_DOMAIN>[:PORT]',
)
Goodmorning,
i have some trouble in wrinting a callback method that it's used to login to my web app (hosted in gae)
#app.route('/callback')
def callback_handling():
env = os.environ
code = request.args.get('code')
json_header={'Content-Type': 'application/x-www-form-urlencoded'}
token_url = "https://{domain}/oauth/token".format(domain='mydomain')
token_payload = {
'client_id': 'myid',
'client_secret': 'mysecret',
'redirect_uri': 'http://localhost:8080/callback',
'code': code,
'grant_type': 'authorization_code'
}
encoded = urllib.urlencode(token_payload)
user_url = "https://{domain}/userinfo?access_token={access_token}"\
.format(access_token=token_info['access_token'])
user_info = urlfetch.Fetch(user_url, method=urlfetch.GET, headers=json_header)
session['profile'] = user_info
return redirect('/dashboard')
i costantly get error 401 unathorized..from debug console any ideas to fix?..reading over the internet i understand that i must use urlfetch from gae because is the only lib allowed in gae (requests does not in localhost)..
In your code, is this : "https://{domain}/userinfo" your own endpoint or is this some third-party that you are trying to auth with?
Either way the 401 unauthorised seems legit, and being thrown by the endpoint, If "https://{domain}/userinfo" is pointing to your own endpoint then being able to see that logic would help, alternatively if it is a third-party I would recommend testing your request with something like postman, to see what your request looks like, headers etc. and make sure that it matches what the endpoint is expecting.
Hope this helps.