score:2

Accepted answer

please check if i have correctly configured axios.interceptors.

i think it works. but i suggest that you should test it carefully.this is a good article to refer https://blog.liplex.de/axios-interceptor-to-refresh-jwt-token-after-expiration/

have i placed it in the right place, i.e. above theitems class. ?

you should create a service function to wrap axios and api configs,and interceptor of course

axios.interceptors.response is assigned to the interceptor variable. what should i do with this variable?

it is just a variable used to define the interceptor. don't care about it. if you want to avoid assigning it, just do it inside a function like this automating access token refreshing via interceptors in axios

i have to wait 24 hours to test whether it works, or is it possible in a different way, faster?

you can change the token saved in your localstorage, and do that

where should i put 'client_id', 'secret_id', 'grant_type'?

if you store it inside localstorage, it's accessible by any script inside your page (which is as bad as it sounds as an xss attack can let an external attacker get access to the token).

don't store it in local storage (or session storage). if any of the 3rd part scripts you include in your page gets compromised, it can access all your users' tokens.

the jwt needs to be stored inside an httponly cookie, a special kind of cookie that's only sent in http requests to the server, and it's never accessible (both for reading or writing) from javascript running in the browser.

score:1

please check if i have correctly configured axios.interceptors.

from what i can see the configuration seems ok, as it's the same of this answer https://stackoverflow.com/a/53294310/4229159

have i placed it in the right place, i.e. above theitems class. ?

that is something that i can't answer, every application is different, it's not the best place to put it, but might be ok for an example. in your app however it should be together with all the api calls (for example)

axios.interceptors.response is assigned to theinterceptor variable. what should i do with this variable?

as you can see, the variable that got answered from the call to /refresh_token for assigned to config.headers['authorization'] = 'bearer ' + response.data.access_token; if you backend reads from there the auth value you should be fine

i have to wait 24 hours to test whether it works, or is it possible in a different way, faster?

you should wait unless the backend can change that, and expire the token in less time (eg in 5 or 2 minutes)

where should i put 'client_id', 'secret_id', 'grant_type'?

seems like the backend should have that, unless they are public ones... you are probably the best to know whether that belongs to the config for the call or if you are authenticating with them. if you are authenticating with them and they are the ones that grant you a token, then you shouldn't put it in the client side, as it is a security risk

score:1

1) configuration looks fine to me. but your solution won't work when there are multiple parallel requests and all of them trying to refresh auth token at the same time. believe me this is a issue is really hard to pin point. so better be covered upfront.

2) no. not the right place. create a separate service (i call it api.service) and do all the network/api commutation using that.

3) there is no use of interceptor variable. you can avoid assigning it to a variable.

4) if have control over the api you can reduce the timeout for a bit. also i think 24 hours is bit too long. else no option i guess.

5) not sure you have to deal with them.

bellow is a working code of api.service.ts. you might have to change few things here and there to fit that in to your application. if you get the concept clearly it wont be hard. also it cover multiple parallel request problem as well.

import * as querystring from 'query-string';

import axios, { axiosrequestconfig, method } from 'axios';

import { accountservice } from '../account.service'; //i use account service to authentication related services
import { storageservice } from './storage.service'; //i use storage service to keep the auth token. inside it it uses local storage to save values

var instance = axios.create({
  baseurl: 'your api base url goes here',
});
axios.defaults.headers.common['content-type'] = 'application/json';

export const apiservice = {
  get,
  post,
  put,
  patch,
  delete: deleterecord,
  delete2: deleterecord2
}

function get<t>(controller: string, action: string = '', urlparams: string[] = [], queryparams: any = null) {
  return apirequest<t>('get', controller, action, null, urlparams, queryparams);
}

function post<t>(controller: string, action: string = '', data: any, urlparams: string[] = [], queryparams: any = null) {
  return apirequest<t>('post', controller, action, data, urlparams, queryparams);
}

function put<t>(controller: string, action: string = '', data: any, urlparams: string[] = [], queryparams: any = null) {
  return apirequest<t>('put', controller, action, data, urlparams, queryparams);
}

function patch<t>(controller: string, action: string = '', data: any, urlparams: string[] = [], queryparams: any = null) {
  return apirequest<t>('patch', controller, action, data, urlparams, queryparams);
}
function deleterecord(controller: string, action: string = '', urlparams: string[] = [], queryparams: any = null) {
  return apirequest<any>('delete', controller, action, null, urlparams, queryparams);
}

function deleterecord2<t>(controller: string, action: string = '', urlparams: string[] = [], queryparams: any = null) {
  return apirequest<t>('delete', controller, action, null, urlparams, queryparams);
}

function apirequest<t>(method: method, controller: string, action: string = '', data: any, urlparams: string[] = [], queryparams: any = null) {
  var url = createurl(controller, action, urlparams, queryparams);
  var options = createrequestoptions(url, method, data);

  return instance.request<t>(options)
    .then(res => res && res.data)
    .catch(error => {
      if (error.response) {
        //handle error appropriately: if you want to display a descriptive error notification this is the place
      } else {
        //handle error appropriately: if you want to display a a generic error message
      }
      throw error;
    });
}

function createurl(controller: string, action: string = '', urlparams: string[] = [], queryparams: any = null) {
  let url = controller + (action ? '/' + action : '');

  urlparams.foreach(param => {
    url += '/' + param;
  });

  let params = '';
  if (queryparams) {
    params += '?' + querystring.stringify(queryparams);
  }

  return url += params;
}

function createrequestoptions(url: string, method: method, data: any, responsetype?: any) {
  var authtoken = storageservice.getauthtoken();
  var jwttoken = authtoken != null ? authtoken.authtoken : '';

  var options: axiosrequestconfig = {
    url,
    method,
    data,
    headers: {
      'authorization': 'bearer ' + jwttoken
    },
  }

  if (responsetype) {
    options.responsetype = responsetype;
  }

  return options;
}

let isrefreshing = false;
let failedqueue: any[] = [];

const processqueue = (error: any, token: string = '') => {
  failedqueue.foreach(prom => {
    if (error) {
      prom.reject(error);
    } else {
      prom.resolve(token);
    }
  });

  failedqueue = [];
}

instance.interceptors.response.use(undefined, (error) => {
  const originalrequest = error.config;
  if (originalrequest && error.response && error.response.status === 401 && !originalrequest._retry) {
    if (isrefreshing) {
      return new promise(function (resolve, reject) {
        failedqueue.push({ resolve, reject })
      }).then(authtoken => {
        originalrequest.headers.authorization = 'bearer ' + authtoken;
        return axios(originalrequest);
      }).catch(err => {
        return err;
      })
    }

    originalrequest._retry = true;
    isrefreshing = true;

    return new promise(function (resolve, reject) {
      accountservice.refreshtoken()
        .then(result => {
          if (result.succeeded) {
            originalrequest.headers.authorization = 'bearer ' + result.authtoken;
            axios(originalrequest).then(resolve, reject);
            processqueue(null, result.authtoken);
          } else {
            reject(error);
          }
        }).catch((err) => {
          processqueue(err);
          reject(err);
        }).then(() => { isrefreshing = false });
    });
  }
  return promise.reject(error);
});

cheers,

score:3

this is what i did before. your configuration is a little different from mine.

const baseurl = localstorage.getitem('domain');
const defaultoptions = {
  baseurl,
  method: 'get',
  headers: {
    'content-type': 'application/json',
  }
};
// create instance
const axiosinstance = axios.create(defaultoptions);
// get token from session
const accesstoken = ...

// set the auth token for any request
instance.interceptors.request.use(config => {
  config.headers.authorization = accesstoken ? `bearer ${accesstoken}` : '';
  return config;
});

// last step: handle request error general case
instance.interceptors.response.use(
  response => response,
  error => {
    // error
    const { config, response: { status } } = error;

    if (status === 401) {
      // unauthorized request: maybe access token has expired!
      return refreshaccesstoken(config);
    } else {
      return promise.reject(error);
    }
  }
});

i think this part should be separated with components - it will be placed on helpers or utils. also, you have to wait for 24 hrs because refreshtoken() method is never called before 24 hrs. you don't need to process client_id, secret_id, grant_type right here.


Related Query

More Query from same tag