Nginx password authentication keeps prompting for password - django

I want to upload a development branch of my website so that I can show it to clients and make tests in an environment as close to production as possible (with code that may not be ready for production). Thus I would like to password protect this site.
I'm developing a website using Django and use nginx for serving the website (with uWsgi). I manage to get prompted for password applying the following directives:
auth_basic "Restricted Content"; # also tried "Private Property"
auth_basic_user_file /etc/nginx/.htpasswd;
But the problem is that after entering the first password properly, it keeps prompting me for the user & password again; as if every API call would need to be authenticated.
I think the issue might be with my configuration file, so here's my site.conf file:
server {
listen 80;
server_name panel.mysite.dev;
root /path/to/my/app/front/dist;
### I've also tried 'auth_basic' here
location / {
root /path/to/my/app/front/dist;
index index.html;
auth_basic "Private Property";
auth_basic_user_file /etc/nginx/.htpasswd;
}
location /media {
rewrite ^(.*)$ http://media.mysite.dev$1;
}
location /static {
rewrite ^(.*)$ http://static.mysite.dev$1;
}
}
server {
listen 80;
server_name api.mysite.dev;
### I've also tried 'auth_basic' here
location /api {
client_max_body_size 25m;
uwsgi_pass unix:/tmp/api.mysite.dev.sock;
include /path/to/my/app/back/uwsgi_params;
}
}
server {
listen 80;
server_name media.mysite.dev;
root /path/to/my/app/media;
add_header 'Access-Control-Allow-Origin' '.*\.mysite\.[com|dev]';
location / {
root /path/to/my/app/media;
}
}
server {
listen 80;
server_name static.mysite.dev;
root /path/to/my/app/static;
if ($http_origin ~* (https?://.*\.mysite\.[com|dev](:[0-9]+)?)) {
set $cors "true";
}
location / {
if ($cors = "true") {
add_header 'Access-Control-Allow-Origin' "$http_origin";
}
}
}
My question: Is there any way to remember the password once entered and allow authenticated users to navigate easily? Or am I missing something trivial?
EDIT:
In my django settings.py:
AUTHENTICATION_BACKENDS = (
'oauth2_provider.backends.OAuth2Backend',
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
...
REST_FRAMEWORK = {
...
DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'oauth2_provider.ext.rest_framework.OAuth2Authentication',
),
Thank you very much in advance. Any help would be much appreciated

Basic authentication uses the Authorization header to transmit user and password. Django REST also uses this header in the TokenAuthentication authentication backend. Nginx does not support multiple Authorization headers, so if you try to login and use Token authentication simultaneously, things will break.
A solution requiring no changes to the Django app would be to use another means of authentication in nginx, e.g., client certificates, or, you can use the ngx_http_auth_request_module to check whether a signed session cookie is set/valid or if the request IP is in a (temporary) whitelist, and redirect the user to a page with a login form otherwise.

There is an excellent, easy and more secured way for Basic Authentication with Nginx. It is, use oAuth2_proxy which has Basic Authentication support including a web form (excellent, browser will not keep connection alive) to input user/password credentials with option to include a password file like htpasswd. It also has support for many other identity providers.
Here's my oAuth2_proxy config file for Basic Auth:
http_address = "0.0.0.0:5160" #use any port you want
upstreams = [
"https://url-that-you-want-to-protect/"
]
request_logging = false
custom_sign_in_logo = "/etc/apache2/small-email-icon.png" #custom logo on form
authenticated_emails_file = "/etc/apache2/emails" #required option, file can be empty
htpasswd_file = "/etc/apache2/.htpasswd" #basic auth password file
display_htpasswd_form = true
client_id = "hfytr76r7686887"
client_secret = "ghgh6767ghgh7654fghj6543dfgh5432"
cookie_name = "_oauth2_proxy"
cookie_secret = "ghgh6gguujgh7654fghj6543dfgh5432"
cookie_expire = "2h15m0s"
# cookie_refresh = ""
cookie_secure = true
footer = "Unauthorized access prohibited."

Related

Http and https confusing for request.build_absolute_uri()? [duplicate]

My original question was how to enable HTTPS for a Django login page, and the only response, recommended that I - make the entire site as HTTPS-only.
Given that I'm using Django 1.3 and nginx, what's the correct way to make a site HTTPS-only?
The one response mentioned a middleware solution, but had the caveat:
Django can't perform a SSL redirect while maintaining POST data.
Please structure your views so that redirects only occur during GETs.
A question on Server Fault about nginx rewriting to https, also mentioned problems with POSTs losing data, and I'm not familiar enough with nginx to determine how well the solution works.
And EFF's recommendation to go HTTPS-only, notes that:
The application must set the Secure attribute on the cookie when
setting it. This attribute instructs the browser to send the cookie
only over secure (HTTPS) transport, never insecure (HTTP).
Do apps like Django-auth have the ability to set cookies as Secure? Or do I have to write more middleware?
So, what is the best way to configure the combination of Django/nginx to implement HTTPS-only, in terms of:
security
preservation of POST data
cookies handled properly
interaction with other Django apps (such as Django-auth), works properly
any other issues I'm not aware of :)
Edit - another issue I just discovered, while testing multiple browsers. Say I have the URL https://mysite.com/search/, which has a search form/button. I click the button, process the form in Django as usual, and do a Django HttpResponseRedirect to http://mysite.com/search?results="foo". Nginx redirects that to https://mysite.com/search?results="foo", as desired.
However - Opera has a visible flash when the redirection happens. And it happens every search, even for the same search term (I guess https really doesn't cache :) Worse, when I test it in IE, I first get the message:
You are about to be redirected to a connection that is not secure - continue?
After clicking "yes", this is immediately followed by:
You are about to view pages over a secure connection - continue?
Although the second IE warning has an option to turn it off - the first warning does not, so every time someone does a search and gets redirected to a results page, they get at least one warning message.
For the 2nd part of John C's answer, and Django 1.4+...
Instead of extending HttpResponseRedirect, you can change the request.scheme to https.
Because Django is behind Nginx's reverse proxy, it doesn't know the original request was secure.
In your Django settings, set the SECURE_PROXY_SSL_HEADER setting:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
Then, you need Nginx to set the custom header in the reverse proxy. In the Nginx site settings:
location / {
# ...
proxy_set_header X-Forwarded-Proto $scheme;
}
This way request.scheme == 'https' and request.is_secure() returns True.
request.build_absolute_uri() returns https://... and so on...
Here is the solution I've worked out so far. There are two parts, configuring nginx, and writing code for Django. The nginx part handles external requests, redirecting http pages to https, and the Django code handles internal URL generation that has an http prefix. (At least, those resulting from a HttpResponseRedirect()). Combined, it seems to work well - as far as I can tell, the client browser never sees an http page that the users didn't type in themselves.
Part one, nginx configuration
# nginx.conf
# Redirects any requests on port 80 (http) to https:
server {
listen 80;
server_name www.mysite.com mysite.com;
rewrite ^ https://mysite.com$request_uri? permanent;
# rewrite ^ https://mysite.com$uri permanent; # also works
}
# django pass-thru via uWSGI, only from https requests:
server {
listen 443;
ssl on;
ssl_certificate /etc/ssl/certs/mysite.com.chain.crt;
ssl_certificate_key /etc/ssl/private/mysite.com.key;
server_name mysite.com;
location / {
uwsgi_pass 127.0.0.1:8088;
include uwsgi_params;
}
}
Part two A, various secure cookie settings, from settings.py
SERVER_TYPE = "DEV"
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True # currently only in Dev branch of Django.
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
Part two B, Django code
# mysite.utilities.decorators.py
import settings
def HTTPS_Response(request, URL):
if settings.SERVER_TYPE == "DEV":
new_URL = URL
else:
absolute_URL = request.build_absolute_uri(URL)
new_URL = "https%s" % absolute_URL[4:]
return HttpResponseRedirect(new_URL)
# views.py
def show_items(request):
if request.method == 'POST':
newURL = handle_post(request)
return HTTPS_Response(request, newURL) # replaces HttpResponseRedirect()
else: # request.method == 'GET'
theForm = handle_get(request)
csrfContext = RequestContext(request, {'theForm': theForm,})
return render_to_response('item-search.html', csrfContext)
def handle_post(request):
URL = reverse('item-found') # name of view in urls.py
item = request.REQUEST.get('item')
full_URL = '%s?item=%s' % (URL, item)
return full_URL
Note that it is possible to re-write HTTPS_Response() as a decorator. The advantage would be - not having to go through all your code and replace HttpResponseRedirect(). The disadvantage - you'd have to put the decorator in front of HttpResponseRedirect(), which is in Django at django.http.__init__.py. I didn't want to modify Django's code, but that's up to you - it's certainly one option.
if you stick your entire site behind https, you don't need to worry about it on the django end. (assuming you don't need to protect your data between nginx and django, only between users and your server)

Cookies with non-ASCII causing all endpoints to give status code 400

I have a website using aspnetcore 2.1 and Kestrel (IIS as proxy) and someone else on our domain sets a cookie that contains non-ASCII characters. I know this is wrong, but I can't make sure they don't and I still want the cookie (otherwise it is easy just to remove it in my web.config).
The result being: Cookie is set incorrectly on one site and when the user "unknowingly" goes to our website, our website gives an error 400 on all requests.
Is there a way of removing the bad characters in the cookie header before it hits Kestrel? I thought I could use a HttpModule/Middleware/etc to remove it, but it looks like Kestrel is the first one to get a HttpContext.
No workaround at the side of dotnetcore. If you are using nginx behind of your dotnetcore application, you can delete all of the non-ascii character like that:
set_by_lua_block $cookie_ascii {
local cookie = ngx.var.http_cookie
if cookie == nil or cookie == '' then return cookie end
local cookie_ascii, n, err = ngx.re.gsub(cookie, "[^\\x00-\\x7F]", "")
return cookie_ascii
}
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:5000;
...
proxy_set_header Cookie $cookie_ascii;
...
}
}

How to avoid basic authentication for AWS ELB health-check with nginx configuration

I'm having a trouble trying to implement basic authentication for ELB healthcheck.
I've searched quite a bit to figure out the nginx file configuration to avoid 401 error shown below, which ELB returns due to basic authentication
unhealthy in target-group hogehoge due to (reason Health checks failed with these codes: [401])
I've tried to modify nginx.conf so as to avoid it, but it doesn't work.
The code below gives me [emerg] "server" directive is not allowed here error.
http {
server {
location / {
if (!$http_x_forwarded_for) {
auth_basic 'Please enter ID and password';
auth_basic_user_file /usr/src/redmine/.htpasswd;
}
}
}
}
How can I avoid 401 error by ELB healthcheck due to basic authentication?
Thanks for the help.
The easiest approach would be to create a location for the ELB, for example:
location /elb-status {
access_log off;
return 200;
}
You will just need to change the Ping Path to be /elb-status
If you want to see something on your browser while testing you may need to change the content-type since defaults to application/octet-stream and the browser will offer to save the file, so something like this should work:
location /elb-status {
access_log off;
return 200 'your text goes here';
add_header Content-Type text/plain;
}
If you would like to check against the user-agent something like this could be used:
set $block 1;
# Allow all the ELB health check agents.
if ($http_user_agent ~* '^ELB-HealthChecker\/.*$') {
set $block 0;
}
if (!$http_x_forwarded_for) {
set $block 1
}
if ($block = 1) {
auth_basic 'Please enter ID and password';
auth_basic_user_file /usr/src/redmine/.htpasswd;
}

Can NGINX change the response code after a proxy_pass?

So I have an internal API server namespaced under /api/, and I want to pass all other requests to an Amazon S3 static site using proxy_pass. This all works fine, it's just since Amazon is serving a single page app, I want to always return the same HTML file. They way I did this with the S3 server, was to set the index and error page as the same file. It all looks fine on the surface, but for all other requests besides /, the S3 instance returns a 404. Can I use NGINX to change this to a 200 before returning it to the client?
server {
listen 80;
server_name example.com;
location /api/ {
# serve internal app
}
location / {
proxy_pass http://example.amazonaws.com/;
# ALWAYS RETURN A 200
}
}
You should be able to use the error_page and proxy_intercept_errors directives to achieve this. Something like this should do the trick.
location / {
proxy_pass http://example.amazonaws.com/;
proxy_intercept_errors on;
error_page 404 =302 /your_html_file
}
error_page
proxy_intercept_errors
You can internally rewrite all URLs to the document you want served. This avoids the error handling cycle and problematic redirects.
It would be something like (untested):
location / {
proxy_pass http://example.amazonaws.com/;
rewrite ^.* /index.html
}
Note that you will want to only use full or root-relative URLs in your doc, because you don't know if the docs is served from a subdirectory.
You'd also be wise to have JS code validate the URL and optionally redirect to one you consider valid. Otherwise 3rd party sites could link to offensive URLs and get them in search indexes!

Django Nginx X-Accel-Redirect for protected files on Webfaction

If you want to torment someone until the end of time, just get them to configure Django and Nginx X-Accel-Redirect. This is literally impossible, I have been trying for days.
I am trying to only allow certain files to be downloaded from logged in views in django using Nginx on webfaction. Here is what I have:
Custom Nginx app listening on port 27796 under /static. Here is the conf.
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 27796;
server_name myurl.com;
root /home/ucwsri/webapps/static_media_ucwsri_nginx;
location / {
autoindex on;
}
location ^.*/protected-files {
internal;
alias /home/ucwsri/webapps/static_media_ucwsri_nginx/protected;
}
All static content is in /home/ucwsri/webapps/static_media_ucwsri_nginx, and is being correctly served by this Nginx app.
The files I want protected are here:
/home/ucwsri/webapps/static_media_ucwsri_nginx/protected
Which is the alias listed under the location ^.*/protected-files block in Nginx.
The view simply makes an Http Response thus:
response = HttpResponse()
url = "/static/protected-files/some-file.pdf"
response['X-Accel-Redirect'] = url
return response
Where the 'some-file.pdf' file exists in
/home/ucwsri/webapps/static_media_ucwsri_nginx/protected
Whatever I try I get a 404 from Nginx when trying to get that file as a POST request that goes to that view. I have tried everything I can think of, every location combination block, nothing works. Always a 404.
Someone please put me out of my misery and tell me what I have done wrong. This is truly brutal for something seemingly so simple.
First, your location ^.*/protected-files is nonsense. I guess, you've missed ~ modifier, but even in that case it would be useless.
Second, you have not protected /protected/ folder. Direct request to /protected/some-file.pdf will download that file without any protection.
Third, you have /static/protected-files/some-file.pdf in X-Accel-Redirect, but you didn't mention any static folder before.
So, I would suggest following config:
server {
listen 27796;
server_name myurl.com;
root /home/ucwsri/webapps/static_media_ucwsri_nginx;
location / {
autoindex on;
}
location ^~ /protected/ {
internal;
}
And django should be:
response = HttpResponse()
url = "/protected/some-file.pdf"
response['X-Accel-Redirect'] = url
return response
Summary:
Protect real folder.
X-Accel-Redirect is URI, just think about it as if user put that URI in browser address bar. The only difference is that internal will allow access with X-Accel-Redirect while forbid direct user access from browser.