2

I need some flexibility in my authentication scheme. By this flexibility I mean that I do not want to rely exclusively on User model or on any model. In pseudo-code I want to get this kind of logic:

class MyCustomAuthentication(authentication.BaseAuthentication)
    def authenticate(self, request):
        email = request.META.get('X_EMAIL')
        password = request.META.get('X_PASSWORD')
        # Here I want to connect to my database
        # then make a query and verify if there exists a row that
        # corresponds to email and password
        # If it exists, then authentication is passed
        # if not, then it is not passed

@api_view()
@authentication_classes((MyCustomAuthentication))
def items(request):
    return Response({"message":"Hello world!"})

So, as you see I do not want ro rely on ORM, I just want to use my friendly sql to do the whole business myself. But I do not know how and what I should return from authenticate.

2
  • I guess the question would be... why not use the ORM? Doing unnecessary raw SQL in Django would be an anti-pattern, though of course you can still do it. Django authentication is based around having a User model so it's not going to work for you. You need to make your own middleware, following how the Django auth middleware works: github.com/django/django/blob/master/django/contrib/auth/… Commented Mar 14, 2015 at 11:11
  • Doing unnecessary raw SQL would be an anti-pattern, for sure. But the problem is, I want my user define what database to use. In my application (baby application which now has a tiny code library), he or she can switch between 10 different relational and non-relational databases. So, an associated user information can be stored in any kind of database. For this reason, I find it more flexible just to rely on hard-coded sql or other kinds of queries. Commented Mar 14, 2015 at 11:49

3 Answers 3

11

Jacobian, you need to import the @authentication_classes(...) decorator. To do that just add the following line at the top of your file:

from rest_framework.decorators import authentication_classes

Source: http://www.django-rest-framework.org/api-guide/views/

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

Comments

1

I post some code which I use in my project.

In settings.py

AUTHENTICATION_BACKENDS = (
    'ai60.weixin.auth_backends.WeiXinAuthBackend',
    'ai60.accounts.auth_backends.AI60AccountBackend',
    'mezzanine.core.auth_backends.MezzanineBackend',
    'django.contrib.auth.backends.ModelBackend',
)

In accounts/auth_backends.py

from __future__ import unicode_literals
from django.contrib.auth.backends import ModelBackend
from ai60.accounts.models import Account
from ai60.accounts.phone_token import PhoneTokenGenerator

from mezzanine.utils.models import get_user_model

User = get_user_model()


class AI60AccountBackend(ModelBackend):
    """
    Extends Django's ``ModelBackend`` to allow login via phone and token, or
    phone and password, or email and password.
    """

    def authenticate(self, **kwargs):
        if not kwargs:
            return

        if 'phone' in kwargs and 'token' in kwargs:
            phone = kwargs.pop('phone', None)
            request = kwargs.pop('request', None)
            token = kwargs.pop('token', None)

            phone_token = PhoneTokenGenerator(request)
            if phone_token.check_token(phone, token) == '':
                try:
                    user = Account.objects.get(phone=phone).user
                    return user
                except Account.DoesNotExist:
                    return

        if 'phone' in kwargs and 'password' in kwargs:
            phone = kwargs.pop('phone', None)
            password = kwargs.pop('password', None)
            try:
                user = Account.objects.get(phone=phone).user
                if user.check_password(password):
                    return user
            except Account.DoesNotExist:
                return

        if 'email' in kwargs and 'password' in kwargs:
            email = kwargs.pop('email', None)
            password = kwargs.pop('password', None)
            try:
                user = User.objects.get(email=email)
                if user.check_password(password):
                    return user
            except User.DoesNotExist:
                return

Then, in my login form

class LoginForm(Html5Mixin, forms.Form):
    """
    username: phone or email
    """
    username = forms.CharField(label='phone or email')
    password = forms.CharField(label='password',
                               widget=forms.PasswordInput(render_value=False))

    def clean(self):
        """
        Authenticate the given phone/email and password. If the fields
        are valid, store the authenticated user for returning via save().
        """
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')
        user = None
        if validate_phone(username):
            user = authenticate(phone=username, password=password)
        elif validate_email(username):
            user = authenticate(email=username, password=password)
        if user:
            self._user = user
            return self.cleaned_data
        else:
            raise forms.ValidationError('please enter valid account and password')

    def save(self):
        """
        Just return the authenticated user - used for logging in.
        """
        return getattr(self, '_user', None)

1 Comment

Fantastic! Tnak you, sir!
1

You can write any number of authentication backends for different forms of authentication. Django will try them all unless a PermissionDenied exception is raised, and will return the result of the first matching backend.

Your backend must, however, return a user-like object, or None if authentication is unsuccessful. It does not necessarily have to be a model, but it must implement enough of the user model's methods and attributes to be used as such. Take a look at Django's AnonymousUser for a non-model example.

1 Comment

Thank you, sir! And can you, please, advise why do I get an error name "authentication_classes" is not defined? What should I import to make it work?

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.