20

I have an application written in Django and I have to extend it and include some other solution as an "app" in this application.

For example, my app to be integrated is named "my_new_app" Now there is a backend authentication written for the main application and I cannot use it.

I have a MySQL DB to query from and the main app uses Cassandra and Redis mostly.

Is there any way I can use a separate authentication backend for the new app "my_new_app" and run both in the same domain?

5 Answers5

48

You can have multiple authentication backends. Just set the AUTHENTICATION_BACKENDS in settings.py of your Django project to list the backend implementations you want to use. For example I often use a combination of OpenID authentication and the standard Django authentication, like this in my settings.py:

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'django_openid_auth.auth.OpenIDBackend',
    )

In this example Django will first try to authenticate using django.contrib.auth.backends.ModelBackend, which is the default backend of Django. If that fails, then it moves on to the next backend, django_openid_auth.auth.OpenIDBackend.

Note that your custom backends must be at a path visible by Django. In this example I have to add django_openid_auth to INSTALLED_APPS, otherwise Django won't be able to import it and use it as a backend.

Also read the relevant documentation, it's very nicely written, easy to understand: https://docs.djangoproject.com/en/dev/topics/auth/customizing/

janos
  • 120,954
  • 29
  • 226
  • 236
9

I've been through this problem before. This is the code I used.

This is the authentication backend at the api/backend.py

from django.contrib.auth.models import User

class EmailOrUsernameModelBackend(object):
    def authenticate(self, username=None, password=None):
        if '@' in username:
            kwargs = {'email': username}
        else:
             kwargs = {'username': username}
        try:
            user = User.objects.get(**kwargs)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None

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

And this is my settings.py

AUTHENTICATION_BACKENDS = (
    'api.backend.EmailOrUsernameModelBackend',
    'django.contrib.auth.backends.ModelBackend',
)

This code will enable you to use email to authenticate the default Django user even in Django admin.

Edwin Lunando
  • 2,726
  • 3
  • 24
  • 33
  • 1
    although your solution is also good but it does not address exactly what i want, i will post my solution in a few hours. BTW i have got the idea from your solution so here's a +1 to you. – Rai Muhammad Ehtisham Jun 13 '13 at 08:54
  • Using `if '@' in username:` to identify whether the username is an email is a pretty bad way to achieve it if usernames can contain `@`. You should at least use pattern matching or identify the chosen option at the source. – vintagexav Jul 15 '15 at 22:14
  • 2
    Instead `if '@' in username:`, use the `django.core.validators.validate_email` like this: `def validateEmail(email): try: validate_email(email) return True except ValidationError: return False` – M.Void Jul 26 '16 at 09:29
  • Or just don't allow '@' in your usernames.... If you allow both '@' and '.' in a username, then you end up with ambiguity here that needs to be fixed regardless of how stringent your validation is (because an infinite number of valid usernames are also valid emails under this schema). You could introduce some precedence to solve this I suppose, but probably it's easier to reason about if you make your usernames disjoint with valid email addresses (by forbidding '@' in the username). Then this solution is fine (though I would still validate the email address after checking for `@`). – DylanYoung Feb 05 '21 at 19:44
2

Using multiple backend authentications is as simple as pie. You just need to understand the workflow of Django apps.

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.Backend1',
    'django_openid_auth.auth.Backend2',
    )

For example you have the following two backends defined. Django will first go to the first backend and you just need to put some logic in that backend so that, if its not related to that backend it get forwarded to the other backend or returned without any results. In case of no results django will automatically shift the request from first backend to the second and if available third one. I spend a lot of time on this and found out that it was not that complex.

  • I don't understand why you posted an answer that's pretty much the same as mine, posted 2 weeks earlier... :( – janos Jun 30 '13 at 15:09
  • Actually buddy i did'nt get exactly what you wanted to say, so i followed your answer and did some magic of my own and volla. Your answer is good. If you join both of our answers and put one more on the bottom it would be the best answer on this issue. Good luck – Rai Muhammad Ehtisham Jul 02 '13 at 06:29
  • 3
    You could have dropped me a comment, I would have clarified ;-) I clarified it now, what do you think? Let me know if you still think it should be amended. – janos Jul 02 '13 at 06:46
  • Its perfect now. Thats the best answer for the question – Rai Muhammad Ehtisham Jul 04 '13 at 06:40
1

Using Multiple AUTHENTICATION BACKENDS is very easy, you just need to add this to settings.py

AUTHENTICATION_BACKENDS = (
    'social_core.backends.open_id.OpenIdAuth',
    'social_core.backends.google.GoogleOpenId',
    'social_core.backends.google.GoogleOAuth2',
    'social_core.backends.google.GoogleOAuth',
    'social_core.backends.facebook.FacebookOAuth2',
    'django.contrib.auth.backends.ModelBackend',
)

and this may create a problem at signup page so add a login argument in your signup view in views.py file like this login(request, user, backend='django.contrib.auth.backends.ModelBackend')

def signup_view(request):
    if request.method=='POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            user=form.save()
            login(request, user, backend='django.contrib.auth.backends.ModelBackend')
            return redirect('home')
0

Done a little safety and reassemble for code. cause provided solutions wasn't perfectly matching necessary.

my version for the previous: https://stackoverflow.com/a/17078818/14972653 with my addition and @M.Void comment:

This is the authentication backend at the api/backend.py

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.core.validators import validate_email

user_model = get_user_model()


class EmailOrUsernameModelBackend(ModelBackend):
    """EmailOrUsernameModelBackend - UsernameOrEmail custom authentication backend 
    with email or username validation on same field

    provides case insensitive validation of username and email

    BEWARE - if you allow users to register two usernames in differren cases like: Alex and alex - there would be error.

    still uses ModelBackend so provides permissions and is_active validation
    """

    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get(user_model.USERNAME_FIELD)
        if username is None or password is None:
            return
        try:
            validate_email(username)
        except ValidationError as e:
            kwargs = {'username__iexact': username}  # remove __iexact to make it case sensitive
        else:
            kwargs = {'email__iexact': username}  # remove __iexact to make it case sensitive
        try:
            user = user_model.objects.get(**kwargs)
        except user_model.DoesNotExist:
            return None
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

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

and of course add it to your authentication backends:

AUTHENTICATION_BACKENDS = (
    'api.backend.EmailOrUsernameModelBackend',
    'django.contrib.auth.backends.ModelBackend',
)

agriev
  • 1
  • 2