With email sending configured, the next step is to send a verification link to a newly registered user. Upon clicking this verification link, the user account should become active. This can be done in the send_verification_link_email util method. To create tokens, I am using the Simple JWT package that is recommended with Django Rest Framework.
from rest_framework_simplejwt.tokens import RefreshToken
verification_token = RefreshToken.for_user(user)
verification_token.set_exp(
from_time=verification_token.current_time,
lifetime=timedelta(minutes=settings.EMAIL_VERIFICATION_TIMELIMIT)
)
To create the verification link, rather than format the entire URL as a string, I use a combination of Django settings and URL reverse lookups. To begin with, the method used to include the URL end points for the user_auth app was wrong. In the main urls.py file in server directory, I need to define a namespace for the user_auth app.
urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include('user_auth.urls', namespace='user_auth')),
]
And in the urls.py in the user_auth module, I need to define an app_name:
app_name = 'user_auth'
urlpatterns = [
path('register-user', RegisterUserView.as_view(), name='register-user'),
path('verify-user/', VerifyUserView.as_view(), name='verify-user'),
]
It is possible to
extract the domain name along with the protocol (http or https) from the
request object. But rather than pass the request object to a utility function,
the base domain name along with the protocol can be a setting parameter which
will change with the environment. So, in the settings.py file:
# Base URL BASE_URL = KEYS.BASE_URL
And in the enviromment,
this BASE_URL can be defined as http://localhost:8000
which is the development server. Once this is done, using string formatting and
URL reverse lookups, the verification link can be inserted into email message.
message_body = (
"Hello,\n"
"Thank you for registering with Online Edu!\n"
"\n"
"You are not yet ready to use your account.
Before you can login to the website,
please verify your email by clicking on this link:\n"
"{base_url}{token_url} \n"
"\n"
"Please click on this link within 15 minutes of receiving this email.\n"
"\n"
"Thank you,\n"
"Online Edu"
).format(
base_url=settings.BASE_URL,
token_url=reverse(
'user_auth:verify-user',
args=[verification_token]
)
)
With this, the
amount of hardcoding is the least and with changes in deployment or for that
matter in the URL end-points, no changes will need to be made in the utility
method that sends the email.
To handle the verification,
we need another view class. From the Simple JWT documentation, there are ready
made views that extract the token and verify it. However, in my case, I need to
verify the token and if verified, the new user account needs to be set to
active. Sadly, the Simple JWT documentation is very scant and to figure out all
that can be done by this package, it is necessary to browse through the Github
code. For now, this view class will do as clicking the link is a GET request
with the token as a URL parameter rather than embedded in the header which would
be the case with more API requests. Therefore, a lot of the code is custom, though
it might be possible to replace it later with boilerplate code.
class VerifyUserView(APIView):
'''Checks token received on clicking verification link'''
def get(self, *args, **kwargs):
verification_token = self.kwargs['token']
if not verification_token:
return Response(
data='Missing token',
status=status.HTTP_400_BAD_REQUEST
)
token_data = TokenRefreshSerializer(
data={'refresh': verification_token}
)
try:
if not token_data.is_valid():
error_list = [token_data.errors[e][0].title()
for e in token_data.errors]
return Response(
data=error_list[0],
status=status.HTTP_400_BAD_REQUEST
)
else:
# Set the user to active
user_data = RefreshToken(verification_token)
user_obj = User.objects.get(id=int(user_data['user_id']))
user_obj.is_active = True
user_obj.save()
except Exception as e:
return Response(
data=str(e),
status=status.HTTP_400_BAD_REQUEST
)
return Response(status=status.HTTP_200_OK)
If no token is passed to the URL end point or if the token is invalid or has been tampered with, the view will return a 400 bad request response. There is a TokenRefreshSerializer serializer in Simple JWT which takes the token as data. This then has a validation method which checks for expiry and also things like blacklisting of tokens. Though this is a serializer, it does not seem possible to extract the .data attribute and then check the payload for the user information. To be able to do this, I import the RefreshToken model from SimpleJWT and pass the verification token to it. This allows to extract the user_id from the payload. This is necessary to set the is_active to True.
This is the first task that has resulted in me thinking about JWTs and how they can be used. Specifically, how to handle an expired token and what can be done with it. This is to be handled on the frontend as a user who clicks on the verification link after it expires will need a refresh options.
No comments:
Post a Comment