Previous post: Building a Twitter Clone with Django REST Framework – I
User Authentication
We will perform Authentication using JWT or JSON Web Token. JWT is a secret token that is generated and sent to the browser when a user successfully logs in with his credentials. Subsequently, whenever the user accesses any link, the token is sent back to the server in the request header. The server checks for the validity of the token before granting access.
User ————-> Login Successful ——————————–> Server: Generates JWT
User <————- Returns JWT to browser ——————– Server
User ————-> Sends JWT in request header ————> Server validates JWT signature
User <————- Access Granted ——————————– Server sends Response
We will be using Python’s JWT Module – PyJWT here. I did try out Django Rest Framework’s JWT, but I found it hard to get it working with little/unclear documentation. Also, the process of token generation and encryption is better understandable using PyJWT. It is also very flexible for customization. To install:
pip install PyJWT
settings.py
#In settings.py
import uuid
#RANDOM AUTHENTICATION TOKEN GENERATOR
AUTH_TOKEN = uuid.uuid4().hex.upper()
Let’s write the views for Authentication:
import jwt
#set Expiration time for the token. Best practise is to set it less than 10 minutes
EXP_TIME = datetime.timedelta(minutes=5)
def GetToken(username):
'''
Purpose: Get Access token
Input:
username (mandatory) <str> Account user
password (mandatory) <str> Password
Output: Token that expires in 60 minutes
'''
try:
user = TUser.objects.get(username=username)
if user:
try:
payload = {'id':user.id,'username':user.username,'exp':datetime.datetime.utcnow()+EXP_TIME}
token = {'token':jwt.encode(payload,settings.AUTH_TOKEN).decode('utf8')}
# jwt.encode({'exp': datetime.utcnow()}, 'secret')
return token
except Exception as e:
error = {'Error_code': status.HTTP_400_BAD_REQUEST,
'Error_Message': "Error generating Auth Token"}
logger.error(e)
return Response(error, status=status.HTTP_403_FORBIDDEN)
else:
error = {'Error_code': status.HTTP_400_BAD_REQUEST,
'Error_Message': "Invalid Username or Password"}
return Response(error, status=status.HTTP_403_FORBIDDEN)
except Exception as e:
error = {'Error_code': status.HTTP_400_BAD_REQUEST,
'Error_Message': "Internal Server Error"}
logger.error(e)
return Response(error,status=status.HTTP_400_BAD_REQUEST)
def Auth(request,username):
'''
Purpose: Login to the Application
Input:
token (mandatory) <str> user token
Output: User object of the logged in user
'''
try:
token = request.session.get('authtoken').get('token')
payload = jwt.decode(token,settings.AUTH_TOKEN)
user = TUser.objects.get(username=username)
if payload.get('username') == user.username:
serializer = TUserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
error = {'Error_code': status.HTTP_403_FORBIDDEN,
'Error_Message': "Invalid User"}
logger.error(error)
return Response(error,status=status.HTTP_403_FORBIDDEN)
except (jwt.ExpiredSignature, jwt.DecodeError, jwt.InvalidTokenError) as e:
error = {'Error_code': status.HTTP_403_FORBIDDEN,
'Error_Message': "Token is Invalid/Expired"}
logger.error(e)
return Response(error,status=status.HTTP_403_FORBIDDEN)
except Exception as e:
error = {'Error_code': status.HTTP_403_FORBIDDEN,
'Error_Message': "Internal Server Error"}
logger.error(e)
return Response(error,status=status.HTTP_403_FORBIDDEN)
@api_view(['POST'])
def Login(request,username=None,password=None):
'''
Authenticate if username and password is correct.
Input
Output: return User object or Error
'''
username = request.query_params.get('username')
password = request.query_params.get('password')
try:
user = TUser.objects.get(username=username)
if user.password == password:
token = GetToken(username)
user.token = token['token']
user.save()
request.session['authtoken'] = token
serializer = TUserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
error = {'Error_code': status.HTTP_400_BAD_REQUEST,
'Error_Message': "Invalid Username or Password"}
return Response(error,status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
error = {'Error_code': status.HTTP_400_BAD_REQUEST,
'Error_Message': "Invalid Username"}
logger.error(e)
return Response(error,status=status.HTTP_400_BAD_REQUEST)
App Users – REST APIs
urls.py: https://github.com/shilpavijay/TwitterClone/blob/main/TUsers/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('signup', views.AccountSignup),
path('login', views.Login),
path('<str:username>/account', views.AccountUpdate),
path('token', views.GetToken),
path('auth', views.Auth),
path('<str:loggedin_user>/<str:user>/follow', views.FollowUser),
path('<str:username>/followers', views.GetFollowers),
path('<str:username>/following', views.GetFollowing),
path('<str:username>/block', views.Block_user),
path('users',views.users), #debugging
]
serializer.py : https://github.com/shilpavijay/TwitterClone/blob/main/TUsers/serializer.py
from rest_framework import serializers
from TUsers.models import TUser
class TUserSerializer(serializers.ModelSerializer):
class Meta:
model = TUser
fields = '__all__'
views.py : This file content is too large to be placed here and is far more readable on Github. Link: https://github.com/shilpavijay/TwitterClone/blob/main/TUsers/views.py
App Tweets – REST APIs
urls.py: https://github.com/shilpavijay/TwitterClone/blob/main/Tweets/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('create', views.CreateTweet),
path('<str:username>/', views.Timeline),
path('<int:tweet_id>/delete', views.DeleteTweet),
path('<int:tweet_id>', views.ShowTweet),
path('<int:tweet_id>/reply', views.Reply),
path('<int:tweet_id>/retweet', views.Retweet),
path('<int:tweet_id>/like', views.Like),
path('search', views.Search),
path('all_tweets', views.all_tweets), #debugging
]
serializer.py: https://github.com/shilpavijay/TwitterClone/blob/main/Tweets/serializer.py
from rest_framework import serializers
from Tweets.models import TCtweets
from django import forms
class TCtweetsSerializer(serializers.ModelSerializer):
class Meta:
model = TCtweets
fields = '__all__'
class TCValidator(forms.Form) :
username = forms.CharField()
tweet_text = forms.CharField()
class ReplyValidator(forms.Form):
username = forms.CharField()
reply_text = forms.CharField()
class RetweetValidator(forms.Form):
username = forms.CharField()
views.py: https://github.com/shilpavijay/TwitterClone/blob/main/Tweets/views.py
The complete code for this project can be found in my GitHub repository: https://github.com/shilpavijay/TwitterClone
In the next post, we will use some of these API end-points and see how they work.
Next post: Building a Twitter Clone with Django REST Framework – III
Pingback: Building a Twitter Clone with Django REST Framework – III – Reverie
Pingback: Twitter Clone with Django REST Framework – I – Reverie