django-graphql-jwt JWT_COOKIE_SAMESITE not working - django

I'm using Django GraphQL JWT Library and Django GraphQL Auth
I keep getting this error
google chrome error
With this react code (trimmed for relevancy) on both http://localhost:3000/ and https://localhost:3000/
const [login] = useMutation(LOGIN_MUTATION, {
variables: {
email: email,
password: password
},
onCompleted: ({ tokenAuth }) => {
if (tokenAuth.success) {
setToken(tokenAuth.token);
}
}
});
Now when I run this mutation from the graphiql page it works and I end up with a JWT cookie but not on the react site
mutation {
tokenAuth(
email:"********"
password:"*********"
){
token
refreshToken
success
errors
}
}
This doesn't work
GRAPHQL_JWT = {
"JWT_COOKIE_SAMESITE": 'None',
"JWT_ALLOW_ARGUMENT": True
}
Adding these didn't work
"CSRF_COOKIE_SECURE": True,
"SESSION_COOKIE_SECURE": True,
"CSRF_COOKIE_SAMESITE": 'None',
"SESSION_COOKIE_SAMESITE": 'None',
"JWT_VERIFY_EXPIRATION": True,
Adding these to django settings also didn't work
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = 'None'
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = 'None'
I've been stuck on this for about 3 days now and am about ready to throw myself in a river and go build tables. Please help.

Support for the JWT_COOKIE_SAMESITE setting was added for django-graphql-jwt on version v0.3.2. You can check the releases here releases. While the current django-graphql-auth package relies on django-graphql-jwt v0.3.0. Updating your requirements will solve this issue.

so the issue is you are using
"JWT_COOKIE_SAMESITE": 'None'
which only works if
"JWT_COOKIE_SECURE": True
and JWT_COOKIE_SECURE means the cookie will only be sent over HTTPS connection and this won't work with HTTP connection.
considering you have HTTP and the backend is using the same domain as frontend then all you need to add is
"JWT_COOKIE_SAMESITE": 'Lax'
"JWT_COOKIE_SECURE": False
cookies are default into “SameSite=Lax” which means cookies are only set when the domain in the URL of the browser matches the domain of the cookie

Related

Flask POST requests fail when JWT_COOKIE_CSRF_PROTECT=True

My POST requests to flask backend only work with JWT_COOKIE_CSRF_PROTECT = False, but GET requests work
config:
CSRF_ENABLED = True
CORS_SUPPORTS_CREDENTIALS = True
JWT_TOKEN_LOCATION = ['cookies']
I access flask through axios from the Vue app
const path1 = `/limit_engine/balance`;
axios
.post(path1, { withCredentials: true })
.then((response) => {
console.log(response.data["balance"]);
})
.catch((error) => {
console.error(error);
});
https://flask-jwt-extended.readthedocs.io/en/stable/options/#jwt-cookie-csrf-protect
suggests JWT_COOKIE_CSRF_PROTECT should be always True in production, so I cannot keep it False then
Try to debug the request by examining headers. If you are sending requests from the browser, you can use any of Dev Tools (Chrome for example). Take a look at the Network tab, look for your POST request, find out which cookies are sent.
If you can't find CSRF token in the request then you should pass it from the backend to the frontend and keep it in cookies storage.
After whole morning having trouble with this I realized CSRF token is only read from request headers as seen here: https://flask-jwt-extended.readthedocs.io/en/stable/_modules/flask_jwt_extended/view_decorators/ not from cookies, so in Vue you need to manually append this header to your requests.
Relevant source code to add to your flask app and to your Vue app:
In flask app:
app.config['JWT_ACCESS_CSRF_HEADER_NAME'] = "X-CSRF-TOKEN"
app.config['JWT_REFRESH_CSRF_HEADER_NAME'] = "X-CSRF-REFRESH-TOKEN"
app.config['JWT_CSRF_IN_COOKIES'] = False
In your flask app login function:
from flask_jwt_extended import (
jwt_required, create_access_token,
jwt_refresh_token_required, create_refresh_token,
get_jwt_identity, set_access_cookies,
set_refresh_cookies, get_raw_jwt, get_csrf_token
)
new_token = create_access_token(identity=current_user.id, fresh=False)
new_refresh_token=create_refresh_token(identity=current_user.id)
response = jsonify({
'data': {
'message':'Ok',
'type': 'user',
'id': current_user.id,
'meta': {
'accessToken': new_token,
'access_csrf_token': get_csrf_token(new_token),
'refreshToken': new_refresh_token,
'refresh_csrf_token': get_csrf_token(new_refresh_token)
}
}
})
set_refresh_cookies(response, new_refresh_token)
set_access_cookies(response, new_token)
return (response)
In your Vue app in your login fuction "edit according if you use or not refresh token logic":
axios.defaults.headers.common['X-CSRF-TOKEN']=response.data.data.meta.access_csrf_token
axios.defaults.headers.common['X-CSRF-REFRESH-TOKEN']=response.data.data.meta.refresh_csrf_token
And lastly do the same in yout Vue TokenRefreshPlugin or the method you use
I guess there are more approaches like getting the CSRF headers from the cookies, but this one seems to work for me for now at least. The important point is adding this headers manually in Vue requests, because using axios.defaults.withCredentials = true is not enough.
Also check header includes csrf token in the requests as akdev suggests.
you can add csrf exception for request.
or follow:-
https://flask-jwt-extended.readthedocs.io/en/3.0.0_release/tokens_in_cookies/

cookie httponly not sent

I'm building an api with api platform and a front with react (using the react template of apiplatform). I configured authentification and a return to client with httponly cookie which contains the jwt. But when my front does a request, it does not send this cookie... And I absolutly don't know why, I thought it was automaticaly done by browser till it's on same domain.
Here is an example of the network history from my client :
my app is running on https://localhost:3000/
Do you see something wrong in theses request ? Or does anyone has an idea of what it could come from ?
My app and api are using https and have a valid certificate...
If you need any additional info, feel free to ask, and thanks all !!!
I assume you work with either xhr or fetch.
Cookies ignore ports, but cross origin policy does not.
You work with two urls (http://localhost:8443 and http://localhost:3000). So your app is making cross origin request because ports differ.
xhr requires to set its withCredentials property to true in order to send cookies with cross-origin request.
fetch requires its credentials parameter to be set to include.
Server side, set the Access-Control-Allow-Credentials to true.
Also note that your cookie is samesite=strict. In production, if you use two domains for your app and your api, it will never be sent.
The real question here is why using a cookie instead of Authorization header ?
Ok, I didn't know... I've found nothing on it when I was trying to solve my prob.
I'm using cookie httponly because :
I want to try it :D
Lot of security articles says that it's more secure because client api can't access theses cookies, browser manages it. It seems to counter xss and stealth of cookies, but if my cookie is stored with localforage, I think I do not have this problem, but with localStorage I do, no ?
It's cool no ! I've done too many project with classic bearer auth, I can improve it now
A big thanks for your nice answer rugolinifr !
Okay, I'm still having my issue finally... My browser is not sending the cookie...
My auth request returning bearer cookie (valid, tested with postman)
My cookie received from auth request
My GET request without that auth cookie
I'm missing something but I don't find it...
I've set credentials, Access-Control-Allow-Credentials, samesite is 'none' for sending it everywhere. Is there something else to do ? Or maybe I'm doing a stupid little thing that is wrong ?
I can't answer in comment because there's code...
So, It's managed by the react admin base of api-platform (https://api-platform.com/docs/admin/), but my config is like this :
const fetchHeaders = {
credentials: 'include',
};
const fetchHydra = (url, options = {}) =>
baseFetchHydra(url, {
...options,
headers: new Headers(fetchHeaders),
});
const apiDocumentationParser = (entrypoint) =>
parseHydraDocumentation(entrypoint, { headers: new Headers(fetchHeaders) }).then(
({ api }) => ({ api }),
(result) => {
...
},
);
const dataProvider = baseHydraDataProvider(entrypoint, fetchHydra, apiDocumentationParser, true);
So, all get, post etc request for datas are based on this conf
But my first call for authentication is done like that :
login: ({ username, password }) => {
const request = new Request(`${entrypoint}/authentication_token`, {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: new Headers({ 'Content-Type': 'application/json' }),
});
return fetch(request).then((response) => {
if (response.status < 200 || response.status >= 300) {
localStorage.removeItem('isAuthenticated');
throw new Error(response.statusText);
}
localStorage.setItem('isAuthenticated', 'true');
});
},
ok, I've found solution :
add credentials to the auth request, if header is not added, cookie won't be stored by browser.
And second point :
const fetchHydra = (url, options = {}) =>
baseFetchHydra(url, {
...options,
credentials: 'include',
});
credentials: 'include' is not in headers option... Nice !
Faced the same problem.Tried out many solutions but didn't work.At last found out it was the cors configuration of node backend that was causing the problem. Configured cors like the following way to solve the problem.
const corsConfig = {
origin: true,
credentials: true,
};
app.use(cors(corsConfig));
app.options('*', cors(corsConfig));

CSRF_Token in POST method using Vue.js and Django Rest Framework

I'm trying to send a POST request from a Vue.js template to my API created with Django.
When sending I get a 403 CSRF token missing or incorrect error. Since I separated the front and the back, I don't have a view with {csrf_token} on the Django side.
How do I send my form?
I tried some exemples on the web using cookies but i'm beginners and need more explaination about the POST subject and CSRF
I have a Djano View (and urls associated) juste like this :
def get_csrf_token(request):
token = get_token(request)
return JsonResponse({'token': token})
Whe i'm requesting the url, obtained the JSON with the token.
And on the Front side i'm using this method to get the Token :
getToken: function() {
this.loading = true;
this.$http.get('/qualite/get-token/')
.then((response) => {
this.token =response.data;
this.loading = false;
})
.catch((err) => {
this.loading = false;
console.log(err);
})
},
addNc: function() {
let headers = {
'Content-Type': 'application/json;charset=utf-8'
};
if(this.token !== '') {
headers['HTTP_X-XSRF-TOKEN'] = this.token
}
this.loading = true;
this.$http.post('/qualite/api/nc/',this.newNc, {headers: headers})
.then((response) => {
this.loading = false;
})
.catch((err) => {
this.loading = false;
console.log(err)
})
},
For the CSRF you get by default after user login aside with the session, if you're using SessionAuthentication (It's the default authentication used in DRF).
You have to send it with each request in the header, you can refer the this link to know more about the header sent, as it's name is changed and can be configured.
Note also that in the settings you have to make sure that CSRF_COOKIE_HTTPONLY is set to False (which is the default), to be able to read it from the client side JS.
Another path would be removing CSRF enforcement per requests (But it's highly not recommended for security concerns), you can find more about this in the answer here.
Use a Token-based authentification.
Same issue i was encountered with,
the problem was, i had used Class based view and at the time of registered the url i forget to mention as_view() with class Name.
ex:- class PostData(APIView)
before :- path('post_data', PostData)
after correction:- path('post_data', PostData.as_view())

Django, React-Native Connection Network Request Failed

I'm working on a react-native-based mobile application and doing some operations with python in the background. I wanted to do both these transactions and connect to the database via Django rest api. But I get connection error.
I have used other rest-api and tried it. I also tried the rest api on the postman and it worked smoothly.
I tried everything, but I couldn't find a solution.
local rest url: http://localhost:8000/api/venues/
and fetch code:
componentDidMount() {
return fetch('http://localhost:8000/api/venues/?format=json')
.then((response) => response.json())
.then((responseJson) => {
console.log(responseJson);
this.setState({
isLoading: false,
dataSource: responseJson,
}, function(){
});
})
.catch((error) =>{
console.error(error);
});
}
also my django setting:
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = [
'http://localhost:3030',
]
CORS_ORIGIN_REGEX_WHITELIST = [
'http://localhost:3030',
]
add these configurations after adding cors as middleware (change to your port number)
https://stackoverflow.com/a/69186898/12497001 provides a very good answer!
As mentioned in the Android Studio documentation, emulators use a special address network (https://developer.android.com/studio/run/emulator-networking.html).
To address requests to your own machine, you can use the address http://10.0.2.2

Can't send post request from JavaScript when CSRF_COOKIE_SECURE is enabled

API Endpoint: /api/items/ has authentication_classes = (SessionAuthentication, )
In my Vue.js code I have:
getCookie: function (name) {
match = document.cookie.match(new RegExp(name + '=([^;]+)'));
if (match) return match[1];
return
},
...
saveApiCall: function (data) {
this.$http.post("/api/items/", data, { headers: { 'X-CSRFToken': this.getCookie('csrftoken') } }).then(function (response) {
this.close();
}).catch(function (response) {
this.form_errors = response.body;
});
},
but it doesn't work when I set these settings:
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
How to send this $http.post request when csrftoken is secure?
As per the Django docs:
If [CSRF_COOKIE_HTTPONLY] is set to True, client-side JavaScript will not be able to access the CSRF cookie.
Designating the CSRF cookie as HttpOnly doesn’t offer any practical protection because CSRF is only to protect against cross-domain attacks. If an attacker can read the cookie via JavaScript, they’re already on the same domain as far as the browser knows, so they can do anything they like anyway. (XSS is a much bigger hole than CSRF.)
Just set CSRF_COOKIE_HTTPONLY = False (or remove it from settings altogether, as that is the default) and you should be able to access it from JavaScript.