At work I was debugging an issue that was coming from one of the Vuejs components and it was registered on Airbrake. The error indicated that a value we expect to have in one of the Vuex storages was not there.
Architecture wise we have an initial boot process that run on page load and add the values to the storage. If this succeeds, then we create the main Vuejs app component to have a reactive web app.
We mostly use Vuex for shared state for components. There is also a wrapper class around Axios to help manage HTTP requests between the backend API and the client.
So any communication that happens with the store and backend REST goes through this HTTP wrapper class.
Now in the initial boot process, if one of the requests fail, it causes one of the important data to be not present in the store. In this case, we should not proceed to render the application.
We have a generic error handler for any errors other than 401 during the main boot process. So if it is not 401, we simply stop the execution.
On the other hand, if a 401 error occurs it is handled in the HTTP wrapper class level. Here the code uses an interceptor design pattern to simply intercept all the responses from the server and check if any of them is a 401. This means if the initial boot validation or any other response for the store responded with a 401, they are handled here.
Axios has interceptors that can be used on the response to easily achieve that.
In the wrapper what it was doing was simply setting the browser page location to the login page URL when it detects a 401.
This approach is an easy solution and as 401 indicates that the user is unauthenticated. So it makes sense to make the user go back to the login page. Most developers also use this approach and for your application it might be totally acceptable.
However the first problem I noticed with this approach was when I tried to debug the initial bug. When I emulate a 401 response from the server and try to follow the front-end execution, I couldn’t really see the error happening on the Chrome browser console. It is because the “redirection” causes the new page to load on the browser and delete the previous network and console logs in the developer tools.
A handy tip for debugging this in case is to tick the log checkbox in the network tab and console area under Chrome developer tools. After this is turned on, it will keep the previous logs even after the page refreshed. So in this case, I used that option to see the error happening locally.
To reproduce the error, I had to send a 401 response to the initial boot. During debugging, I learned that we continue to render the application on 401 while the page is being redirected to the login page by the HTTP wrapper class.
I raised the concern about why we should not have a step to jump to the login page with the team lead. He was adamant to keep having it. His main reason being it is a single place to handle any 401 error. So I agreed to disagree. So in our case the solution was to not continue rendering the app on 401 in the initial boot so it wouldn’t trigger any error due to missing data. And during this time the app would be redirected back to the login page.
So here’s why I think it’s a bad idea to redirect to another page in the HTTP wrapper class.
The HTTP class/module should have a single responsibility. It’s responsibility is to direct and manage HTTP requests not to redirect the user.
When a higher Vue component uses the Vuex store which triggers an HTTP request, it would be expected the HTTP wrapper to return a success or a failed promise response always. However when now the wrapper can any moment do something completely different, the code execution becomes unpredictable and harder to debug even.
It would be also possible that whatever the work the Vue component is doing after getting the response is cut in the middle because of the redirection.
Now let’s assume that the user was filling a form and when they submit the data, the server sends back a 401. With the redirection, whatever thing they did on the form is lost. I think this is a bad user experience considering we are trying to build a reactive web application.
Suppose you wanted to create a sign in button that periodically checks whether the user is authenticated or not and renders the button if the user is not. With the simple solution of redirection, it would not be possible to achieve this requirement.
I think we can do better. For all the other error types, the Vue components handle them and render an error. I think we can still do the same for 401. However additionally since this is a specific error for users not to be authenticated – we can ideally show a sign in modal view.
So once they sign in, they can continue where they left off with the form without losing any data.
Now I was also thinking how Google would handle this from a user perspective. So what I did was opened my gmail, started typing a draft email, and then in another tab signed out from Google. Then I went to the email tab and clicked send.
For a brief moment I saw some error message on the screen and I was redirected to their login page. So as I said the redirection approach is pretty general.
However that doesn’t mean that you should also take the same solution. It depends on your application and what sort of user experience you like to have for the user.
Interceptors are a powerful mechanism. However when you use it, you have to think about the side effects of your changes as well. Ideally they should be used to transform data, or do some other activity that is independent from other functionalities of the application.