I'm using django-rest-framework-simplejwt for user registration.
Following this tutorial enter link description here
I code like following:
class RegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(
style={'input_type': 'password'}, write_only=True,
)
password2 = serializers.CharField(
style={'input_type': 'password'},max_length=20
)
tokens = serializers.SerializerMethodField()
class Meta:
model = UserProfile
fields = ['username', 'email', 'password', 'password2', 'tokens']
def get_tokens(self, user):
user = UserProfile(
email=self.validated_data['email'],
username=self.validated_data['username']
)
password = self.validated_data['password']
password2 = self.validated_data['password2']
if password != password2:
raise serializers.ValidationError({'password': 'Passwords must match.'})
user.set_password(password)
tokens = RefreshToken.for_user(user)
refresh = text_type(tokens)
access = text_type(tokens.access_token)
data = {
"refresh": refresh,
"access": access
}
return data
def save(self):
user = UserProfile(
email=self.validated_data['email'],
username=self.validated_data['username']
)
password = self.validated_data['password']
password2 = self.validated_data['password2']
if password != password2:
raise serializers.ValidationError({'password': 'Passwords must match.'})
user.set_password(password)
user.save()
return user
in view:
class UserCreateView(generics.CreateAPIView):
'''create user'''
serializer_class = RegistrationSerializer
The problem is each time I create a user,I can get the return of the 2 two tokens,however in data base I can't find the token.
So I guess I didn't store them,so should I store the token?
JWT can be used for database-less authentication. because it encodes data needed for authentication in tokens. Your app will be able to authenticate users after decoding tokens with data embedded in it.
But if you want to store tokens in simplejwt you can use OutstandingingToken model which is implemented in simplejwt to store tokens in database.
Before using OutstandingToken, make sure you put rest_framework_simplejwt.token_blacklist in your INSTALLED_APPS list of your project settings.
Related
I'm working on a project where user can register using his mobile no and password( after verifying with otp) what i'm doing is inside username field i'm saving user's phone no. as username is a mandatory field. And I'm using simple_jwt to get access token and refresh token. Everything is working fine
urls.py
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns =[
path('register', RegisterView.as_view() ),
path('otp/send', SendOtpView.as_view() ),
path('otp/verify', VerifyOtpView.as_view() ),
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
instead of creating a loginSerializer i'm using rest_framework_simplejwt inbuild class TokenObtainPairView
But when i go to the url
auth/api/token/ it ask me for username and password, which is confusing as a user . how can i change the name of the username to phone.
I don't have any idea how to do it as I'm new to the djangorestframework
serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class LoginSerializer(TokenObtainPairSerializer):
password = serializers.CharField(max_length=65, min_length=8, write_only=True)
**phone = serializers.CharField(max_length=20, source='username')**
class Meta:
model = User
fields = ['phone', 'password']
I tried doing this but then it add on another field with the name phone instead of replacing username. I even don't know whether it will work or not .
Ok. Somehow i found a way to do it. I created a model serializer an used an custom token generator method
https://django-rest-framework-simplejwt.readthedocs.io/en/latest/creating_tokens_manually.html
serializers.py
# for creating token manually
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
class LoginSerializer(serializers.ModelSerializer):
password = serializers.CharField(max_length=65, min_length=8, write_only=True)
phone = serializers.CharField(max_length=20, source='username')
tokens = serializers.SerializerMethodField(read_only=True)
def get_tokens(self, obj):
return obj['tokens']
class Meta:
model = User
fields = ['phone', 'password', 'tokens']
def validate(self, attrs):
param_phone = attrs.get('username', "")
param_password=attrs.get('password', '')
if not param_phone.isnumeric():
raise serializers.ValidationError(
{'phone': ("Only Phone Number is accepted")}
)
if not len(param_phone) == 10:
raise serializers.ValidationError(
{'phone': ("Phone Number length can only be of 10 digits")}
)
user = auth.authenticate(username=param_phone, password=param_password)
if User.objects.filter(username=param_phone, is_active=False).exists():
raise AuthenticationFailed("Account disabled. Contact admin")
if not user:
raise AuthenticationFailed("Invalid Credential. Try again")
tokens = get_tokens_for_user(user)
return {
'username':param_phone,
'tokens': tokens
}
return super().validate(attrs)
views.py
class LoginView(GenericAPIView):
serializer_class = LoginSerializer
def post(self, request):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(serializer.data, status=status.HTTP_200_OK)
I'm trying to develop a Facebook social authentication feature on an application that uses a custom Django user model and django-rest-framework-social-oauth2 as the social authentication package. My custom user model is called 'Account' and it inherits from the AbstractBaseUser class. The Account model is shown below:
class Account(AbstractBaseUser):
# Account model fields:
email = models.EmailField(verbose_name='email', max_length=60, unique=True)
username = models.CharField(max_length=30, unique=True)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
date_joined = models.DateTimeField(verbose_name='date joined', auto_now_add=True)
last_login = models.DateTimeField(verbose_name='last login', auto_now=True)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
# The user will log in with their email instead of username:
USERNAME_FIELD = 'email'
# Required fields when registering, other than the email:
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']
# Telling the Account object how to use the account manager:
objects = MyAccountManager()
The function that handles creating a new user is called 'create_user' and is defined within my custom written MyAccountManager class which extends the Django BaseUserManager class. This is given below:
class MyAccountManager(BaseUserManager):
def create_user(self, email, username, first_name, last_name, password=None):
# Checking to see if correct function parameters have been passed in:
if not email:
raise ValueError('Users must have an email address')
if not username:
raise ValueError('Users must have a username')
if not first_name:
raise ValueError('Users must have a first name')
if not last_name:
raise ValueError('Users must have a last name')
# Creating the new user:
user = self.model(
email = self.normalize_email(email),
username = username,
first_name = first_name,
last_name = last_name,
)
user.set_password(password)
user.save(using = self._db)
return user
I've set up a working django-rest-framework-social-oauth2 url for creating a new user with a Facebook account. The relevant Facebook configuration in the Django settings.py file is shown below:
SOCIAL_AUTH_FACEBOOK_KEY = config('SOCIAL_AUTH_FACEBOOK_KEY')
SOCIAL_AUTH_FACEBOOK_SECRET = config('SOCIAL_AUTH_FACEBOOK_SECRET')
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { 'fields': 'id, name, email' }
The issue that I've been having is the following:
When the create_user function is called for a user that is using Facebook social login, the parameters email, first_name and last_name, that are required in the create_user function are not being provided by Facebook and I'm getting the error message shown in the image. The error message states the following:
create_user() missing 2 required positional arguments: 'first_name' and 'last_name'
Error Message from Django
Does anyone know how I would be able to access these additional parameters (email, first name, last name) from Facebook so that the correct parameters are passed into the create_user function?
Further Information
On implementing the pipeline suggestion I am still left with the same issue whereby the custom create_user function is missing both the first_name and last_name parameters. I think the reason that this occurring is due to the suggested pipeline cleanup_social_account function being called after create_user, where in my case both first_name and last_name are required fields, and as such a user object cannot be created in the database if they are not provided at the time the create_user function is called.
I am receiving this error due to the following function in the suggested custom pipeline:
social_core.pipeline.user.create_user
The code for this function in the social_core installed library is the following:
def create_user(strategy, details, backend, user=None, *args, **kwargs):
if user:
return {'is_new': False}
fields = dict((name, kwargs.get(name, details.get(name)))
for name in backend.setting('USER_FIELDS', USER_FIELDS))
if not fields:
return
return {
'is_new': True,
'user': strategy.create_user(**fields)
}
The details parameter passed into the above function contains the values that I need (first_name and last_name). However, they are not actually being added into the fields variable when it is created. The fields variable is shown above and is defined by the following:
fields = dict((name, kwargs.get(name, details.get(name)))
for name in backend.setting('USER_FIELDS', USER_FIELDS))
In summary:
The issue appears to be that first_name and last_name are not appearing within backend.settings('USER_FIELDS', USER_FIELDS), and therefore are not being added to the fields variable, and as such are not being passed into strategy.create_user(**fields).
So social_auth auto-populates those fields for me when I just get name and email from Facebook. It knows to bring in first_name and last_name. Since it doesn't seem to be working for you, you can create a custom pipeline function.
settings.py:
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'social_core.pipeline.social_auth.associate_by_email',
'social_core.pipeline.user.create_user',
# YOUR CUSTOM PIPELINE FUNCTION HERE. I CREATED A FILE/MODULE
# NAMED pipeline.py AND STUCK IT IN THERE. MAKE SURE TO PUT THIS
# AFTER CREATE USER.
'path.to.custom.pipeline.cleanup_social_account',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
)
pipeline.py:
def cleanup_social_account(backend, uid, user=None, *args, **kwargs):
"""
3rd party: python-social-auth.
Social auth pipeline to cleanup the user's data. Must be placed
after 'social_core.pipeline.user.create_user'.
"""
# Check if the user object exists and a new account was just created.
if user and kwargs.get('is_new', False):
*** THIS IS UNTESTED, BUT FACEBOOK'S DATA SHOULD COME INTO THE DETAILS KWARG ***
user.first_name = kwargs['details']['first_name']
user.last_name = kwargs['details']['last_name']
user.save()
return {'user': user}
Just add SOCIAL_AUTH_GOOGLE_OAUTH2_USER_FIELDS = ['username', 'email', 'first_name', 'last_name'] in your settings.py file and these fields should be added to **kwargs in you create_user method.
Sample Usage
In settings.py file add this
SOCIAL_AUTH_GOOGLE_OAUTH2_USER_FIELDS = ['first_name', 'last_name', 'email']
In your UserManager class modify create_user like below
def create_user(self, password=None, **kwargs):
"""Create and return a `User` with an email, username and password."""
first_name = kwargs.get('first_name')
last_name = kwargs.get('last_name')
email = kwargs.get('email')
if first_name is None or last_name is None:
raise TypeError(f"Invalid FirstName: {first_name}, LastName: {last_name}, Email: {email}")
if email is None:
raise TypeError("Users must have an email address.")
user = self.model(
first_name=first_name,
last_name=last_name,
email=self.normalize_email(email),
)
user.set_password(password)
user.save()
return user
I am working on a authentication application where user's can login via
(email or mobile) and (password or otp)
Framework/ Library Used
Django Rest Framework and djangorestframework-simplejwt
I am trying to add multiple claims in the jwt token getting generated.
Below is my LoginView and LoginSerializer.
View
class Login(TokenObtainPairView):
serializer_class = LoginSerializer
Serializer
class LoginSerializer(TokenObtainPairSerializer):
mobile = serializers.CharField(allow_blank=True)
email = serializers.EmailField(allow_blank=True)
password = serializers.CharField(allow_blank=True)
otp = serializers.CharField(allow_blank=True)
#classmethod
def get_token(cls, user):
token = super().get_token(user)
token['name'] = user.first_name
return token
def validate(self, attrs):
mobile = attrs.get("mobile", None)
email = attrs.get("email", None)
password = attrs.get("password", None)
otp = attrs.get("otp", None)
user = authenticate(mobile=mobile, email=email, password=password, otp=otp)
if user is None and self.password:
raise serializers.ValidationError(
detail="Incorrect Username or Password.", code=HTTP_401_UNAUTHORIZED
)
if user.is_active:
refresh = self.get_token(user)
data = dict()
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)
return data
if user.is_locked:
raise serializers.ValidationError(
detail="Account Locked. Contact Support.", code=HTTP_423_LOCKED
)
raise serializers.ValidationError(
detail="User Account is Deactivated.",
code=HTTP_401_UNAUTHORIZED
)
But i am getting email can't be blank error when sending a valid phone number and password in request. This is because of TokenObtainPairSerializer which checks for User.USERNAME_FIELD (which in my case is email.)
How can i handle this situation or get it working ?
I have two types of users in the system, I want to assign the appropriate group at the time of signing up. Referring to How to customize user profile when using django-allauth, I thought I can override the Signup form and do something like:
class CustomSignupForm(forms.Form):
login_widget = forms.TextInput(attrs={'type': 'email',
'placeholder': _('email'),
'autofocus': 'autofocus',
'class': 'form-control'
})
email = forms.EmailField(label='Email', widget=login_widget)
password = PasswordField(label='Password', widget=forms.PasswordInput(attrs={'class': 'form-control'}))
password2 = PasswordField(label='Re-type Password', widget=forms.PasswordInput(attrs={'class': 'form-control'}))
def save(self, request, user):
role = request.GET.get('type')
print(role)
group = role or "group1"
g = Group.objects.get(name=group)
user.groups.add(g)
user.save()
But I keep getting the error below:
save() missing 1 required positional argument: 'user'
Also, I have configured allauth to use email to login.
Thanks for your help.
signup is the method to override not save.
class LocalSignupForm(forms.Form):
pass
def signup(self, request, user):
role = request.session.get('user_type')
group = role or "Default"
g = Group.objects.get(name=group)
user.groups.add(g)
user.save()
Also the settings has to be
ACCOUNT_SIGNUP_FORM_CLASS = 'useraccount.forms.LocalSignupForm'
I have an app with 2 user levels; Superuser, and Staff. Superuser will login to this app and create a new user and assign the user either Superuser or Staff permissions, but they are not logging in through the default Django admin, they will be logging into a custom admin. How do I display the permission checkboxes in my form and how do I save them for that new user being created? Is it merely just a normal checkbox or is there something I need to override to be able to accomplish this?
Here is my CreateUserForm as it is now.
class CreateUserForm(forms.Form):
username = forms.EmailField(max_length=50)
email = forms.EmailField()
first_name = forms.CharField(max_length=150)
last_name = forms.CharField(max_length=150)
password1 = forms.CharField(max_length=30, widget=forms.PasswordInput(render_value=False), label='Password')
password2 = forms.CharField(max_length=30, widget=forms.PasswordInput(render_value=False), label='Password Confirmation')
address_1 = forms.CharField(max_length=50)
address_2 = forms.CharField(max_length=50)
city = forms.CharField(max_length=50)
province = forms.CharField(max_length=2)
country = forms.CharField(max_length=50)
postal_code = forms.CharField(max_length=10)
work_phone = forms.CharField(max_length=20)
mobile_phone = forms.CharField(max_length=20)
fax = forms.CharField(max_length=20)
url = forms.CharField()
comments = forms.CharField(widget=forms.Textarea)
def clean_username(self):
try:
User.objects.get(username=self.cleaned_data['username'])
except User.DoesNotExist:
return self.cleaned_data['username']
raise forms.ValidationError("Sorry, this username has already been taken. Please choose another.")
def clean_email(self):
try:
User.objects.get(email=self.cleaned_data['email'])
except User.DoesNotExist:
return self.cleaned_data['email']
raise forms.ValidationError("Sorry, this email has already been taken. Please choose another.")
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError("You must type the same password each time.")
if ' ' in self.cleaned_data['username']:
raise forms.ValidationError("username must not contain spaces")
return self.cleaned_data
def save(self):
new_user = User.objects.create_user(
username = self.cleaned_data['username'],
email = self.cleaned_data['email']
)
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.set_password(self.cleaned_data['password'])
new_user.is_active = True
new_user.save()
new_profile = UserProfile(
# TODO:
)
Thanks
If you mean the permission checkboxes for is_staff and is_superuser, then you're probably best off using a ModelForm w/ the User model. This will automatically take care of everything for you.
class UserForm(forms.ModelForm):
class Meta:
model = User
exclude = ('last_login', 'date_joined')
EDIT per OP update: You can just add 2 forms.BooleanField() fields to your form for is_superadmin and is_staff (or name them differently), and much like you're already doing in the save method you can do new_user.is_staff = self.cleaned_data['is_staff']. You may consider using a choice field instead, w/ a dropdown w/ 3 entries, "Normal User", "Staff User", "Admin User", and then set is_staff and is_superadmin according to the user's selection.
It's also still possible to use a ModelForm on the User model and just add the necessary extra fields in addition. It may or may not be worth it to you, depending how custom you're getting.
Another note about your form - when it comes to editing existing users, this won't be able to be used for that w/ the current clean methods on username/email. But if you tweak those to exclude the current instance (if set) from the lookup, you will be able to use this form for editing existing users (though since it's not a model form you'd need to populate the initial data manually).