0

I use custom User, and I have an email_verified field for this user. I'd like when a user sign in, to be rejected if this field is false.

I can't do it in views.py since users can sign in from various sources (Django site but REST APIs too). The whole purpose is to avoid to write N times the logic for N sign in sources. I'd like to override a method (login() ? authenticate() ?) in models.py to do that only once.

I quickly read the doc about customizing authentication but did'nt find what I'm looking for.

Thanks for help.

David Dahan
  • 10,576
  • 11
  • 64
  • 137
  • Seem like you want a custom authentication backend, have you got your custom **User** model working properly? – Anzel Dec 29 '14 at 01:00
  • everything is explained in the link you provided, in particular this section: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#writing-an-authentication-backend – dnozay Dec 29 '14 at 01:19
  • I guess that does not justify a downvote. Please read the comment above the Anzel answer. Thanks. – David Dahan Dec 29 '14 at 01:22

1 Answers1

8

Please refer to Django Doc: Writing an authentication backend, it's probably what you're after. It covers both your use case on normal login and REST APIs like token authentication:

The authenticate method takes credentials as keyword arguments. 
Most of the time, it’ll just look like this:

class MyBackend(object):
    def authenticate(self, username=None, password=None):
        # Check the username/password and return a User.
        ...
But it could also authenticate a token, like so:

class MyBackend(object):
    def authenticate(self, token=None):
        # Check the token and return a User.
        ...
Either way, authenticate should check the credentials it gets, 
and it should return a User object that matches those credentials, 
if the credentials are valid. If they’re not valid, it should return None.

Once you have written your custom auth backend, you just need to change your default auth backend in your settings.py like this:

AUTHENTICATION_BACKENDS = ('project.path.to.MyBackend',)

Update

Rather than overriding the default authenticate behaviour, you can just include both Backends in your settings, like:

AUTHENTICATION_BACKENDS = ('project.path.to.MyBackend',
                           'django.contrib.auth.backends.ModelBackend',)

The order of the Backends matters, you can read the source code and have a better understanding how the default authenticate and things work together (Read here)

AFAIK this is the preferred way to customise authenticate, as you may one day change your default Backend to something like RemoteUserBackend or whatever (like from RestFramework) and so you can just place your logic (MyBackend) by order in your settings and no need to worry about breaking the code.

Hope this helps.

Anzel
  • 19,825
  • 5
  • 51
  • 52
  • Thanks! Reading the doc, it seems Authentication Backend is generally used to have other sources of Authentication (eg. LDAP). That's not my case. You're right I guess it could work, but I don't need to write a whole new authenticate() method. I just want to override it, like a classic override, using Super(). It should be possible... – David Dahan Dec 29 '14 at 01:20
  • @DavidW. of course you can do that, it's basically the default `ModelBackend` you're trying to **super**, which you can just put both backends together in your `settings.py`, I will update my answer and show you how – Anzel Dec 29 '14 at 01:24
  • The doc says: 'Django tries authenticating across all of its authentication backends. If the first authentication method fails, Django tries the second one, and so on, until all backends have been attempted.' I guess it means if my first backend (where I'll put the email_verified condition) is wrong, it will just try the second one and will get access granted. How ever, I'd like to grant access only if both Backends are 'OK'. Am I missing something? Thanks again for help. – David Dahan Dec 29 '14 at 11:47
  • @DavidW. you're correct, thus you need to implement the whole authenticate logic in your custom backend (or inherit `ModelBackend` class and do your **super**), if you look into the source, it's only a couple of lines, plus adding your custom logic `email_verified`. – Anzel Dec 29 '14 at 13:07
  • @DavidW. not a problem and glad it helps :) – Anzel Dec 29 '14 at 13:13
  • I think this post help me. I meet to problem. #1 how to project.path.to.MyBackend It is installApp.pythonClass.MyBackend #2 override, I don't want to use original username auth, just remain mybackend https://www.agiliq.com/blog/2019/11/django-custom-authentication-backend/ – beehuang Mar 12 '20 at 05:02