Temporary users in Django
up vote
0
down vote
favorite
I would like to implement temporary users in my project. The reason is that people should be able to auth in my application via Facebook Oauth or Facebook Account Kit.
I've got 2 decisions, but each one is not ideal.
1) Create User and TemporaryUser models. The first one will have all info about regular user + unique db fields. This TemporaryUser will have only phone_number
or facebook_id
. Temporary user will be created on auth/
endpoint and response will have auth_token
to be able perform registration later with all needed fields.
The issue is: how will I determine that user is temporary and his auth_token
is legit only for registration/
endpoint?
2) Create basic User model with 2 types: regular
or temporary
. This model will have only general fields. Also there will be different models with OneToOne relation with User. Only Users with type regular
will be able to have that instances with OneToOne relation.
The issue is: User model should have USERNAME_FIELD
, REQUIRED_FIELDS
to be able to login by admin panel + users with different types should have different managers.
UPDATED
class User(AbstractBaseUser, TimeStampedModel, PermissionsMixin):
is_regular = models.BooleanField(
default=False
)
id = models.CharField(
max_length=11,
primary_key=True,
default=custom_uuid,
editable=False
)
phone_number = PhoneNumberField(
null=True
)
facebook_id = models.CharField(
max_length=255,
null=True
)
objects = UserManager()
USERNAME_FIELD = 'phone_number'
REQUIRED_FIELDS =
django
add a comment |
up vote
0
down vote
favorite
I would like to implement temporary users in my project. The reason is that people should be able to auth in my application via Facebook Oauth or Facebook Account Kit.
I've got 2 decisions, but each one is not ideal.
1) Create User and TemporaryUser models. The first one will have all info about regular user + unique db fields. This TemporaryUser will have only phone_number
or facebook_id
. Temporary user will be created on auth/
endpoint and response will have auth_token
to be able perform registration later with all needed fields.
The issue is: how will I determine that user is temporary and his auth_token
is legit only for registration/
endpoint?
2) Create basic User model with 2 types: regular
or temporary
. This model will have only general fields. Also there will be different models with OneToOne relation with User. Only Users with type regular
will be able to have that instances with OneToOne relation.
The issue is: User model should have USERNAME_FIELD
, REQUIRED_FIELDS
to be able to login by admin panel + users with different types should have different managers.
UPDATED
class User(AbstractBaseUser, TimeStampedModel, PermissionsMixin):
is_regular = models.BooleanField(
default=False
)
id = models.CharField(
max_length=11,
primary_key=True,
default=custom_uuid,
editable=False
)
phone_number = PhoneNumberField(
null=True
)
facebook_id = models.CharField(
max_length=255,
null=True
)
objects = UserManager()
USERNAME_FIELD = 'phone_number'
REQUIRED_FIELDS =
django
Why FB users should be temporary? BTW, its not a good idea to use 2 auth models. I don't think you can authenticate a user if its not related to a User model (for 2nd solution)
– ruddra
Nov 7 at 10:26
@ruddra Flow: front-end authorize user by facebook and gives me facebook_token -> back-end do request with this token and facebook_id will be in return -> back-end checks whether user exists in db -> if yes - auth user, else - it should be created temp user with auth token (to be sure that it's the same user and there is no need to verify user one more tome on registration) -> front-end let user to fill in required fields and than register real user
– Ernst
Nov 7 at 10:31
in that case, you can create a user with dummy data, or override the Django's own User Model make fewer fields required.
– ruddra
Nov 7 at 10:34
@ruddra I can't change my fields, all of them should stay required. What if user will close app on screen with all needed fields for users? User will stay with that dummy data on the next login and he will skip registration screen. Also this solution will combine auth/registration endpoints that I don't like.
– Ernst
Nov 7 at 10:45
add a comment |
up vote
0
down vote
favorite
up vote
0
down vote
favorite
I would like to implement temporary users in my project. The reason is that people should be able to auth in my application via Facebook Oauth or Facebook Account Kit.
I've got 2 decisions, but each one is not ideal.
1) Create User and TemporaryUser models. The first one will have all info about regular user + unique db fields. This TemporaryUser will have only phone_number
or facebook_id
. Temporary user will be created on auth/
endpoint and response will have auth_token
to be able perform registration later with all needed fields.
The issue is: how will I determine that user is temporary and his auth_token
is legit only for registration/
endpoint?
2) Create basic User model with 2 types: regular
or temporary
. This model will have only general fields. Also there will be different models with OneToOne relation with User. Only Users with type regular
will be able to have that instances with OneToOne relation.
The issue is: User model should have USERNAME_FIELD
, REQUIRED_FIELDS
to be able to login by admin panel + users with different types should have different managers.
UPDATED
class User(AbstractBaseUser, TimeStampedModel, PermissionsMixin):
is_regular = models.BooleanField(
default=False
)
id = models.CharField(
max_length=11,
primary_key=True,
default=custom_uuid,
editable=False
)
phone_number = PhoneNumberField(
null=True
)
facebook_id = models.CharField(
max_length=255,
null=True
)
objects = UserManager()
USERNAME_FIELD = 'phone_number'
REQUIRED_FIELDS =
django
I would like to implement temporary users in my project. The reason is that people should be able to auth in my application via Facebook Oauth or Facebook Account Kit.
I've got 2 decisions, but each one is not ideal.
1) Create User and TemporaryUser models. The first one will have all info about regular user + unique db fields. This TemporaryUser will have only phone_number
or facebook_id
. Temporary user will be created on auth/
endpoint and response will have auth_token
to be able perform registration later with all needed fields.
The issue is: how will I determine that user is temporary and his auth_token
is legit only for registration/
endpoint?
2) Create basic User model with 2 types: regular
or temporary
. This model will have only general fields. Also there will be different models with OneToOne relation with User. Only Users with type regular
will be able to have that instances with OneToOne relation.
The issue is: User model should have USERNAME_FIELD
, REQUIRED_FIELDS
to be able to login by admin panel + users with different types should have different managers.
UPDATED
class User(AbstractBaseUser, TimeStampedModel, PermissionsMixin):
is_regular = models.BooleanField(
default=False
)
id = models.CharField(
max_length=11,
primary_key=True,
default=custom_uuid,
editable=False
)
phone_number = PhoneNumberField(
null=True
)
facebook_id = models.CharField(
max_length=255,
null=True
)
objects = UserManager()
USERNAME_FIELD = 'phone_number'
REQUIRED_FIELDS =
django
django
edited Nov 8 at 11:43
asked Nov 7 at 10:22
Ernst
74112
74112
Why FB users should be temporary? BTW, its not a good idea to use 2 auth models. I don't think you can authenticate a user if its not related to a User model (for 2nd solution)
– ruddra
Nov 7 at 10:26
@ruddra Flow: front-end authorize user by facebook and gives me facebook_token -> back-end do request with this token and facebook_id will be in return -> back-end checks whether user exists in db -> if yes - auth user, else - it should be created temp user with auth token (to be sure that it's the same user and there is no need to verify user one more tome on registration) -> front-end let user to fill in required fields and than register real user
– Ernst
Nov 7 at 10:31
in that case, you can create a user with dummy data, or override the Django's own User Model make fewer fields required.
– ruddra
Nov 7 at 10:34
@ruddra I can't change my fields, all of them should stay required. What if user will close app on screen with all needed fields for users? User will stay with that dummy data on the next login and he will skip registration screen. Also this solution will combine auth/registration endpoints that I don't like.
– Ernst
Nov 7 at 10:45
add a comment |
Why FB users should be temporary? BTW, its not a good idea to use 2 auth models. I don't think you can authenticate a user if its not related to a User model (for 2nd solution)
– ruddra
Nov 7 at 10:26
@ruddra Flow: front-end authorize user by facebook and gives me facebook_token -> back-end do request with this token and facebook_id will be in return -> back-end checks whether user exists in db -> if yes - auth user, else - it should be created temp user with auth token (to be sure that it's the same user and there is no need to verify user one more tome on registration) -> front-end let user to fill in required fields and than register real user
– Ernst
Nov 7 at 10:31
in that case, you can create a user with dummy data, or override the Django's own User Model make fewer fields required.
– ruddra
Nov 7 at 10:34
@ruddra I can't change my fields, all of them should stay required. What if user will close app on screen with all needed fields for users? User will stay with that dummy data on the next login and he will skip registration screen. Also this solution will combine auth/registration endpoints that I don't like.
– Ernst
Nov 7 at 10:45
Why FB users should be temporary? BTW, its not a good idea to use 2 auth models. I don't think you can authenticate a user if its not related to a User model (for 2nd solution)
– ruddra
Nov 7 at 10:26
Why FB users should be temporary? BTW, its not a good idea to use 2 auth models. I don't think you can authenticate a user if its not related to a User model (for 2nd solution)
– ruddra
Nov 7 at 10:26
@ruddra Flow: front-end authorize user by facebook and gives me facebook_token -> back-end do request with this token and facebook_id will be in return -> back-end checks whether user exists in db -> if yes - auth user, else - it should be created temp user with auth token (to be sure that it's the same user and there is no need to verify user one more tome on registration) -> front-end let user to fill in required fields and than register real user
– Ernst
Nov 7 at 10:31
@ruddra Flow: front-end authorize user by facebook and gives me facebook_token -> back-end do request with this token and facebook_id will be in return -> back-end checks whether user exists in db -> if yes - auth user, else - it should be created temp user with auth token (to be sure that it's the same user and there is no need to verify user one more tome on registration) -> front-end let user to fill in required fields and than register real user
– Ernst
Nov 7 at 10:31
in that case, you can create a user with dummy data, or override the Django's own User Model make fewer fields required.
– ruddra
Nov 7 at 10:34
in that case, you can create a user with dummy data, or override the Django's own User Model make fewer fields required.
– ruddra
Nov 7 at 10:34
@ruddra I can't change my fields, all of them should stay required. What if user will close app on screen with all needed fields for users? User will stay with that dummy data on the next login and he will skip registration screen. Also this solution will combine auth/registration endpoints that I don't like.
– Ernst
Nov 7 at 10:45
@ruddra I can't change my fields, all of them should stay required. What if user will close app on screen with all needed fields for users? User will stay with that dummy data on the next login and he will skip registration screen. Also this solution will combine auth/registration endpoints that I don't like.
– Ernst
Nov 7 at 10:45
add a comment |
1 Answer
1
active
oldest
votes
up vote
1
down vote
accepted
From discussion, there are many ways to tackle this issue. But using two different models for authentication purpose is a bad idea. Because Django Auth was designed assuming one User Model.
This is how I would have approached:
- Rather than using
facebook_id
, I would have storedemail
from facebook's graph API. While creating the password, I would have used an unique UUID. - Put a temporary flag(a Model Boolean Field) to track if the user is registered through social media. So when he tries to login to normal path or registration, I would know that he does not have a password, so I would send him to reset his password. After successful reset password, I would have removed that temporary flag.
Other information which are absolute necessary, I would have used a separate model to store them. For example:
class Profile(models.Model):
...
phone_number = models.CharField(max_length=30)
user = models.OneToOneField(settings.AUTH_USER_MODEL)
And checked with login that if the User has a profile or not:
if not hasattr(user, 'profile'):
# redirect to more information page
For other permissions for accessing admin site should be restricted creation through normal registration or social registration. For these users, those fields(
is_staff
,is_superuser
etc should be False by default. For admin user creation, you can use createsuperuser command. For staff users, you can later assign a normal user to staff by making theis_staff
flagTrue
from adminsite orDjango Shell
.
Update(from comments...)
You can use custom Backend for authenticating facebook user:
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
class CustomModelbackend(ModelBackend):
def authenticate(self, phone_no=None, password=None, facebook_id=None, **kwargs):
# Override
UserModel = User
try:
user = UserModel.objects.get(Q(phone_no=phone_no) | Q(facebook_id=facebook_id))
if password and user.check_password(password):
return user
elif facebook_id:
return user
except Exception as e:
# log exception
and add it to AUTHENTICATION_BACKENDS in settings.py
:
AUTHENTICATION_BACKENDS = ['path.to.custom_backend.CustomBackend']
and call authenticate method like this:
authenticate(phone_no=phone_number, password=password) # for normal auth
authenticate(facebook_id=facebook_id) # for social auth
But you can't make your phone_no
unique as there is chance of the phone_no being empty for social login, but you can put it in USERNAME_FIELD
. So you will see warnings when you run django developer server(runserver command)
Update (2)
You can try like this:
NORMAL_USER = "N"
OAUTH_USER = "O"
AT_USER = "A"
USER_TYPES=(
(NORMAL_USER, 'Normal User'),
(OAUTH_USER, 'Oauth User'),
(AT_USER, 'Account Toolkit User')
)
class User(...):
username = models.CharField(max_length=255, default=uuid.uuid4, unique=True) # it might be unnecessary
account_type = models.CharField(max_length=2, choices = USER_TYPES, default=NORMAL_USER)
identifier = models.CharField(max_length=255)
...
class Meta:
unique_togather = ('account_type', 'identifier',)
# for Oauth Creation
User.objects.create(account_type=OAUTH_USER, identifier=facebook_id) # or email
# for Toolkit Creation
User.objects.create(account_type=AT_USER, identifier=phone_number)
# For normal User
User.objects.create(identifier=username, username=username)
#backend, and does not need to use ModelBackend, it will work with adminsite
class CustomBackend(...):
def authenticate(self, **kwargs):
try:
identifier= kwargs.get('username')
password=kwargs.get('password')
account_type=kwargs.get('account_type', NORMAL_USER)
user = User.objects.get(identifier=identifier, account_type=account_type)
if user.check_password(password):
return user
except Exception:
# log exception
# authentication
from django.contrib.auth import authenticate
user = authenticate(username=identifier, password=password, account_type=OAUTH_USER)
You wouldn't need a temporary flag for the initial password prompt; a user that hasn't set a password will have apassword_hash
ofNone
, where a user that just forgot theirs will just have a mismatching hash when they input the wrong one. TheProfile
pattern is a good one, though, and very often a better way to go than a custom user model. I wouldn't usehasattr
to check for a profile, though. I'm not sure if the ORM will set that to None or just not create it, and I wouldn't depend on that behavior.Profile.objects.get(user=user)
might be a little heavy, but it's more concrete.
– kungphu
Nov 8 at 5:41
@kungphu I am not againstCustomUser
Model. Because the requirement here is to store some data which are absolutely necessary, but does not come through social login(from comments in question). Also, hasattr usage is per documentation found here (in the 2nd example).
– ruddra
Nov 8 at 5:48
@ruddra In my application users will not be able to perform registration by email, only Facebook Oauth2 or Account Kit. So I can create Basic User model with id, facebook_id and phone_number. User will be able to register by facebook_id or phone_number. I decided to add flag is_regular (to be sure that user finished registration and he can have profile and other info), but the main problem is that I need something for USERNAME_FIELD.
– Ernst
Nov 8 at 9:29
@Ernst please see my updated section in the answer.
– ruddra
Nov 8 at 9:55
@ruddra Than you for your help. I can't putphone_number
into USERNAME_FIELD as facebook user can have only email, but not always phone_number. One of possible solution is to putusername
into USERNAME_FIELD as each regular user will have it, but I don't like idea to put there dummy data for temporary users
– Ernst
Nov 8 at 10:44
|
show 5 more comments
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
From discussion, there are many ways to tackle this issue. But using two different models for authentication purpose is a bad idea. Because Django Auth was designed assuming one User Model.
This is how I would have approached:
- Rather than using
facebook_id
, I would have storedemail
from facebook's graph API. While creating the password, I would have used an unique UUID. - Put a temporary flag(a Model Boolean Field) to track if the user is registered through social media. So when he tries to login to normal path or registration, I would know that he does not have a password, so I would send him to reset his password. After successful reset password, I would have removed that temporary flag.
Other information which are absolute necessary, I would have used a separate model to store them. For example:
class Profile(models.Model):
...
phone_number = models.CharField(max_length=30)
user = models.OneToOneField(settings.AUTH_USER_MODEL)
And checked with login that if the User has a profile or not:
if not hasattr(user, 'profile'):
# redirect to more information page
For other permissions for accessing admin site should be restricted creation through normal registration or social registration. For these users, those fields(
is_staff
,is_superuser
etc should be False by default. For admin user creation, you can use createsuperuser command. For staff users, you can later assign a normal user to staff by making theis_staff
flagTrue
from adminsite orDjango Shell
.
Update(from comments...)
You can use custom Backend for authenticating facebook user:
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
class CustomModelbackend(ModelBackend):
def authenticate(self, phone_no=None, password=None, facebook_id=None, **kwargs):
# Override
UserModel = User
try:
user = UserModel.objects.get(Q(phone_no=phone_no) | Q(facebook_id=facebook_id))
if password and user.check_password(password):
return user
elif facebook_id:
return user
except Exception as e:
# log exception
and add it to AUTHENTICATION_BACKENDS in settings.py
:
AUTHENTICATION_BACKENDS = ['path.to.custom_backend.CustomBackend']
and call authenticate method like this:
authenticate(phone_no=phone_number, password=password) # for normal auth
authenticate(facebook_id=facebook_id) # for social auth
But you can't make your phone_no
unique as there is chance of the phone_no being empty for social login, but you can put it in USERNAME_FIELD
. So you will see warnings when you run django developer server(runserver command)
Update (2)
You can try like this:
NORMAL_USER = "N"
OAUTH_USER = "O"
AT_USER = "A"
USER_TYPES=(
(NORMAL_USER, 'Normal User'),
(OAUTH_USER, 'Oauth User'),
(AT_USER, 'Account Toolkit User')
)
class User(...):
username = models.CharField(max_length=255, default=uuid.uuid4, unique=True) # it might be unnecessary
account_type = models.CharField(max_length=2, choices = USER_TYPES, default=NORMAL_USER)
identifier = models.CharField(max_length=255)
...
class Meta:
unique_togather = ('account_type', 'identifier',)
# for Oauth Creation
User.objects.create(account_type=OAUTH_USER, identifier=facebook_id) # or email
# for Toolkit Creation
User.objects.create(account_type=AT_USER, identifier=phone_number)
# For normal User
User.objects.create(identifier=username, username=username)
#backend, and does not need to use ModelBackend, it will work with adminsite
class CustomBackend(...):
def authenticate(self, **kwargs):
try:
identifier= kwargs.get('username')
password=kwargs.get('password')
account_type=kwargs.get('account_type', NORMAL_USER)
user = User.objects.get(identifier=identifier, account_type=account_type)
if user.check_password(password):
return user
except Exception:
# log exception
# authentication
from django.contrib.auth import authenticate
user = authenticate(username=identifier, password=password, account_type=OAUTH_USER)
You wouldn't need a temporary flag for the initial password prompt; a user that hasn't set a password will have apassword_hash
ofNone
, where a user that just forgot theirs will just have a mismatching hash when they input the wrong one. TheProfile
pattern is a good one, though, and very often a better way to go than a custom user model. I wouldn't usehasattr
to check for a profile, though. I'm not sure if the ORM will set that to None or just not create it, and I wouldn't depend on that behavior.Profile.objects.get(user=user)
might be a little heavy, but it's more concrete.
– kungphu
Nov 8 at 5:41
@kungphu I am not againstCustomUser
Model. Because the requirement here is to store some data which are absolutely necessary, but does not come through social login(from comments in question). Also, hasattr usage is per documentation found here (in the 2nd example).
– ruddra
Nov 8 at 5:48
@ruddra In my application users will not be able to perform registration by email, only Facebook Oauth2 or Account Kit. So I can create Basic User model with id, facebook_id and phone_number. User will be able to register by facebook_id or phone_number. I decided to add flag is_regular (to be sure that user finished registration and he can have profile and other info), but the main problem is that I need something for USERNAME_FIELD.
– Ernst
Nov 8 at 9:29
@Ernst please see my updated section in the answer.
– ruddra
Nov 8 at 9:55
@ruddra Than you for your help. I can't putphone_number
into USERNAME_FIELD as facebook user can have only email, but not always phone_number. One of possible solution is to putusername
into USERNAME_FIELD as each regular user will have it, but I don't like idea to put there dummy data for temporary users
– Ernst
Nov 8 at 10:44
|
show 5 more comments
up vote
1
down vote
accepted
From discussion, there are many ways to tackle this issue. But using two different models for authentication purpose is a bad idea. Because Django Auth was designed assuming one User Model.
This is how I would have approached:
- Rather than using
facebook_id
, I would have storedemail
from facebook's graph API. While creating the password, I would have used an unique UUID. - Put a temporary flag(a Model Boolean Field) to track if the user is registered through social media. So when he tries to login to normal path or registration, I would know that he does not have a password, so I would send him to reset his password. After successful reset password, I would have removed that temporary flag.
Other information which are absolute necessary, I would have used a separate model to store them. For example:
class Profile(models.Model):
...
phone_number = models.CharField(max_length=30)
user = models.OneToOneField(settings.AUTH_USER_MODEL)
And checked with login that if the User has a profile or not:
if not hasattr(user, 'profile'):
# redirect to more information page
For other permissions for accessing admin site should be restricted creation through normal registration or social registration. For these users, those fields(
is_staff
,is_superuser
etc should be False by default. For admin user creation, you can use createsuperuser command. For staff users, you can later assign a normal user to staff by making theis_staff
flagTrue
from adminsite orDjango Shell
.
Update(from comments...)
You can use custom Backend for authenticating facebook user:
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
class CustomModelbackend(ModelBackend):
def authenticate(self, phone_no=None, password=None, facebook_id=None, **kwargs):
# Override
UserModel = User
try:
user = UserModel.objects.get(Q(phone_no=phone_no) | Q(facebook_id=facebook_id))
if password and user.check_password(password):
return user
elif facebook_id:
return user
except Exception as e:
# log exception
and add it to AUTHENTICATION_BACKENDS in settings.py
:
AUTHENTICATION_BACKENDS = ['path.to.custom_backend.CustomBackend']
and call authenticate method like this:
authenticate(phone_no=phone_number, password=password) # for normal auth
authenticate(facebook_id=facebook_id) # for social auth
But you can't make your phone_no
unique as there is chance of the phone_no being empty for social login, but you can put it in USERNAME_FIELD
. So you will see warnings when you run django developer server(runserver command)
Update (2)
You can try like this:
NORMAL_USER = "N"
OAUTH_USER = "O"
AT_USER = "A"
USER_TYPES=(
(NORMAL_USER, 'Normal User'),
(OAUTH_USER, 'Oauth User'),
(AT_USER, 'Account Toolkit User')
)
class User(...):
username = models.CharField(max_length=255, default=uuid.uuid4, unique=True) # it might be unnecessary
account_type = models.CharField(max_length=2, choices = USER_TYPES, default=NORMAL_USER)
identifier = models.CharField(max_length=255)
...
class Meta:
unique_togather = ('account_type', 'identifier',)
# for Oauth Creation
User.objects.create(account_type=OAUTH_USER, identifier=facebook_id) # or email
# for Toolkit Creation
User.objects.create(account_type=AT_USER, identifier=phone_number)
# For normal User
User.objects.create(identifier=username, username=username)
#backend, and does not need to use ModelBackend, it will work with adminsite
class CustomBackend(...):
def authenticate(self, **kwargs):
try:
identifier= kwargs.get('username')
password=kwargs.get('password')
account_type=kwargs.get('account_type', NORMAL_USER)
user = User.objects.get(identifier=identifier, account_type=account_type)
if user.check_password(password):
return user
except Exception:
# log exception
# authentication
from django.contrib.auth import authenticate
user = authenticate(username=identifier, password=password, account_type=OAUTH_USER)
You wouldn't need a temporary flag for the initial password prompt; a user that hasn't set a password will have apassword_hash
ofNone
, where a user that just forgot theirs will just have a mismatching hash when they input the wrong one. TheProfile
pattern is a good one, though, and very often a better way to go than a custom user model. I wouldn't usehasattr
to check for a profile, though. I'm not sure if the ORM will set that to None or just not create it, and I wouldn't depend on that behavior.Profile.objects.get(user=user)
might be a little heavy, but it's more concrete.
– kungphu
Nov 8 at 5:41
@kungphu I am not againstCustomUser
Model. Because the requirement here is to store some data which are absolutely necessary, but does not come through social login(from comments in question). Also, hasattr usage is per documentation found here (in the 2nd example).
– ruddra
Nov 8 at 5:48
@ruddra In my application users will not be able to perform registration by email, only Facebook Oauth2 or Account Kit. So I can create Basic User model with id, facebook_id and phone_number. User will be able to register by facebook_id or phone_number. I decided to add flag is_regular (to be sure that user finished registration and he can have profile and other info), but the main problem is that I need something for USERNAME_FIELD.
– Ernst
Nov 8 at 9:29
@Ernst please see my updated section in the answer.
– ruddra
Nov 8 at 9:55
@ruddra Than you for your help. I can't putphone_number
into USERNAME_FIELD as facebook user can have only email, but not always phone_number. One of possible solution is to putusername
into USERNAME_FIELD as each regular user will have it, but I don't like idea to put there dummy data for temporary users
– Ernst
Nov 8 at 10:44
|
show 5 more comments
up vote
1
down vote
accepted
up vote
1
down vote
accepted
From discussion, there are many ways to tackle this issue. But using two different models for authentication purpose is a bad idea. Because Django Auth was designed assuming one User Model.
This is how I would have approached:
- Rather than using
facebook_id
, I would have storedemail
from facebook's graph API. While creating the password, I would have used an unique UUID. - Put a temporary flag(a Model Boolean Field) to track if the user is registered through social media. So when he tries to login to normal path or registration, I would know that he does not have a password, so I would send him to reset his password. After successful reset password, I would have removed that temporary flag.
Other information which are absolute necessary, I would have used a separate model to store them. For example:
class Profile(models.Model):
...
phone_number = models.CharField(max_length=30)
user = models.OneToOneField(settings.AUTH_USER_MODEL)
And checked with login that if the User has a profile or not:
if not hasattr(user, 'profile'):
# redirect to more information page
For other permissions for accessing admin site should be restricted creation through normal registration or social registration. For these users, those fields(
is_staff
,is_superuser
etc should be False by default. For admin user creation, you can use createsuperuser command. For staff users, you can later assign a normal user to staff by making theis_staff
flagTrue
from adminsite orDjango Shell
.
Update(from comments...)
You can use custom Backend for authenticating facebook user:
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
class CustomModelbackend(ModelBackend):
def authenticate(self, phone_no=None, password=None, facebook_id=None, **kwargs):
# Override
UserModel = User
try:
user = UserModel.objects.get(Q(phone_no=phone_no) | Q(facebook_id=facebook_id))
if password and user.check_password(password):
return user
elif facebook_id:
return user
except Exception as e:
# log exception
and add it to AUTHENTICATION_BACKENDS in settings.py
:
AUTHENTICATION_BACKENDS = ['path.to.custom_backend.CustomBackend']
and call authenticate method like this:
authenticate(phone_no=phone_number, password=password) # for normal auth
authenticate(facebook_id=facebook_id) # for social auth
But you can't make your phone_no
unique as there is chance of the phone_no being empty for social login, but you can put it in USERNAME_FIELD
. So you will see warnings when you run django developer server(runserver command)
Update (2)
You can try like this:
NORMAL_USER = "N"
OAUTH_USER = "O"
AT_USER = "A"
USER_TYPES=(
(NORMAL_USER, 'Normal User'),
(OAUTH_USER, 'Oauth User'),
(AT_USER, 'Account Toolkit User')
)
class User(...):
username = models.CharField(max_length=255, default=uuid.uuid4, unique=True) # it might be unnecessary
account_type = models.CharField(max_length=2, choices = USER_TYPES, default=NORMAL_USER)
identifier = models.CharField(max_length=255)
...
class Meta:
unique_togather = ('account_type', 'identifier',)
# for Oauth Creation
User.objects.create(account_type=OAUTH_USER, identifier=facebook_id) # or email
# for Toolkit Creation
User.objects.create(account_type=AT_USER, identifier=phone_number)
# For normal User
User.objects.create(identifier=username, username=username)
#backend, and does not need to use ModelBackend, it will work with adminsite
class CustomBackend(...):
def authenticate(self, **kwargs):
try:
identifier= kwargs.get('username')
password=kwargs.get('password')
account_type=kwargs.get('account_type', NORMAL_USER)
user = User.objects.get(identifier=identifier, account_type=account_type)
if user.check_password(password):
return user
except Exception:
# log exception
# authentication
from django.contrib.auth import authenticate
user = authenticate(username=identifier, password=password, account_type=OAUTH_USER)
From discussion, there are many ways to tackle this issue. But using two different models for authentication purpose is a bad idea. Because Django Auth was designed assuming one User Model.
This is how I would have approached:
- Rather than using
facebook_id
, I would have storedemail
from facebook's graph API. While creating the password, I would have used an unique UUID. - Put a temporary flag(a Model Boolean Field) to track if the user is registered through social media. So when he tries to login to normal path or registration, I would know that he does not have a password, so I would send him to reset his password. After successful reset password, I would have removed that temporary flag.
Other information which are absolute necessary, I would have used a separate model to store them. For example:
class Profile(models.Model):
...
phone_number = models.CharField(max_length=30)
user = models.OneToOneField(settings.AUTH_USER_MODEL)
And checked with login that if the User has a profile or not:
if not hasattr(user, 'profile'):
# redirect to more information page
For other permissions for accessing admin site should be restricted creation through normal registration or social registration. For these users, those fields(
is_staff
,is_superuser
etc should be False by default. For admin user creation, you can use createsuperuser command. For staff users, you can later assign a normal user to staff by making theis_staff
flagTrue
from adminsite orDjango Shell
.
Update(from comments...)
You can use custom Backend for authenticating facebook user:
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
class CustomModelbackend(ModelBackend):
def authenticate(self, phone_no=None, password=None, facebook_id=None, **kwargs):
# Override
UserModel = User
try:
user = UserModel.objects.get(Q(phone_no=phone_no) | Q(facebook_id=facebook_id))
if password and user.check_password(password):
return user
elif facebook_id:
return user
except Exception as e:
# log exception
and add it to AUTHENTICATION_BACKENDS in settings.py
:
AUTHENTICATION_BACKENDS = ['path.to.custom_backend.CustomBackend']
and call authenticate method like this:
authenticate(phone_no=phone_number, password=password) # for normal auth
authenticate(facebook_id=facebook_id) # for social auth
But you can't make your phone_no
unique as there is chance of the phone_no being empty for social login, but you can put it in USERNAME_FIELD
. So you will see warnings when you run django developer server(runserver command)
Update (2)
You can try like this:
NORMAL_USER = "N"
OAUTH_USER = "O"
AT_USER = "A"
USER_TYPES=(
(NORMAL_USER, 'Normal User'),
(OAUTH_USER, 'Oauth User'),
(AT_USER, 'Account Toolkit User')
)
class User(...):
username = models.CharField(max_length=255, default=uuid.uuid4, unique=True) # it might be unnecessary
account_type = models.CharField(max_length=2, choices = USER_TYPES, default=NORMAL_USER)
identifier = models.CharField(max_length=255)
...
class Meta:
unique_togather = ('account_type', 'identifier',)
# for Oauth Creation
User.objects.create(account_type=OAUTH_USER, identifier=facebook_id) # or email
# for Toolkit Creation
User.objects.create(account_type=AT_USER, identifier=phone_number)
# For normal User
User.objects.create(identifier=username, username=username)
#backend, and does not need to use ModelBackend, it will work with adminsite
class CustomBackend(...):
def authenticate(self, **kwargs):
try:
identifier= kwargs.get('username')
password=kwargs.get('password')
account_type=kwargs.get('account_type', NORMAL_USER)
user = User.objects.get(identifier=identifier, account_type=account_type)
if user.check_password(password):
return user
except Exception:
# log exception
# authentication
from django.contrib.auth import authenticate
user = authenticate(username=identifier, password=password, account_type=OAUTH_USER)
edited Nov 8 at 16:30
answered Nov 8 at 3:47
ruddra
7,82832546
7,82832546
You wouldn't need a temporary flag for the initial password prompt; a user that hasn't set a password will have apassword_hash
ofNone
, where a user that just forgot theirs will just have a mismatching hash when they input the wrong one. TheProfile
pattern is a good one, though, and very often a better way to go than a custom user model. I wouldn't usehasattr
to check for a profile, though. I'm not sure if the ORM will set that to None or just not create it, and I wouldn't depend on that behavior.Profile.objects.get(user=user)
might be a little heavy, but it's more concrete.
– kungphu
Nov 8 at 5:41
@kungphu I am not againstCustomUser
Model. Because the requirement here is to store some data which are absolutely necessary, but does not come through social login(from comments in question). Also, hasattr usage is per documentation found here (in the 2nd example).
– ruddra
Nov 8 at 5:48
@ruddra In my application users will not be able to perform registration by email, only Facebook Oauth2 or Account Kit. So I can create Basic User model with id, facebook_id and phone_number. User will be able to register by facebook_id or phone_number. I decided to add flag is_regular (to be sure that user finished registration and he can have profile and other info), but the main problem is that I need something for USERNAME_FIELD.
– Ernst
Nov 8 at 9:29
@Ernst please see my updated section in the answer.
– ruddra
Nov 8 at 9:55
@ruddra Than you for your help. I can't putphone_number
into USERNAME_FIELD as facebook user can have only email, but not always phone_number. One of possible solution is to putusername
into USERNAME_FIELD as each regular user will have it, but I don't like idea to put there dummy data for temporary users
– Ernst
Nov 8 at 10:44
|
show 5 more comments
You wouldn't need a temporary flag for the initial password prompt; a user that hasn't set a password will have apassword_hash
ofNone
, where a user that just forgot theirs will just have a mismatching hash when they input the wrong one. TheProfile
pattern is a good one, though, and very often a better way to go than a custom user model. I wouldn't usehasattr
to check for a profile, though. I'm not sure if the ORM will set that to None or just not create it, and I wouldn't depend on that behavior.Profile.objects.get(user=user)
might be a little heavy, but it's more concrete.
– kungphu
Nov 8 at 5:41
@kungphu I am not againstCustomUser
Model. Because the requirement here is to store some data which are absolutely necessary, but does not come through social login(from comments in question). Also, hasattr usage is per documentation found here (in the 2nd example).
– ruddra
Nov 8 at 5:48
@ruddra In my application users will not be able to perform registration by email, only Facebook Oauth2 or Account Kit. So I can create Basic User model with id, facebook_id and phone_number. User will be able to register by facebook_id or phone_number. I decided to add flag is_regular (to be sure that user finished registration and he can have profile and other info), but the main problem is that I need something for USERNAME_FIELD.
– Ernst
Nov 8 at 9:29
@Ernst please see my updated section in the answer.
– ruddra
Nov 8 at 9:55
@ruddra Than you for your help. I can't putphone_number
into USERNAME_FIELD as facebook user can have only email, but not always phone_number. One of possible solution is to putusername
into USERNAME_FIELD as each regular user will have it, but I don't like idea to put there dummy data for temporary users
– Ernst
Nov 8 at 10:44
You wouldn't need a temporary flag for the initial password prompt; a user that hasn't set a password will have a
password_hash
of None
, where a user that just forgot theirs will just have a mismatching hash when they input the wrong one. The Profile
pattern is a good one, though, and very often a better way to go than a custom user model. I wouldn't use hasattr
to check for a profile, though. I'm not sure if the ORM will set that to None or just not create it, and I wouldn't depend on that behavior. Profile.objects.get(user=user)
might be a little heavy, but it's more concrete.– kungphu
Nov 8 at 5:41
You wouldn't need a temporary flag for the initial password prompt; a user that hasn't set a password will have a
password_hash
of None
, where a user that just forgot theirs will just have a mismatching hash when they input the wrong one. The Profile
pattern is a good one, though, and very often a better way to go than a custom user model. I wouldn't use hasattr
to check for a profile, though. I'm not sure if the ORM will set that to None or just not create it, and I wouldn't depend on that behavior. Profile.objects.get(user=user)
might be a little heavy, but it's more concrete.– kungphu
Nov 8 at 5:41
@kungphu I am not against
CustomUser
Model. Because the requirement here is to store some data which are absolutely necessary, but does not come through social login(from comments in question). Also, hasattr usage is per documentation found here (in the 2nd example).– ruddra
Nov 8 at 5:48
@kungphu I am not against
CustomUser
Model. Because the requirement here is to store some data which are absolutely necessary, but does not come through social login(from comments in question). Also, hasattr usage is per documentation found here (in the 2nd example).– ruddra
Nov 8 at 5:48
@ruddra In my application users will not be able to perform registration by email, only Facebook Oauth2 or Account Kit. So I can create Basic User model with id, facebook_id and phone_number. User will be able to register by facebook_id or phone_number. I decided to add flag is_regular (to be sure that user finished registration and he can have profile and other info), but the main problem is that I need something for USERNAME_FIELD.
– Ernst
Nov 8 at 9:29
@ruddra In my application users will not be able to perform registration by email, only Facebook Oauth2 or Account Kit. So I can create Basic User model with id, facebook_id and phone_number. User will be able to register by facebook_id or phone_number. I decided to add flag is_regular (to be sure that user finished registration and he can have profile and other info), but the main problem is that I need something for USERNAME_FIELD.
– Ernst
Nov 8 at 9:29
@Ernst please see my updated section in the answer.
– ruddra
Nov 8 at 9:55
@Ernst please see my updated section in the answer.
– ruddra
Nov 8 at 9:55
@ruddra Than you for your help. I can't put
phone_number
into USERNAME_FIELD as facebook user can have only email, but not always phone_number. One of possible solution is to put username
into USERNAME_FIELD as each regular user will have it, but I don't like idea to put there dummy data for temporary users– Ernst
Nov 8 at 10:44
@ruddra Than you for your help. I can't put
phone_number
into USERNAME_FIELD as facebook user can have only email, but not always phone_number. One of possible solution is to put username
into USERNAME_FIELD as each regular user will have it, but I don't like idea to put there dummy data for temporary users– Ernst
Nov 8 at 10:44
|
show 5 more comments
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53187522%2ftemporary-users-in-django%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Why FB users should be temporary? BTW, its not a good idea to use 2 auth models. I don't think you can authenticate a user if its not related to a User model (for 2nd solution)
– ruddra
Nov 7 at 10:26
@ruddra Flow: front-end authorize user by facebook and gives me facebook_token -> back-end do request with this token and facebook_id will be in return -> back-end checks whether user exists in db -> if yes - auth user, else - it should be created temp user with auth token (to be sure that it's the same user and there is no need to verify user one more tome on registration) -> front-end let user to fill in required fields and than register real user
– Ernst
Nov 7 at 10:31
in that case, you can create a user with dummy data, or override the Django's own User Model make fewer fields required.
– ruddra
Nov 7 at 10:34
@ruddra I can't change my fields, all of them should stay required. What if user will close app on screen with all needed fields for users? User will stay with that dummy data on the next login and he will skip registration screen. Also this solution will combine auth/registration endpoints that I don't like.
– Ernst
Nov 7 at 10:45