Skip to main content
Version: 5.0

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:

  1. Guarding our routes so a user cannot navigate to various places within our application unless they are logged in.
  2. 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.

src/composables/authentication.ts

_73
import { Capacitor } from '@capacitor/core';
_73
import {
_73
Auth0Provider,
_73
AuthConnect,
_73
AuthResult,
_73
ProviderOptions,
_73
} from '@ionic-enterprise/auth';
_73
import { useSession } from '@/composables/session';
_73
_73
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))

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.

src/router/index.ts

_43
import { createRouter, createWebHistory } from '@ionic/vue-router';
_43
import { RouteRecordRaw } from 'vue-router';
_43
import TabsPage from '../views/TabsPage.vue';
_43
_43
const routes: Array<RouteRecordRaw> = [
_43
{
_43
path: '/',
_43
redirect: '/tabs/tab1',
_43
},
_43
{
_43
path: '/tabs/',
_43
component: TabsPage,
_43
children: [
_43
{
_43
path: '',
_43
redirect: '/tabs/tab1',
_43
},
_43
{
_43
path: 'tab1',
_43
component: () => import('@/views/Tab1Page.vue'),
_43
},
_43
{
_43
path: 'tab2',
_43
component: () => import('@/views/Tab2Page.vue'),
_43
},
_43
{
_43
path: 'tab3',
_43
component: () => import('@/views/Tab3Page.vue'),
_43
},
_43
],
_43
},
_43
{
_43
path: '/auth-action-complete',
_43
component: () => import('@/views/AuthActionCompletePage.vue'),
_43
},
_43
];
_43
_43
const router = createRouter({
_43
history: createWebHistory(import.meta.env.BASE_URL),
_43
routes,
_43
});
_43
_43
export default router;

Currently, the router always allows access to all routes.

src/router/index.ts

_48
import { createRouter, createWebHistory } from '@ionic/vue-router';
_48
import {
_48
NavigationGuardNext,
_48
RouteLocationNormalized,
_48
RouteRecordRaw,
_48
} from 'vue-router';
_48
import TabsPage from '../views/TabsPage.vue';
_48
import { useAuthentication } from '@/composables/authentication';
_48
_48
const routes: Array<RouteRecordRaw> = [
_48
{
_48
path: '/',
_48
redirect: '/tabs/tab1',
_48
},
_48
{
_48
path: '/tabs/',
_48
component: TabsPage,
_48
children: [
_48
{
_48
path: '',
_48
redirect: '/tabs/tab1',
_48
},
_48
{
_48
path: 'tab1',
_48
component: () => import('@/views/Tab1Page.vue'),
_48
},
_48
{
_48
path: 'tab2',
_48
component: () => import('@/views/Tab2Page.vue'),
_48
},
_48
{
_48
path: 'tab3',
_48
component: () => import('@/views/Tab3Page.vue'),
_48
},
_48
],
_48
},
_48
{
_48
path: '/auth-action-complete',
_48
component: () => import('@/views/AuthActionCompletePage.vue'),
_48
},
_48
];
_48
_48
const router = createRouter({
_48
history: createWebHistory(import.meta.env.BASE_URL),
_48
routes,
_48
});
_48
_48
export default router;

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.

src/router/index.ts

_58
import { createRouter, createWebHistory } from '@ionic/vue-router';
_58
import {
_58
NavigationGuardNext,
_58
RouteLocationNormalized,
_58
RouteRecordRaw,
_58
} from 'vue-router';
_58
import TabsPage from '../views/TabsPage.vue';
_58
import { useAuthentication } from '@/composables/authentication';
_58
_58
const routes: Array<RouteRecordRaw> = [
_58
{
_58
path: '/',
_58
redirect: '/tabs/tab1',
_58
},
_58
{
_58
path: '/tabs/',
_58
component: TabsPage,
_58
children: [
_58
{
_58
path: '',
_58
redirect: '/tabs/tab1',
_58
},
_58
{
_58
path: 'tab1',
_58
component: () => import('@/views/Tab1Page.vue'),
_58
},
_58
{
_58
path: 'tab2',
_58
component: () => import('@/views/Tab2Page.vue'),
_58
},
_58
{
_58
path: 'tab3',
_58
component: () => import('@/views/Tab3Page.vue'),
_58
},
_58
],
_58
},
_58
{
_58
path: '/auth-action-complete',
_58
component: () => import('@/views/AuthActionCompletePage.vue'),
_58
},
_58
];
_58
_58
const router = createRouter({
_58
history: createWebHistory(import.meta.env.BASE_URL),
_58
routes,
_58
});
_58
_58
const checkAuthStatus = async (
_58
to: RouteLocationNormalized,
_58
from: RouteLocationNormalized,
_58
next: NavigationGuardNext
_58
) => {
_58
next();
_58
};
_58
_58
router.beforeEach(checkAuthStatus);
_58
_58
export default router;

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.

src/router/index.ts

_63
import { createRouter, createWebHistory } from '@ionic/vue-router';
_63
import {
_63
NavigationGuardNext,
_63
RouteLocationNormalized,
_63
RouteRecordRaw,
_63
} from 'vue-router';
_63
import TabsPage from '../views/TabsPage.vue';
_63
import { useAuthentication } from '@/composables/authentication';
_63
_63
const routes: Array<RouteRecordRaw> = [
_63
{
_63
path: '/',
_63
redirect: '/tabs/tab1',
_63
},
_63
{
_63
path: '/tabs/',
_63
component: TabsPage,
_63
children: [
_63
{
_63
path: '',
_63
redirect: '/tabs/tab1',
_63
},
_63
{
_63
path: 'tab1',
_63
component: () => import('@/views/Tab1Page.vue'),
_63
},
_63
{
_63
path: 'tab2',
_63
component: () => import('@/views/Tab2Page.vue'),
_63
meta: { requiresAuth: true },
_63
},
_63
{
_63
path: 'tab3',
_63
component: () => import('@/views/Tab3Page.vue'),
_63
meta: { requiresAuth: true },
_63
},
_63
],
_63
},
_63
{
_63
path: '/auth-action-complete',
_63
component: () => import('@/views/AuthActionCompletePage.vue'),
_63
},
_63
];
_63
_63
const router = createRouter({
_63
history: createWebHistory(import.meta.env.BASE_URL),
_63
routes,
_63
});
_63
_63
const checkAuthStatus = async (
_63
to: RouteLocationNormalized,
_63
from: RouteLocationNormalized,
_63
next: NavigationGuardNext
_63
) => {
_63
if (to.matched.some((r) => r.meta.requiresAuth)) {
_63
// TODO: something different
_63
}
_63
next();
_63
};
_63
_63
router.beforeEach(checkAuthStatus);
_63
_63
export default router;

If at least one segment in the to route requires authentication, we should do something different.

src/router/index.ts

_66
import { createRouter, createWebHistory } from '@ionic/vue-router';
_66
import {
_66
NavigationGuardNext,
_66
RouteLocationNormalized,
_66
RouteRecordRaw,
_66
} from 'vue-router';
_66
import TabsPage from '../views/TabsPage.vue';
_66
import { useAuthentication } from '@/composables/authentication';
_66
_66
const routes: Array<RouteRecordRaw> = [
_66
{
_66
path: '/',
_66
redirect: '/tabs/tab1',
_66
},
_66
{
_66
path: '/tabs/',
_66
component: TabsPage,
_66
children: [
_66
{
_66
path: '',
_66
redirect: '/tabs/tab1',
_66
},
_66
{
_66
path: 'tab1',
_66
component: () => import('@/views/Tab1Page.vue'),
_66
},
_66
{
_66
path: 'tab2',
_66
component: () => import('@/views/Tab2Page.vue'),
_66
meta: { requiresAuth: true },
_66
},
_66
{
_66
path: 'tab3',
_66
component: () => import('@/views/Tab3Page.vue'),
_66
meta: { requiresAuth: true },
_66
},
_66
],
_66
},
_66
{
_66
path: '/auth-action-complete',
_66
component: () => import('@/views/AuthActionCompletePage.vue'),
_66
},
_66
];
_66
_66
const router = createRouter({
_66
history: createWebHistory(import.meta.env.BASE_URL),
_66
routes,
_66
});
_66
_66
const checkAuthStatus = async (
_66
to: RouteLocationNormalized,
_66
from: RouteLocationNormalized,
_66
next: NavigationGuardNext
_66
) => {
_66
if (to.matched.some((r) => r.meta.requiresAuth)) {
_66
const { isAuthenticated } = useAuth();
_66
if (!(await isAuthenticated())) {
_66
return next('/tabs/tab1');
_66
}
_66
}
_66
next();
_66
};
_66
_66
router.beforeEach(checkAuthStatus);
_66
_66
export default router;

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.

src/router/index.ts

_43
import { createRouter, createWebHistory } from '@ionic/vue-router';
_43
import { RouteRecordRaw } from 'vue-router';
_43
import TabsPage from '../views/TabsPage.vue';
_43
_43
const routes: Array<RouteRecordRaw> = [
_43
{
_43
path: '/',
_43
redirect: '/tabs/tab1',
_43
},
_43
{
_43
path: '/tabs/',
_43
component: TabsPage,
_43
children: [
_43
{
_43
path: '',
_43
redirect: '/tabs/tab1',
_43
},
_43
{
_43
path: 'tab1',
_43
component: () => import('@/views/Tab1Page.vue'),
_43
},
_43
{
_43
path: 'tab2',
_43
component: () => import('@/views/Tab2Page.vue'),
_43
},
_43
{
_43
path: 'tab3',
_43
component: () => import('@/views/Tab3Page.vue'),
_43
},
_43
],
_43
},
_43
{
_43
path: '/auth-action-complete',
_43
component: () => import('@/views/AuthActionCompletePage.vue'),
_43
},
_43
];
_43
_43
const router = createRouter({
_43
history: createWebHistory(import.meta.env.BASE_URL),
_43
routes,
_43
});
_43
_43
export default router;

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.

src/composables/authentication.service.ts
src/views/Tab1Page.vue

_78
import { Capacitor } from '@capacitor/core';
_78
import {
_78
Auth0Provider,
_78
AuthConnect,
_78
AuthResult,
_78
ProviderOptions,
_78
} from '@ionic-enterprise/auth';
_78
import { useSession } from '@/composables/session';
_78
_78
const isNative = Capacitor.isNativePlatform();
_78
const provider = new Auth0Provider();
_78
const authOptions: ProviderOptions = {
_78
audience: 'https://io.ionic.demo.ac',
_78
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_78
discoveryUrl:
_78
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_78
logoutUrl: isNative
_78
? 'io.ionic.acdemo://auth-action-complete'
_78
: 'http://localhost:8100/auth-action-complete',
_78
redirectUri: isNative
_78
? 'io.ionic.acdemo://auth-action-complete'
_78
: 'http://localhost:8100/auth-action-complete',
_78
scope: 'openid offline_access email picture profile',
_78
};
_78
let authResult: AuthResult | null = null;
_78
_78
const { clearSession, getSession, setSession } = useSession();
_78
_78
const getAuthResult = async (): Promise<AuthResult | null> => {
_78
return getSession();
_78
};
_78
_78
const saveAuthResult = async (authResult: AuthResult | null): Promise<void> => {
_78
if (authResult) {
_78
await setSession(authResult);
_78
} else {
_78
await clearSession();
_78
}
_78
};
_78
_78
const isReady: Promise<void> = AuthConnect.setup({
_78
platform: isNative ? 'capacitor' : 'web',
_78
logLevel: 'DEBUG',
_78
ios: {
_78
webView: 'private',
_78
},
_78
web: {
_78
uiMode: 'popup',
_78
authFlow: 'PKCE',
_78
},
_78
});
_78
_78
export const useAuthentication = () => ({
_78
getAccessToken: async (): Promise<string | undefined> => {
_78
await isReady;
_78
const res = await getAuthResult();
_78
return res?.accessToken;
_78
},
_78
isAuthenticated: async (): Promise<boolean> => {
_78
const authResult = await getAuthResult();
_78
return (
_78
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_78
);
_78
},
_78
login: async (): Promise<void> => {
_78
await isReady;
_78
authResult = await AuthConnect.login(provider, authOptions);
_78
await saveAuthResult(authResult);
_78
},
_78
logout: async (): Promise<void> => {
_78
await isReady;
_78
const authResult = await getAuthResult();
_78
if (authResult) {
_78
await AuthConnect.logout(provider, authResult);
_78
saveAuthResult(null);
_78
}
_78
},
_78
});

Add a function to the useAuthentication() composable API that gets the access token:

src/composables/authentication.service.ts
src/views/Tab1Page.vue

_57
<template>
_57
<ion-page>
_57
<ion-header>
_57
<ion-toolbar>
_57
<ion-title>Tab 1</ion-title>
_57
</ion-toolbar>
_57
</ion-header>
_57
<ion-content :fullscreen="true">
_57
<ion-header collapse="condense">
_57
<ion-toolbar>
_57
<ion-title size="large">Tab 1</ion-title>
_57
</ion-toolbar>
_57
</ion-header>
_57
_57
<ion-button v-if="authenticated" @click="logoutClicked"
_57
>Logout</ion-button
_57
>
_57
<ion-button v-else @click="loginClicked">Login</ion-button>
_57
_57
<pre>{{ accessToken }}</pre>
_57
</ion-content>
_57
</ion-page>
_57
</template>
_57
_57
<script setup lang="ts">
_57
import {
_57
IonButton,
_57
IonPage,
_57
IonHeader,
_57
IonToolbar,
_57
IonTitle,
_57
IonContent,
_57
} from '@ionic/vue';
_57
import { useAuthentication } from '@/composables/authentication';
_57
import { ref } from 'vue';
_57
_57
const { getAccessToken, isAuthenticated, login, logout } = useAuthentication();
_57
const accessToken = ref<string>();
_57
const authenticated = ref<boolean>();
_57
_57
const checkAuthentication = async (): Promise<void> => {
_57
authenticated.value = await isAuthenticated();
_57
accessToken.value = await getAccessToken();
_57
};
_57
_57
const loginClicked = async (): Promise<void> => {
_57
await login();
_57
checkAuthentication();
_57
};
_57
_57
const logoutClicked = async (): Promise<void> => {
_57
await logout();
_57
checkAuthentication();
_57
};
_57
_57
checkAuthentication();
_57
</script>

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.

src/composables/authentication.service.ts
src/views/Tab1Page.vue

_78
import { Capacitor } from '@capacitor/core';
_78
import {
_78
Auth0Provider,
_78
AuthConnect,
_78
AuthResult,
_78
ProviderOptions,
_78
} from '@ionic-enterprise/auth';
_78
import { useSession } from '@/composables/session';
_78
_78
const isNative = Capacitor.isNativePlatform();
_78
const provider = new Auth0Provider();
_78
const authOptions: ProviderOptions = {
_78
audience: 'https://io.ionic.demo.ac',
_78
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_78
discoveryUrl:
_78
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_78
logoutUrl: isNative
_78
? 'io.ionic.acdemo://auth-action-complete'
_78
: 'http://localhost:8100/auth-action-complete',
_78
redirectUri: isNative
_78
? 'io.ionic.acdemo://auth-action-complete'
_78
: 'http://localhost:8100/auth-action-complete',
_78
scope: 'openid offline_access email picture profile',
_78
};
_78
let authResult: AuthResult | null = null;
_78
_78
const { clearSession, getSession, setSession } = useSession();
_78
_78
const getAuthResult = async (): Promise<AuthResult | null> => {
_78
return getSession();
_78
};
_78
_78
const saveAuthResult = async (authResult: AuthResult | null): Promise<void> => {
_78
if (authResult) {
_78
await setSession(authResult);
_78
} else {
_78
await clearSession();
_78
}
_78
};
_78
_78
const isReady: Promise<void> = AuthConnect.setup({
_78
platform: isNative ? 'capacitor' : 'web',
_78
logLevel: 'DEBUG',
_78
ios: {
_78
webView: 'private',
_78
},
_78
web: {
_78
uiMode: 'popup',
_78
authFlow: 'PKCE',
_78
},
_78
});
_78
_78
export const useAuthentication = () => ({
_78
getAccessToken: async (): Promise<string | undefined> => {
_78
await isReady;
_78
const res = await getAuthResult();
_78
return res?.accessToken;
_78
},
_78
isAuthenticated: async (): Promise<boolean> => {
_78
const authResult = await getAuthResult();
_78
return (
_78
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_78
);
_78
},
_78
login: async (): Promise<void> => {
_78
await isReady;
_78
authResult = await AuthConnect.login(provider, authOptions);
_78
await saveAuthResult(authResult);
_78
},
_78
logout: async (): Promise<void> => {
_78
await isReady;
_78
const authResult = await getAuthResult();
_78
if (authResult) {
_78
await AuthConnect.logout(provider, authResult);
_78
saveAuthResult(null);
_78
}
_78
},
_78
});

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.

Terminal

_10
npm install axios

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.

src/composables/backend-api.ts

_17
import axios from 'axios';
_17
_17
const baseURL = 'https://cs-demo-api.herokuapp.com';
_17
_17
const client = axios.create({
_17
baseURL,
_17
headers: {
_17
Accept: 'application/json',
_17
'Content-Type': 'application/json',
_17
},
_17
});
_17
_17
export const useBackendAPI = () => {
_17
return {
_17
client,
_17
};
_17
};

We start with a very basic Axios client that connects to our backend API.

src/composables/backend-api.ts

_21
import axios, { InternalAxiosRequestConfig } from 'axios';
_21
_21
const baseURL = 'https://cs-demo-api.herokuapp.com';
_21
_21
const client = axios.create({
_21
baseURL,
_21
headers: {
_21
Accept: 'application/json',
_21
'Content-Type': 'application/json',
_21
},
_21
});
_21
_21
client.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
_21
return config;
_21
});
_21
_21
export const useBackendAPI = () => {
_21
return {
_21
client,
_21
};
_21
};

Create a stub for the interceptor. Since we want to modify the request, we need to attach the function to client.interceptors.request.

src/composables/backend-api.ts

_24
import axios, { InternalAxiosRequestConfig } from 'axios';
_24
import { useAuthentication } from './authentication';
_24
_24
const baseURL = 'https://cs-demo-api.herokuapp.com';
_24
_24
const client = axios.create({
_24
baseURL,
_24
headers: {
_24
Accept: 'application/json',
_24
'Content-Type': 'application/json',
_24
},
_24
});
_24
_24
client.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
_24
const { getAccessToken } = useAuthentication();
_24
const token = await getAccessToken();
_24
return config;
_24
});
_24
_24
export const useBackendAPI = () => {
_24
return {
_24
client,
_24
};
_24
};

Get the access token.

src/composables/backend-api.ts

_27
import axios, { InternalAxiosRequestConfig } from 'axios';
_27
import { useAuthentication } from './authentication';
_27
_27
const baseURL = 'https://cs-demo-api.herokuapp.com';
_27
_27
const client = axios.create({
_27
baseURL,
_27
headers: {
_27
Accept: 'application/json',
_27
'Content-Type': 'application/json',
_27
},
_27
});
_27
_27
client.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
_27
const { getAccessToken } = useAuthentication();
_27
const token = await getAccessToken();
_27
if (token && config.headers) {
_27
config.headers.Authorization = `Bearer ${token}`;
_27
}
_27
return config;
_27
});
_27
_27
export const useBackendAPI = () => {
_27
return {
_27
client,
_27
};
_27
};

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.

src/composables/backend-api.ts

_17
import axios from 'axios';
_17
_17
const baseURL = 'https://cs-demo-api.herokuapp.com';
_17
_17
const client = axios.create({
_17
baseURL,
_17
headers: {
_17
Accept: 'application/json',
_17
'Content-Type': 'application/json',
_17
},
_17
});
_17
_17
export const useBackendAPI = () => {
_17
return {
_17
client,
_17
};
_17
};

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.

src/composables/backend-api.ts

_27
import axios, { InternalAxiosRequestConfig } from 'axios';
_27
import { useAuthentication } from './authentication';
_27
_27
const baseURL = 'https://cs-demo-api.herokuapp.com';
_27
_27
const client = axios.create({
_27
baseURL,
_27
headers: {
_27
Accept: 'application/json',
_27
'Content-Type': 'application/json',
_27
},
_27
});
_27
_27
client.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
_27
const { getAccessToken } = useAuthentication();
_27
const token = await getAccessToken();
_27
if (token && config.headers) {
_27
config.headers.Authorization = `Bearer ${token}`;
_27
}
_27
return config;
_27
});
_27
_27
export const useBackendAPI = () => {
_27
return {
_27
client,
_27
};
_27
};

Start with the existing code in src/composables/backend-api.ts.

src/composables/backend-api.ts

_29
import axios, { InternalAxiosRequestConfig } from 'axios';
_29
import { useAuthentication } from './authentication';
_29
_29
const baseURL = 'https://cs-demo-api.herokuapp.com';
_29
_29
const client = axios.create({
_29
baseURL,
_29
headers: {
_29
Accept: 'application/json',
_29
'Content-Type': 'application/json',
_29
},
_29
});
_29
_29
client.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
_29
const { getAccessToken } = useAuthentication();
_29
const token = await getAccessToken();
_29
if (token && config.headers) {
_29
config.headers.Authorization = `Bearer ${token}`;
_29
}
_29
return config;
_29
});
_29
_29
client.interceptors.response.use((response) => response);
_29
_29
export const useBackendAPI = () => {
_29
return {
_29
client,
_29
};
_29
};

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.

src/composables/backend-api.ts

_34
import axios, { InternalAxiosRequestConfig } from 'axios';
_34
import { useAuthentication } from './authentication';
_34
_34
const baseURL = 'https://cs-demo-api.herokuapp.com';
_34
_34
const client = axios.create({
_34
baseURL,
_34
headers: {
_34
Accept: 'application/json',
_34
'Content-Type': 'application/json',
_34
},
_34
});
_34
_34
client.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
_34
const { getAccessToken } = useAuthentication();
_34
const token = await getAccessToken();
_34
if (token && config.headers) {
_34
config.headers.Authorization = `Bearer ${token}`;
_34
}
_34
return config;
_34
});
_34
_34
client.interceptors.response.use(
_34
(response) => response,
_34
(error) => {
_34
return Promise.reject(error);
_34
}
_34
);
_34
_34
export const useBackendAPI = () => {
_34
return {
_34
client,
_34
};
_34
};

We need to handle the error. For now just reject.

src/composables/backend-api.ts

_34
import axios, { InternalAxiosRequestConfig } from 'axios';
_34
import { useAuthentication } from './authentication';
_34
import { useSession } from './session';
_34
import router from '@/router';
_34
_34
const baseURL = 'https://cs-demo-api.herokuapp.com';
_34
_34
const client = axios.create({
_34
baseURL,
_34
headers: {
_34
Accept: 'application/json',
_34
'Content-Type': 'application/json',
_34
},
_34
});
_34
_34
client.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
_34
const { getAccessToken } = useAuthentication();
_34
const token = await getAccessToken();
_34
if (token && config.headers) {
_34
config.headers.Authorization = `Bearer ${token}`;
_34
}
_34
return config;
_34
});
_34
_34
client.interceptors.response.use(
_34
(response) => response,
_34
(error) => {
_34
if (error.response.status === 401) {
_34
const { clearSession } = useSession();
_34
clearSession().then(() => router.replace('/tabs/tab1'));
_34
}
_34
return Promise.reject(error);
_34
}
_34
);

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.

src/composables/backend-api.ts

_27
import axios, { InternalAxiosRequestConfig } from 'axios';
_27
import { useAuthentication } from './authentication';
_27
_27
const baseURL = 'https://cs-demo-api.herokuapp.com';
_27
_27
const client = axios.create({
_27
baseURL,
_27
headers: {
_27
Accept: 'application/json',
_27
'Content-Type': 'application/json',
_27
},
_27
});
_27
_27
client.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
_27
const { getAccessToken } = useAuthentication();
_27
const token = await getAccessToken();
_27
if (token && config.headers) {
_27
config.headers.Authorization = `Bearer ${token}`;
_27
}
_27
return config;
_27
});
_27
_27
export const useBackendAPI = () => {
_27
return {
_27
client,
_27
};
_27
};

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!! 🤓