Jacobian
Jacobian

Reputation: 10862

How to make custom authentication not based on User model

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.

Upvotes: 2

Views: 4088

Answers (3)

RLott
RLott

Reputation: 340

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/

Upvotes: 11

吕祥钊
吕祥钊

Reputation: 156

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)

Upvotes: 1

knbk
knbk

Reputation: 53699

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.

Upvotes: 1

Related Questions