Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
April 17, 2023 10:12 pm GMT

Implementing JWT Authentication and User Profile with Django Rest API Part 3

Introduction

Welcome back to part three of our React and Django series, where we're creating a Notes app from scratch. As we know, security is of utmost importance in any web application, and authentication is a crucial aspect of it. This is where JWT comes in. JSON Web Tokens (JWT) is a widely used standard for securely transmitting information between parties as a JSON object. JWT allows us to authenticate users and secure our application by encrypting and transmitting user data in the form of a token. It's a great option for authentication because it allows us to store user information directly in the token, making it easy to verify the user's identity with every subsequent request. In this post, we'll be implementing JWT authentication to ensure that our users' data stays safe and secure. So, let's dive in and learn how to secure our Notes app with JWT authentication

Installing Neccessary Packages

Before we start off we need to install the necessary django packages required for JWT these are:

pip install djangorestframework-simplejwt
The djangorestframework-simplejwt package provides a simple way to implement JWT authentication in Django REST framework applications. It includes views and serializers for generating and refreshing JWT tokens, as well as a built-in token authentication backend for validating tokens.

Next we can then include it in the installed apps and add it to the settings:

INSTALLED_APPS = [    'rest_framework_simplejwt.token_blacklist',]

Token blacklisting involves maintaining a list of tokens that have been revoked or expired, and checking each incoming token against this list to ensure that it is still valid. This can help to prevent security vulnerabilities, such as token theft or replay attacks.

Next,we setup the JWT:

SIMPLE_JWT = {    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=180),    'REFRESH_TOKEN_LIFETIME': timedelta(days=50),    'ROTATE_REFRESH_TOKENS': True,    'BLACKLIST_AFTER_ROTATION': True,    'UPDATE_LAST_LOGIN': False,    'ALGORITHM': 'HS256',    'VERIFYING_KEY': None,    'AUDIENCE': None,    'ISSUER': None,    'JWK_URL': None,    'LEEWAY': 0,    'AUTH_HEADER_TYPES': ('Bearer',),    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',    'USER_ID_FIELD': 'id',    'USER_ID_CLAIM': 'user_id',    'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),    'TOKEN_TYPE_CLAIM': 'token_type',    'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',    'JTI_CLAIM': 'jti',    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),}

Here is an explanation of some of the key settings:

ACCESS_TOKEN_LIFETIME: This sets the lifetime of the access token, which is the token that grants access to protected resources. In this case, it is set to 180 minutes (or 3 hours).

REFRESH_TOKEN_LIFETIME: This sets the lifetime of the refresh token, which is used to obtain a new access token after the original token expires. In this case, it is set to 50 days.

ROTATE_REFRESH_TOKENS: This setting controls whether or not refresh tokens are rotated when a new access token is issued. If it is set to True, a new refresh token will be issued with each new access token.

BLACKLIST_AFTER_ROTATION: This setting controls whether or not refresh tokens that have been rotated are blacklisted. If it is set to True, any previous refresh tokens will be invalidated when a new one is issued.

ALGORITHM: This sets the algorithm used to sign the JWT tokens. In this case, it is set to HS256, which is a symmetric-key algorithm that uses a shared secret key to sign and verify tokens.

AUTH_HEADER_TYPES: This specifies the types of authentication headers that can be used to send JWT tokens. In this case, it is set to ('Bearer',) which is the most commonly used type.

AUTH_HEADER_NAME: This sets the name of the HTTP header that will be used to send the authentication token. In this case, it is set to 'HTTP_AUTHORIZATION'.

USER_ID_FIELD: This sets the name of the field that will be used to identify the user in the JWT token. In this case, it is set to 'id'.

USER_ID_CLAIM: This sets the name of the claim that will be used to store the user ID in the JWT token. In this case, it is set to 'user_id'.

AUTH_TOKEN_CLASSES: This sets the classes of authentication tokens that will be accepted by the authentication system. In this case, it is set to ('rest_framework_simplejwt.tokens.AccessToken',) which is the default token class.

JTI_CLAIM: This sets the name of the claim that will be used to store the unique identifier of the JWT token. In this case, it is set to 'jti'.

Configuring the urls

Now that we have configured the settings, we can now move to configuring our authentication urls, where we will we will create the URL for access token and refresh token in the app level since we are only authenticating one app:

urlpatterns = [    #Authentication    path('token/', views.MyTokenObtainPairView.as_view(), name='token_obtain_pair'),    path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),]

Great now that we have that setup we can create a Profile for our Notes app users such that only authenticated users have access to the app.We can do that by first:

  1. Creating User Model
from django.contrib.auth.models import AbstractUserclass CustomUser(AbstractUser):    bio = models.CharField(max_length=255, blank=True)    cover_photo = models.ImageField(upload_to='covers/', null=True, blank=True)

By creating a custom user model that extends AbstractUser, you can add fields that are specific to your application and provide additional functionality beyond what is available in the default User model.

2.Updating our original Note Model

user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True, blank=True, related_name='notes')

This line of code defines a foreign key relationship between the Note model and the CustomUser model. The ForeignKey field creates a many-to-one relationship between Note and CustomUser, meaning that each Note is associated with a single CustomUser instance, but a CustomUser can have many Note instances.

3.**Creating Views for UserProfile, Register and Login

#Login Userclass MyTokenObtainPairView(TokenObtainPairView):    serializer_class = MyTokenObtainPairSerializer#Register Userclass RegisterView(generics.CreateAPIView):    queryset = CustomUser.objects.all()    permission_classes = (AllowAny,)    serializer_class = RegisterSerializer#api/profile  and api/profile/update@api_view(['GET'])@permission_classes([IsAuthenticated])def getProfile(request):    user = request.user    serializer = ProfileSerializer(user, many=False)    return Response(serializer.data)@api_view(['PUT'])@permission_classes([IsAuthenticated])def updateProfile(request):    user = request.user    serializer = ProfileSerializer(user, data=request.data, partial=True)    if serializer.is_valid():        serializer.save()    return Response(serializer.data)#api/notes@api_view(['GET'])@permission_classes([IsAuthenticated])def getNotes(request):    public_notes = Note.objects.filter(is_public=True).order_by('-updated')[:10]    user_notes = request.user.notes.all().order_by('-updated')[:10]    notes = public_notes | user_notes    serializer = NoteSerializer(notes, many=True)    return Response(serializer.data)

The first code defines a custom token obtain view that uses a custom serializer for obtaining a JSON Web Token (JWT) pair for a user.

The second code defines a view for registering a new user. It uses the built-in Django CreateAPIView and sets the permission class to AllowAny, which means anyone can access this view.

The next two codes define views for getting and updating the user's profile. The getProfile view returns the serialized data for the currently authenticated user's profile, while the updateProfile view updates the profile with the data in the request. Both views require the user to be authenticated with the IsAuthenticated permission class.

Finally, the last code defines a view for getting the user's notes. It first filters public notes and then the user's notes before combining them and returning them in serialized formThe first code defines a custom token obtain view that uses a custom serializer for obtaining a JSON Web Token (JWT) pair for a user.
The second code defines a view for registering a new user. It uses the built-in Django CreateAPIView and sets the permission class to AllowAny, which means anyone can access this view.
The next two codes define views for getting and updating the user's profile. The getProfile view returns the serialized data for the currently authenticated user's profile, while the updateProfile view updates the profile with the data in the request. Both views require the user to be authenticated with the IsAuthenticated permission class.
Finally, the last code defines a view for getting the user's notes. It first filters public notes and then the user's notes before combining them and returning them in serialized form. This view also requires the user to be authenticated with the IsAuthenticated permission class.. This view also requires the user to be authenticated with the IsAuthenticated permission class.

  1. Adding our Serializers
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):    @classmethod    def get_token(cls, user):        token = super().get_token(user)        # Add custom claims        token['username'] = user.username        token['email'] = user.email        # ...        return tokenclass RegisterSerializer(serializers.ModelSerializer):    password = serializers.CharField(        write_only=True, required=True, validators=[validate_password])    password2 = serializers.CharField(write_only=True, required=True)    email = serializers.EmailField(        required=True,        validators=[UniqueValidator(queryset=CustomUser.objects.all())]    )    class Meta:        model = CustomUser        fields = ('username', 'email', 'password', 'password2', 'bio', 'cover_photo')    def validate(self, attrs):        if attrs['password'] != attrs['password2']:            raise serializers.ValidationError(                {"password": "Password fields didn't match."})        return attrs    def create(self, validated_data):        user = CustomUser.objects.create(            username=validated_data['username'],            email=validated_data['email'],            bio=validated_data['bio'],            cover_photo=validated_data['cover_photo']        )        user.set_password(validated_data['password'])        user.save()        return userclass ProfileSerializer(serializers.ModelSerializer):    notes = NoteSerializer(many=True, read_only=True)    class Meta:        model = CustomUser        fields = '__all__'

MyTokenObtainPairSerializer: a subclass of TokenObtainPairSerializer that adds custom claims (username, email, etc.) to the token payload.
RegisterSerializer: a serializer for the user registration process. It validates the password fields, checks for duplicate emails, and creates a new CustomUser instance if all fields are valid.
ProfileSerializer: a serializer for the user profile data. It includes all fields from the CustomUser model and also serializes the related notes using NoteSerializer.

  1. ** Configuring URL's **
#Authenticationpath('token/', views.MyTokenObtainPairView.as_view(), name='token_obtain_pair'),path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),path('register/', views.RegisterView.as_view(), name='auth_register'),#Profilepath('profile/', views.getProfile, name='profile'),path('profile/update/', views.updateProfile, name='update-profile'),

NB: I haven't included the Note url because I explained it in part one of the series

Okay Great with this steps done, we can now test whether our back-end is working as expected but first we need to apply migrations as we have created and modified our tables:

python manage.py makemigrations
python manage.py migrate

Testing our Backend with Postman

Lets now test our backend using Postman,Postman is a popular software application that allows developers to test and debug APIs. With Postman, developers can send HTTP requests to a web server and view the response.

  1. Registering Our User

Register User

we make a POST request to 'api/register/' to create a new user

2.Logging in

login

For logging in our new user we will use use the POST 'api/token' endpoint to generate our access and refresh tokens to allow authentication, we will can manually copy them as we are using postman to test and are yet to configure the endpoint with our react frontend

3.Checking our UserProfile
In-order as to process an authenticated request using postman we have to paste in the access token we got when we logged in to the bearer token in authorization like so GET 'api/profile':

bearer

thus getting the user profile that we created:

User Profile

4.Get your Notes
A user can now access their notes when authenticated through the GET 'api/notes' endpoint:

notes

Note that the list is empty as the user has not created a note yet

Conclusion

In conclusion, I want to extend my heartfelt thanks to you for taking the time to read this article on JWT authentication in the Django backend of our React Django notes app. I hope that this article has been helpful and informative as we work towards creating a fully authenticated app. In the next part of this series, we will dive deeper into integrating authentication with the React frontend so that we can see the app in its full authenticated glory. If you're interested in exploring the code, you can find the link to the app on Github(ReactDjango Notes App). Once again, thank you for reading and I look forward to sharing the next part of this series with you soon.


Original Link: https://dev.to/ki3ani/implementing-jwt-authentication-and-user-profile-with-django-rest-api-part-3-3dh9

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To