Joomla users in Django (Django authentication backend that populates users from Joomla)
I once needed to use our existing Joomla users in my new Django API. The problem is that I could not just copy Joomla users to the Django database, because:
- Joomla's password hashing system is different from Django.
- J-users and D-users had a different set of fields (this is easy to fix, but still)
So instead, I made a custom authentication backend for Django, and now I can confidently say that
Django can authenticate users using the Joomla database, without having to decrypt password hashes or copy all users from Joomla DB at the same time.
Algorithm:
- connect Joomla database to Django project
- create a JoomlaUser model to populate users from the Joomla database
- implement
check_joomla_password() , which checks user passwords in the same way as Joomla - add a custom "Joomla Auth Backend" that copies each user from Joomla to Django the first time they log in
Implementation:
To understand what is going on, you must have some experience with Django. The code should be modified according to your django project. However, the code is taken from a working draft with minimal changes, and it should be easy to configure for your needs.
1. connect to Joomla DB:
DATABASES = { 'default': {"your default DB settings"}, 'joomla_db': { 'ENGINE': 'django.db.backends.mysql', 'OPTIONS': {}, 'NAME': 'joomla_database_name',
2. create a custom Joomla model
- Read https://docs.djangoproject.com/en/2.1/howto/legacy-databases/
- Think about where to save the new Joomla user model. In my project, I created a
'users' application "where my user models live and the custom Joomla backend will be hosted. - check how the user is stored in the existing Joomla database:
python manage.py inspectdb --database="joomla_db" - Find and carefully study the user table.
- Add to
users/models.py :
class JoomlaUser(models.Model): """ Represents our customer from the legacy Joomla database. """ username = models.CharField(max_length=150, primary_key=True) email = models.CharField(max_length=100) password = models.CharField(max_length=100)
Now go to the django ./manage.py shell and try to populate some users, for example
>>> from users.models import JoomlaUser >>> print(JoomlaUser.objects.get(username='someuser')) JoomlaUser object (someuser) >>>
If everything works, go to the next step. Otherwise, look at the errors, correct the settings, etc.
3. Verify Joomla User Passwords
Joomla does not store a user password, but a password hash, for example, $2y$10$aoZ4/bA7pe.QvjTU0R5.IeFGYrGag/THGvgKpoTk6bTz6XNkY0F2e
Starting with Joomla v3.2, user passwords are hashed using the BLOWFISH algorithm.
So, I downloaded the Python Blowfish implementation:
pip install bcrypt echo bcrypt >> requirements.txt
And created the Joomla password check function in users/backend.py :
def check_joomla_password(password, hashed): """ Check if password matches the hashed password, using same hashing method (Blowfish) as Joomla >= 3.2 If you get wrong results with this function, check that the Hash starts from prefix "$2y", otherwise it is probably not a blowfish hash from Joomla. :return: True/False """ import bcrypt if password is None: return False
Old versions Warning! Joomla <3.2 uses a different hashing method (md5 + salt), so this function will not work. In this case, read joomla's password encryption and implement hash verification in python, which will probably look something like this:
Unfortunately, the old Joomla instance does not work for me, so I could not test this feature for you.
4. Joomla Authentication Server
You are now ready to create a Joomla authentication backend for Django.
Read how to change django authentication backends: https://docs.djangoproject.com/en/dev/topics/auth/customizing/
Register the Jango backend (doesn't exist yet) in project/settings.py :
AUTHENTICATION_BACKENDS = [
- Create Joomla Backend authentication in
users/backend.py :
from django.contrib.auth.models import User from .models import JoomlaUser """ check password function we wrote before """ def check_joomla_password(password, hashed): ... class JoomlaBackend: def authenticate(self, request, username=None, password=None): """ IF joomla user exists AND password is correct: create django user return user object ELSE: return None """ try: joomla_user = JoomlaUser.objects.get(username=username) except JoomlaUser.DoesNotExist: return None if check_joomla_password(password, joomla_user.password):
Test and documentation
Congratulations - now your customers from the old Joomla site can use their credentials on the new Django site or other APIs, etc.
Now add the appropriate tests and documentation to cover this new code. This logic is pretty tricky, so if you donβt do tests and documents (lazy dude), maintaining the project will be painful for your (or someone else's) ass.
Regards, @ Dmitry German