Reputation: 1092
I have to implement a backward-compatible Django server with PHP app. Legacy app is using LegacyUser
model for authorization which more or less like:
class LegacyUser(models.Model):
id = models.BigAutoField(primary_key=True)
email = models.CharField(max_length=255, unique=True)
password = models.CharField(max_length=120, blank=True, null=True)
...
other_data_fields
...
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
EMAIL_FIELD = 'email'
In a new system, I do not have to add new records of LegacyUser
(but I can).
For now, the legacy system does not allow to create multiple users per Group. Actually LegacyUser
should be considered as a group but I was implemented as a user.
Right now I have to implement multiple Users
per each LegacyUser
so I have added proper Django user for authorization like:
class User(AbstractUser):
username = None
email = models.EmailField(unique=True)
legacy_user = models.ForeignKey(LegacyUser, on_delete=models.DO_NOTHING)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['publisher']
class Meta:
managed = True
db_table = 'user'
and in base.py
settings:
...
AUTH_USER_MODEL = 'api.LegacyUser'
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'AUTH_HEADER_TYPES': ('Bearer',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'id',
}
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend', #Supports User
'common.auth.backends.LegacyBackend' #Supports LegacyUser
]
...
The new application should enable to login both LegacyUser
and User
. After authorization LegacyUser.id should be used as USER_ID_CLAIM.
This means if I have one LegacyUser like:
{
"id": 1,
"email": [email protected],
"password": "hashed_password",
...
}
And two Users e.g.
{
"id": 1,
"email": [email protected],
"password": "hashed_password",
"legacy_user_id": 1,
...
}
{
"id": 2,
"email": [email protected],
"password": "hashed_password",
"legacy_user_id": 1,
...
}
Valuse of LegacyUser.id
or User.legacy_user.id
should be visible in request.
Whats more email
field have to be unique altogether in LegacyUser
and User
Is this possible to have two User Authorization models? AUTH_USER_MODEL
only enables me to have one such a model
AUTH_USER_MODEL = 'api.LegacyUser'
The solution which came to my mind (to archive backward-compatible apps) is to copy current passwords and emails from LegacyUser
to the new model User
model and mark them as the most privileges.
Having synchronized passwords for both new and legacy system is also a must so I can use some stored procedure, that changes the password in both places on the update, in the database? For me, it smells very bad and maybe there is some other way to do this without synchronizing these passwords or just using two tables for authorization?
Edit: To solve this issue I have created MySQL view with unified user data needed only for authorization like:
CREATE OR REPLACE VIEW unified_user AS SELECT email as email, password as password, is_active as is_active, last_login as last_login FROM user UNION ALL SELECT email as email, password as password, 1 as is_active, null as last_login FROM legacy_user;
So synchronization of passwords will be done automatically as view will be updated
Upvotes: 0
Views: 2311
Reputation: 1921
You need a flag to differentiate auth for legacy users and new user. Let me focus on Session-based auth.
AUTHENTICATION_BACKENDS = [
'common.auth.backends.CustomUserBackend'
]
And remove it django.contrib.auth.middleware.AuthenticationMiddleware
from middlewares and place your CustomAuthenticationMiddleware.
MIDDLEWARE = [
...
'Your CustomAuthenticationMiddleware',
....
]
at CustomAuthenticationMiddleware
, attach your user based on the session variable.
implement those methods for class CustomUserBackend
. Please read Auth
def get_user(self, user_id):
# read is_legacy_user from session
# Try to get user based on session is_legacy_user
# Note that this method is called in CustomAuthenticationMiddleware middleware.
# and you have to read from 2 separate table. Session is your friend to differentiate which table we need to fetch.
def authenticate(self, request, email, password, is_legacy_user):
if is_legacy_user:
#write the logic for legacy user.
else:
#write the logic for new user.
From view call the authenticate
function (from django.contrib.auth import authenticate
).
For legacy user
user = authenticate(email, password, is_legacy_user=True)
# Store the user ID in session as well as is_legacy_user value
For new user
authenticate(email, password, is_legacy_user=False)
# Store the user ID in session as well as is_legacy_user value
I hope this is how you will solve the session-based auth issue. If that work; will move to Token-based.
Upvotes: 1
Reputation: 408
You want to look at [a https://docs.djangoproject.com/en/2.2/topics/auth/customizing/. You can have as many different ways to authenticate as you wish. Part of the information stored in the Session record is which authentication backend was used successfully. It's slightly involved, but they give you all of the control necessary to do pretty much whatever you like.
I used this on a system a number of years ago where the primary user/password information was coming from an external subscription management server. If the user/pass did not work on the normal User-auth system, I checked a different system. If it succeeded, I created a new User on the fly.
Upvotes: 0