-
I believe authentication is one of the most common use cases, yet I am having a hard time finding an out-of-the-box solution for authenticating the user with a React/Vue client (which will use JWT I guess). Where can I find a minimal example? |
Beta Was this translation helpful? Give feedback.
Replies: 32 comments
-
Just in case if you use Django: https://github.com/flavors/django-graphql-jwt |
Beta Was this translation helpful? Give feedback.
-
I am running into this issue as well. (current stack is Django-graphene as graphql backend with apollo-client/react on the front end) https://github.com/flavors/django-graphql-jwt provides a method to generate a jwt token (which I have working) however I haven't seen a good solution for storing the jwt in a cookie in apollo once the token has been generated. (https://www.rdegges.com/2018/please-stop-using-local-storage/) I am hunting through making a working to this solution now, but a general use cases would be useful. I will provide updates if I am able to find solutions though I admit this may not be the best place for it as graphql-python is covering backend only. update/sidenote: encryption for user/password from client-backend should be handled via SSL certs (HTTPS) |
Beta Was this translation helpful? Give feedback.
-
@TitanFighter thanks, i have taken a look at the flavor project, i think it is helpful but need some more time to adapt. Will provide my experience once I have progress. I also am aware of storing plan text token, but it is now only my secondary concern given they can be short lived (5min default). However this would mean a strategy to regularly update token need to be implemented. My biggest concern right now is actually how to implement Oauth and jwt together... @Diggitysc Let's team up on this cause I feel like we are doing the same thing Update take a look at |
Beta Was this translation helpful? Give feedback.
-
@gotexis Sounds great! I will not be using Oauth but I think the configuration on the react side will be the same (cookie containing jwt). I have a feeling it may just be a lack of understanding on my part regarding the interaction between the front-end/backend, but I will come back with something once I have it. As a side note I am appalled by the huge number of demos that give examples with local storage without providing an alternative solution that utilizes something secure (on the react end) Further reading on the issue itself and advantages of cookies vs local storage: https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage TLDR of article above, storing JWT in local storage creates a large surface vulnerability to XSS (all javascript on local domain has access to local/session storage, if you have a js dependency that is compromised, which has happened multiple times historically, all data stored in JWT is exposed/captured). JWT in cookie is still vulnerable to CSRF exploits but most frameworks have CSRF protection (which is annoying to set up with CORS but is secure). |
Beta Was this translation helpful? Give feedback.
-
At this stage with a lot of looking around I am starting to agree with the following two articles: http://cryto.net/%7Ejoepie91/blog/2016/06/13/stop-using-jwt-for-sessions/ I am not going to go as far as the author and say that JWT is not worthwhile, simply that individuals should be aware of use cases for JWT and the risks involved, particularly for login/session management. Use cases I believe the best use case for JWT would be a microservice where a web browser and extraneous javascript code is not involved. This mitigates the risk of XSS issues. For JWT with a web browser (and storage in local ), there needs to be a large amount of control over javascript/the machine that is executing the code to avoid XSS if there is any sensitive information contained in the web application. This seems particularly unlikely in the current react eco-system. I do not currently see the advantages of JWT in Cookies over cookie session management. (may again be due to lack of research) However I think technologically I may be stuck with combining the two due to current limitations. I will keep digging |
Beta Was this translation helpful? Give feedback.
-
As I recall from reading one article, the main problem of criticism of JWT is the lack of a suitable alternative... |
Beta Was this translation helpful? Give feedback.
-
It looks like https://github.com/flavors/django-graphql-jwt solved the authentication/JWT cookie problem with their latest update. To implement: follow settings recommendations here: https://django-graphql-jwt.domake.io/en/stable/quickstart.html#installation However also add a revoke_token mutation, listed in schema here: https://django-graphql-jwt.domake.io/en/stable/refresh_token.html Then in urls.py add:
Then copy the resolve_me query courtsey of https://www.howtographql.com/graphql-python/4-authentication (in query)
What does this do? So if you execute a tokenAuth mutation with user/password, then run the "me" query, you should get back the relevant user to the tokenAuth request. Additionally if you check your browser cookies, you will notice a big JWT cookie in place. Why do I need this? The following video is extremely informative (time stamp to relevant information but for angularjs) TLDW:
Solution with jwt_cookie decorator: As a last bit, be sure that a logout includes a call to revoke_token. On the react side, if you are using apollo just set credentials to 'same-origin': For legacy discussion (from which I plucked information) see the relevant issues page here: flavors/django-graphql-jwt#75 Update on current issues: Read apollo/django implementation on comment below Thanks to everyone that contributes to graphql-python and all the django-graphql-jwt contributors here: https://django-graphql-jwt.domake.io/en/stable/contributors.html |
Beta Was this translation helpful? Give feedback.
-
Haha, actually I just saw your GraphQL howto repo and couldn't believe what I've seen. You are becoming the champion of GraphQL sir :) OK so both you and @mongkok author of GraphQL_JWT recommended me to use cookies instead of LocalStorage.... Hmmmm OK ket's do it then :) Too bad I am at exactly where you are at, just with Vue, not React, but it's basically the same. CSRF Token not set.What I have tried:
Nothing worked.... OMFG I have even checked the So exactly as you said
And that means the introspection query cannot proceed, so nothing works .... Checking:
adamchainz/django-cors-headers#162 p.s. Plz help me do Oauth buddy :) |
Beta Was this translation helpful? Give feedback.
-
Update on Apollo/Django implementation: If you are ejecting (react-create-app) and running your javascript code from django you shouldn't need the below (as csrf tokens should be assigned/accessible via the same port). I haven't tested that aspect out, but if someone does please let me know if you ran into any trouble or not. For the rest of us, often times django-graphene is running from some port (typically 8000) for dev purposes while a javascript dev environment is running on some other port (typically 3000). This creates a problem where when CSRF protection is enabled, a CSRF cookie is never set for the javascript environment to give a proper security return. This means you need to expose a CSRF cookie so that it can then be ingested and set on the javascript front end so it can make a call to the /graphql/ end point to get a JWT token cookie (via django-graphql-jwt) for further XSS/CSRF safe authentication (quite the sentence). Special thanks to this article here https://fractalideas.com/blog/making-react-and-django-play-well-together-single-page-app-model/ on how to expose CSRF cookies. From the article:
For debugging it is helpful to set CORS_EXPOSE_HEADERS (to make it viewable). If you really want to dig deep on the django side of things you will want to put pdb breaks in /django/middleware/csrf.py (in env lib environment) Now that CORS is set up, set up a url endpoint that is passing a CSRF token. As mentioned in the original article, this is fine as "the browser’s same-origin policy prevents an attacker from getting access to the token with a cross-origin request" In your baseline apps views.py:
and in urls.py add:
If you go to your app/csrf, you should get a fresh csrf request token OK now to Apollo You are going to need to do a couple of things
I am using boost as I like the condensed syntax, additionally js-cookie is a nice and clean for cookie setting You will need to edit the url csrf endpoint to your implementation of the csrf url above and the uri to your graphql endpoint.
If you are using Apollo provider for the apollo link in react component:
With all that you should have an app that is both XSS (Httponly JWT token in a cookie) and CSRF (CSRF cookie fetch/response) secure. @gotexis My guess is you are going to need a similar async call for your OAuth provider. You might also take a look at https://auth0.github.io/auth0.js/index.html |
Beta Was this translation helpful? Give feedback.
-
Update: So the above solution works if you are generating a single graphql call per page. However if you are bundling (via higher order components) several graphql calls per page (such as multiple queries to display different self-contained data components) due to the asychronized nature of javascript you will likely run into a CSRF token problem as multiple requests will generate distinct csrf tokens. A pseudo code example from hacker news clone: // generates csrf token/post to graphql users // generates csrf token/post to graphql linksIn async time: Sometimes you get lucky and A short term solution is to re-fetch on Forbidden CSRF error, but with a bit of bad luck this can slow down page load performance as a function of the number of post requests on a page. Other work around suggestions are welcome at this point as I am a bit burnt out on the problem itself. Long term it would be nice to have csrf token generation/checking within graphene itself in a manner that avoids these sorts of problems |
Beta Was this translation helpful? Give feedback.
-
@Diggitysc Thanks a lot four your code. I added a caching functionality for the csrf token. It only calls the /csrf/ api once per session. let csrftoken;
async function getCsrfToken() {
if (csrftoken) return csrftoken;
csrftoken = await fetch('http://localhost:8000/csrf/')
.then(response => response.json())
.then(data => data.csrftoken)
return await csrftoken
} const apolloClient = new ApolloClient({
uri: httpEndpoint,
// tell apollo to include credentials for csrf token protection
// async operation with fetch to get csrf token
request: async (operation) => {
const csrftoken = await getCsrfToken();
Cookies.set('csrftoken', csrftoken);
// set the cookie 'csrftoken'
operation.setContext({
// set the 'X-CSRFToken' header to the csrftoken
headers: {
'X-CSRFToken': csrftoken,
},
});
},
credentials: 'include',
cache: new InMemoryCache(),
}) |
Beta Was this translation helpful? Give feedback.
-
Now I also see the problem with calling two gql queries on one page. I get a This happons usually on Firefox and on chrome within the incognito window |
Beta Was this translation helpful? Give feedback.
-
I have ended up sending the csrf request before any rendering is done. let csrftoken;
async function getCsrfToken() {
if (csrftoken) return csrftoken;
csrftoken = await fetch('http://localhost:8000/csrf/')
.then(response => response.json())
.then(data => data.csrftoken)
return await csrftoken
};
getCsrfToken().then(function(token) {
new Vue({
router,
apolloProvider: createProvider({}, token),
render: h => h(App)
}).$mount("#app")
}); // Call this in the Vue app file
export function createProvider(options = {}, token) {
Cookies.set("csrftoken", token);
options.httpLinkOptions = {
headers: {
"X-CSRFToken": token
},
credentials: "include"
};
// Create apollo client
const { apolloClient, wsClient } = createApolloClient({
...defaultOptions,
...options
});
apolloClient.wsClient = wsClient; The solution does not seem to be ideal but works for now. I think you could do it better if you use some apollo function (apollo network interface - or something like that) I'm open to other coding solutions. |
Beta Was this translation helpful? Give feedback.
-
@bastiW I think it actually may be handled better via the backend itself cutting the javascript entirely out of the process to avoid the async collision problem and to provide automatic security rather than implemented security. I have some rough ideas on how to handle this, but I am not a CSRF expert by any stretch. It may be worthwhile to reach out to the csrf.py team from django itself: https://github.com/django/django/blob/master/django/middleware/csrf.py Listing out my rough ideas and some downsides: Set an httponly cookie from django/graphene with a unique request identifier and a short time out. Which would look like
Downsides:
|
Beta Was this translation helpful? Give feedback.
-
@Diggitysc How have you been able to support logout? In my mind there are probably two steps that need to occur: 1) revoking the token, 2) deleting the cookie on the response to the client. I have an idea how to delete the cookie on the response to the client using a custom GraphQLView approach similar to what is suggested here: https://stackoverflow.com/a/46364738/1248666 As for revoking the token, I'm not sure how that would work. It seems like revoking the token would require the client to pass the token in the mutation call - which would be impossible since it's set in an httpOnly cookie. There's this open issue in the django-graphql-jwt repository, perhaps this will be solved in a dedicated graphql mutation at some point: flavors/django-graphql-jwt#102 |
Beta Was this translation helpful? Give feedback.
-
@johnpaul89 The code above from Mar 27th has examples of implementing both the csrf portal and the frontend. @bastiW Has an updated version of the front end code you can utilize from May 29th. If you implement this though you will need to be making a single graphql call from each front end page as multiple-component graphql calls generate multiple csrf token asynchronously which can lead to csrf token failures. I am hoping that the graphene team creates a backend solution to this problem in the future (unique id csrf tokens perhaps?) but as of yet that is where it stands. |
Beta Was this translation helpful? Give feedback.
-
@Diggitysc I'm confused what the purpose of the |
Beta Was this translation helpful? Give feedback.
-
@dspacejs A CSRF token is a barrier against CSRF attacks see: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) Django core generates a CSRF token with each api request and sends it to the client. It then expects the token to be passed back from the client. This helps mitigate forged requests that are attempting to inject data from an exterior source as they will not have access to the client token storage. If they happen to have access to client token storage otherwise, the malicious user can execute code on the clients behalf (XSS) so mitigating CSRF in that instance is moot. To read up on Django's precise implementation go here: https://docs.djangoproject.com/en/2.2/ref/csrf/ and scroll down to the "How it works" paragraph. |
Beta Was this translation helpful? Give feedback.
-
@Diggitysc sorry what I meant was why does the Cookies.set('csrftoken', csrftoken); If the CSRF header is what's being read by the server: operation.setContext({
headers: {
'X-CSRFToken': csrftoken,
},
}); From my understanding the cookie is set for the client's domain (e.g |
Beta Was this translation helpful? Give feedback.
-
@dspacejs I might be getting a bit lost in my own lack of understanding around the implementation. Also the fact that cookies are headers is giving me a bit of brain crosstraffic at the moment. I might have been overzealous in setting the header and a cookie. Try removing the cookie and see if CSRF failures arise. It has been awhile since I worked on this particular problem, but at the time I remember there being failures if I did not have both. At that stage of things I was definitely in a mode of "try everything until it works" rather than a methodical approach. It may have also been a misdiagnosis related to the async csrf token call/assignment problem. |
Beta Was this translation helpful? Give feedback.
-
@Diggitysc I just tried and it looks like both are required. Not sure why that is, I was just wondering how it works 😁 |
Beta Was this translation helpful? Give feedback.
-
Hi @Diggitysc, I'm implementing JWT authentication with Django+GraphQL, with the package
My question is that how could I customize the input (a I've been looking into the source code for a while, and it seems I need to override Could you please help and suggest? Many thanks! |
Beta Was this translation helpful? Give feedback.
-
If you are utilizing a 3rd party server I would look into how people are integrating with auth0/oauth |
Beta Was this translation helpful? Give feedback.
-
Hi @Diggitysc @dspacejs in the code you wrote on Mar 28, we are getting a token which is HttpOnly , do you know of any other ways to add so that to make it secure and of Samesite.? So that all those three cookie boxes in the developer tools for chrome can be ticked. Thanks for that code, it has helped me greatly. |
Beta Was this translation helpful? Give feedback.
-
@johnpaul89 Django has two settings to adjust HttpOnly cookies and the csrf cookie domain in settings.py |
Beta Was this translation helpful? Give feedback.
-
@Diggitysc I'm looking at your code example above and I think there's one edge case that's not accounted for yet. Wouldn't a user opening two windows/tabs cause issues on the first window/tab with the CSRF token changing? In order for that to work we would need to set the header value right before sending each request. @dspacejs I know this is a little old but if you're curious the reason both the header and cookie values are required is because it's a CSRF protection technique called double submit cookies in which no state is maintained on the server side. You can read more about it here: https://owasp.org/www-project-cheat-sheets/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie |
Beta Was this translation helpful? Give feedback.
-
@CCoffie So my understand of CSRF protection is that the token should change with each request as the purpose is to validate that the frontend sender request is not being hijacked by a 3rd party. So the token should change with a new tab/window. However unless the team recently fixed the issue (would love a check/update if anyone has the time) there is a problem mention here #593 (comment) (same thread) Django assumes synchronous tokens (single front end call from django view), but javascript front ends and specifically multiple wrapped react components (with apollo graphql calls) generate multiple tokens that are highly likely to be generated asynchronously. This leads to a CSRF token mismatch and an error. If you only have a single request per page this is not an issue, even with the tab/window, though the first token will expire with the second tab/window opening (as intended) |
Beta Was this translation helpful? Give feedback.
-
@Diggitysc That's the intent of the CSRF protection in Django to change the CSRF token with each page view. The issue I believe I'm seeing is the cookie value is read on initial page load and not right before request is sent. If there's a way to add the header right before the request is sent that would be ideal. On a side note, The changing of the CSRF value isn't necessary when setting a CSRF header value and is only necessary when you're sending the CSRF token within the post body. This is because CORS will prevent a cross origin from setting a header on their request. |
Beta Was this translation helpful? Give feedback.
-
@CCoffie That is good to know! There may be a way to make an await method that links into apollo client to set the header right before the request. @bastiW Made this code adjustment: #593 (comment) that sends the csrf request before any rendering is done so that is a start. I am currently pinned to other tasks and I am not sure when I will be able to cycle back around to working on this. |
Beta Was this translation helpful? Give feedback.
Update on Apollo/Django implementation:
If you are ejecting (react-create-app) and running your javascript code from django you shouldn't need the below (as csrf tokens should be assigned/accessible via the same port). I haven't tested that aspect out, but if someone does please let me know if you ran into any trouble or not.
For the rest of us, often times django-graphene is running from some port (typically 8000) for dev purposes while a javascript dev environment is running on some other port (typically 3000). This creates a problem where when CSRF protection is enabled, a CSRF cookie is never set for the javascript environment to give a proper security return.
This means you need to e…