Realtime Django Part 6: Build a Chat application RabbitMQ and uWSGI websockets (Extras)
This part is going to be about improving the Chat application, I might not take my time to explain some things but if there’s anything that’s not clear enough; Feel free to ask a question.
Frontend Improvements
Implementing a loading screen
If you paid close attention, you’ll notice that before we determine if we should join a chat session or not. An empty chat page is displayed (Since it’s the default view of the component).
We don’t want users to see this. We want to show them a beautiful “loading page” and then redirect then display the appropriate view.
To do this, we need to add a new “loading” state to our component
By default it’s set to true which means the chat interface is still loading (In the background we’re trying to join a chat session)
Next, we need to set the change modify the conditions in the template (Some content has been collapsed). We now have a new block that’s displayed when the other conditions fail. disqus.svg
is a loader that i downloaded from loader.io You can download it from the Github repo. You’ll see it soon.
If you save and reload the application, you should see the loading screen (At least the “Loading…” text) forever!
We need to tell our component that the we’ve started a new session by setting the “loading” state to false which makes one of the previous conditions true. We can hook that into the fetchChatSession
method.
Instead of simply setting this.loading = false
we used the setTimeout
function to delay it for 2 seconds. I did this so we can see the loading screen. Without this, it might just flash away since the operation is pretty fast.
If you’re doing something intensive in the background you’ll want to get rid of setTimeout
and display your content to the user as soon as possible.
At the end of the created
hook, do the same
That’s all for this section, I’ll leave you to figure out the CSS.
Automatically scrolling to the bottom of the screen when messages exceed the window height
You’ll also notice that the chat window doesn’t scroll the bottom once our messages exceeds the height of the containing div. That’s really annoying. Imagine if you had to scroll on your mobile phone to read the latest message from Whatsapp. Let’s fix it.
We can quickly fix this by hooking into the updated
life cycle. It’s called after a Component’s state has been updated and it’s been re-rendered.
PAY ATTENTION TO THIS!
If you try to scroll too early for example in mounted
, the ref
might not be available because it’s in an if block
, the ref is only set when the page has finished loading and we’ve started a new session.
The first condition that’s true when the page is mounted is the “loading” block (i.e v-else) when mounted
is called, the ref is not set in that block. MOUNTED IS ONLY CALLED ONCE. The $refs Object will eventually be updated by Vue, but by then you won’t be able to call it again because MOUNTED is only called once and it won’t be called again when $refs is updated
If you decide to scroll as soon as you recieve a message in onMessage
, The page would appear not to scroll because Vue wouldn’t have re-rendered the component hence chatBody.scrollHeight
won’t be updated. (The message hasn’t been appended to the div)
updated
is the perfect place because we can be sure that the new message has been added to the body and our chatBody
ref is available.
We’re doing an extra Check for the chatBody
ref because if a user is trying to start a chat session, the ref wouldn’t be set (It’s only set when a user tries to join a chat session). This might be a good time to refactor the “Start Chatting” screen into a different component.
If you don’t understand what I’ve explained above, ask a question and I’ll gladly answer.
Play sounds when the notification window is not focused
This is a common feature is a lot of web based chat apps. If you’re on a different tab and you receive a message, a sound (and sometimes a browser notification) would be played to alert you and draw your focus back to it. Let’s see how we can add that to our chat app.
HTML5
and JavaScript
make it easy for us to play audio. Let’s add a new “notification” property to our component state
We placed the audio file in the static folder, so it can be served directly. If you place in in assets, Vue router will think it’s a browser request and it’ll redirect you to the auth page. This behaviour is documented here. You can get the plucky.ogg file from the Github Repo
In your onMessage
method, add the following:
This simply checks if the current tab (document) is not focused, If it’s not then we’ll play the sound.
That’s it for the frontend and UI/UX Improvements.
Backend Improvements
JSON web tokens with djsoer
If you have some experience with tokens and authentication you might be asking yourself “What’s the expiry date of the tokens obtained from djoser
”. It turns out that by default there’s no expiry date. We’re putting our users at risk because if an attacker obtains the token, that means they’ll have lifetime access to a user’s account and localStorage
and sessionStorage
aren’t the best place to store sensitive information (Which includes tokens)
If an account is compromised, there should be a way to “expire” the token and make it Invalid.
To enable JWT
’s in djoser
we need to install an extension for django rest framework (Remember djoser
is built around django rest framework)
Then we need to update our application’s settings. Go to your application settings and update the REST_FRAMEWORK
setting:
update urls.py
Run python manage.py runserver
. If everything went well the server should start without any errors.
Now use curl and run this (With a valid username and password in your database)
Notice that we’re using a different endpoint /jwt/create
instead of /token/create
You should get a token back. You can take that token to JWT.io and look inside, below is an image of what is contained in my own token.
You can view all the information inside the JWT. The Goal of JSON web tokens is not to store sensitive information (Like credit card details, addresses etc). It’s to allow us transfer information securely between the client and the server.
If an attacker alters this JWT by modifying it’s data when he sends it to the server the JWT verification will fail because he signed it with a different key. YOU MUST PROTECT YOUR SECRET KEY AT ALL COSTS!
Now that we have that out of the way, we need to update the signIn
method in Vue to use the new endpoint.
We also need to change the Authorization
header from Token
to JWT
in the Chat.vue
component
That’s all. We’ve switched our auth scheme to JWT and we can still chat as Usual.
Refreshing the token
If you continue using the chat application, after sometime you might notice that messages aren’t going through because the token has expired.
This is the response you’ll get from the API.
{"detail":"Signature has expired.}
Telling us that the token has expired. The default expiry time is 300 Seconds (5 minutes). This can be increased by setting:
JWT_EXPIRATION_DELTA
to a valid datetime e.g datetime.timedelta(minutes=30)
for 30 minutes. But we’re still left with the problem of expired tokens. We can increase the expiry time/make the token active forever but that’s not ideal because if an attacker obtains the token, they’ll have lifetime access to a user’s account.
We can also store the username and password and initiate a login but that’s a NO NO!. You could get away with it in a mobile app or server side environment, but it’s still a bad idea.
The best way is to have shortlived tokens and refresh them (obtain a new token). We can easily incorporate that into our Vue app by creating a new method that’s called at a specific interval (Before the token expires).
The endpoint for refreshing tokens in djoser
is /jwt/refresh
but that’s very easy to guess. We can change it to something more obscure to make it difficult to discover. This is security by Obfuscation. We’re just making it harder for attackers to find the endpoint for refreshing tokens. Even if they use a bruteforcing software like DirBuster It can take them weeks (Even months) to get the link.
The first thing to do is to enable the refresh functionality and increase the expiration time by the following settings:
Now update urls.py
to include the link:
In the raise_404
view, we simply raise a Http404
exception.
Finally, we need to add the token refresh logic to Chat.vue
In the created
hook register the function to be called every 240 seconds (4 minutes)
That’s it! Your chat shouldn’t get interrupted because the token is silently refreshed in the background.
If you need to customize JSON web tokens in django rest framework, please take a look at the options available in the documentation