So I am trying to deploy my site and basically tried
python manage.py check --deploy
and followed what ever it told me:
WARNINGS:
?: (security.W004) You have not set a value for the SECURE_HSTS_SECONDS setting. If your entire site is served only over SSL, you may want to consider setting a value and enabling HTTP Strict Transport Security. Be sure to read the documentation first; enabling HSTS carelessly can cause serious, irreversible problems.
?: (security.W006) Your SECURE_CONTENT_TYPE_NOSNIFF setting is not set to True, so your pages will not be served with an 'x-content-type-options: nosniff' header. You should consider enabling this header to prevent the browser from identifying content types incorrectly.
?: (security.W007) Your SECURE_BROWSER_XSS_FILTER setting is not set to True, so your pages will not be served with an 'x-xss-protection: 1; mode=block' header. You should consider enabling this header to activate the browser's XSS filtering and help prevent XSS attacks.
?: (security.W008) Your SECURE_SSL_REDIRECT setting is not set to True. Unless your site should be available over both SSL and non-SSL connections, you may want to either set this setting True or configure a load balancer or reverse-proxy server to redirect all connections to HTTPS.
?: (security.W012) SESSION_COOKIE_SECURE is not set to True. Using a secure-only session cookie makes it more difficult for network traffic sniffers to hijack user sessions.
?: (security.W016) You have 'django.middleware.csrf.CsrfViewMiddleware' in your MIDDLEWARE, but you have not set CSRF_COOKIE_SECURE to True. Using a secure-only CSRF cookie makes it more difficult for network traffic sniffers to steal the CSRF token.
?: (security.W017) You have 'django.middleware.csrf.CsrfViewMiddleware' in your MIDDLEWARE, but you have not set CSRF_COOKIE_HTTPONLY to True. Using an HttpOnly CSRF cookie makes it more difficult for cross-site scripting attacks to steal the CSRF token.
?: (security.W018) You should not have DEBUG set to True in deployment.
?: (security.W019) You have 'django.middleware.clickjacking.XFrameOptionsMiddleware' in your MIDDLEWARE, but X_FRAME_OPTIONS is not set to 'DENY'. The default is 'SAMEORIGIN', but unless there is a good reason for your site to serve other parts of itself in a frame, you should change it to 'DENY'.
?: (security.W020) ALLOWED_HOSTS must not be empty in deployment.
Basically setting all those to True in settings.py and set Debug mode to False, and put SECURE_HSTS_SECONDS = 300
However, I forgot to set up my site on a server before doing this and now when I tried to access it, it gives me this error in browser:
Secure Connection Failed
An error occurred during a connection to 127.0.0.1:8001. SSL received a record that exceeded the maximum permissible length. Error code: SSL_ERROR_RX_RECORD_TOO_LONG
The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.
Please contact the website owners to inform them of this problem.
and those in cmd:
[14/Sep/2016 17:40:46] code 400, message Bad request syntax ('\x16\x03\x01\x00|\x01\x00\x00x\x03\x02\xd3\xb8S<\t¿°\xfd½U»ïä\x98\x99h\xb9¥±T~\x129\x05á\xc0V\t\x9a\xe3\x82E\x00\x00\x10\xc0\t\xc0\x13\xc0')
[14/Sep/2016 17:40:46] You're accessing the development server over HTTPS, but it only supports HTTP.
Okay so it clearly tell me the problem: I am not supporting HTTPs but the setting is set to support it. But the weird thing is even after I delete all those settings, the site stays this way and I just can't even access it in development with DEBUG = True.
I even tried to use git to revert to the version before I did this, and it doesn't help. The same error remains. Now I am really worried that maybe I just destroyed it? Please somebody help me
Have you tried a different browser to access your (dev) website? Maybe its because of the value you gave on the setting SECURE_HSTS_SECONDS. If this is too high (say 31536000 == 1 year) the browser will keep hitting the https version of your site.
However, you can clear the browser's HSTS setting. Maybe this can help.
For a better understanding of HSTS (if this is the issue) read this article.
Let me know if this helped you.
Related
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.
I added several variations of the domain to CORS_ALLOWED_ORIGINS and CSRF_TRUSTED_ORIGINS, and
ALLOWED_HOSTS. I added django.template.context_processors.csrf to context_processors. I had neither CSRF_COOKIE_SECURE nor SESSION_COOKIE_SECURE set to true (which I'm hoping I can change). I can't think of anything else I could do to get it to work on Django admin, and it only stopped working after I set up session authentication, (which included configuring csrf and session cookies). When I didn't have any authentication for the frontend, Django login worked fine, and even after, like I said, it was working fine on localhost.
I would really appreciate any suggestions on something else I could try to fix this issue.
Update: Now I'm getting this error message Referer checking failed - https://myapp.herokuapp.com/admin/login/?next=/admin/ does not match any trusted origins.
I thought maybe the fact that it was a heroku subdomain was the issue, so I added a domain, and an SSL certificate. The new domain is getting the exact same error.
My CORS_ALLOWED_ORIGINS includes https://myapp.herokuapp.com and https://www.example.com and, because the slash kept being copied from the browser, https://www.example.com/. And even though there's an SSL certificate, I included the http:// version of each address as well.
The error "Referer checking failed - ... does not match any trusted origins" is not related to CORS_ALLOWED_ORIGINS. It is about the CSRF_TRUSTED_ORIGINS setting.
Make sure to include https://myapp.herokuapp.com:
CSRF_TRUSTED_ORIGINS = ["https://myapp.herokuapp.com", "https://www.myapp.herokuapp.com"]
I have an Angular app using Electron as the desktop wrapper. And there's a separate Django backend which provides HTTP APIs to the Electron client.
So normally when I call the login API the response header will have a Set-Cookie field containing the sessionId. And I can clearly see that sessionId in Postman, however, I can't see this cookie in my Angular app (Dev tools of Electron).
After some further debugging I noticed a warning sign beside my Set-Cookie in dev tools. It said that the cookie is blocked due to the SameSite being set to Lax. So I found a way to modify the server code to return a None samesite (together with a Secure property; I'm using HTTP):
# settings.py
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = 'None'
which did work (and the warning sign is gone) but the cookie is still not visible.
So what's the problem here? Why can't (and How can) I see that cookie so as to make sure that the login works in the actual client, not just Postman?
(btw, now both ends are being developed in localhost.)
There's no need to worry. A good way to check if it works is to actually make a request that requires login (after the API has been Postman tested) and see if the desired data are returned. If so, you are good to go (especially when the warning is gone).
If the sessionId cookie is saved it should automatically be included in the request. Unless there's something wrong with the cookie's path; but a / path would be fine.
Why is the cookie not visible: it's probably due to the separation of front and back ends. In Electron, the pages are typically some local HTML files, as one common step during configuration is to probably modify loadURL or something like that in main.js, for instance:
mainWindow.loadURL(`file://${__dirname}/dist/your-project/index.html`);
So the "site" you are accessing from Electron can be considered as local filesystem (which has no domain and hence no cookie at all), and you should see an empty file:// entry in dev tools -> application -> storage -> cookie. It doesn't mean a local path containing all cookies of the Electron app. Although your backend may be on the same local machine, you are accessing as http:// instead of file:// so the browser (Electron) will treat it as an actual web server.
Therefore, your cookies should be stored in another entry like http(s)://localhost and you can't see it in Electron. (Note that the same cookie will work in both HTTP and HTTPS)
If you use Chrome instead to test, you may be able to see it in all cookies. In some cases where the frontend and backend are deployed to the same host you may see the cookie in dev tools. But I guess there're always some reasons why you need Electron to create a desktop app (e.g. Python scripts).
Further reading
Using HTTPS
Although moving to HTTPS does not necessarily solve the original problem, it may be worth doing in order to prevent potential problems and get ready for the publish.
In your case, for the backend, you can use django-sslserver as a temporary solution before getting your SSL, but it uses a self-signed certificate and may make your frontend complain.
To fix this, consider adding the following code to the main process:
# const { app } = require('electron');
if (!app.isPackaged) {
app.commandLine.appendSwitch('ignore-certificate-errors');
}
Now it provides a good way to distinguish between development (unpacked) and production (packed) and only disables certificate check in development in order to make the code work.
Assuming that SESSION_COOKIE_SECURE in your config refers to cookie's secure flag, You ll have to set
SESSION_COOKIE_SECURE = False
because if this flag is set to True the browser will allow this cookie to be set only if you are using an https connection.
PS: This is just for your localhost. Hopefully you ll be using an Https connection in other environments.
I am trying to redirect my Django + Heroku app from http to https, but I am surprised that I did not find any safe and straightforward way.
According to Heroku:
Issue
You have configured an SSL endpoint and now you want your application
to use https for all requests.
Resolution
Redirects need to be performed at the application level as the Heroku
router does not provide this functionality. You should code the
redirect logic into your application.
Under the hood, Heroku router (over)writes the X-Forwarded-Proto and
the X-Forwarded-Port request headers. The app checks X-Forwarded-Proto
and respond with a redirect response when it is not https but http.
...
Django
Set SECURE_SSL_REDIRECT to True.
So it must be done at Django. This is the most complete answer I found, and this one is also similar.
Django 1.8 will have core support for non-HTTPS redirect (integrated
from
django-secure):
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
In order for SECURE_SSL_REDIRECT to be handled you have to use the
SecurityMiddleware:
MIDDLEWARE = [
...
'django.middleware.security.SecurityMiddleware',
]
Note that both use
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
It seems that without this setting, it is not working on Heroku. And now comes the interesting/scary part. As explained in the docs:
SECURE_SSL_REDIRECT
...
If turning this to True causes infinite redirects, it probably means
your site is running behind a proxy and can’t tell which requests are
secure and which are not. Your proxy likely sets a header to indicate
secure requests; you can correct the problem by finding out what that
header is and configuring the SECURE_PROXY_SSL_HEADER setting
accordingly.
Then, checking about SECURE_PROXY_SSL_HEADER:
Warning
You will probably open security holes in your site if you set this
without knowing what you’re doing. And if you fail to set it when you
should. Seriously.
Which makes me want to find a safer solution... In this other question it says it should be fine, but I don't find it convincing enough to ignore such a warning.
Has Django really not any other solution that is safe to implement?
I am using version 1.11
Update:
I found the django-sslify package, but it also requires setting SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https'), so I guess it doesn't make a difference in terms of potential security holes. Please, correct me if this assumption is wrong.
I think HTTP_X_FORWARDED_PROTO is dangerous if you use it blindly because it makes Django think you are receiving an HTTPS request (even if HTTP_X_FORWARDED_PROTO is, in fact, spoofed).
But, if you are behind a properly functioning load balance/proxy (like AWS) then you can be confident that HTTP_X_FORWARDED_PROTO is being set correctly. In this case, HTTP_X_FORWARDED_PROTO serves to tell Django that it's fine, don't worry about it (because you are trusting the proxy to not allow through a spoofed header), and stop trying to continuously redirect to SSL.
Finally, HTTP_X_FORWARDED_PROTO behind a proxy/load balancer is necessary even if you aren't using SECURE_SSL_REDIRECT = True (e.g. if the redirection is happening in a properly configured web server before getting to Django), because it also affects the is_secure() function on the request, which would always be false if your proxy is swallowing the original request (e.g. it's common to go from HTTPS between the client and your proxy/load balancer to HTTP between your proxy/load balancer and a web server).
Source: Django docs: SECURE_PROXY_SSL_HEADER
I've developed my own website on Django for a while, and today I started to learn how to deploy it. I added this to my settings.py:
SECURE_SSL_REDIRECT = True,
This caused the development server to stop working properly, with this error message:
[13/Jan/2018 16:56:49] code 400, message Bad request syntax ('\x16\x03\x01\x00À\x01\x00\x00¼\x03\x03ßà\x84¼+Jnßþn-ñ\x88ý©vAþK\x83¤²êT\x86\x0b.\x8em\x0b:â\x00\x00\x1cÚÚÀ+À/À,À0̨̩À\x13À\x14\x00\x9c\x00\x9d\x00/\x005\x00')
[13/Jan/2018 16:56:49] code 400, message Bad HTTP/0.9 request type ('\x16\x03\x01\x00À\x01\x00\x00¼\x03\x03\x87')
[13/Jan/2018 16:56:49] You're accessing the development server over HTTPS, but it only supports HTTP.
[13/Jan/2018 16:56:49] You're accessing the development server over HTTPS, but it only supports HTTP.
[13/Jan/2018 16:56:49] code 400, message Bad request version ('JJÀ+À/À,À0̨̩À\x13À\x14\x00\x9c\x00\x9d\x00/\x005\x00')
[13/Jan/2018 16:56:49] You're accessing the development server over HTTPS, but it only supports HTTP.
Why has my server stopped working properly?
Note that when I changed the setting back to SECURE_SSL_REDIRECT = False, the problem didn't go away.
You configured your django site to enforce https by setting SECURE_SSL_REDIRECT = True - which is very good idea for a production setup.
If you set the SECURE_SSL_REDIRECT setting to True, SecurityMiddleware will permanently (HTTP 301) redirect all HTTP connections to HTTPS.
For this reason (and also others) you usually have separate settings for development and produciton. There are a few things that nearly always differ.
Read this to get known to some approches on how to deal with it: Django: How to manage development and production settings?
NOTE
If your browser received 301 once from your site - changing the setting back might have no direct effect, as the browser cached the target URL and does not send a request on HTTP. You need to clear or disable your browsers cache in that case.
The browser has cached the http->https redirect from the previous request when it was working with SECURE_SSL_REDIRECT=True.
Turning it off server side will not effect that cached redirect.
You can selectively clear that for your dev server's url/ip (not everything in the browser cache) and get things working by:
Shutdown your Django dev server
Go to http://127.0.0.1:8000 - it will give you a 404
Open up Chrome's dev tools
Click and hold on the "Reload" button
Select: "Empty Cache & Hard Reload"
Restart Django dev server
Hit http://127.0.0.1:8000 again
If you are part of a team, you can use a variable to set the development environment. for e.g. DJANGO_DEV=development
After that you can check, if current environment is a DEV env and set the specific values.
Read more about this approach on this answer
You might try editing your Edit Configuration and run the server on a different port. In pycharm I changed run -> edit_configurations -> host = 127.0.0.1, Port = 8001.
I then reran the Python Interpreter and it launched again in a new browser without the https. You might need to first change the settings options to select SECURE_SSL_REDIRECT = False in your local_settings or settings.