Sử dụng Axios interceptors làm mới token với refresh token trong jwt

Nội dung bài viết

Axios interceptor la gi? Tại sao lại được quan tâm nhiều đến vậy, hiệu quả của việc sử dụng interceptors trong Axios như thế nào? Và cơ chế lấy lại token khi token hết hạn khi sử dụng interceptors axios kết hợp với refreshToken. 


Wow sau một bài viết về thực hành "JSON Web Token (JWT) - Thực hành sử dụng refresh token khi token hết hạn với nodejs và express js" thì có rất nhiều bạn gửi tin nhắn về vấn đề này. Điều đó cho thấy hiện tại JSON WEB TOKEN (jwt) đã được sử dụng rất rộng rãi trong việc phát triển API. Nhưng bên cạnh đó, thì nhiều bạn đã hiểu cơ chế lấy lại token khi nó hết hạn kèm theo đó là việc sử dụng refreshToken


Nhưng đó là hiểu quy trình, vậy còn việc apply vào thực thế thế nào. Khi mà trường hợp khách hàng đột nhiện token hết hạn?? Vậy bài viết này sẽ giúp bạn làm được điều đó khi sử dụng Axios interceptor.


Axios interceptor là gì?


Interceptor có thể hiểu như một bước tường lưới chặn các request, response của ứng dụng để cho phép kiểm tra, thêm vào header hoặc thay đổi các param của request, response. Nó cho phép chúng ta kiểm tra các token ứng dụng, Content-Type hoặc tự thêm các header vào request. Điều này cho phép chúng ta tận dụng tối đa thao tác chỉnh sửa header, body, param request gửi lên server sao cho hợp lý nhất, bảo mật nhất.


Sử dụng axios interceptors.response.use làm mới token


Và bây giờ là thực hành đây. Tôi sẽ lấy code ở bài trước mà chúng ta đã tạo cho backend một API cho phép user login tạo token và refreshToken các bạn có thể lấy CODE tại Github. Ở đây sẽ sử dụng axios để đạt được điều đó, phương pháp đầu tiên là chặn sau yêu cầu mỗi lần request, vì vậy axios.interceptors.response.use() phương pháp này sẽ được sử dụng tại bài này.


Tips: Tổng hợp bài viết về JSON WEB TOKEN (jwt)


Setup cơ bản axios với interceptors


Ở bài trước chúng ta chỉ code ở server thôi, giờ chúng ta sẽ làm việc tại Client. 

### create file script.js

const btn_get_data_with_auto = document.getElementById('get-data-with-auto');
const btn_get_data_without_auto = document.getElementById('get-data-without-auto');
const btn_get_token = document.getElementById('get-token');
const btn_results = document.getElementById('results');

//get token o localStorage
function getLocalToken() {
    const token = window.localStorage.getItem('token')
    console.log('token >>>', token);
    return token
}

//get token o refreshToken
function getLocalRefreshToken() {
    const token = window.localStorage.getItem('refreshToken')
    return token
}


//cau hinh axios
const instance = axios.create({
    baseURL: 'http://localhost:3000/',
    timeout: 300000,
    headers: {
        'Content-Type': 'application/json',
    }
})

instance.setToken = (token) => {
    instance.defaults.headers['x-access-token'] = token
    window.localStorage.setItem('token', token)
}

function getToken() {
    return instance.post('/login', {
        username: 'anonystick.com',
        password: 'anonystick.com',
    })
}

function refreshToken () {
    return instance.post('/token',{
        refreshToken: getLocalRefreshToken()
    })
}


function getDataWithAuto() {
    return instance.get('/users',{
        params: {
            auto: 'yes',
        },
        headers: {
            'x-access-token': getLocalToken() // headers token
        }

    })
}

function getDataWithOutAuto() {
    return instance.get('/users',{
        params: {
            auto: 'no'
        },
        headers: {
            'x-access-token': getLocalToken() // headers token
        }
    })
}

// getToken();

// response parse
instance.interceptors.response.use((response) => {
    
    const {code, auto} = response.data
    if (code === 401) {
        if(auto === 'yes'){

            console.log('get new token using refresh token', getLocalRefreshToken())
            return refreshToken().then(rs => {
                console.log('get token refreshToken>>', rs.data)
                const { token } = rs.data
                instance.setToken(token);
                const config = response.config
                config.headers['x-access-token'] = token
                config.baseURL = 'http://localhost:3000/'
                return instance(config)

            })
        }
    }
    return response
}, error => {
    console.warn('Error status', error.response.status)
    // return Promise.reject(error)
    if (error.response) {
        return parseError(error.response.data)
    } else {
        return Promise.reject(error)
    }
})


//click login de lay token va refreshtoken

btn_get_token.addEventListener('click', () => {
    console.log('click get data');
    getToken().then(res => {
        const { status, token, refreshToken } = res.data
        if (status === 'Logged in') {
            window.localStorage.setItem('token', token)
            window.localStorage.setItem('refreshToken', refreshToken)
            return btn_results.textContent = `Token is ${token} \n and refreshToken is ${refreshToken}`
            // console.log(res.data);
        }
    })

    
})

//click tu dong lay du lieu khi token het han
btn_get_data_with_auto.addEventListener('click', () => {
    console.log('click get data');
    getDataWithAuto().then(res => {
        const {code, message, elements} = res.data
        return btn_results.textContent = JSON.stringify(elements)
    })
})

//click lay du lieu nhung token het han thi thong bao
btn_get_data_without_auto.addEventListener('click', () => {
    
    getDataWithOutAuto().then(res => {
        console.log('click get data btn_get_data_without_auto>>>', res.data);
        const {code, message, elements} = res.data
        if(code === 403){
            return btn_results.textContent = message
        }
        if(code === 401){
            return btn_results.textContent = message
        }

        return btn_results.textContent = JSON.stringify(elements)
    })
})

// getToken();
export default instance


console.log('hello');

### create file html

  
    <!DOCTYPE html>
<html>
  <head>
    <title>Get list Users</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1>Get list Users</h1>
    <p>Welcome to Admin</p>
    <button id="get-token">Get Token vs refreshToken</button>
    <button id="get-data-without-auto">Get Data chưa tự động khi token hết hạn</button>
    <button id="get-data-with-auto">Get Data tự động khi token hết hạn lấy lại token mới sử dụng refreshToken</button>
    <h3>Status: </h3><p id="results"></p>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script type="module" src="/javascripts/script.js"></script>
  </body>
</html>

Đây là toàn bộ mã nguồn của chương trình chúng ta và chúng ta sẽ đi lần lượt việc giải thích sau:


axios.interceptors.response.use thực hiện đánh chặn


Giao diện back-end thường sẽ có cấu trúc dữ liệu trả về kèm theo các code, chẳng hạn như:

{status: 'success', code: 401, auto: req.query.auto, "message": 'Unauthorized access.' }

Giải thích một số thông số của các backend hiện nay. status: Thể hiện request này success hay error message: Nếu success hay error thì trả về kết quả đúng và sai cái gì? code: 401, hay 403 thể hiện quyền truy cập. Như tôi ở đây, thỏa thuận với back-end khi code === 401auto = 'yes' như vậy tôi hiểu là token đã hết hạn cần phải tự động làm mới token.

// response parse
instance.interceptors.response.use((response) => {
    
    const {code, auto} = response.data
    if (code === 401) {
        if(auto === 'yes'){

            console.log('get new token using refresh token', getLocalRefreshToken())
            return refreshToken().then(rs => {
                console.log('get token refreshToken>>', rs.data)
                const { token } = rs.data
                instance.setToken(token);
                const config = response.config
                config.headers['x-access-token'] = token
                config.baseURL = 'http://localhost:3000/'
                return instance(config)

            })
        }
    }
    return response
}, error => {
    console.warn('Error status', error.response.status)
    // return Promise.reject(error)
    if (error.response) {
        return parseError(error.response.data)
    } else {
        return Promise.reject(error)
    }
})

Quy trình là nó thế này, ở demo này tôi có 3 button đó là get token, get data khi token hết hạn và get data tự động khi token hết hạn. Như hình ảnh đây. 

Axios interceptors la gi

  • Khi tôi click get token thì nó có nhiệm vụ là login nếu thành công thì sẽ có được token và refreshToken được lưu trữ ở localStorage (Không khuyến khích lưu token ở localStorage, lý do đọc tại đây "Token nên lưu ở đâu?"
  • Button get data khi token hết hạn là một ví dụ khi hết hạn token thì bạn không thể nhận được data. 
  • Button get data khi token hết hạn và get data tự động là sẽ tự động set một token mới và lấy dữ liệu.


Như vậy là đã quá rõ rồi, bạn nào chưa hiểu vui lòng nhắn tin đến page để được giải đáp sâu hơn nhé!

Có thể bạn đã bị missing