jazzband / djangorestframework-simplejwt

A JSON Web Token authentication plugin for the Django REST Framework.
https://django-rest-framework-simplejwt.readthedocs.io/
MIT License
3.98k stars 660 forks source link

Allow httpOnly cookie storage #71

Open jaycle opened 5 years ago

jaycle commented 5 years ago

Similar to #23 but with a different motivation.

To protect against XSS, I would like the option to store the JWT in an HttpOnly cookie. django-rest-framework-jwt has this feature as an optional setting but that project I believe is abandoned and also has a vulnerability due to preventing the usage of django's CSRF token (see: https://github.com/GetBlimp/django-rest-framework-jwt/pull/434). Combining an HttpOnly cookie with CSRF token would be a pretty rock solid solution.

References: https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage https://stackoverflow.com/questions/44133536/is-it-safe-to-store-a-jwt-in-localstorage-with-reactjs

derek-adair commented 5 years ago

This is super important for me going forward... and it should be for everyone who uses this project. It SEEMS to be rather trivial to set the cookie.

Unsure what other side effects this may have.

fergyfresh commented 5 years ago

@derek-adair I pick the cookie out in nginx and put it in the authorization header. This is a workaround, but it works for me.

derek-adair commented 5 years ago

I'll hopefully be submitting a PR for this in the near future!

davesque commented 5 years ago

Cool, @derek-adair . Looking forward to seeing that!

mrodal commented 5 years ago

I could give it a try too, but I'm not sure how the refresh tokens come into play with cookies.. I think they don't make sense with cookies,

So.. I guess that in order to touch the less amount of code possible, the best solution would be to cookie both tokens, but yeah, the refresh tokens doesn't add any security here

derek-adair commented 5 years ago

@mrodal - yes it does, when its an httpOnly cookie. This may only add an extra step, but exposing your tokens to native, insecure browser api's is not good.

https://blog.codinghorror.com/protecting-your-cookies-httponly/

derek-adair commented 5 years ago

I'm pretty sure the scenario described is the way to go. If anyone else has a more thorough understanding of how to secure these tokens i'd love to learn about it.

While modern browsers support and prevent reading/writing via httpOnly. there are a fair amount of legacy browsers that allow for reading/writing of these headers. So this will really only slow down script kiddies, but its a pretty low effort feature that will add security.

It's my understanding that JWT is inherently insecure and requires certain steps to harden that are likely beyond the scope of this project. I'm certainly no expert on this subject so I welcome any corrections or direction on how to implement this project, or JWT in general... securely.

mrodal commented 5 years ago

what do you mean by "it does"? What I meant is that as far as i know, refresh tokens provide an extra security layer because they are less exposed (they are only used when the access token expires)

But if they are stored in cookies, both travel in each request, so I don't see how refresh tokens make it more secure

davesque commented 5 years ago

@mrodal makes a good point. Refresh tokens are supposed to be sent over the wire less frequently (only when obtaining a new access token or access/refresh pair). It's a bit awkward in the context of web browsers, but JWTs can potentially be used by non-browser clients as well. The access/refresh dichotomy makes more sense in that broader scope.

davesque commented 5 years ago

IIRC, another option would be that the refresh token cookie is only set for the refresh view's path. Then, it would only be sent over the wire for refresh requests in particular.

derek-adair commented 5 years ago

@mrodal it's my understanding that httpOnly + same site cookie is more secure than storing it in memory or localStorage (XSS vulnerability).

If the refresh token is not persisted in a cookie where would you suggest storing it?

Honestly this shit is soooo obtuse... this is the healthiest dialog i've participated around this subject. I'm almost convinced to NOT implement JWT for web browsers. However, i'm not sure the alternative for a single page app.

IIRC, another option would be that the refresh token cookie is only set for the refresh view's path. Then, it would only be sent over the wire for refresh requests in particular.

Could you elaborate? Didn't know it was possible to do this.

derek-adair commented 5 years ago

Does django mitigate XSS?

As someone who possesses JUST enough knowledge about security to fuck it up... i'm quite confused as to how to implement ANY jwt setup securely (in a web browser).

mrodal commented 5 years ago

If the refresh token is not persisted in a cookie where would you suggest storing it?

I would actually not use it at all when using cookies.. Since it requires extra logic in the front-end and, as I said before, I don't see the benefit from using it. This is what Im doing in a mobile app that's also going to be a spa in the future.

Take into account that storing it in the cookie makes you possibly vulnerable to CSRF attacks if you don't use CSRF tokens, since the auth token is automatically added to each request...

Does django mitigate XSS?

If you are using django templates, you are protected by default when printing data from the server side. But you could still be vulnerable if you generate content on the front-end, although last time I checked, browsers also help quite a lot to prevent these attacks.

derek-adair commented 5 years ago

I would actually not use it at all when using cookies

So just make them re-auth when it expires and/or store the actual token for longer?

Since it requires extra logic in the front-end

Now that you mention it... man i wish i would have came to that conclusion before I implemented a replay request thing in my redux app.... ha!

This is what Im doing in a mobile app that's also going to be a spa in the future.

Right, it's clear this is where the miscommunication.

Take into account that storing it in the cookie makes you possibly vulnerable to CSRF attacks if you don't use CSRF tokens, since the auth token is automatically added to each request...

What about if you are using CSRF tokens? Should be good is my understanding.

derek-adair commented 5 years ago

Jeeeeez. So the refresh token seems to be useless in a browser... however....

httpOnly cookie still seems essential for the access-token.

mrodal commented 5 years ago

So just make them re-auth when it expires and/or store the actual token for longer?

Make the expiration time longer.. assuming https is being used, the risk of it being captured in the middle is negligible, and since its an httpOnly cookie, from js it cant be accessed either.. the only possibility I see is an attacker gaining physical access to the device and copying the cookies, but that I believe is always present anyways

What about if you are using CSRF tokens? Should be good is my understanding

Yeah, if you are using both CSRF and httpOnly cookies for the access token you are pretty much covered

Please someone correct me if I'm missing something

davesque commented 5 years ago

@derek-adair This is what I was talking about with the cookie path. If the refresh token is set for the refresh view path, the browser will only include a Cookie header with the refresh token when making requests to the refresh view. So the refresh token is still used more securely and isn't sent over the wire as often as an access token.

What @mrodal is saying about CSRF also applies. Since cookies are automatically sent with any request that matches them, they don't care about what initiated that request or from where it originated. So you can't really tell if the user triggered the request intentionally. You need to include the CSRF token with the request so that Django can verify the request came from an "authorized" source e.g. a redux app hosted on a trusted server. All this stuff is bothersome but necessary. Since JWTs are still relatively new tech, they require that you think more about the nuts and bolts of auth system and why they work the way they do.

As XSS goes, Django does provide out-of-the-box protection against it, but it's not perfect. There are still certain cases in which it can be disabled whether intentionally or unintentionally. You still need to think about how it could happen and verify that the possibility doesn't apply to you.

yoerivdm commented 4 years ago

An HttpOnly Cookie access token combined with a Refresh on a certain url (refresh view) looks nice to me.

As for SPA-clients, you can protect against CSRF by setting the x-requested-with header and checking that one on the server (see https://markitzeroday.com/x-requested-with/cors/2017/06/29/csrf-mitigation-for-ajax-requests.html).

@davesque How would you cover both web and mobile scenario's? I have the same django rest backend for web and mobile. As for mobile, it's ok to send both tokens and store them on the device (secure storage it is). When handling a web-request I want to use the cookie tokens?

Andrew-Chen-Wang commented 4 years ago

I am very... confused as to the purpose of this? Why would you use JWT authentication for web when you have session cookies already? So assuming this is for the web application / the website, here's my opinion:

If anything, you're going to be implementing a lot more JS which means a new point of attack (as previously mentioned in the XSS and CSRF token misplacements). Seems more like a headache to me; providing this would be a disservice.

DRF should really only be used for mobile application and occasionally be used with AJAX on an insecure endpoint. Sure, you can tack on some IP rate limit, but, in reality, you should be using a user rate limit and sending 403 forbidden back to unauthenticated users. But with a user rate limit, you're sending refresh tokens via http which can easily be eavesdropped on... so again:

What's the point of this? Feels more like a security vulnerability to users than a practical feature.

Also a reminder: I could also be interpreting this issue incorrectly :) lemme know if I did.

yoerivdm commented 4 years ago

@Andrew-Chen-Wang I just think we need both :-)

I have one API for both web and mobile, would like to use cookies for the websession part and jwt for API auth. If we can't differentiate the method (cookie vs jwt) then the jwt-token should be stored in a httponly cookie, not readable by js. In this case a few more measures need to be taken to further secure the token.

https://blog.logrocket.com/jwt-authentication-best-practices/

belhassen07 commented 4 years ago

Shouldn't we store the refresh token rather than the access token inside the http only cookie. JWT access tokens are best stored in memory with a countdown to refresh using the refresh endpoint. If you store the access token in the http cookie, like this patch/plugin suggests, where do I store the refresh token.

juhanakristian commented 4 years ago

I liked @davesque's idea of storing both in HttpOnly cookies but setting the refresh token cookie for the refresh path only. This way there's no need to store tokens in localStorage but the refresh token isn't sent with every request.

belhassen07 commented 4 years ago

What's the progress on this issue?

schlunsen commented 4 years ago

I've made a fork using a pull request allowing httponly and merged it with the current master. You can find it here: https://github.com/schlunsen/django-rest-framework-simplejwt

derek-adair commented 4 years ago

DRF should really only be used for mobile application

Definitely false.

derek-adair commented 4 years ago

@Andrew-Chen-Wang This is a really heated and opinionated subject to the point where some have sworn JWT is insecure by design.

You can read up on why you would want to store your tokens here. Honsetly not sure of the quality of that article I just really am sick to death of this subject.

EDIT:

If anything, you're going to be implementing a lot more JS

You set and read cookies. It's really not any different than any other storage method. The ideal solution would be transparent to the client and have no effect if you do not want to use secure cookies.

belhassen07 commented 4 years ago

@schlunsen Sticking with the original package looks safer to me especially when there's still discussions about the PR. I'm no django expert, so I can't tolerate risk, thank you!

Andrew-Chen-Wang commented 4 years ago

@derek-adair If you use that fork (you can pip install via a link) and deploy and publish on GitHub a project using React, Django, and SimpleJWT, then I'd love to see it! I've also added my two cents to the PR that you should probably change. If anything, you may need to just download the fork, import it into your project, and manually change a couple things.

Otherwise, regarding your comment, I agree that this is a very heated topic -- to the point that Calude Peroz, a core Django Developer, made a PR for JWT implementation only to get fired down by other core developers (mainly James Bennett) for security reasons. The link to the discussion. However, I will need to point out that the links James sent regarding security issues is not up to date with the updated RFCs AFTER they have been fixed -- luckily, PyJWT was one of the libraries to be fixed after I'd say the 30 links (including child links) out of 45 (if I can count correctly and if I even read all of them) sent.

The reason I said DRF is for mobile apps is because when I look at React + DRF websites, they all use this package and they all have the same level of implementation issues. If you can create a package that utilizes this fork, then I'd be happy to look over it! I have a Swift and Kotlin repo for using SimpleJWT here; you can make a PR there, and, in the requirements.txt, change the drf-simplejwt to the forked repo. If it's a worthy example, then the merging process can quicken.

Anyhow, tests are not enough for these kinds of packages. A production ready test is necessary. If you'd like, you can also use https://github.com/pydanny/cookiecutter-django to setup your project, deploy on Heroku or an EC2 instance or python anywhere, and testing and analyzing can be done much faster from me and Dave. Otherwise, I promise, this PR will never get merged. It's a hyped fork with no real implementation, yet.

derek-adair commented 4 years ago

@Andrew-Chen-Wang THANK you for this django thread. This should be a great read and maybe there is some clarity here. Like I said, I gave up on JWT do to my lack of comprehension of how one might exploit JWT AND the ridiculous/tedious arguments in said threads.

The reason I said DRF is for mobile apps is because when I look at React + DRF websites, they all use this package

I can agree that this project is only suited for mobile apps and not websites. One of the (presumably) many ways you can use DRF w/o this project is by having django serve the UI.

derek-adair commented 4 years ago

Regarding a production ready test... I had started implementing JWT in a seed project I use. It currently shamefully uses localstorage, and I broke the refresh middleware at somepoint... but it could serve as starting point. I also built this under the assumption that you could set httpOnly cookies form JS (you cant).

Which has lead me to believe that what @fergyfresh is doing... (setting cookie in nginx) seems to be the way its supposed to be used.

derek-adair commented 4 years ago

OOOoooof! Ya those are some really compelling and succinct arguments for not using JWT at all.

Andrew-Chen-Wang commented 4 years ago

Correct. I wouldn’t say JWT is completely bad, as it is useful for mobile apps. All companies including Google, Instagram, Facebook, GitHub, etc. use JWT but add the OAuth protocol in addition to it. If you plan on making just a mobile package, this is the one to check out as the protocols for OAuth are pretty similar to the ones set up here. If you do a website with login, you should just stick with sessions and move on. It’s a tried and tested method. Same with OAuth + JWT but not JWT by itself.

derek-adair commented 4 years ago

Last april Oauth's JWT got cracked.

yoerivdm commented 4 years ago

Still missing websocket and image auth when using jwt. Any suggestions for that? Besides using an image service :-)

Always used (httpOnly) cookie-based tokens because of this

Andrew-Chen-Wang commented 4 years ago

@yoerivdm It depends which websocket you're talking about. For example, the native Django 3.0 websocket (you can check out my configuration at https://github.com/pydanny/cookiecutter-django) would decode method to verify if the token is valid. If you use django channels, then you would use their database_sync_to_async method and do the same validation with the authentication.py.

It would depend heavily on which method you are going for.

Andrew-Chen-Wang commented 4 years ago

@derek-adair When you give developers too much flexibility, you begin to open up several loopholes. Like many many loopholes. Again, this is why I recommend JWT to be only used for mobile clients.

jamehii commented 4 years ago

I believe saving jwt in the cookie in the first place was trying solve the problem of making sure user "stays logged in even after page refresh"

Wouldn't it be better to just set

  1. user's session cookie and
  2. Csrf cookie in the Response along with jwt access & refresh tokens ?

Simply call : login(request) to automatically create the user session before returning the Response

Then the session & csrf middleware will automatically set both csrf & session cookies with HttpOnly & Same-Site = Lax ( default can be changed )

This way, jwt tokens no longer needs to be saved in cookie in order for user stay logged in after page refresh. User now has the session & csrf cookie with HttpOnly & Same-Site = Lax. This also can resolve both XSS & CSRF attack.

Then whenever user logout....simply create an Api endpoint that will call : logout(request) function to delete user's session automatically.

Not sure if I'm getting this right ?

If yes, wouldn't this also mean it has no difference by using user session alone? Why the need for jwt ? Jwt is needed unless for the mobile apps.

To summarize, may be: For browser, use user's session cookie for api call

For mobile apps, use jwt for api call

ahmedosama5200 commented 3 years ago

@Andrew-Chen-Wang

this is why I recommend JWT to be only used for mobile clients.

In case of single page applications, I have to use JWT. Are you planning to release this feature any soon ?

Andrew-Chen-Wang commented 3 years ago

@ahmedosama5200 , @davesque said he needed to take a thorough review of it, but his day job keeps him pretty busy. I can't say I have too much to know about SPA security, but we did set up template repositories for some frameworks to get you started with our best knowledge of how SimpleJWT should work in your monorepo:

263 shows all the JS frameworks we have completed. All you need to do is click on the one you need and press "Use this template". It's not a fork but a copy. It has the entire authentication configuration done for you so you can immediately start working on your SPA.

Bottom line: just don't expose yourself to XSS by any means, as a number 1 rule for any site but particularly JS frameworks. Good luck!

ahmedosama5200 commented 3 years ago

@Andrew-Chen-Wang I took a look at the react template. It uses localstorage to store jwt tokens which is insecure. Any malicious script can read off localstorage values. The best place to store them is in httponly cookies.

I had a react SPA before. here is how I did it, in case someone needed it.

1=> the client sends authentication info (username & password or oauth2 token). On the server, a jwt is created if user is authenticated. we send them back in the response data for mobile clients and also we set an httponly cookie for the web client.

access_token = str(AccessToken.for_user(user))

res = Response({
    'access_token': access_token
}, status=status_code)
res.set_cookie(ACCESS_TOKEN_COOKIE_NAME, access_token, max_age=settings.SIMPLE_JWT.get(
    'ACCESS_TOKEN_LIFETIME').total_seconds(), secure=not settings.CUSTOM_DEBUG, httponly=True)

return res

for that to work, we need to set CORS_ALLOW_CREDENTIALS = True in settings.py

2=> Whenever the client makes a request, the cookies will be sent by default. in case of using axios, you need to set withCredentials:true.

3=> Now, we need to extend JWTAuthentication backend. It expects the request to have an authorization header. what we will do, is to check if we have a cookie named 'access-token' or whatever we named it, we will set the authorization header manually and let JWTAuthentication do its job.

class CookieHandlerJWTAuthentication(JWTAuthentication):
    def authenticate(self, request):
        # If cookie contains access token, put it inside authorization header
        access_token = request.COOKIES.get(ACCESS_TOKEN_COOKIE_NAME)
        if(access_token):
            request.META['HTTP_AUTHORIZATION'] = '{header_type} {access_token}'.format(
                header_type=settings.SIMPLE_JWT['AUTH_HEADER_TYPES'][0], access_token=access_token)

        return super().authenticate(request)

I am not sure if this is the best way to do this. Also we probably should include a refresh token for more security, I guess. Please let me know what you think

Andrew-Chen-Wang commented 3 years ago

@ahmedosama5200 A PR is definitely appreciated.

My initial thought after reviewing all these JS template frameworks was just to get something started. I didn't think a simple documentation was enough for most people to implement this safely. Thus, I wanted template repositories to make sure everyone had a safe and secure way of doing all this.

Unfortunately, if you look at the first conversation in JWT-Angular, we had to go with local storage at first. Luckily, loicgasser is currently making changes to incorporate httpOnly cookies. Because I don't have perms to release this package, I can only say take a look at loicgasser's work.


If anything, I think adding a refresh token isn't necessary (instead just use HS512), and I wish this PR would just turn into its own package for the simple reason of "this shouldn't be token authentication." Because, at the end of the day, the purpose of all this is implementing a stateful session backend.

Also, I've never programmed a JS framework in its entirety before, not to mention incorporating a backend. So please let me know if that's wrong.

trentmurray commented 3 years ago

@Andrew-Chen-Wang - I'm inclined to think you're right. With a little redirect trickery in Django, you can happily use session based authentication with an SPA with no problems.

From a dns point of view, you can structure: api.app.com > django-app sign-in.app.com > django-app app.app.com > vue-app / react-app whatever frontend

When a user browses to app.app.com it checks to see if they have a valid session (via api.app.com/authenticated/). If they aren't authenticated, redirect to sign-in.app.com.

sign-in.app.com renders a template which simply has "window.location.href = app.app.com/login".

Now you have a valid CSFR token and session. You then send the username/password to sign-in.app.com/account/login/ to authenticate the credentials.

The sessionid originally set by the first django-app render now has a valid authenticated user session.

No need for JWTs. You'll notice if you go to many enterprise applications they redirect you all over the place - a some of this is setting server sessions and cookies to avoid storing tokens.

Would it help anyone if I set up a skeleton of this solution in github?

Andrew-Chen-Wang commented 3 years ago

@trentmurray I think an issue with this strategy is localhost testing. I think in production, that should work (if you're setting the session cookie location to be .domain.com), but I worry about local debugging. Additionally, I wouldn't know how that'd do with unittesting with Django's Client class.

leogout commented 3 years ago

Hi, I have been reading through the whole issue without being able to answer this question : Is it possible to use an HttpOnly cookie to pass an access token to the backend with SimpleJWT ? Not a refresh token, as it seems to be the issue here, but an access token.

I do not blame anyone, the answer is certainly here, but I am not able find it.

EDIT: All right I think I found the answer. For other lost people like me : NO, it is not currently possible. Although @ahmedosama5200 shared a very useful piece of code.

EDIT 2 : Ok that piece of code works great and now I am wondering why not using this method ? I can probably make a PR if you do not have time !

EDIT 3 : Well actually it does not work as expected in all cases and things are too complicated for me to understand.

Andrew-Chen-Wang commented 3 years ago

@leogout I think I've come to the conclusion of what people want: currently Django's session middleware is implementing stateful sessions. What people want here is stateless sessions, meaning it's backed by nothing. For both, we encrypt and hit the database to make sure the user is there (ref: https://code.djangoproject.com/ticket/23011). But the statefulness of sessions middleware is we back it by a cache/database.

If we want stateless sessions, then the only move to do is have a single token. Not necessarily a refresh or access; it's just the amount of time before a generated token is invalid.

So to answer your question directly, yes but why bother?

leogout commented 3 years ago

@Andrew-Chen-Wang My thought exactly. I just went with a simple LocalStorage + Authorization header solution. Allowing credentials through CORS + joining the cookie is the same amount of work for a JWT or for a stateful session. Except stateful cookie based sessions are less error prone and last longer than a JWT...

Andrew-Chen-Wang commented 3 years ago

Hm, local storage is still not safe. Lemme elaborate throughout my learning process here:

To me, stateless authentication is still not safe in the hands of the developers since we're all so bad at following rules and standards and protocols etc. Luckily, the default in this repository is the secret key for your Django application, but if you use a weak algorithm, then you secret key can be leaked.

I've said it before but giving too much flexibility to the developer when it comes to security is not good. For example, since you mentioned localStorage, you're at risk for losing all user's confidence in a XSS scripting attack. httpOnly cookies avoid XSS for authorization details. Even if you've lost to a XSS attack with the cookies, at least they can't deal damage to ALL users' confidentiality/details, only some certain details. Twitter gets a XSS attack a bunch, but people's usernames aren't changed.

I think the localStorage option has been the default for every tutorial. I want to merge one of the two PRs for httpOnly cookies soon just to experiment and watch out for CVEs.

navaneeth-dev commented 3 years ago

I'm really confused. Should I use session or jwt for react app? Also i might make a mobile app in the future, so what should i use then?

motaz-hejaze commented 3 years ago

Sure jwt

On Sun, 6 Dec 2020, 8:20 am RizeXor, notifications@github.com wrote:

I'm really confused. Should I use session or jwt for react app? Also i might make a mobile app in the future, so what should i use then?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/SimpleJWT/django-rest-framework-simplejwt/issues/71#issuecomment-739462031, or unsubscribe https://github.com/notifications/unsubscribe-auth/AELSCEYGVWUWVLLBGTAQXPLSTMPBZANCNFSM4GSRP6LA .

Andrew-Chen-Wang commented 3 years ago

@RizeXor You should use JWT for mobile apps for security reasons (not secure connections, no csrf token, etc.). You don't have cookies to set in transit for mobile apps, so you use JWT.

When it comes to a ReactJS app, the current implementation method would be JWT. There's already a repository set up for you that you can download as a template so that you don't need to set it up yourself. Ref #263