In Part-1 we have learned about user authentication using the jwt access token in the Ionic&Vue application. This is the continuation article here our main goal to use refresh token on the expiration of the access token.
That's all about the steps to implement the refresh token in the Ionic&Vue application.
NestJS(Nodejs) Todos API:
In Part-1 we discussed steps to set up the Nestjs API. In that API we have a secured endpoint called 'Todos'. In the next step, we are going to consume this 'Todo' API from our angular application.
http://localhost:3000/todos
Use Access Token As Authorization Header To Secured Endpoint:
Now we will consume the secured endpoint(in our sample 'Todos' endpoint) by sending the access token value as the authorization header.
So to work with the todos endpoint let's create a separate store module as below.
src/store/modules/todo.js:
import axios from "axios"; const store = () => ({ todos: [], }); const getters = { getAllTodos(state) { return state.todos; }, }; const actions = { async fetchTodos({ commit }) { var response = await axios.get("http://localhost:3000/todos"); commit("saveAllTodos", response.data); }, }; const mutations = { saveAllTodos(state, payload) { state.todos = payload; }, }; export default { namespaced: true, store, getters, actions, mutations, };
- (Line: 1) Imported Axios library.
- (Line: 3-5) Initialized the todos store state.
- (Line: 7-11) Todo getter where we created a function that will return all the todos from the state. This getter function will be consumed by the vue components.
- (Line: 13-18) Todo actions where we created a function that will call the todos endpoint and response will be passed to the mutation function.
- (Line: 20-24) Mutation function that updates our store state.
Regist our 'todo.js' store module inside of root module
src/store/index.js:
import {createStore} from 'vuex'; import AuthModule from './modules/auth'; import TodoModule from './modules/todo'; const store = createStore({ modules:{ auth:AuthModule, todo:TodoModule } }); export default store;Now update our 'Dashboard.vue' page vue component to display the todos
src/pages/Dashboard.vue:
<template> <master-layout pageTitle="Dashboard"> <ion-card> <ion-card-header> <ion-card-title>Welcome!</ion-card-title> </ion-card-header> <ion-card-content> <ion-item> <ion-label>Owner Name:</ion-label> <ion-label>{{ authData.userName }}</ion-label> </ion-item> </ion-card-content> </ion-card> <ion-card> <ion-card-header> <ion-button expand="full" @click="showTodos()">Show Todos</ion-button> <ion-card-title>Here all your todos</ion-card-title> </ion-card-header> <ion-card-content> <ion-item v-for="(todo,index) in allTodos" :key="index"> <ion-label>{{todo}}</ion-label> </ion-item> </ion-card-content> </ion-card> </master-layout> </template>
- Here we added one more 'ion-card' component inside of its added button, now on clicking the button we are going to invoke todos secured endpoint and then bind the results.
<script> import { IonCard,IonCardHeader,IonCardTitle,IonCardContent,IonItem,IonLabel,IonButton } from "@ionic/vue"; import { mapGetters, mapActions } from "vuex"; export default { components: { IonCard,IonCardHeader,IonCardTitle,IonCardContent,IonItem,IonLabel,IonButton }, computed: { ...mapGetters("auth", { authData: "getAuthData", }), ...mapGetters("todo",{ allTodos:"getAllTodos" }) }, methods:{ ...mapActions('todo',{ fetchTodos:"fetchTodos" }), async showTodos(){ await this.fetchTodos(); } } }; </script>
- (Line: 14-16) The todos 'mapGetters' defined to fetch all todos
- (Line: 19-21) The todos 'mapActions' defined.
- (Line: 22-24) This is a button callback method that invoking the action method to call a secured endpoint.
src/shared/jwtInterceptor.js:
import axios from "axios"; import store from '../store/index'; const jwtInterceptor = axios.create({}); jwtInterceptor.interceptors.request.use((config) => { const authData = store.getters["auth/getAuthData"]; config.headers.common["Authorization"] = `bearer ${authData.token}`; return config; }); export default jwtInterceptor;
- (Line:4) Creating an instance of Axios and assigned to variable 'jwtInterceptor'. Now, 'jwtInterceptor' works exactly as 'Axios'.
- (Line: 6) Configuring interceptor for the 'jwtInterceptor' instance.
- (Line: 7) Fetching token data from the store.
- (Line: 8) Adding an authorization header. so if we make calls suing the 'jwtInterceptor' the authorization header will be added to the request.
Now update our todos store action method to use the 'jwtInterceptor' to invoke the secured endpoint as below.
src/store/modules/todo.js:
import jwtInterceptor from '../../shared/jwtInterceptor'; const actions = { async fetchTodos({ commit }) { var response = await jwtInterceptor.get("http://localhost:3000/todos"); commit("saveAllTodos", response.data); }, };
Refresh Token Flow:
- Refresh Token is a random string key that will be created along with the JWT access token and return to the valid client on successful logging in.
- Now for all subsequent requests will use the access token, but the access token is a short-lived token whereas the refresh token lives more time than the access token.
- On the expiration of the access token, the user instead of authenticating himself again passing his user name and password, the user can send the refresh token.
- The server on receiving a refresh token first validates against the storage(database, cache, etc).
- For a valid refresh token server will create a new access token and refresh token(like when authenticate using user name and password) return it to the user.
Integrate Refresh Token Endpoint:
Now in our interceptor we need to update logic in such a way that if the access token expires then we need to invoke the refresh token endpoint after receiving new 'access' and 'refresh' tokens we need to update them in browser local storage and also need to restore our vuex state management after that we need to invoke our secured endpoint with a valid access token as its authorization header.
NestJS API Refresh Token Endpoint URL:- http://localhost:3000/auth/refreshtoken Payload:{ "access_token":"", "refresh_token":"" }Now let's create an action method that uses the token endpoint response to update the token values
src/store/modules/auth.js:
async saveTokensToStorage({commit}, payload){ await Storage.set({ key: "access_token", value: payload.access_token, }); await Storage.set({ key: "refresh_token", value: payload.refresh_token, }); commit("saveTokenData", payload); }
- On the successful response from the endpoint, a new access token and refresh token will be updated in both device storage and in the vuex store as well.
src/shared/jwtInterceptors.js:
import axios from "axios"; import store from "../store/index"; const jwtInterceptor = axios.create({}); jwtInterceptor.interceptors.request.use((config) => { const authData = store.getters["auth/getAuthData"]; if (authData == null) { return config; } config.headers.common["Authorization"] = `bearer ${authData.token}`; return config; }); jwtInterceptor.interceptors.response.use( (response) => { return response; }, async (error) => { if (error.response.status === 401) { const authData = store.getters["auth/getAuthData"]; const payload = { access_token: authData.token, refresh_token: authData.refreshToken, }; var response = await axios.post( "http://localhost:3000/auth/refreshtoken", payload ); await store.dispatch("auth/saveTokensToStorage", response.data); error.config.headers[ "Authorization" ] = `bearer ${response.data.access_token}`; return axios(error.config); } else { return Promise.reject(error); } } ); export default jwtInterceptor;
- Here on the response of the interceptor, we are handling our logic to invoke the refresh token endpoint. Here in the error response check for the status of '401', if so then we invoke the refresh token endpoint and refresh our expired application token.
That's all about the steps to implement the refresh token in the Ionic&Vue application.
Support Me!
Buy Me A Coffee
PayPal Me
Wrapping Up:
Hopefully, I think this article delivered some useful information on access token implementation in the Ionic&Vue application. I love to have your feedback, suggestions, and better techniques in the comment section below.
Excellent tutorial. Thank you for contributing such great content.
ReplyDelete