Mysterious Django API Behavior In Production - django

Long time reader but newly joined the community. I have a mysterious issue that I would like to seek help with. Since this is a part of the code that is currently running in production I need to redact parts of the info and will provide information is and when required. If needed I will also make a new sample code to make sure we can all test and also guide me the right way to troubleshoot the issue.
Currently, there is an web application ( django/wagtail/coderedcms ) backend that need to be converted into a mobile app. To allow the mobile app to communicate with the Backend server, I have implemented an API endpoint using the Django-rest-framework. To authenticate the user, I have already implemented a simple authentication endpoint using Django-rest-simplejwt. This part is working fine. One of the requirement for the app is that the user must be able to view/update their profile through the mobile app.
When working in dev server in my localhost, The behaviour is as intended, each user may login using the application, the backend will send back a token, and the tokan will be use subsequently to access the various part off the application.
When the same codebase is being implemented in production( currently in UAT stage) one behaviour that is found, If multiple user is logged in at the same time, The server will only return the profile of the first user that request the profile.
meaning :
user A logged in ....
user B logged in ....
user B request to view his profile ( profile B is shown)
user A request to view his profile ( profile B is shown) <- correct behaviour should be Profile A is shown
This baffled me as during testing on localhost, the behavior are:
user A logged in ....
user B logged in ....
user B request to view his profile ( profile B is shown)
user A request to view his profile ( profile A is shown)
I dont know if this is due to docker, nginx or the django production setting.
However, now I can only narrow down to Nginx and docker as I am using the same configuration in localhost beside the debug=true status.
Any help or pointer would be useful.
I thank you in advance for all that read and answer this silly but mysterious question
Sincerely,
Ashraf
Edit
In reply to Ogulcan Olguner
from production server Postman wield this in the header responds
allow →GET, POST, OPTIONS
cache-control →max-age=300
connection →keep-alive
content-encoding →gzip
content-type →application/json
date →Wed, 25 Nov 2020 20:59:03 GMT
expires →Wed, 25 Nov 2020 21:03:30 GMT
server →nginx/1.17.4
strict-transport-security →max-age=31536000
transfer-encoding →chunked
vary →Accept, Origin, Cookie
x-cdn →Incapsula
x-content-type-options →nosniff
x-frame-options →ALLOWALL
x-iinfo →4-22327133-22328885 NNYN CT(2 2 0) RT(1606337880159 62932) q(0 0 0 -1) r(1 1) U16
x-wagtail-cache →hit
for LocalHost
allow →POST, GET, OPTIONS
content-length →204
content-type →application/json
date →Wed, 25 Nov 2020 20:57:45 GMT
server →WSGIServer/0.2 CPython/3.8.5
vary →Accept, Origin
x-content-type-options →nosniff
x-frame-options →DENY

I am guessing since there is not much information about why the error could be caused. If you are not running a separate cache system in production (such as Redis), this error may be caused by the client. Have you tried the same scenario with a tool like Postman?

Related

Keystone session cookie only working on localhost

Edit:
After investigating this further, it seems cookies are sent correctly on most API requests. However something happens in the specific request that checks if the user is logged in and it always returns null. When refreshing the browser a successful preflight request is sent and nothing else, even though there is a session and a valid session cookie.
Original question:
I have a NextJS frontend authenticating against a Keystone backend.
When running on localhost, I can log in and then refresh the browser without getting logged out, i.e. the browser reads the cookie correctly.
When the application is deployed on an external server, I can still log in, but when refreshing the browser it seems no cookie is found and it is as if I'm logged out. However if I then go to the Keystone admin UI, I am still logged in.
In the browser settings, I can see that for localhost there is a "keystonejs-session" cookie being created. This is not the case for the external server.
Here are the session settings from the Keystone config file.
The value of process.env.DOMAIN on the external server would be for example example.com when Keystone is deployed to admin.example.com. I have also tried .example.com, with a leading dot, with the same result. (I believe the leading dot is ignored in newer specifications.)
const sessionConfig = {
maxAge: 60 * 60 * 24 * 30,
secret: process.env.COOKIE_SECRET,
sameSite: 'lax',
secure: true,
domain: process.env.DOMAIN,
path: "/",
};
const session = statelessSessions(sessionConfig);
(The session object is then passed to the config function from #keystone-6/core.)
Current workaround:
I'm currently using a workaround which involves routing all API requests to '/api/graphql' and rewriting that request to the real URL using Next's own rewrites. Someone recommended this might work and it does, sort of. When refreshing the browser window the application is still in a logged-out state, but after a second or two the session is validated.
To use this workaround, add the following rewrite directive to next.config.js
rewrites: () => [
{
source: '/api/graphql',
destination:
process.env.NODE_ENV === 'development'
? `http://localhost:3000/api/graphql`
: process.env.NEXT_PUBLIC_BACKEND_ENDPOINT,
},
],
Then make sure you use this URL for queries. In my case that's the URL I feed to createUploadLink().
This workaround still means constant error messages in the logs since relative URLs are not supposed to work. I would love to see a proper solution!
It's hard to know what's happening for sure without knowing more about your setup. Inspecting the requests and responses your browser is making may help figure this out. Look in the "network" tab in your browser dev tools. When you make make the request to sign in, you should see the cookie being set in the headers of the response.
Some educated guesses:
Are you accessing your external server over HTTPS?
They Keystone docs for the session API mention that, when setting secure to true...
[...] the cookie is only sent to the server when a request is made with the https: scheme (except on localhost)
So, if you're running your deployed env over plain HTTP, the cookie is never set, creating the behaviour you're describing. Somewhat confusingly, in development the flag is ignored, allowing it to work.
A similar thing can happen if you're deploying behind a proxy, like nginx:
In this scenario, a lot of people choose to have the proxy terminate the TLS connection, so requests are forwarded to the backend over HTTP (but on a private network, so still relatively secure). In that case, you need to do two things:
Ensure the proxy is configured to forward the X-Forwarded-Proto header, which informs the backend which protocol was used originally request
Tell express to trust what the proxy is saying by configuring the trust proxy setting
I did a write up of this proxy issue a while back. It's for Keystone 5 (so some of the details are off) but, if you're using a reverse proxy, most of it's still relevant.
Update
From Simons comment, the above guesses missed the mark 😭 but I'll leave them here in case they help others.
Since posting about this issue a month ago I was actually able to work around it by routing API requests via a relative path like '/api/graphql' and then forwarding that request to the real API on a separate subdomain. For some mysterious reason it works this way.
This is starting to sound like a CORS or issue
If you want to serve your front end from a different origin (domain) than the API, the API needs to return a specific header to allow this. Read up on CORS and the Access-Control-Allow-Origin header. You can configure this setting the cors option in the Keystone server config which Keystone uses to configure the cors package.
Alternatively, the solution of proxying API requests via the Next app should also work. It's not obvious to me why your proxying "workaround" is experiencing problems.

nginx API cross origin calls not working only from some browsers

TLDR: React app's API calls are returning with status code 200 but without body in response, happens only when accessing the web app from some browsers.
I have a React + Django application deployed using nginx and uwsgi on a single centOS7 VM.
The React app is served by nginx on the domain, and when users log in on the javascript app, REST API requests are made to the same nginx on a sub domain (ie: backend.mydomain.com), for things like validate token and fetch data.
This works on all recent version of Firefox, Chrome, Safari, and Edge. However, some users have complained that they could not log in from their work network. They can visit the site, so obviously the javascript application is served to them, but when they log in, all of the requests come back with status 200, except the response has an empty body. (and the log in requires few pieces of information to be sent back with the log in response to work).
For example, when I log in from where I am, I would get response with status=200, and a json object with few parameters in the body of the response.
But when one of the users showed me the same from their browser, they get Status=200 back, but the Response is empty. They are using the same version of browsers as I have. They tried both Firefox and Chrome with the same behaviours.
After finally getting hold of one of the user to send me some screenshots. I found the problem. In my browser that works with the site, the API calls to the backend had Referrer Policy set to strict-origin-when-cross-origin in the Headers. However on their browser, the same was showing up as no-referrer-when-downgrade.
I had not explicitly set the referrer policy so the browsers were using each of their default values, and it differed between different versions of browsers (https://developers.google.com/web/updates/2020/07/referrer-policy-new-chrome-default)
To fix this, I added add_header 'Referrer-Policy' 'strict-origin-when-cross-origin'; to the nginx.conf file and restarted the server. More details here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
The users who had trouble before can now access the site API resources after clearing cache in their browsers.

How to resolve Bad Request - Header Field Too Long while redirecting the user to loginRedirect in Msal.js

From our web application we are authenticating user [Azure AD B2C] through Msal.js and version is 1.2.1. Facing an issue while redirecting user to loginRedirect() when acquireTokenSilent throwing "User Login Required".
Issue is "Bad Request - Header Field Too Long" and Http Status is 400, browser [Chrome -Ver - 80.0.3987] cookies had chunk of entries like below -
Forcing user to logout() and redirecting to loginRedirect() did not resolve the issue. Still cookies are available, probably cookies are not cleaning due to this -
msal.js
I want to clear cookies by using "clearMsalCookie" method, but it requires "State". Where do I get the State? or else is there a way to delete the msal cookies. However "resetCacheItems" not clearing the cookies....
Regards,
Deb
--

Django Upstream Caching (Vary On Headers) Not working

I have a view which displays user specific, meaning the content of the response for the same URL is unique per individual authenticated user.
Ideally, these pages would be cached in the browser. However, that does not appear to be the case in Chrome or Firefox (on production or locally).
The development server is processing the view each time, despite the fact that I've set the #vary_on_cookies decorator.
I have the right middleware in place (in the right order):
django.middleware.cache.UpdateCacheMiddleware
django.middleware.cache.FetchFromCacheMiddleware
Do I need to set CACHE_MIDDLEWARE_ANONYMOUS_ONLY = False?
One thing that I've noticed is that the request is sending this cache control header:
Cache-Control:max-age=0
I assume that that might be the root problem. Or is this related to the development server?
Any suggestions?

Django session gets confused behind proxy, already logged in

Currently we're having some issues with a user of our product who uses a proxy on their internal network.
According to their system administrator the proxy is open to port 80 and 443, and doesn't do anything with cookies and such, only blocks out some sites.
The problem: when user X logs in to our application, user Y also gets logged in on a computer who didn't use out application before (but is behind the same proxy)?! This shouldn't be possible (django default auth app is used)?
We're using is Apache, Nginx, Django 1.0 and Postgresql. Also note that it does work when ran with runserver, but not with nginx.
This only occurs with this user with the proxy, on other networks, it does work.
Anyone experienced this before? If so, how'd you solve it?
Thanks in advance!
Stefan
This might be a problem with the cache related headers sent out, for example Cache-Control.
By default, nothing stops a proxy from caching pages served to logged-in users. By sending Cache-Control: private or Cache-Control: max-age=0, you tell the proxy not to cache the page at all, which is needed for private pages.
You can control this with the cache_page decorator per-view,
or by setting CACHE_MIDDLEWARE_ANONYMOUS_ONLY=True to completely disable caching for logged-in users. Of course, this can slow down your page, depending on how complex it is. In that case, you might want to look into doing more fine-grained caching.