Protect the Routes
Overview
Now that we are authenticating with a provider we need to look at protecting our routes. This protection takes two major forms:
- Guarding our routes so a user cannot navigate to various places within our application unless they are logged in.
- Protecting our backend API such that users cannot access data without a valid access token. Our role is to pass the access token to our API.
We will also see how to handle the possibility that our APIs may now issue 401 errors in cases where our access token has expired or is otherwise invalid.
We will build upon the application we created in the getting started tutorial in order to implement route guards for our application's routes as well as to add HTTP interceptors to attach access tokens to outgoing requests and to handle potential 401 errors in responses.
Let's Code
As mentioned previously, this tutorial builds upon the application created when doing the getting started tutorial. If you have the code from when you performed that tutorial, then you are good to go. If you need the code you can make a copy from our GitHub repository.
Route Guards
We are using the Tab1Page
to manage our authentication status. Let's assume that the Tab2Page
and Tab3Page
should only be accessible if the user is authenticated. We already have a method that determines if the user is authenticated or not. In its most basic form, we use the existence of an AuthResult
with an access token to determine whether or not we are authenticated. Other tutorials show how this can be expanded.
Within the router configuration code, create a function that checks the authentication status for routes that require an authenticated user. This function will then be added to the navigation pipeline.
Currently, the router always allows access to all routes.
Import a couple of items related to Vue Router navigation guards. Also import our authentication composable so we will be able to access the isAuthenticated()
function.
Create a guard and add it to the navigation pipeline such that it runs before any navigation. For now, it runs on all routes and it simply allows navigation.
If at least one segment in the to
route requires authentication, we should do something different.
For the routes that require authentication, if the user is not authenticated, navigate to our authentication page instead.
Currently, the router always allows access to all routes.
Import a couple of items related to Vue Router navigation guards. Also import our authentication composable so we will be able to access the isAuthenticated()
function.
Create a guard and add it to the navigation pipeline such that it runs before any navigation. For now, it runs on all routes and it simply allows navigation.
If at least one segment in the to
route requires authentication, we should do something different.
For the routes that require authentication, if the user is not authenticated, navigate to our authentication page instead.
Test this in your app. You should see that you cannot navigate to Tab2Page
or Tab3Page
unless you are authenticated. This is exactly what we want and it works well.
To verify the redirection in route, run the application in a web browser and navigate directly to http://localhost:8100/tabs/tab2
while not authenticated.
Provide the Access Token
When a user logs in using Auth Connect the application receives an AuthResult
that represents the authentication session. The AuthResult
object provides access to several types of tokens:
- ID Token: The ID token contains information pertaining to the identity of the authenticated user. The information within this token is typically consumed by the client application.
- Access Token: The access token signifies that the user has properly authenticated. This token is typically sent to the application's backend APIs as a bearer token. The token is verified by the API to grant access to the protected resources exposed by the API. Since this token is used in communications with the backend API, a common security practice is to give it a very limited lifetime.
- Refresh Token: Since access tokens typically have a short lifetime, longer lived refresh tokens are used to extend the length of a user's authentication session by allowing the access tokens to be refreshed.
The most common way for the backend API to protect its routes is to require that requests include a valid access token. As such, we are going to have to send the access token with each request.
Add a function to the useAuthentication()
composable API that gets the access token:
Modify the Tab1Page
to grab the access token and display it.
Add a function to the useAuthentication()
composable API that gets the access token:
Modify the Tab1Page
to grab the access token and display it.
We would not normally grab the access token and display it like that. This is just being done to make sure everything is working. Log in and out a few times. You should see a token while logged in but not while logged out.
It is common to use a library like Axios to perform HTTP operations. Axios also makes it easy to modify outgoing requests via Interceptors. We will use these to add the access token to outbound HTTP requests. Note that this is just an example of the type of thing you need do. You do not have to use Axios, but can use whatever technology you would like to use.
We will create a composition API function that manages an Axios client
for our backend API. Once that exists, we will add an interceptor to the client
that adds the access token to outbound requests.
We start with a very basic Axios client that connects to our backend API.
Create a stub for the interceptor. Since we want to modify the request, we need to attach the function to client.interceptors.request
.
Get the access token.
If a token exists, attach it to the header.
We start with a very basic Axios client that connects to our backend API.
Create a stub for the interceptor. Since we want to modify the request, we need to attach the function to client.interceptors.request
.
Get the access token.
If a token exists, attach it to the header.
If we have an access token, it will now be attached to any request that is sent to https://cs-demo-api.herokuapp.com
(our backend API). Note that you could have multiple APIs that all recognize the provided access token. In that case you would create various composable API functions with similar code. Providing proper abstraction layers for such a scenario is left as an exercise for the reader.
Handle 401 Errors
Now that the access token is sent to the backend, we need to also handle the case where the backend rejects the access token resulting in a 401 - Unauthorized response status. Since we need to examine the response, this interceptor is attached to client.interceptors.response
.
The interceptor needs to be built out to clear the session data and navigate to the login page when a 401 error occurs.
Start with the existing code in src/composables/backend-api.ts
.
Create a placeholder where we will add our interceptor code to client.interceptors.response
. Note that we do not do anything for a successful response.
We need to handle the error. For now just reject.
If a 401
error occurs, clear the session from storage and redirect to our login page.
Start with the existing code in src/composables/backend-api.ts
.
Create a placeholder where we will add our interceptor code to client.interceptors.response
. Note that we do not do anything for a successful response.
We need to handle the error. For now just reject.
If a 401
error occurs, clear the session from storage and redirect to our login page.
Next Steps
Currently, if we have an AuthResult
with an access token we assume the user is properly authenticated. If you would like to expand this logic to first make sure the access token has not expired, and try to refresh it if it has, then please have a look at the tutorial on refreshing the session.
Happy coding!! 🤓