3

I've a custom user model and an authentication backend. When I'm using shell the authentication function works fine but when it comes to Admin site login, it is just not working.

The code comes from Official Django docs

settings.py

# Authentication
AUTH_USER_MODEL = 'BBUser.UserInfo'
AUTHENTICATION_BACKENDS = [
    'BBUser.models.AuthBackend',
    'BBUser.models.AdminAuthBackend',
]

models.py

class UserInfo(AbstractBaseUser, PermissionsMixin):
    openid = models.CharField(max_length=50, null=False, unique=True)
    nickname = models.CharField(max_length=30, null=True)
    ...

    USERNAME_FIELD = 'openid'
    REQUIRED_FIELDS = []
    ...

    objects = UserManager()


class AdminAuthBackend(object):
    def authenticate(self, openid=None, password=None):
        print "custom auth"
        login_valid = ('100' == openid)
        pwd_valid = ('tobeno.2' == password)
        if login_valid and pwd_valid:
            try:
                user = UserInfo.objects.get(openid=openid)
            except UserInfo.DoesNotExist:
                user = UserInfo(openid=openid, password='get from .py')
                user.is_staff = True
                user.is_superuser = True
                user.save()
            return user
        return None

    def get_user(self, user_id):
        try:
            return UserInfo.objects.get(pk=user_id)
        except UserInfo.DoesNotExist:
            return None

As I've put a print in AdminAuthBackend.authenticate(), so I know when it is executed.

When I'm invoking authenticate() from shell, the custom auth method is invoked while in admin page, it is not.

Any help is appreciated.

4 Answers 4

8

I found the answer myself.

After digging into Django's source code, I find this snippet in django.contrib.auth.forms that handles the post request from admin site's login form:

def clean(self):
    username = self.cleaned_data.get('username')
    password = self.cleaned_data.get('password')

    if username and password:
        self.user_cache = authenticate(username=username,
                                       password=password)
        if self.user_cache is None:
            raise forms.ValidationError(
                self.error_messages['invalid_login'],
                code='invalid_login',
                params={'username': self.username_field.verbose_name},
            )
        else:
            self.confirm_login_allowed(self.user_cache)

    return self.cleaned_data

Notice that although it uses auth.authentication to handle the login action, it actually parses fixed fields username and password, which means the default admin site login form neglects your own AuthenticationBackends.

To solve this, I need to implement my own login-form for admin site.

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

1 Comment

Thanks! I took a different approach and ended up making authenticate more flexible by allowing username and password: def authenticate(self, request, username=None, password=None, key=None): if key is None: # use username/password to log in an admin pass # my original code below
5

This is my solution.. hope it will help someone:

AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend' #add this line
    'your auth backend'
]

the default django.contrib.auth.backends.ModelBackend class is retained to ensure the default username / password authentication workflow used by the django administration. Then your custom authentication back-end class is added, so you can make your changes.

3 Comments

Hi :),Please provide a little more description to your solution.
for some reason not true for mine. it skips ModelBackend and goes to my custom backend. This is even though I have a custom user class but the request from Admin still uses user and password. i think the first answer here is the only solution in this case.
I cannot edit since an edit must be at least 6 characters, but you're missing the comma between the list elements, which will result in a single element being the concatenation of the two strings
1

I got exactly the same issue when using a custom model with email authentification. I manage to solve this issue by creating a new backend class. From what I understand, django is looping over all the backends defined in your application checking if any has the exact import paramaters.

In you case, maybe something like that will work

class EmailAdminBackend(ModelBackend):
def authenticate(self, request, username=None, password=None):
    UserModel = get_user_model()
    try:
        # Check if the user exists in Django's database
        user = UserModel.objects.get(openid=username)
    except :
        return None
    if user.check_password(password):
        return user
    return None

Comments

0

Although it is late answer, it can help someone: How you customize the authentication backend it doesn't matter. But one thing you should remember in mind that you have to include ModelBacked in AUTHENTICATION_BACKENDS. Otherwise you can't login to your django admin.

Now your AUTHENTICATION_BACKENDS should look like:

AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',
    'BBUser.models.AdminAuthBackend',
    ]

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.