JWT Cookie sent as Set-Cookie causing "Invalid Signature" in DRF jwt - django

I've been trying to solve this problem a week ago, from now on after looking for a solution in almost every forum, blog and lib's github issues I realized that it gonna be easier asking here.
I have a django app using JWT for authentication (Web and Mobile), when I change the user email the mobile app (react native) keeps sending the old jwt in cookies to server which leads to an "Invalid Signature" response (in any endpoint including login)
Here is my djangorestframework-jwt conf:
JWT_AUTH = { 'JWT_VERIFY_EXPIRATION': True, 'JWT_AUTH_COOKIE': "JWT", 'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=3000), 'JWT_ALLOW_REFRESH': True, }
Setting this line
'JWT_AUTH_COOKIE': "JWT",
To
'JWT_AUTH_COOKIE': None,
The server won't look for jwt cookies in request however the next api calls don't find token in Authorization Header which leads to
Authentication credentials were not provided
Even sending the token in Header.
At web app there is no problem with that, so I'd like to know how can I fix it, looking for a way to stop sending JWT cookie from mobile app.

Related

Django SimpleJWT: Some questions with token authentication

I am implementing authentication in Django using SimpleJWT, and have a few questions regarding the same. To give some background I have multiple domains with my backend, some with normal login (username and password), and some with SSO logins.
Question 2:
Suppose, I store the access tokens in local storage and send the access token to all APIs, and I'm also refreshing it before it expires. But what will happen if the user closes the browser, and we are not able to refresh the access token. The access token expires and the user gets logged out. How can we keep the user logged in for a certain amount of time (say 30 days)?
When the Access token expires, you use the Refresh token to obtain a new Access token.
This works because Refresh token has a long life, typically up to 30 days (but can be even longer if you want).
Example:
User closes browser
Comes back 10 days later
User sends a request to the server
Server will return 401 Unauthorized response because Access token has expired
Your app will send a request to obtain a new Access token using the Refresh token
If the Refresh token is valid, server will return a new Access token
If the Refresh token is expired, server will return a 401 response. That means user needs to login again.
Security considerations
Personally, I think JWT for most web apps is not an suitable idea because of conflicting opinions and advice on how to securely store the tokens.
Since a Refresh token is really powerful, it is not advised to store it in the browser. So where do you store it? And that's when this misleading idea of "backendless" web services powered by JWT starts to fall apart.
Paradoxes regarding storing tokens:
Store it in localstorage: Vulnerable to XSS attacks.
This is really serious because the XSS vulnerabilities can also come from third party JS libraries, not just from your own code. Hackers can hijack a third-party library on NPM to inject malicious code and you might be unknowingly using it in your project (it might be a dependency of a dependency of another dependency...).
Store it in httponly cookies: Safe from XSS attacks but requires a first-party backend server (because third-party auth servers can't set cookies for another domain).
If you stop to think about it, you'll notice that this case is exactly similar to the regular session auth where a session token is saved in the cookie. So why not just use session auth instead of this complicated JWT setup?
I'm going to advise you to thoroughly research this and decide whether you really need JWT for your web apps.
JWT auth using cross-origin cookies
Since you mention that your frontend apps connect to an API server in another domain, using JWT seems alright.
If you control the API server, you can setup CORS headers to allow the API server to set cookies on your apps' domains.
Important:
Since this involves Cookies, it is vulnerable to CSRF attacks. But > that is easier to prevent using CSRF tokens.
That means, with every POST request, you'll need to send CSRF token
and the API server must also validate that CSRF token
Here's a diagram I make of the auth flow in that case:
For Question 2, add this code on your settings.py file
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=30),
'REFRESH_TOKEN_LIFETIME': timedelta(days=30),
}

Using JWT authentication with Django/DRF and Storing JWTs in HttpOnly Cookies

I am trying to build a web app using Django and DRF at the back-end and ReactJs at the front end and I want to keep them separate (i.e. avoid Server Side Rendering).For authentication purposes, I want to employ JWT and I am using djangorestframework-jwt for that. I have read it at several places that it is not secure to store JWTs in the local storage so I am trying to use HttpOnly cookies for that. One can achieve that by configuring the django server to send HttpOnly by overriding the following default settings of the drf-jwt package in the settings.py file of your project JWT_AUTH = { 'JWT_AUTH_COOKIE': '<cookie name>', } which is set to none by default. The server sends the httpOnly cookie as anticipated but there are a few issues I am facing:
1.Same Domain Restraint
I am aware that httpOnly cookies wont be attached to the request headers unless the request is being made to the server which is hosted on the some domain. In my case I am using localhost:8000 for django and localhost:3000 for my react project so the browser doesnt attach the cookie as the request is made to a different port. I tried running both app on port 3000 simultaneously, and the browser did attach the cookie in the header and I did get the a 302 response from the server. However, it opened door to all sorts of problems due domain clash. I reckon I can solve this problem using nginx reverse proxy or something like that but I am not sure about it. Do guide me how can I serve both apps on the same host during the development.
2. Token Refresh Problem
When I refer to the view setup to refresh the token, I run into a bad request error even when the browser does attach the cookie along the request header. This is the server response in the browser
{"token":["This field is required."]}
Thanks if for reading it all the way down here!
In order for things to be secure:
You need CORS (Quickstart: CORS_ALLOWED_HOSTS=["http://localhost:3000"], CORS_ALLOW_CREDENTIALS=True)
The short-lived token (session) cookie (5-15mins), should NOT have HTTP-ONLY setting
The refresh token cookie SHALL have HTTP-ONLY setting
Then your basic flow is:
On login Django creates session token and sends it
Your SPA reads the cookie and adds its value to the authorization header (Authorization: JWT ...token...)
Any request to Django should be made with that Authorization header
The refresh flow is:
Send a request to the refresh token endpoint following the documentation of the library you use
Django then reads the HTTP-ONLY cookie and verifies it
If valid, Django sends a new refresh token as HTTP-ONLY cookie along with a new short-lived token session cookie
Once the refresh token has expired, you log the user out.
An article here goes into detail using GraphQL, but the cookie part and handling of most of the frontend code you should be able to adapt to REST.

Can django #login_required be used on APIs used by a mobile app?

We're using django-notifications-hq to provide users with persistent notifications in our app. Until recently, they lived only in the web app, however now we want to show the notifications in the mobile app as well.
Also, our server is set up with CSRF.
All the endpoints provided by django-notifications-hq use #login_required annotation to verify that user is authenticated.
However, when we're trying to call any of those endpoints from our mobile app, we get 403 response. To be specific, the first OPTIONS request returnd 200, but then the following request for those APIs always results in 403.
This is not a problem when we're using i.e. django-rest-framework's permissions.IsAuthenticated.
The 403 returns this in the response:
<h1>Forbidden <span>(403)</span></h1>
<p>CSRF verification failed. Request aborted.</p>
<p>You are seeing this message because this site requires a CSRF cookie when submitting forms. This cookie is required for security reasons, to ensure that your browser is not being hijacked by third parties.</p>
<p>If you have configured your browser to disable cookies, please re-enable them, at least for this site, or for 'same-origin' requests.</p>
Suggesting it's a CSRF problem, even though mobile app obviously doesn't provide cookies in their requests (it's token-based authorization).
Is this a general issue with #login_required (perhaps it can't be used at all with token-based authorization) or is this some misconfiguration on our part? I couldn't find a clear answer when looking for solutions.
Is there a significant implementation difference between django's #login_required vs django-rest-framework
#permission_classes((permissions.IsAuthenticated,))?

Django session causes angular with JWT "token missing or incorrect" error

Setup
An angular application (~1.5) and django with django (1.9.9) rest framework (3.5.3) running on a server with nginx serving the client directly as static files from / and the api through uwsgi from /api
djangorestframework-jwt is in use to provide and validate the JWT tokens for the client.
Reproducing the error:
User logs into the client front end (angular app) successfully.
User clicks a link and GETs some data from the api.
User clicks to edit the data and changes form data and PUTs it to the api
All is well
User gots to /api and logs in via the DRF browsable API with same username and password
Goes back to / and tries to submit a form with PUT again.
Angular forwards the following error from the django server.
{"data":{"detail":"CSRF Failed: CSRF token missing or incorrect."},"status":403, JWT..., "statusText":"Forbidden"}
Finding the cause
Trying to narrow down the issue, I find that there is no csrf cookie nor session cookie found after logging in with the client app. I'm using JWT for authentication, so it shouldn't need any session.
The JWT token is being saved to local storage, not cookies.
After logging into the /api or /administration applications which are handled by django without angular at all, a csrftoken cookie and session cookie are set for that domain.
If I delete the csrf cookie, the angular app still can't PUT or POST. But if I delete the session cookie, it starts to work properly again.
Question
How should I be setting up the Django and/or Angular apps so that the user can login to either one without causing conflict?
Things I've considered
If users don't login to the backend app at all, there is no problem for them. But some will need to occasionally.
If I cause the session cookie to be deleted by Angular, the user will be logged out of the Django app, causing frustration if they need to have both open.
Solution
In working on clarifying this well enough to write a SO post, I've found a solution. Essentially, change Django settings to authenticate against the JWT first, so it never tries to check the session.
See below for more detail.
Since Django tends to process settings in the order they are listed, I tried moving the JSONWebTokenAuthentication line to the top of the list instead of the bottom as it would normally be when following the example on the REST framework JWT Auth docs page.
This worked out. So instead of throwing away the question I was almost done with, I've posted it here for your reading enjoyment.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
A misleading feature of this error is that the csrf token didn't seem to be the actual problem. It was the session token that angular was passing back because angular was being served from the same host as the API.

OAuthException error in app center mobile authentication

I'm trying to do authentication from app center for mobile devices but I get this error when I try to exchange code parameter for access token:
{
"error": {
"message": "Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request",
"type": "OAuthException",
"code": 100
}
}
Auth token parameter is in Query String format because my app uses server-side authentication.
The url I'm calling is https://graph.facebook.com/oauth/access_token and the redirect_uri parameter I'm sending to is like the following one:
http://www.example.com?ref=app_directory&code=codefromfb&fb_source=appcenter_mobile&fb_appcenter=1
www.example.com is the value I've set in mobile site url field in app settings.
Authentication from app center for web it's ok.
I don't understand what it's wrong in redirect uri form mobile devices...
Could you help me?
I found this post referencing needing a trailing slash on the URI
redirect_uri error in oauth for facebook django app
I had the same error. I couldn't solve it but found a workaround:
I ignore the code param that is sent to my mobile web app by Facebook automatically; instead I make a request for code myself, then I exchange code for access_token using the same redirect_uri I used to request for code.
To make it easier to apply the workaround, in your app > settings > permissions, you can change Auth Token Parameter from query string to URI fragment. Then Facebook won't send you code param automatically--you will have to make a request for it--that's what is needed.
Another way to solve it is to implement client-side authentification flow using URI fragment or parse URI fragment at the client-side and send access_token to the server as a param. I didn't test this approach yet.
Redirect URLs that are working for app center authentication
desktop: http://www.example.com/?fb_source=appcenter&fb_appcenter=1
mobile: http://www.example.com/?ref=app_directory
(part fb_source=appcenter_mobile&fb_appcenter=1 should be excluded for mobile, I think that it's FB bug)
Where:
http/https - depends on request
www.example.com - you should use exactly same string as saved at application settings (https://developers.facebook.com/apps/YOUR_APPLICATION_NUMBER/summary) Domain name is case sensitive for Facebook (also bug)