Vue3 Introduction Notes Seven - Login Function

This series of notes will focus on how to set up a project using Vue3 and interact with the backend API. It won’t cover the basic features of Vue. For the basic features of Vue, you can refer to this video Get a Quick Start with Vue in Four Hours. I got started with Vue by watching this video and thought it was pretty good.

Code address: https://github.com/yexia553/vue_study/tree/%E9%85%8D%E7%BD%AEvue-router/vue3-notes

The content of this note is a bit difficult. It is recommended to read it several times.

Be sure to read the code in the note in combination with the content of the GitHub repository and the videos mentioned in the blog. Otherwise, it may not be easy to understand.

This note uses several third - party packages. It is recommended that beginners execute cnpm install in the project root directory to install these dependencies before reading the blog content to avoid errors later.

Introduction to the Login Function

Many websites have a login function. After visitors enter their account and password on the page, the page will request the backend API for authentication. If the authentication is successful, they will be redirected to the home page. Let’s briefly break down the specific steps in this process.

  1. First, there should be a login page where visitors can enter their account and password, and there should also be a login button.
  2. The backend should have an authentication - related API for the page to call to check whether the account and password provided by the visitor are correct.
  3. After the authentication check in the previous step passes, the front - end will obtain a token. This token indicates that the visitor is legitimate and needs to be stored for use when requesting the backend later.

The above is roughly the process of implementing a login function. In this process, axios will be used to call the API. In addition, JWT is used for backend authentication, and Vuex is needed for state management. I will introduce these three knowledge points separately below.

Axios Request to the API and Axios Encapsulation

People doing front - end development must have heard of axios. I won’t write about the introduction of axios as there is a lot of relevant content on the Internet.

The native axios requires writing a lot of code every time when calling the API. I have done some encapsulation, and the code is placed in src/api/request.js,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

import axios from 'axios'
import config from './config.js'
import Cookies from 'js - cookie'
import { ElMessage } from 'element - plus'
import store from '../store/index.js'
import { useRouter } from 'vue - router';


const NETWORK_ERROR = 'Network request error. Please try again later...'


const service = axios.create({
baseURL: config.baseApi,
})

service.interceptors.request.use((req) => {
// You can do some things before the request
// Such as customizing headers, JWT - tokens, etc.
return req
})

service.interceptors.response.use((res) => {
// Process the response obtained from the request
if (res.status === 200) {
// A status code of 200 indicates that the request is normal. You can return the requested data or do some other things
return res
} else {
// A status code other than 200 indicates that the request may have an error
// ElMessage.error(NETWORK_ERROR)
// return Promise.reject(NETWORK_ERROR)
// The encapsulation here is not perfect. Throwing an exception directly will prevent the page from properly alerting the user. Return it like this for now and modify it later
return res
}
})

let tokenRefresher = async () => {
let router = useRouter()
let now = new Date().getTime()
if (now - Cookies.get('last_token_refresh_time') > 1000 * 60 * 4) {
let res = await service({
url: '/api/token/refresh/',
method: 'post',
headers: {
'Authorization': `Bearer ${Cookies.get('access_token')}`
},
data: {
refresh: `${Cookies.get('refresh_token')}`
}
})
if (res.status === 200) {
store.commit('setAccessToken', res.data.access)
} else if (res.status === 403) {
// The refresh token has expired. Re - login is required
store.commit('clearRefreshToken')
store.commit('clearAccessToken')
router.push({
name: 'login'
})
}
}
}

function request(options) {
options.method = options.method || 'get' // If the method parameter is not passed in, it defaults to a get request
if (options.method.toLowerCase() === 'get') {
// console.log(options)
options.params = options.data
}
// If the access_token can be obtained from the cookie, add it to the header
if (Cookies.get('access_token')) {
// Check if the token needs to be updated before setting the token
tokenRefresher()
service.defaults.headers.common['Authorization'] = `Bearer ${Cookies.get('access_token')}`
}
return service(options)
}

export default request

Actually, this part of the encapsulation is not very good and has few additional functions, but the entire encapsulation idea is in it. It is highly recommended to understand the part of axios encapsulation in combination with the video How to Encapsulate Axios in Vue3. It is very useful in actual work. Before I came into contact with axios encapsulation, I wrote a lot of repetitive code every time I called an API. After modification, it is indeed much more convenient.

The tokenRefresher function in the above code is used to update the access token. You can ignore it for now and come back to understand this part after reading the following JWT and API parts.

Introduction to JWT and Its Application in the Login Function

Before reading the following content, it is recommended to carefully read the following two blogs in their entirety:

  1. [JSON Web Token Tutorial](https://www.ruanyifeng.com/blog/2018/07/json_web_token - tutorial.html) - Ruan Yifeng
  2. Introduction to JWT - Step by Step

You don’t need to delve into the details. Just understand the operation process.

I will briefly summarize the process of JWT in the login scenario to facilitate understanding the following content.

  1. After the visitor enters the account and password on the page and clicks login, the front - end will request the /api/token/ API of the backend. If the authentication is successful, the backend will return an access token and a refresh token to the front - end.
  2. The access token is used to access the backend API. Therefore, this token must be carried in subsequent requests to access the API normally. The refresh token is used to refresh the access token. Generally, the validity period of the access token is short. For example, in the Django DRF framework, by default, the validity period of the access token is only 5 minutes, while the validity period of the refresh token is 8 hours.
  3. After the front - end obtains the access token and refresh token, it needs to find a place to store them for later use, such as in cookies or localstorage.
  4. There should be a checking mechanism to check whether the access token has expired. If it has expired, the refresh token should be used to update it in a timely manner to obtain a new access token, and the access token stored in the previous step should also be updated.

Centralized Management of APIs in Vue3

In the case of front - end and back - end separation, the front - end generally needs a method to manage the called APIs, which is more convenient for later maintenance, updates, and modifications. Here, I will introduce a method I like. This method is suitable for small - to - medium - sized front - end projects where the number of called APIs is not very large.

  1. First, create a config.js file in the src/api directory with the following content,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Project environment configuration
*/
// This is a way to use Vite: https://cn.vitejs.dev/guide/env - and - mode.html#env - files
const env = import.meta.env.MDOE || 'dev'

const envConfig = {
dev: {
baseApi: 'http://localhost:8000',
},
test: {
baseApi: 'test.example.com/api',
},
prod: {
baseApi: 'example.com/api',
}
}

export default {
env,
...envConfig[env]
}

It is not difficult to see that this file is used for environment management. For example, dev represents the local development environment, test represents the online testing environment, and prod represents the production environment. You can also add other configurations according to your needs.
This config.js is also used in the above - mentioned encapsulation of axios. You can go back and look at the code.

  1. Then, create an api.js file in the src/api directory. This file is where all the called APIs will be placed. The code is as follows,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import request from "./request.js";


export default {
login(params) {
return request({
url: '/api/token/',
method: 'post',
data: params,
mock: false
})
},
refreshToken(params) {
return request({
url: '/api/token/refresh',
method: 'post',
data: params,
mock: false
})
}
}

The api.js currently contains only two interfaces. One is login, which is used when logging in, and the other is refreshtoken, which is used to refresh the access token, as introduced in the JWT part above.

  1. After creating api.js, it also needs to be bound to Vue so that it can be called.
    Modify the src/main.js file. The modified content is as follows,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element - plus'
import 'element - plus/dist/index.css'
import router from './router/index.js'
import * as ElementPlusIconsVue from '@element - plus/icons - vue'
import './assets/main.css'
import api from './api/api.js'


const app = createApp(App)

for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}

app.use(router)
app.use(ElementPlus)
app.mount('#app')
app.config.globalProperties.$api = api

The modified parts are to introduce src/api/api.js in the import; and then configure api to the global properties of Vue in the last line for easy reference later.

So far, the centralized management of APIs in Vue is completed.

Implementation of the Login Page

After so much preparatory work above, it is all for implementing the login page. Now let’s implement it. Create a login folder in the src/views directory, and then create a Login.vue file in it. The content of the file is as follows. Pay attention to the comments in the code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<template>
<el - form :model="formData" status - icon class="login - container" ref="formRef">
<h3 class="login - title">Login</h3>
<el - form - item label="Username" prop="username" label - width="80px">
<el - input type="input" auto - complete="off" placeholder="Please enter your username" v - model="formData.username"></el - input>
</el - form - item>
<el - form - item label="Password" prop="password" label - width="80px">
<el - input type="password" auto - complete="off" placeholder="Please enter your password" v - model="formData.password"></el - input>
</el - form - item>
<el - form - item class="login - submit">
<!-- Here, the login function is bound through @click to trigger the login function when clicked -->
<el - button type="primary" class="login - submit" @click="login()">Login</el - button>
</el - form - item>
</el - form>
</template>

<script>
import { getCurrentInstance, reactive } from 'vue';
import { defineComponent } from 'vue - demi';
import { useRouter } from 'vue - router';
import { ElMessageBox } from 'element - plus'; // This is used to pop up a prompt when the account and password are incorrect
import store from '../../store/index.js'; // Here, Vuex is introduced. Ignore it for now. It will be introduced later

export default defineComponent({
setup() {
const { proxy } = getCurrentInstance() // Note this. The following login function will use it
const router = useRouter()
// In Vue3, reactive is used to obtain form data
const formData = reactive({
username: '',
password: '',
});
// Use an asynchronous way to request the API
let login = async () => {
let res = await proxy.$api.login(formData) // Call login through $api
if (res.status === 200) { // If the return code is 200, it indicates that the account and password are correct and the verification has passed
// The following two lines of code are to obtain the access token and refresh token returned by the backend and store them for later use
store.commit('setAccessToken', res.data.access)
store.commit('setRefreshToken', res.data.refresh)
store.commit('updateLastRefreshTime') // Update the time of the last refresh of the access token, which is used to compare whether the access token has expired. Here, it should be related to the content of JWT
router.push({ // Redirect to the main page
name:'main'
})
} else {
// If the account and password are incorrect, prompt and return to the login page
ElMessageBox.alert('The account or password is incorrect. Please try again!')
router.push({
name: 'login'
})
}
};
return {
formData,
login,
}
}
})
</script>


<style lang="less" scoped>
.login - container {
border - radius: 15px;
background - clip: padding - box;
margin: 180px auto;
width: 350px;
padding: 35px 35px 15px 35px;
background - color: #fff;
border: 1px solid #eaeaea;
box - shadow: 0 0 25px #cac6c6;
}
.login - title {
margin: 0px auto 40px auto;
text - align: center;
color: #505458;
}
.login - submit {
margin: 10px auto 0 auto;
justify - content: center;
}
</style>

The above code implements the style and function of the page login, but we are still missing a route pointing to this page. Now let’s configure it. Modify the content of src/router/index.js. The modified content is as follows,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

import { createRouter, createWebHashHistory } from 'vue - router'

const routes = [
{
path: '/',
name:'main',
redirect: '/home',
component: () => import('../views/Main.vue'),
children: [
{
path: '/home',
name: 'home',
component: () => import('../views/home/Home.vue'),
},
{
path: '/other',
name: 'other',
component: () => import('../views/other/Other.vue'),
},
]
},
{
path: '/login',
name: 'login',
component: () => import('../views/login/Login.vue')
}
]

const router = createRouter({
history: createWebHashHistory(),
routes
})

export default router

In fact, only one place has been modified, that is, adding a first - level route `/login