Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication (JWT / CSRF) #593

Closed
gotexis opened this issue Mar 12, 2019 · 33 comments
Closed

Authentication (JWT / CSRF) #593

gotexis opened this issue Mar 12, 2019 · 33 comments
Labels
Docs enhancement Issues that can be resolved with better docs question wontfix

Comments

@gotexis
Copy link

gotexis commented Mar 12, 2019

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?

@TitanFighter
Copy link

Just in case if you use Django: https://github.com/flavors/django-graphql-jwt

@Diggitysc
Copy link

Diggitysc commented Mar 12, 2019

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)

@gotexis
Copy link
Author

gotexis commented Mar 12, 2019

@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
Apollo-auto-refreshing-jwt https://github.com/newsiberian/apollo-link-token-refresh

@Diggitysc
Copy link

Diggitysc commented Mar 13, 2019

@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).

@Diggitysc
Copy link

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/
http://cryto.net/%7Ejoepie91/blog/2016/06/19/stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work/

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

@gotexis
Copy link
Author

gotexis commented Mar 15, 2019

As I recall from reading one article, the main problem of criticism of JWT is the lack of a suitable alternative...

@Diggitysc
Copy link

Diggitysc commented Mar 18, 2019

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:

`from graphql_jwt.decorators import jwt_cookie`
 
urlpatterns = [
    path('graphql/', jwt_cookie(GraphQLView.as_view(graphiql=True))),
]

Then copy the resolve_me query courtsey of https://www.howtographql.com/graphql-python/4-authentication

(in query)

me = graphene.Field(UserType)

def resolve_me(self, info):
       user = info.context.user
       if user.is_anonymous:
           raise Exception('Not logged in!')

       return user

What does this do?
The jwt_cookie decorator assigns a JWT cookie AND logs the relevant user requesting the token in. Pretty handy!

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)
https://www.youtube.com/watch?v=WzfJgCOMIsU&feature=youtu.be&t=24m20s

TLDW:

  • XSS insecure
  • Cookies are secure (with HttpOnly and Secure flags w/ CSRF protection, feel free to keep off for dev/test if not exposed to the web)
  • Javascript cannot access cookies (when HttpOnly is set to true which prevents XSS), so how do we get relevant data?

Solution with jwt_cookie decorator:
The browser stores a cookie with the relevant JWT data required. The client-in-browser can then access relevant endpoints that expose the sensitive data (username etc) without exposing the JWT or other authentication data to XSS attacks via javascript (javascript cannot access cookies). Django has CSRF protection (CSRF in separate token that is not HttpOnly so it is readable) in place to provide cookie security (be sure to use settings carefully to avoid security/implementation problems).

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':
https://www.apollographql.com/docs/react/recipes/authentication.html

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

@gotexis
Copy link
Author

gotexis commented Mar 25, 2019

@Diggitysc

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

flavors/django-graphql-jwt#85

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:

  • Use Django-CORS-Header
    -- ALLOW_ALL_ORIGIN: True
  • SESSION_COOKIE_SAMESITE: None
  • CSRF_COOKIE_SAMESITE = 'None'
  • SESSION_COOKIE_HTTPONLY = False
  • CSRF_COOKIE_HTTPONLY = False
  • disable Clickjacking.Middleware
  • CSRF_TRUSTED_ORIGINS = ["10.0.75.1:8080", "10.0.75.1"]
  • adding ensure_csrf decorator to wrap GraphQLView

Nothing worked.... OMFG

I have even checked the dispatch function of Graphene's Views, and did saw they implement a ensure_csrf on one of the functions.

So exactly as you said

CSRF cookie on request as that halts the post process altogether

And that means the introspection query cannot proceed, so nothing works ....

Checking:

  • Add a breakpoint to CSRF middleware to see if the CSRF has been set on header: checked, it seems that cookies can successfully be included in responses, can even be set on the client browser, but there is no way to read it from my SPA, because SPA cannot access cross-site cookies...

  • Just make a GET call to obtain the CSRF_TOKEN before initializing the Vue App... Some would claim this would defeat the purpose of CSRF protection, but currently I see this is the only effective way, but then maybe it's better for CSRF to be disabled for good.

adamchainz/django-cors-headers#162

p.s. Plz help me do Oauth buddy :)

@Diggitysc
Copy link

Diggitysc commented Mar 27, 2019

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:

INSTALLED_APPS = [
    ...,
    'corsheaders',
    ...
]

MIDDLEWARE = [
    ...,
    # just after django.middleware.security.SecurityMiddleware
    'corsheaders.middleware.CorsMiddleware',
    ...,
]

CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = ['<host>:3000']
CSRF_TRUSTED_ORIGINS = ['<host>:3000']

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:

from django.http import JsonResponse
from django.middleware.csrf import get_token

def csrf(request):
    return JsonResponse({'csrfToken': get_token(request)})

and in urls.py add:


from . import views

urlpatterns = [
    ...
    path('csrf/', views.csrf),
    ...
]

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

  1. Set a 'csrftoken' cookie
  2. 'X-CSRFToken' header response
  3. Do the above asynchronously to avoid csrf refresh problems (huge pain to figure out)

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.

import Cookies from 'js-cookie';
import ApolloClient from 'apollo-boost';
import { InMemoryCache } from 'apollo-cache-inmemory';

const client = new ApolloClient({
    uri: '<graphql-uri>',
    // tell apollo to include credentials for csrf token protection
    credentials: 'include',
    // async operation with fetch to get csrf token
    request: async (operation) => {
      const csrftoken = await fetch('<url csrf endpoint>/csrf/')
        .then(response => response.json())
        .then(data => data.csrfToken);
     // set the cookie 'csrftoken'
      Cookies.set('csrftoken', csrftoken);
      operation.setContext({
        // set the 'X-CSRFToken' header to the csrftoken
        headers: {
          'X-CSRFToken': csrftoken,
        },
      });
    },
    cache: new InMemoryCache(),
  });

If you are using Apollo provider for the apollo link in react component:

import { ApolloProvider } from 'react-apollo';

<ApolloProvider client={client}>

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

@Diggitysc
Copy link

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 links

In async time:
/csrf/ is called on first components (cookie is set)
/csrf/ is called on second component (cookie is set)
component 1 fails post as csrf does not match (Forbidden)
component 2 succeeds post request

Sometimes you get lucky and
/csrf/ is called on first component (cookie is set)
component 1 succeeds post request
/csrf/ is called on second component (cookie is set)
component 2 succeeds post request

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

@phalt phalt added the Docs enhancement Issues that can be resolved with better docs label Apr 25, 2019
@bastiW
Copy link

bastiW commented May 28, 2019

@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(),
    })

@bastiW
Copy link

bastiW commented May 28, 2019

Now I also see the problem with calling two gql queries on one page. I get a 403 CSRF verification failed. Request aborted.

This happons usually on Firefox and on chrome within the incognito window

@bastiW
Copy link

bastiW commented May 29, 2019

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.

@Diggitysc
Copy link

@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

  1. Graphene receives a request
  2. Graphene sets the httpOnly cookie X-CSRF-:
  3. Graphene checks for the specific X-CSRF tokens associated with the request
  4. The cookies are set with an extremely short expire rate to clean up the cruft

Downsides:

  1. This seems like it would be much more intensive a process (specific key look up instead of single key)
  2. Performance degradation increases linearly with additional requests. With 100 requests that would be 100 cookies to track and manage.
  3. As far as I know browsers don't always obey cookie deletion rules

@stale
Copy link

stale bot commented Jul 28, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Jul 28, 2019
@stale stale bot closed this as completed Aug 4, 2019
@mcabrams
Copy link

mcabrams commented Sep 14, 2019

@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

@mcabrams
Copy link

FWIW, I have solved for now like so (note, I'm actually not revoking a token currently, but would love to learn how to do so):

# urls.py
from django.contrib import admin
from django.urls import path

from graphene_django.views import GraphQLView
from graphql_jwt.decorators import jwt_cookie

class CustomGraphQLView(GraphQLView):
    def dispatch(self, request, *args, **kwargs):
        response = super().dispatch(request, *args, **kwargs)
        response = self._delete_cookies_on_response_if_needed(request, response)
        return response

    def _delete_cookies_on_response_if_needed(self, request, response):
        data = self.parse_body(request)
        operation_name = self.get_graphql_params(request, data)[2]

        if operation_name and operation_name == 'Logout':
            response.delete_cookie('JWT')

        return response


urlpatterns = [
    path('admin/', admin.site.urls),
    path('graphql', jwt_cookie(CustomGraphQLView.as_view(graphiql=True))),
]
# schema.py
....

class Logout(graphene.Mutation):
    """ The real action happens in our custom GraphQLView """
    # Just because we have to have a field, we put this; the meat occurs in the
    # GraphQLView where we remove the cookie
    noop = graphene.Field(graphene.Boolean)

    def mutate(self, info):
        # TODO: Consider revoking token here?
        pass

....

@Diggitysc
Copy link

Diggitysc commented Sep 16, 2019

@mcabrams For my JWT solution I am utilizing: https://django-graphql-jwt.domake.io/

https://django-graphql-jwt.domake.io/en/latest/refresh_token.html?highlight=revoke#long-running-refresh-tokens outlines how to use revoke token with the framework.

Or if you are being super lazy:

The django schema:

import graphene
import graphql_jwt


class Mutation(graphene.ObjectType):
    token_auth = graphql_jwt.ObtainJSONWebToken.Field()
    verify_token = graphql_jwt.Verify.Field()
    refresh_token = graphql_jwt.Refresh.Field()
    revoke_token = graphql_jwt.Revoke.Field()

schema = graphene.Schema(mutation=Mutation)

Then for the graphql call to revoke the jwt token when logout is triggered:

mutation RevokeToken($refreshToken: String!) {
  revokeToken(refreshToken: $refreshToken) {
    revoked
  }
}

@johnpaul89
Copy link

@Diggitysc @gotexis @mcabrams @mongkok Do you know how to send a refreshToken using a cookie and then implementing it on the frontend side like on this code https://github.com/benawad/jwt-auth-example/tree/master/web where this guy is creating a separate link '/refresh_token/' which has the refresh token cookie and then which updates the accesstoken hence persisting the user without be logged in.

Check this three latest tutorial of his https://www.youtube.com/results?search_query=ben+awad and see how he is storing tokens and sending them using a cookie.

The problem that I'm getting is how to send the refreshToken using a cookie on its path .
Please help.

@Diggitysc
Copy link

@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.

@demeralde
Copy link

demeralde commented Sep 27, 2019

@Diggitysc I'm confused what the purpose of the csrftoken cookie is. It's set but the value is never used anywhere in the future. Am I missing something?

@Diggitysc
Copy link

@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.

@demeralde
Copy link

@Diggitysc sorry what I meant was why does the csrfcookie need to be set on the client:

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 localhost:3000), therefore it's not accessible by the server (e.g localhost:8000). And since the CSRF token is sent with requests via the X-CSRFToken header shouldn't there be no need for the csrftoken cookie on the client anyway?

@Diggitysc
Copy link

@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.

@demeralde
Copy link

demeralde commented Sep 27, 2019

@Diggitysc I just tried and it looks like both are required. Not sure why that is, I was just wondering how it works 😁

@csyan5
Copy link

csyan5 commented Oct 1, 2019

Hi @Diggitysc, I'm implementing JWT authentication with Django+GraphQL, with the package django-graphql-jwt(https://django-graphql-jwt.domake.io/en/latest/). Basically I need to enable a third-party server for a ticket validation. The process is like the following:

  1. User goes to my frontend and click login. Then it goes to the third-party to input their username and password. This will return a ticket.

  2. My frontend will send the ticket to my backend (Django) and my backend will then validate this ticket with that third-party server. This will return the username and set a token to cookies (generated from django-graphql-jwt) if the ticket is valid.

My question is that how could I customize the input (a ticket instead of username and password) for generating the token. I found the documentation here (https://django-graphql-jwt.domake.io/en/latest/customizing.html). But it seems no enough details for me to implement.

I've been looking into the source code for a while, and it seems I need to override JSONWebTokenMutation(https://github.com/flavors/django-graphql-jwt/blob/master/graphql_jwt/mutations.py#L19) and token_auth decorator (https://github.com/flavors/django-graphql-jwt/blob/master/graphql_jwt/decorators.py#L66).

Could you please help and suggest? Many thanks!

@Diggitysc
Copy link

@ChangShengYan

If you are utilizing a 3rd party server I would look into how people are integrating with auth0/oauth

@johnpaul89
Copy link

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.
Big up.

@Diggitysc
Copy link

@johnpaul89 Django has two settings to adjust HttpOnly cookies and the csrf cookie domain

in settings.py
CSRF_COOKIE_HTTPONLY = <true/false> (for HttpOnly cookies)
CSRF_COOKIE_DOMAIN = '<ip address/range>' (for samesite/whitelisted site)

@CCoffie
Copy link

CCoffie commented Feb 5, 2020

@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

@Diggitysc
Copy link

@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)

@CCoffie
Copy link

CCoffie commented Feb 6, 2020

@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.

@Diggitysc
Copy link

@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.

@graphql-python graphql-python locked and limited conversation to collaborators Apr 14, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
Docs enhancement Issues that can be resolved with better docs question wontfix
Projects
None yet
Development

No branches or pull requests