Auth token
There is a popular task in frontend development: to check if the users token is valid before data fetching and perform some actions if it is not. In this case study we will discuss how to implement this task with the help of @farfetched/core
package.
Case study
A case study is a detailed study of a specific subject, such a service or feature. It is a way to show how Farfetched can be used to solve a real-world problem.
The code in it is not supposed to be ready to use "as is", it is just an example of how Effector and Farfetched can be used to deal with a specific problem.
Kick-off
Let us say we have a website with a login form. After the user submits the form, we send a request to the server and get some token in response. We store this token in the local storage or in the cookies and use it for some subsequent requests.
If something on the website tries to fetch data from the server that requires authentication, we need to check if the token is still valid. If it is not, we need to refresh the token, and continue the previous request.
In different cases, we can have different information about the token. In general, we can divide all cases into two groups:
Client do have access to the content of the token
Sometimes the client has access to the content of the token. For example, if the token is stored in the local storage, we can get it with the help of localStorage.getItem('SOME_KEY')
. In this case, we can check if the token is valid by decoding it and checking the expiration date.
In this case, we assume that the token is a JWT token that contains the expiration date and could be decoded without any additional information.
Client do not have access to the content of the token
Sometimes the client does not have access to the content of the token. For example, if the token is stored in the cookie that marked as httpOnly
, we cannot get it with the help of document.cookie
. In this case, we need to send a request to the server and check if the response is 200 OK
or 401 Unauthorized
.
These two cases are very similar, so implementation will be the same for both of them with a little difference in the definition of unauthorized access.
Implementation
Farfetched provides a Barrier abstraction that allows us to implement this task in a declarative way. Its usage splits into two parts: barrier creation and barrier application. Let us start with the barrier creation since it is the most important part.
Barrier creation
The creation of the barrier depends on the type of the token.
If the client has access to the content of the token, we can create a barrier with explicit checking of the token expiration date.
Let us assume that the token is a JWT token that contains the expiration date and could be decoded without any additional information. Such a token would be stored in a Store and can be accessed across the application.
import { createBarrier } from '@farfetched/core';
const $authToken = createStore(/* ... */);
const authBarrier = createBarrier({
active: combine($authToken, (token) => isTokenInvalid(token)),
});
If the client does not have access to the content of the token, we have to rely on the response from the server on particular request. In this case, we can create a barrier with explicit checking of the response status of every request which requires authentication.
import { createBarrier, isHttpErrorCode } from '@farfetched/core';
const authBarrier = createBarrier({
activateOn: {
failure: isHttpErrorCode(401),
},
});
It is only difference between these two cases. However, in both cases, we need to refresh the token if it is invalid. Let us say that refreshing the token is a Mutation.
import { createBarrier, createMutation } from '@farfetched/core';
const renewTokenMutation = createMutation(/* ... */);
const authBarrier = createBarrier({
/* ... */
perform: [renewTokenMutation],
});
Now we have a Barrier that will be activated if the token is invalid and will refresh the token if it is activated. It is time to apply this Barrier to the requests that require authentication.
Barrier application
This part is very simple. We just need to applyBarrier
to every Query or Mutation that requires authentication.
import { createQuery, applyBarrier } from '@farfetched/core';
const someQuery = createQuery({ /* ... */ });
applyBarrier(someQuery, { barrier: authBarrier });
That is it! Now every time someQuery
is called, authBarrier
will be checked. If the token is invalid, authBarrier
will be activated, someQuery
will be suspended, and renewTokenMutation
will be called. After renewTokenMutation
is finished, someQuery
will be resumed in case of suspension or restarted with the latest parameters in case of failure.
Conclusion
Barrier API is very flexible and allows us to implement different tasks in a declarative way. In this case study, we have discussed how to implement the task of checking the token validity before data fetching.
However, this is not the only task that can be implemented with the help of barriers. You can use them to implement anything that requires some actions to be performed before particular operation with suspension of this operation until the actions are finished.