How to Create a Refresh Token Feature in your Strapi Application
Tags: strapi, javascript, headlesscms, webdev
Published: 2022-10-19 09:48:18

Author: Mary Okosun

Every user-centric backend service, like Strapi, depends on authentication and user management because different users may have varying roles and permissions. [Strapi is an open-source and headless content management system (CMS) that gives developers the freedom to use their favourite tools and frameworks during development](https://strapi.io/).

## Why Authenticate Your Strapi Application?
By allowing only authenticated users (or processes) to access their protected resources, authentication enables organizations to maintain the security of their networks. Authentication is the process of validating that a person or entity is, in fact, who or what it claims to be.

## Prerequisites

To follow in this project tutorial:
- You need to download and install [Node.js](https://nodejs.org/en/) (versions 16.x are recommended by Strapi).
- You need to have [npm](https://docs.npmjs.com/cli/v6/commands/npm-install) (version 6 only) or [yarn](https://yarnpkg.com/getting-started/install) to run the CLI installation scripts.
- You need to have a basic knowledge of JavaScript and Vue.js.

## Goals

This tutorial gives an approach on how to add a user as a content-type, how to test an authenticated user and why you need to authenticate a user. You will learn how to implement a refresh token for an authenticated user.
You will also learn how to create a mini-app using Vue.js, a JavaScript framework, and Strapi that showcases how an authenticated user can have access to the dashboard by creating a refresh token for this user.
At the end of this tutorial, you should know how to create a refresh token feature in your Strapi application.

## Backend Implementation

You will be using Strapi for the backend implementation. This would be done in multiple steps:

**1. Scaffolding a Strapi Project**
You will be running the Strapi project locally and using the Strapi CLI (Command Line Interface) installation scripts as it is the fastest way to get Strapi running locally. A new Strapi instance, **strapi-refresh-token-backend**, will be created in a specified directory on your machine.
```bash
        npx create-strapi-app strapi-refresh-token-backend --quickstart
        #OR
        yarn create-strapi-app strapi-refresh-token-backend --quickstart
```
In the directory you specified, the code snippets above will create a new Strapi project. It should automatically open `http://localhost:1337/admin/auth/register-admin` on your browser. If not, you can start the admin panel on your browser by executing the following command in your terminal.
```bash
        npm run develop
        # OR
        yarn run develop
```
To register as the system's new admin, a new window would open.

![Strapi Admin Panel](https://paper-attachments.dropbox.com/s_3D03B854C39E456392430789E62C7B37CCBE5E3E216B7AA986951F2A1FD99F37_1661338511025_Screenshot+2022-08-24+at+11.54.37+AM.png)


You can complete the form and press the submit button. You will then be redirected to the admin panel.

![Strapi Admin Dashboard - Overview](https://paper-attachments.dropbox.com/s_3D03B854C39E456392430789E62C7B37CCBE5E3E216B7AA986951F2A1FD99F37_1661338693097_Screenshot+2022-08-24+at+11.56.39+AM.png)


**2. Create a New User**
On the Strapi admin dashboard, navigate to the content manager and in the user collection type, you will create a new entry for a user.

![Strapi Admin Dashboard - Content Manager](https://paper-attachments.dropbox.com/s_3D03B854C39E456392430789E62C7B37CCBE5E3E216B7AA986951F2A1FD99F37_1661350948834_Screenshot+2022-08-24+at+3.21.23+PM.png)


Fill in the following details on the form:

- username: marynoir
- email: mary@gmail.com
- password: *****************

Click on the **Save** button to save your new user.

![Strapi Admin Dashboard - Create new entry for user](https://paper-attachments.dropbox.com/s_3D03B854C39E456392430789E62C7B37CCBE5E3E216B7AA986951F2A1FD99F37_1661953814225_Screenshot+2022-08-31+at+2.49.36+PM.png)


**3. Testing an Authenticated User in an API Client.**
Here, you will test your new user on Postman. You can use an API client of your choice; however, for this tutorial, Postman will be used to test the API endpoints. Navigate to your Postman client and send a POST request to `http://localhost:1337/api/auth/local`.
The response body should be similar to the following:
```
        {
            "identifier": "marynoir",
            "password": "marynoir"
        }
```
- Identifier: This can either be the users’ **email** or **username**.
- Password: This is the password of the user

The response body should be similar to the following:
```
        {
            "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjYxOTQ0OTQ4LCJleHAiOjE2NjQ1MzY5NDh9.yeYcS8kA_TI9JqVn4Xnqu0lKiT4BUgnM7l8HFKJ56hc",
            "user": {
                "id": 1,
                "username": "marynoir",
                "email": "mary@gmail.com",
                "provider": "local",
                "confirmed": true,
                "blocked": false,
                "createdAt": "2022-08-31T11:22:24.613Z",
                "updatedAt": "2022-08-31T11:22:24.613Z"
            }
        }
```    
![Postman API Client](https://paper-attachments.dropbox.com/s_3D03B854C39E456392430789E62C7B37CCBE5E3E216B7AA986951F2A1FD99F37_1661945008668_Screenshot+2022-08-31+at+12.23.07+PM.png)


**4. Introduction to the Refresh Token Feature**
From the response gotten above, you can see the `jwt` in the response body. A jwt token may be used for making permission-restricted API requests. In this tutorial, the jwt token will be used to give the user access to the application and when the token is expired, the users’ access gets restricted.
The user will be mandated to request for another jwt token using the refresh token feature in order to have access to the application again. You will be creating a refresh token by configuring some folders and files in the Strapi directory.

- **Step 1:** In the `.env` file, add the following environment variables:
```
    // .env
    
    REFRESH_SECRET=strapisecret
    REFRESH_TOKEN_EXPIRES=2d
    JWT_SECRET_EXPIRES=360s
    NODE_ENV=development
```
- **Step 2:** In the `src/extensions` folder, create a folder named `users-permissions`. Within this folder, create another folder called `controllers/validation`. Inside this folder, create a file `auth.js`. Add the following code snippets to the `auth.js` file:
        // ../users-permissions/controllers/validation/auth.js
```    
        'use strict';
        const { yup, validateYupSchema } = require('@strapi/utils');
        const callbackBodySchema = yup.object().shape({
          identifier: yup.string().required(),
          password: yup.string().required(),
        });
        module.exports = {
          validateCallbackBody: validateYupSchema(callbackBodySchema)
        };
```

The code above creates a schema, `callbackBodySchema`, that requires an identifier and password for the login authentication. This authentication is similar to the Strapi login system.

- **Step 3:** In the `users-permissions` folder, create a new folder called `utils`. Within this folder, create a file called `index.js` and add the following code snippets to the file:

```js
        // ../users-permissions/utils/index.js
    
        'use strict';
        const getService = name => {
          return strapi.plugin('users-permissions').service(name);
          return
        };
        module.exports = {
          getService,
        };
```
- **Step 4:** In the `users-permissions` folder, create a new file called `strapi-server.js`. Add the following code snippets to the `strapi-server.js` file:
```js
        // ../users-permissions/strapi-server.js
    
        const utils = require('@strapi/utils');
        const { getService } = require('../users-permissions/utils');
        const jwt = require('jsonwebtoken');
        const _ = require('lodash');
        const {
            validateCallbackBody
        } = require('../users-permissions/controllers/validation/auth');
    
        const { setMaxListeners } = require('process');
        const { sanitize } = utils;
        const { ApplicationError, ValidationError } = utils.errors;
        const sanitizeUser = (user, ctx) => {
            const { auth } = ctx.state;
            const userSchema = strapi.getModel('plugin::users-permissions.user');
            return sanitize.contentAPI.output(user, userSchema, { auth });
        };
    
        module.exports = (plugin) => {
         return plugin
        }
```
- **Step 5:** In the next step, you will be using the existing login procedure from Strapi, which can be found in the node-modules folder under `@strapi/plugin-users-permissions/server/controllers/auth.js`.

Add the following snippets within the module.export function:
```js
        // ../users-permissions/strapi-server.js
    
        module.exports = (plugin) => {
            plugin.controllers.auth.callback = async (ctx) => {
                const provider = ctx.params.provider || 'local';
                const params = ctx.request.body;
                const store = strapi.store({ type: 'plugin', name: 'users-permissions' });
                const grantSettings = await store.get({ key: 'grant' });
                const grantProvider = provider === 'local' ? 'email' : provider;
                if (!_.get(grantSettings, [grantProvider, 'enabled'])) {
                    throw new ApplicationError('This provider is disabled');
                }
                if (provider === 'local') {
                    await validateCallbackBody(params);
                    const { identifier } = params;
                    // Check if the user exists.
                    const user = await strapi.query('plugin::users-permissions.user').findOne({
                        where: {
                            provider,
                            $or: [{ email: identifier.toLowerCase() }, { username: identifier }],
                        },
                    });
                    if (!user) {
                        throw new ValidationError('Invalid identifier or password');
                    }
                    if (!user.password) {
                        throw new ValidationError('Invalid identifier or password');
                    }
                    const validPassword = await getService('user').validatePassword(
                        params.password,
                        user.password
                    );
                    if (!validPassword) {
                        throw new ValidationError('Invalid identifier or password');
                    } else {
                         ctx.send({
                            jwt: getService('jwt').issue({
                                id: user.id,
                            }),
                            user: await sanitizeUser(user, ctx),
                        });
                    }
                    const advancedSettings = await store.get({ key: 'advanced' });
                    const requiresConfirmation = _.get(advancedSettings, 'email_confirmation');
                    if (requiresConfirmation && user.confirmed !== true) {
                        throw new ApplicationError('Your account email is not confirmed');
                    }
                    if (user.blocked === true) {
                        throw new ApplicationError('Your account has been blocked by an administrator');
                    }
                    return ctx.send({
                        jwt: getService('jwt').issue({ id: user.id }),
                        user: await sanitizeUser(user, ctx),
                    });
                }
                // Connect the user with a third-party provider.
                try {
                    const user = await getService('providers').connect(provider, ctx.query);
                    return ctx.send({
                        jwt: getService('jwt').issue({ id: user.id }),
                        user: await sanitizeUser(user, ctx),
                    });
                } catch (error) {
                    throw new ApplicationError(error.message);
                }
            }
            return plugin
        }
```
The code above checks if a Strapi provider such as Google or Auth0 is used for login authentication. In this tutorial, you are not using an external provider, so the provider variable would be **local**.

If the provider is local, it would confirm that the user exists using the **identifier** field. If the user exists, it would check if the password in the request body is the same as the password used in registration. If the password matches, the user get logged in.

- **Step 6:** You would need to have a refresh cookie that will be used alongside the jwt token in order to generate a new refresh token. You need to add the following code snippets just below the `sanitizeUser` function in the `strapi-server.js` file:
```js
        // ../users-permissions/strapi-server.js
        const sanitizeUser = (user, ctx) => {
           ...
        };
    
        // issue a JWT
        const issueJWT = (payload, jwtOptions = {}) => {
            _.defaults(jwtOptions, strapi.config.get('plugin.users-permissions.jwt'));
            return jwt.sign(
                _.clone(payload.toJSON ? payload.toJSON() : payload),
                strapi.config.get('plugin.users-permissions.jwtSecret'),
                jwtOptions
            );
        }
    
        // verify the refreshToken by using the REFRESH_SECRET from the .env
        const verifyRefreshToken = (token) => {
            return new Promise(function (resolve, reject) {
                jwt.verify(token, process.env.REFRESH_SECRET, {}, function (
                    err,
                    tokenPayload = {}
                ) {
                    if (err) {
                        return reject(new Error('Invalid token.'));
                    }
                    resolve(tokenPayload);
                });
            });
        }
    
        // issue a Refresh token
        const issueRefreshToken = (payload, jwtOptions = {}) => {
            _.defaults(jwtOptions, strapi.config.get('plugin.users-permissions.jwt'));
            return jwt.sign(
                _.clone(payload.toJSON ? payload.toJSON() : payload),
                process.env.REFRESH_SECRET,
                { expiresIn: process.env.REFRESH_TOKEN_EXPIRES }
            );
        }
```

*In Line 7-14, the function `issueJWT` creates a new jwt token which will be used when requesting for a refresh token.*

*In Line 17-29, the `verifyRefreshToken` function is used to verify that the refresh token passed in the request body while requesting for a new jwt is actually valid. It uses the `jwt.verify()` function to verify that the token is valid with the REFRESH_SECRET` in the .env file. If this is valid, it returns a new token for the user, else it returns an error `Invalid token`.*

*In Line 32-39, the `issueRefreshToken` function is used to create a new refresh token that will be stored in the cookie.*

Now that you can create a refresh token, you need to be able to store this refresh token in the cookies. Replace the content of the `isValidPassword` check with the following code snippets. The snippets sets the refresh token with the name refreshToken as the cookie name if the password is valid.
```js
        // ../users-permissions/strapi-server.js
    
        if (!validPassword) {
          throw new ValidationError('Invalid identifier or password');
        } else {
            ctx.cookies.set("refreshToken", issueRefreshToken({ id: user.id }), {
                httpOnly: true,
                secure: false,
                signed: true,
                overwrite: true,
        });
            ctx.send({
              status: 'Authenticated',
              jwt: issueJWT({ id: user.id }, { expiresIn: process.env.JWT_SECRET_EXPIRES }),
              user: await sanitizeUser(user, ctx),
            });
        }
```
- **Step 7:** Let's test what you have done so far. At this stage, you have been able to refactor a login system for our application. If a registered user logs in, the user should have a jwt token and also a refresh token saved in the cookies. You can send a POST request to the login api route `http://localhost:1337/api/auth/local` and see the `refreshToken` saved in the cookies.
![Postman API Client - Cookies](https://paper-attachments.dropbox.com/s_3D03B854C39E456392430789E62C7B37CCBE5E3E216B7AA986951F2A1FD99F37_1661950492004_Screenshot+2022-08-31+at+1.53.54+PM.png)

- **Step 8:** The next step is to create a function that would take in the refresh token and issue a new jwt for the user. Add the code snippets below the `plugin.controllers.auth.callback` function:
```js
        // ../users-permissions/strapi-server.js
    
        plugin.controllers.auth.callback = async (ctx) => {
            ......
        }   
        plugin.controllers.auth['refreshToken'] = async (ctx) => {
                const store = await strapi.store({ type: 'plugin', name: 'users-permissions' });
                const { refreshToken } = ctx.request.body;
                const refreshCookie = ctx.cookies.get("refreshToken")
    
                if (!refreshCookie && !refreshToken) {
                    return ctx.badRequest("No Authorization");
                }
                if (!refreshCookie) {
                    if (refreshToken) {
                        refreshCookie = refreshToken
                    }
                    else {
                        return ctx.badRequest("No Authorization");
                    }
                }
                try {
                    const obj = await verifyRefreshToken(refreshCookie);
                    const user = await strapi.query('plugin::users-permissions.user').findOne({ where: { id: obj.id } });
                    if (!user) {
                        throw new ValidationError('Invalid identifier or password');
                    }
                    if (
                        _.get(await store.get({ key: 'advanced' }), 'email_confirmation') &&
                        user.confirmed !== true
                    ) {
                        throw new ApplicationError('Your account email is not confirmed');
                    }
                    if (user.blocked === true) {
                        throw new ApplicationError('Your account has been blocked by an administrator');
                    }
                    const refreshToken = issueRefreshToken({ id: user.id })
                    ctx.cookies.set("refreshToken", refreshToken, {
                        httpOnly: true,
                        secure: false,
                        signed: true,
                        overwrite: true,
                    });
                    ctx.send({
                        jwt: issueJWT({ id: obj.id }, { expiresIn: process.env.JWT_SECRET_EXPIRES }),
                        refreshToken: refreshToken,
                    });
                }
                catch (err) {
                    return ctx.badRequest(err.toString());
                }
            }
```
The snippets above get the `refreshToken` from the cookies and saves it as `refreshCookie`. If the `refreshCookie` is not found, it returns an error of `No Authorization`. If the `refreshCookie` is found, it gets verified using the `verifyRefreshToken()` created earlier. Checks such as if the user exist, if the users’ email is not confirmed and if the users’ account has been blocked are made. It creates a new jwt using the `issueJWT()` and assigns it to the user.

- **Step 9:** The final step is to create an api route to refresh our jwt and request for a new token. Add the following code before the **return plugin** statement:

```js
        // ../users-permissions/strapi-server.js
    
        plugin.controllers.auth.callback = async (ctx) => {
            ......
        }
        plugin.controllers.auth['refreshToken']= async (ctx) => {
            ......
        } 
        plugin.routes['content-api'].routes.push({
                method: 'POST',
                path: '/token/refresh',
                handler: 'auth.refreshToken',
                config: {
                    policies: [],
                    prefix: '',
                }
        });
```
Let us test what you have done so far. You can send a POST request to the refresh token api route http://localhost:1337/api/token/refresh and see the new jwt and `refreshToken` in the response body.

![screenshot](https://paper-attachments.dropbox.com/s_3D03B854C39E456392430789E62C7B37CCBE5E3E216B7AA986951F2A1FD99F37_1661509633259_Screenshot+2022-08-26+at+11.26.39+AM.png)

By default, Strapi gives a validation token (jwt) valid for 30 days. For the purpose of this project, you would manually configure the expiration date so that our application can be tested faster. Create a `plugins.js` file in the `config` folder and add the following code snippets:
```js
        // config/plugins.js
    
        module.exports = ({ env }) => ({
            'users-permissions': {
              enabled: true,
              config: {
                jwt: {
                  expiresIn: '15m',
                },
              },
            },
          });
```
## Frontend Implementation
You have built the backend services and the refresh token feature configured; the next step is to create the frontend application to consume the Strapi APIs with Vue.js. The frontend application will be a mini-app that has two (2) screens: the login and dashboard interface. A registered user can log in and be directed to the dashboard screen. When the token of such user expires, the user will be prompted to request for another token. If the user does not request for a new token, they will be logged off the application.

According to the [documentation](https://vuejs.org/guide/introduction.html), Vue.js is a JavaScript framework for building user interfaces. It builds on top of standard HTML, CSS, and JavaScript, and provides a declarative and component-based programming model that helps you efficiently develop user interfaces, be it simple or complex.
To create a new Vue.js project, follow these steps to get started:

1. Navigate to a directory and install the Vue.js package using the command:
```bash
    npm install -g @vue/cli
    # OR
    yarn global add @vue/cli 
```
2. Create a new project using the command:
```bash
    vue create strapi-refresh-token-frontend
```

You will be prompted to pick a preset. Select "manually select features" to pick the features we need. You would select `Vuex`, `Router`, and `Lint/Formatter`. Vuex is a state management library for Vue applications; Router allows to change the URL without reloading the page and Lint/Formatter properly formats the codes.

After successfully creating your project, navigate to the folder directory and run the application:
```bash
    cd strapi-refresh-token-frontend
    npm run serve
    #OR
    yarn run serve
```
The URL `http://localhost:8080/` should open your Vue.js application in your browser.

![Vue layout](https://paper-attachments.dropbox.com/s_FA433A94C11F9AC81352A78C08AC649F89276D4CCDF95A27BFE1CFCA385F461D_1653570230595_Screenshot+2022-05-26+at+2.03.21+PM.png)


### Dependency Installation
You need to install some dependencies such as `axios`. **Axios** is the package dependency that will be used to make the call to the Strapi backend APIs:
```bash
    npm install axios
```
Firstly, delete the `HelloWorld.vue` file in the components folder, the `HomeView.vue`, and `AboutView.vue` files in the views folder as these files are redundant in this project.

Create two new files `Login.vue` and `Dashboard.vue` in the components folder and copy the following contents into the login.vue
```html
        //Login.vue
    
        <template>
          <div class="login">
            <input
              type="text"
              v-model="identifier"
              placeholder="Enter username/email"
            />
            <input 
              type="text" 
              v-model="password" 
              placeholder="Enter password" 
            />
            <div>
              <button @click="login">LOGIN</button>
            </div>
          </div>
        </template>
        <script>
        import axios from "axios";
        export default {
          name: "login",
          data() {
            return {
              identifier: "",
              password: "",
            };
          },
          methods: {
            async login() {
              try {
                const data = {
                  identifier: this.identifier,
                  password: this.password,
                };
                const options = {
                  credentials: "include",
                  withCredentials: true,
                };
                const res = await axios.post(
                  "http://localhost:1337/api/auth/local",
                  data,
                  options
                );
                localStorage.setItem("token", res.data.jwt);
                localStorage.setItem("user", JSON.stringify(res.data.user));
                if (res.status === 200) {
                  this.$router.push("/dashboard");
                }
              } catch (err) {
                console.log(err);
              }
            },
          },
        };
        </script>
        <style scoped>
        .login {
          display: flex;
          flex-direction: column;
          padding: 35px;
          background: #e8e8e8;
        }
        input {
          padding: 15px;
          margin: 5px 0;
          border-radius: 2px;
          border: 1px solid white;
        }
        button {
          background: #36865d;
          color: white;
          border: none;
          padding: 15px 25px;
          width: 100%;
          margin-top: 5px;
          font-weight: bolder;
        }
        button:hover {
          background: #4cab7a;
        }
        </style>
```
In the `Dashboard.vue` file, add the following contents:
```html
        <template>
          <div>
            <h1>User Dashboard</h1>
            <ul>
              <li><b>Username:</b> {{ getUser.username }}</li>
              <li><b>Email:</b> {{ getUser.email }}</li>
              <li><b>Is User Confirmed:</b> {{ getUser.confirmed }}</li>
              <li><b>Is User Blocked: </b>{{ getUser.blocked }}</li>
              <li><b>Provider:</b> {{ getUser.provider }}</li>
            </ul>
          </div>
        </template>
        <script>
        export default {
          name: "dashboard",
          computed: {
            getUser() {
              let jwtPayload = JSON.parse(localStorage.getItem("user"));
              return jwtPayload;
            },
          },
        };
        </script>
        <style scoped>
        ul {
          list-style-type: none !important;
          padding: 0;
        }
        </style>
```
In the views folder, create a `LoginView.vue` file and copy the following content:
```html
        <template>
          <div class="container">
            <Login />
          </div>
        </template>
        <script>
        import Login from "@/components/Login.vue";
        export default {
          name: "LoginView",
          components: {
            Login,
          },
        };
        </script>
        <style scoped>
        .container {
          margin: 50px auto;
          width: 400px;
        }
        </style>
```
A modal will be built that will pop up when the token is expired and requests the user to choose if the application should refresh the token or not. In the components folder, create a `Modal.vue` file and copy the following code snippets:
```html
        <template>
          <div class="modal-overlay" @click="$emit('close-modal')">
            <div class="modal" @click.stop>
              <h6>Token Expired!</h6>
              <p>You are unable to view your dashboard.</p>
              <p>Do you want to refresh your token?</p>
              <button id="no-button" @click="handleNoButton">No</button>
              <button id="yes-button" @click="getRefreshToken">Yes</button>
            </div>
          </div>
        </template>
        <script>
        import axios from "axios";
        export default {
          methods: {
            handleNoButton() {
              this.$router.push("/");
            },
            async getRefreshToken() {
              try {
                const data = {
                  refreshToken: localStorage.getItem("token"),
                };
                const options = {
                  "Access-Control-Allow-Credentials": true,
                  withCredentials: true,
                };
                const res = await axios.post(
                  "http://localhost:1337/api/token/refresh",
                  data,
                  options
                );
                localStorage.setItem("token", res.data.jwt);
                this.$emit("close-modal");
              } catch (err) {
                console.log(err);
              }
            },
          },
        };
        </script>
        <style scoped>
        .modal-overlay {
          position: fixed;
          top: 0;
          bottom: 0;
          left: 0;
          right: 0;
          display: flex;
          justify-content: center;
          background-color: #000000da;
        }
        .modal {
          text-align: center;
          background-color: white;
          height: 200px;
          width: 400px;
          margin-top: 10%;
          padding: 60px 0;
          border-radius: 20px;
        }
        .close {
          margin: 10% 0 0 16px;
          cursor: pointer;
        }
        .close-img {
          width: 25px;
        }
        .check {
          width: 150px;
        }
        h6 {
          font-weight: 500;
          font-size: 28px;
          margin: 20px 0;
        }
        p {
          font-size: 16px;
        }
        button {
          width: 100px;
          height: 40px;
          color: white;
          font-size: 14px;
          border-radius: 12px;
          margin-top: 10px;
          margin-right: 10px;
          border: 1px solid #fff;
        }
        #yes-button {
          background-color: #4cab7a;
        }
        #no-button {
          background-color: #ba0000da;
        }
        </style>
```
Refactor the `index.js` file to suit the changes done so far. It should be similar to the following:
```js
        import { createRouter, createWebHistory } from "vue-router";
        import LoginView from "../views/LoginView.vue";
        const routes = [
          {
            path: "/",
            name: "login",
            component: LoginView,
          },
          {
            path: "/dashboard",
            name: "dashboard",
            // route level code-splitting
            // this generates a separate chunk (about.[hash].js) for this route
            // which is lazy-loaded when the route is visited.
            component: () =>
              import(/* webpackChunkName: "about" */ "../views/Dashboard.vue"),
          },
        ];
        const router = createRouter({
          history: createWebHistory(process.env.BASE_URL),
          routes,
        });
        export default router;
```
## Testing the Application

Now you can test the frontend implementation of the application. Go to `http://localhost:8080/` and you should see the login page

![login page](https://paper-attachments.dropbox.com/s_3D03B854C39E456392430789E62C7B37CCBE5E3E216B7AA986951F2A1FD99F37_1661779727101_Screenshot+2022-08-29+at+2.28.25+PM.png)


Enter the username and password that was created for the user on the Strapi backend entry. If the details are correct, you would be routed to the dashboard page.

![user dashboard](https://paper-attachments.dropbox.com/s_3D03B854C39E456392430789E62C7B37CCBE5E3E216B7AA986951F2A1FD99F37_1661780110070_Screenshot+2022-08-29+at+2.34.41+PM.png)

When the token expires, the modal pops up.

![expired token modal](https://paper-attachments.dropbox.com/s_3D03B854C39E456392430789E62C7B37CCBE5E3E216B7AA986951F2A1FD99F37_1661781378473_Screenshot+2022-08-29+at+2.54.56+PM.png)

If you click on the **No** button, the app routes you back to the login page so you can log in again to regenerate a new token. If you click on the **Yes** button, the app makes an API call to the refresh token API and automatically reissues a new jwt and refresh token. This allows you to continue browsing the application without having to log in each time.
To ascertain that this really works, you can check the `refreshToken` stored in the cookie and the token stored in the `localstorage`. With each click on the **Yes** button, a new **refreshToken** is generated.

![sample](https://paper-attachments.dropbox.com/s_3D03B854C39E456392430789E62C7B37CCBE5E3E216B7AA986951F2A1FD99F37_1661782541557_Screenshot+2022-08-29+at+3.15.02+PM.png)

## Conclusion
In this tutorial, you learned how to add and authenticate a user using jwt. A demonstration on how to implement a refresh token for an authenticated user using the jwt from a user login activity was done.

You can [download the source code for the frontend](https://github.com/Marienoir/Strapi-Refresh-Token-v4/tree/main/strapi-refresh-token-frontend) and [backend](https://github.com/Marienoir/Strapi-Refresh-Token-v4/tree/main/strapi-refresh-token-backend) implementation from Github.