Mad Scientist
Mad Scientist

Reputation: 18553

Authenticate by IP address in Django

I have a small Django application with a view that I want to restrict to certain users. Anyone from a specific network should be able to see that view without any further authentication, based on IP address alone. Anyone else from outside this IP range should be asked for a password and authenticated against the default Django user management.

I assume I have to write a custom authentication backend for that, but the documentation confuses me as the authenticate() function seems to expect a username/password combination or a token. It is not clear to me how to authenticate using IP addresses here.

What would be the proper way to implement IP address-based authentication in Django? I'd prefer to use as much existing library functions as possible for security-related code instead of writing all of it myself.

Upvotes: 20

Views: 19906

Answers (6)

Mp0int
Mp0int

Reputation: 18727

There are two suitable approaches for that kind of authentication:

  • As Decorator: if some of views (but not many of them) requires this check, then it is better to write a decorator for that (something like @Jingo had written)
  • As Middleware: if that check needed to be done by all (or many) views, instead of using a decorator, writing a middleware is a better solution.

A sample middleware can be something like:

ALLOWED_IP_BLOCKS = [......]

class NeedToLoginMiddleware(object):
    def process_request(self, request):
        ip = request.META['REMOTE_ADDR']
        if not ip in ALLOWED_IP_BLOCKS: #ip check
            if not request.user.is_authenticated(): #if ip check failed, make authentication check
                return HttpResponseRedirect(...)
        return None
  • You can make ip check using a list, or a regex as @Jingo mentioned.
  • If you are using django authentication and REMOTE_ADDR is not in ALLOWED_IP_BLOCKS list, then you can use is_authenticated to check if related user had logged in or not. But for using is_authenticated in a custom middleware, your custom middleware must be placed after AuthenticationMiddleware, because request.user is set on that level.

    MIDDLEWARE_CLASSES = (
        ...
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'path.to.my.NeedToLoginMiddleware',
        ...
    )
    
    • If a few views do not require this authentication, then you can make a list of exceptional urls and get the request url from request.path and check if the request url requires ip check/authentication.

More info about custom middleware classes

Upvotes: 15

freylis
freylis

Reputation: 814

def login_by_id(request):
    ip = request.META['REMOTE_ADDR']
    try: UserProfile.objects.get(allow_ip=ip)
    except UserProfile.DoesNotExist: return HttpResponseRedirect('././')
    else:
        # auth here

You need allow_ip in you UserProfile model, which save on registration or changes on edit user page

Upvotes: 1

Ranvijay sachan
Ranvijay sachan

Reputation: 2444

You can try this decorator. I have tested its working fine:

allowedIps = ['129.0.0.1', '127.0.0.1']
def allow_by_ip(view_func):
    def authorize(request, *args, **kwargs):
        user_ip = request.META['REMOTE_ADDR']
        for ip in allowedIps:
            if ip==user_ip:
                return view_func(request, *args, **kwargs)
        return HttpResponse('Invalid Ip Access!')
    return authorize

Upvotes: 4

Paul Whipp
Paul Whipp

Reputation: 16521

IMO, solving this with Django is fine if it's a small non performance critical site.

It's better to keep the unauthorized users fully at bay using your Apache or Nginx service. For example, in Nginx I have these lines in my site configuration:

include allowed_ips.conf;
deny all;
error_page 403 forbidden.html;

allowed_ips.conf is in /etc/nginx and looks (something) like this:

allow 110.222.333.222;  # J Bloggs (sys admin)
allow 777.222.0.0/16;   # Government owned
...

I believe this is better because the relatively slow Django processes never get touched by the blocked IPs. This is significant if you are blocking bots or other country address ranges for performance or security reasons.

Upvotes: 5

Jingo
Jingo

Reputation: 3230

You can also write a small decorator for this purpose:

def login_by_ip(view_func):
    def authorize(request, *args, **kwargs):
        user_ip = request.META['REMOTE_ADDR']
        for ip in allowedIps.allowedIps:
            authenticated_by_ip = re.compile(ip).match(user_ip)
            if authenticated_by_ip:
                return view_func(request, authenticated_by_ip, *args, **kwargs)
        return HttpResponseRedirect('/redirect/path/')
    return authorize

allowedIps is in my case a file (allowedIps.py) which stores the regexes for allowed IPs in a tuple like this:

allowedIps = ('^XXX\.XXX\..+\..+$','^XXX\.XXX\.XXX\..+$', '^XXX\.XXX\.XXX\.XXX$')

Hope this can help or give an idea. Note: if you return authenticated_by_ip to the decorated view, your view will have to accept that parameter, you also can just ommit it, if you dont need it. You can also define the regexes more precisely to only accept digits up to three.

Upvotes: 8

Hedde van der Heide
Hedde van der Heide

Reputation: 22449

There's no need to write an authentication backend for the use case you have written. Writing an IP based dispatcher in the middleware layer will likely be sufficient

If your app's url(s) is/are matched, process_request should check for an authenticated django user and match that user to a whitelist.

Upvotes: 3

Related Questions