Refresh the page without any sense (with runnable front-end and back-end source code, front-end vue, back-end node)

1. Foreword

Imagine that you are browsing pages normally on the web. Suddenly a window pops up, telling you that your login is invalid and jumping back to the login page, asking you to log in again. Are you annoyed. At this time, the role of senseless refresh is reflected.

2. Plan

2.1 redis set expiration time

In the latest technology, tokens are generally stored in the Redis server and have an expiration time set. As long as the request is reissued within the valid time, the expiration time in Redis will be updated, so that only one token is needed. This solution is usually done on the back end.

2.2 dual token mode

2.21 Principle
  • The user logs in and sends the account password to the server. If the login fails, the user returns to the client to log in again. After successful login, the server generates accessToken and refreshToken, and returns the generated token to the client.
  • In the request interceptor, the request header carries the accessToken request data, and the server verifies whether the accessToken has expired. If the token is valid, continue to request data. If the token is invalid, invalidation information will be returned to the client.
  • The client receives the request information sent by the server and determines whether there is accessToken invalidation information in the response interceptor of the secondary encapsulated axios, but no response data is returned. If there is invalid information, bring refreshToken to request a new accessToken.
  • The server verifies whether refreshToken is valid. If valid, the token will be regenerated and the new token and prompt information will be returned to the client. If invalid, invalid information will be returned to the client.
  • The client response interceptor determines whether the response information has a refreshToken that is valid or invalid. Invalid, log out of current login. Valid, re-store the new token and continue to request the data of the last request.
2.22 up code

Backend: node.js, koa2 server, jwt, koa-cors, etc. (You can use koa scaffolding to create projects. This project is based on koa scaffolding. The complete code can be found at the github address at the end of the article)

  1. Create new utils/token.js (double token)
const jwt=require('jsonwebtoken')

const secret='2023F_Ycb/wp_sd' //Key
/*
expiresIn:5 Expiration time, time unit is seconds
You can also write expiresIn:1d to represent one day
1h represents one hour
*/
// This time is for testing, so set the time to 5 seconds for short token and 15 seconds for long token
const accessTokenTime=5
const refreshTokenTime=15

// Generate accessToken
const accessToken=(payload={})=>{ // payload carries user information
    return jwt.sign(payload,secret,{expiresIn:accessTokenTime})
}
//Generate refreshToken
const refreshToken=(payload={})=>{
    return jwt.sign(payload,secret,{expiresIn:refreshTokenTime})
}

module.exports={
    secret,
    accessToken,
    refreshToken
}
  1. router/index.js creates routing interface
const router = require('koa-router')()
const jwt = require('jsonwebtoken')
const { accessToken, refreshToken, secret }=require('../utils/token')
router.get('/', async (ctx, next) => {
  await ctx.render('index', {
    title: 'Hello Koa 2!'
  })
})

router.get('/string', async (ctx, next) => {
  ctx.body = 'koa2 string'
})

router.get('/json', async (ctx, next) => {
  ctx.body = {
    title: 'koa2 json'
  }
})
/*Login interface*/
router.get('/login',(ctx)=>{
  let code,msg,data=null
  code=2000
  msg='Login successful, token obtained'
  data={
    accessToken:accessToken(),
    refreshToken:refreshToken()
  }
  ctx.body={
    code,
    msg,
    data
  }
})

/*Data acquisition interface for testing*/
router.get('/getTestData',(ctx)=>{
  let code,msg,data=null
  code=2000
  msg='Get data successfully'
  ctx.body={
    code,
    msg,
    data
  }
})

/*Verify whether the long token is valid and refresh the short token
  It should be noted here that when refreshing the short token, the new long token will also be returned and the long token will be continued.
  This way active users will not be forced to log out during ongoing operations. Non-activity that has been inactive for a long time
  The user's long token has expired and he needs to log in again.
*/
router.get('/refresh',(ctx)=>{

  let code,msg,data=null
  //Get the long token carried in the request header
  let r_tk=ctx.request.headers['pass']
  //Parse token parameters token key callback function return information
  jwt.verify(r_tk,secret,(error)=>{
    if(error){
      code=4006,
      msg='The long token is invalid, please log in again'
    }
    else{
      code = 2000,
      msg = 'Long token is valid, return new token'
      data = {
        accessToken: accessToken(),
        refreshToken: refreshToken()
      }
    }
    ctx.body={
      code,
      msg:msg?msg:null,
      data
    }
  })
})



module.exports = router

3. Create new utils/auth.js (middleware)

const { secret } = require('./token')
const jwt = require('jsonwebtoken')

/*Whitelist, login and refresh short tokens are not restricted, so there is no need for token verification*/
const whiteList=['/login','/refresh']
const isWhiteList=(url,whiteList)=>{
    return whiteList.find(item => item === url) ? true : false
}

/*middleware
 Verify whether the short token is valid
*/
const auth = async (ctx,next)=>{
    let code, msg, data = null
    let url = ctx.path
    if(isWhiteList(url,whiteList)){
        //Execute next step
        return await next()
    } else {
        // Get the short token carried in the request header
        const a_tk=ctx.request.headers['authorization']
        if(!a_tk){
            code=4003
            msg='accessToken is invalid, no permission'
            ctx.body={
                code,
                msg,
                data
            }
        } else{
            // parse token
            await jwt.verify(a_tk,secret,async (error)=>{
                if(error){
                    code=4003
                    msg='accessToken is invalid, no permission'
                    ctx.body={
                        code,
                        msg,
                        data
                    }
                } else {
                    // token is valid
                    return await next()
                }
            })
        }
    }
}
module.exports=auth
  1. app.js
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')
const cors=require('koa-cors')

const index = require('./routes/index')
const users = require('./routes/users')
const auth=require('./utils/auth')

// error handler
onerror(app)

// middlewares
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))
app.use(cors())
app.use(auth)

app.use(views(__dirname + '/views', {
  extension: 'pug'
}))

// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date()-start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())

// error-handling
app.on('error', (err, ctx) => {
  console.error('server error', err, ctx)
});

module.exports = app

Front-end: vite, vue3, axios, etc. (The complete code can be found at the github address at the end of the article)

  1. Create new config/constants.js
export const ACCESS_TOKEN = 'a_tk' // short token field
export const REFRESH_TOKEN = 'r_tk' // short token field
export const AUTH = 'Authorization' //Header carries short token
export const PASS = 'pass' //Header carries long token
  1. Create new config/storage.js
import * as constants from "./constants"

//Storage short token
export const setAccessToken = (token) => localStorage.setItem(constants.ACCESS_TOKEN, token)
//Storage long token
export const setRefreshToken = (token) => localStorage.setItem(constants.REFRESH_TOKEN, token)
// Get short token
export const getAccessToken = () => localStorage.getItem(constants.ACCESS_TOKEN)
// Get long token
export const getRefreshToken = () => localStorage.getItem(constants.REFRESH_TOKEN)
// Delete short token
export const removeAccessToken = () => localStorage.removeItem(constants.ACCESS_TOKEN)
// Delete long token
export const removeRefreshToken = () => localStorage.removeItem(constants.REFRESH_TOKEN)

3. Create new utils/refresh.js

export {REFRESH_TOKEN,PASS} from '../config/constants.js'
import { getRefreshToken, removeRefreshToken, setAccessToken, setRefreshToken} from '../config/storage'
import server from "./server";

let subscribes=[]
let flag=false //Set the switch to ensure that the short token can only be requested once at a time to prevent customers from doing this and making multiple requests.

/*Add expired requests to the array*/
export const addRequest = (request) => {
    subscribes.push(request)
}

/*Call expired request*/
export const retryRequest = () => {
    console.log('Request the last interrupted data');
    subscribes.forEach(request => request())
    subscribes = []
}

/*The short token expires, carry the token to request the token again*/
export const refreshToken=()=>{
    console.log('flag--',flag)
    if(!flag){
        flag = true;
        let r_tk = getRefreshToken() // Get long token
        if(r_tk){
            server.get('/refresh',Object.assign({},{
                headers:{PASS : r_tk}
            })).then((res)=>{
                //The long token is invalid, log out
                if(res.code===4006){
                    flag = false
                    removeRefreshToken(REFRESH_TOKEN)
                } else if(res.code===2000){
                    // Store new token
                    setAccessToken(res.data.accessToken)
                    setRefreshToken(res.data.refreshToken)
                    flag = false
                    // Request data
                    retryRequest()
                }
            })
        }
    }
}

4. Create new utils/server.js

import axios from "axios";
import * as storage from "../config/storage"
import * as constants from '../config/constants'
import { addRequest, refreshToken } from "./refresh";

const server = axios.create({
    baseURL: 'http://localhost:3000', // your server
    timeout: 1000 * 10,
    headers: {
        "Content-type": "application/json"
    }
})

/*Request interceptor*/
server.interceptors.request.use(config => {
    // Get the short token, carry it to the request header, and verify it on the server side
    let aToken = storage.getAccessToken(constants.ACCESS_TOKEN)
    config.headers[constants.AUTH] = aToken
    return config
})

/*Response interceptor*/
server.interceptors.response.use(
    async response => {
        // Obtain configuration and backend response data
        let {config, data} = response
        console.log('Response prompt information:', data.msg);
        return new Promise((resolve, reject) => {
            // Short token is invalid
            if (data.code === 4003) {
                //Remove expired short token
                storage.removeAccessToken(constants.ACCESS_TOKEN)
                // Store the expired request to request a new short token and request again to achieve a senseless refresh.
                addRequest(() => resolve(server(config)))
                // Carry long token to request new token
                refreshToken()
            } else {
                // Effectively return the corresponding data
                resolve(data)
            }

        })

    },
    error => {
        return Promise.reject(error)
    }
)
export default server

5. Create a new apis/index.js

import server from "../utils/server.js";
/*Log in*/
export const login = () => {
    return server({
        url: '/login',
        method: 'get'
    })
}
/*Request data*/
export const getList = () => {
    return server({
        url: '/getTestData',
        method: 'get'
    })
}

6. Modify App.vue

<script setup>
  import {login,getList} from "./apis";
  import {setAccessToken,setRefreshToken} from "./config/storage";
  const getToken=()=>{
    login().then(res=>{
      setAccessToken(res.data.accessToken)
      setRefreshToken(res.data.refreshToken)
    })
  }
  const getData = ()=>{
    getList()
  }
</script>

<template>
    <button @click="getToken">Login</button>
    <button @click="getData">Request data</button>

</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
2.23 Rendering

3. Complete project code

3.1 address

GitHub – heyu3913/doubleToken: No sense refresh

3.2 run

rear end:

cd server
i
pnpm start

front end

cd my-vue-app
i
pnpmdev

4 PS: Here you will get a free gpt address. You can set it up by yourself without any charge. Register and use:

Industry wizard

There will be times when strong winds and waves break, and the clouds and sails will be hung directly to help the sea