Commit f773314c authored by Alex Kakiris's avatar Alex Kakiris
Browse files

Merge branch 'feat/axios-interceptor' into 'dev'

Make token refresh work correctly

See merge request !3
parents 819f7565 b887497a
Loading
Loading
Loading
Loading
+33 −45
Changes for capif_frontend/src/configs/axiosInstance.js: 33 added lines, 45 removed lines.
Original line number Diff line number Diff line
import axios from 'axios'
import Router from 'next/router'
import { getAccessToken, getRefreshToken, setAccessToken } from './cookies'
import authConfig from 'src/configs/auth'
import { useRouter } from 'next/router'

const DEBUG_SHOW_DATA = true

@@ -10,39 +10,32 @@ const axiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_BASE_URL // http://10.95.82.71:8085 my BOE machine
})

//console.log('NEXT_PUBLIC_API_BASE_URL: ', process.env.NEXT_PUBLIC_API_BASE_URL)
const isRefreshRequest = url => url === authConfig.refreshEndpoint

// REQUEST interceptor for API calls -> if user is logged in, add token to headers of request
// A 401 from these endpoints means bad credentials or no session, not an expired
// access token, so it must not trigger a refresh attempt.
const NO_REFRESH_ENDPOINTS = [authConfig.loginEndpoint, authConfig.refreshEndpoint, authConfig.meEndpoint]
// Never log bodies of these: login sends the password, and both return tokens.
const SENSITIVE_ENDPOINTS = [authConfig.loginEndpoint, authConfig.refreshEndpoint]
const canLogBody = url => DEBUG_SHOW_DATA && !SENSITIVE_ENDPOINTS.includes(url)

// REQUEST interceptor: attach the refresh token to refresh calls, the access token to everything else
axiosInstance.interceptors.request.use(
  req => {
    
    // show log in development mode
    if (process.env.NODE_ENV === 'development') {
      if (req.data && DEBUG_SHOW_DATA) {
      if (req.data && canLogBody(req.url)) {
        console.log(`🔵 ${req.method.toUpperCase()} ${req.url}`, req.data)
      } else {
        console.log(`🔵 ${req.method.toUpperCase()} ${req.url}`)
      }
    }


    const accessToken = getAccessToken()
    
    //const accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwMDc2MDk3MiwianRpIjoiMWZjZTdiZGMtMGU3My00OTgwLTlmNzktMDg5MmZmYjY0NmQwIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InN1cGVyYWRtaW4iLCJuYmYiOjE3MDA3NjA5NzJ9.A2ogmeWRY5lHPAK0fT-dnv_ru21bTKifLMqmRGX2Dzo"

    const refreshToken = getRefreshToken()
    //console.log('Interceptor request accessToken', accessToken)

    // if token exist and request is not refresh, add token to headers
    if (accessToken && req.url !== '/api/refresh') {
      req.headers['Authorization'] = `Bearer ${accessToken}`
    }
    if (req.url === '/api/refresh' && refreshToken) {
      const refreshToken = getRefreshToken()
      req.headers['Authorization'] = `Bearer ${refreshToken}`
    const token = isRefreshRequest(req.url) ? getRefreshToken() : getAccessToken()
    if (token) {
      req.headers['Authorization'] = `Bearer ${token}`
    }

    return req  // return request
    return req
  },
  error => {
    console.error('🔴 REQUEST ERROR: ', error.message)
@@ -51,13 +44,12 @@ axiosInstance.interceptors.request.use(
  }
)

// RESPONSE interceptor for API calls
// RESPONSE interceptor: on an expired access token, refresh once and retry; only if that
// is impossible or fails, send the user to login. All other errors go to the caller.
axiosInstance.interceptors.response.use(
  res => {

    // if env is development, show log
    if (process.env.NODE_ENV === 'development') {
      if (DEBUG_SHOW_DATA) {
      if (canLogBody(res.config.url)) {
        console.log(`🟢 ${res.status} ${res.config.url}`, res.data)
      } else {
        console.log(`🟢 ${res.status} ${res.config.url}`)
@@ -66,38 +58,34 @@ axiosInstance.interceptors.response.use(

    return res
  },

  // try to refresh token if 401 error
  async error => {
    const router = useRouter()
    console.error('🔴 RESPONSE ERROR: ', error.message)
    console.log('Entered 401 handler')

    console.log('Redirecting to login...')
    router.push('/login')
    const originalRequest = error.config
    const status = error.response?.status

    if (
      error.response &&
      error.response.status === 401 &&
      error.response.config.url !== '/api/auth/me' &&
      error.response.config.url !== '/api/refresh'
      status === 401 &&
      originalRequest &&
      !NO_REFRESH_ENDPOINTS.includes(originalRequest.url) &&
      !originalRequest._retry
    ) {
      // try to refresh token
      console.log('Attempting token refresh')
      const refreshToken = getRefreshToken()
      if (refreshToken) {
      originalRequest._retry = true

      if (getRefreshToken()) {
        try {
          // Post request to refresh token
          const response = await axiosInstance.post(authConfig.refreshEndpoint)
          setAccessToken(response.data.access_token)
          //console.log('2 accessToken', response.data.access_token)

          // Retry original request
          return axiosInstance(error.config)
          // Retry the original request; the request interceptor attaches the new access token
          return axiosInstance(originalRequest)
        } catch (refreshError) {
          console.error('🔴 Error refreshing token: ', refreshError)
          console.error('🔴 Error refreshing token: ', refreshError.message)
        }
      }

      // Refresh was not possible or failed: the session is over
      Router.push('/login')
    }

    return Promise.reject(error)
+0 −2
Changes for capif_frontend/src/context/AuthContext.js: 0 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -75,8 +75,6 @@ const AuthProvider = ({ children }) => {
        } catch (error) { // ON ERROR it retries using refresh token
          try {
            console.log('Entered error catch block')
            console.log('Redirecting to login...')
            router.push('/login')
            const response = await axiosInstance.post(authConfig.refreshEndpoint)
            setAccessToken(response.data.access_token)
            console.log('AuthContext access token: ', response.data.access_token)