creyD
creyD

Reputation: 2126

Authentication problem using Nginx and Django Rest Framework

When trying to create a Django Rest Framework based web app, we encountered the following problem: Our whole app should and is protected by our settings.py:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication'
    ),

But for one single route (which provides the schema for the front end code generation) we would like to have basic auth (test:test@host/special_route). The idea was to add the route to nginx like here. Our nginx conf looks as follows:

server {
  ...
  location /special_route {
    auth_basic      "API Schema";
    auth_basic_user_file    /etc/nginx/.htpasswd;
  }
}

Now when accessing this route the authentication popup shows and the password and username are validated, but on success it shows 404. When deleting this piece of code it shows up without the 404, so I am guessing it's not a Django problem.

I tried adding basic authentication to the auth classes, reloading and restarting the server and changing the special_route to avoid misspells. I even experimentally deleted the location and applied the config to the whole server, which worked as expected.

Upvotes: 1

Views: 997

Answers (1)

Andrew
Andrew

Reputation: 8673

Based on your comment the special_route is part of the django app. The issue is that you need to proxy_pass inside the location, since I don't think that this is inherited.

upstream django {
    server 127.0.0.1:8000;
}

server {
    listen 80;
    ...

    location /special_route/ {
        auth_basic              "API Schema";
        auth_basic_user_file    /etc/nginx/.htpasswd;

        proxy_pass http://django;
    }

    location / {
        proxy_pass http://django;
    }

After it is proxied then the normal django rules will apply. I'm not sure how that will work if you are using the Authorization: header for something in your app on that route.

It looks like you are using session auth as well which will still work since it is in a cookie. You could also add auth support for JWT in a cookie, which has the nice side effect of meaning no strange JS magic to send the header. Also I hear it has some XSS benefits, but haven't looked into it yet.

Additionally, DRF supports BasicAuthentication, so you could protect that route in your app directly. The default relies on the built in django users, but you could override that (note, this is from memory, not tested at all):

class SpecialRouteBasicAuth(BasicAuthentication):
    def authenticate_credentials(self, userid, password, request=None):
        # set these the same as other variables in your settings.py
        u = settings.SPECIAL_USER
        p = settings.SPECIAL_PASS
        
        if userid == u and password == p:
            return (AnonymousUser(), None)
        else:
            raise AuthenticationFailed("Invalid Special User/Pass")
     

class SpecialView():
    permission_classes = [AllowAny]
    authentication_classes = [SpecialRouteBasicAuth]

    def get(self, request):
        # do stuff, but don't rely on request.user

This uses the built in AnonymousUser, which has is_authenticated set False. This means the view needs to be "public" with AllowAny. You could imagine a new user class inheriting from that user which just overrides the is_authenticated property if necessary.

Upvotes: 1

Related Questions