1

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.

5
  • is special_route some part of your django app? In your location block there is no proxy_pass or similar directive, perhaps you left that out? Its a little difficult to understand what you want to happen. Commented Nov 5, 2020 at 19:27
  • 1
    Read this question and my comment below. Commented Nov 5, 2020 at 20:55
  • @Andrew Backer Thanks for the quick reply! Yes special route is part of the Django app, the whole server configuration has a working proxy pass so I left that out. Commented Nov 5, 2020 at 21:57
  • 1
    @Ivan Wow, this seems pretty similar, I will check this configuration tomorrow! Thanks for the reply. Commented Nov 5, 2020 at 21:59
  • 1
    Both of you were right, the proxy_pass (...) wasn't inherited from the server config above, which worked with location / for configuring. Does any one of you want to create an answer here or should I just delete the question? Commented Nov 6, 2020 at 6:02

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.