LoginSignup
18
18

More than 3 years have passed since last update.

メモ: Axiosでトークン認証を絡めたリトライがやりたい

Last updated at Posted at 2019-06-14

is何

Axiosでトークン認証つきのリクエストを送って、認証エラーで弾かれたらトークンを新規取得 or 生成した上でリトライしたい
という処理をどのように実装したかのメモ。

どうするの

リトライ処理

リトライ処理自体はそんなに難しくない。
というのも、エラーレスポンスの中にリクエスト設定configが丸々入っているので、それを使って再リクエストしてやればいい。

axios
  .get("https://hogehoge/member_contents/", {
    headers: {
      Authorization: "Bearer: xxxxxxxxxx"
    }
  })
  .catch(async err => await retryHandler(err));

const retryHandler = async err => {
  // 再リクエストを発火
  return await axios(err.config);
};

トークン再取得の処理を挟む

そこで、catch内でaxiosを実行してやる前に、トークンを取得する処理を行う。

axios
  .get("https://hogehoge/member_contents/", {
    headers: {
      Authorization: "Bearer: xxxxxxxxxx"
    }
  })
  .catch(async err => await retryHandler(err));

const retryHandler = async err => {
  // トークンを再取得する
  const res = await axios.get("https://hogehoge/get_token");
  // config内のトークンを書き換える
  err.config.headers.Authorization = `Bearer: ${res.data.token}`;
  // 再リクエストを発火
  return await axios(err.config);
};

おまけ:複数同時リクエストに対応する

トークンを必要とするリクエストが走るのは最大で同時に1つだけ、というのなら上の実装でいいが、
例えばこのようにトークン認証を使ったリクエストが複数走るとなるとちょっとまたコードを変更する必要がある。

let token = "xxxxxxxxxxxx";

axios.get("http://hogehoge/member_contents/1", {
  headers: {
    Authorization: `Bearer: ${token}`
  }
}).catch(async err => await retryHandler(err));

axios.get("http://hogehoge/member_contents/2", {
  headers: {
    Authorization: `Bearer: ${token}`
  }
}).catch(async err => await retryHandler(err));

というのも、この処理だと同時に2つのリクエストに対して再認証を要求するため、
retryHandlerが2回走る = トークンを2回発行するというよくない感じの処理になっている。
というわけで、

  • 2回目のretryHandlerでは1回目の処理が終了するまで待つ
  • トークンは常に最新のものを使う

といった処理が必要になる。それを踏まえて書き直した処理がこちら。

let token = "xxxxxxxxxxxx";
let isFetchingToken = false;

// 遅延処理用のウェイト処理
const wait = (ms) => new Promise(resolve => setTimeout(ms, resolve));

axios.get("http://hogehoge/member_contents/1", {
  headers: {
    Authorization: `Bearer: ${token}`
  }
}).catch(async err => await retryHandler(err));

axios.get("http://hogehoge/member_contents/2", {
  headers: {
    Authorization: `Bearer: ${token}`
  }
}).catch(async err => await retryHandler(err));

const retryHandler = async err => {
  // トークン取得中なら遅延して再び同じ処理を繰り返す
  if (isFetchingToken) {
    await wait(500);
    return await retryHandler(err);
  }

  // 失敗したリクエストのトークンと最新トークンが同一なら再取得
  if (err.config.headers.Authorization === token) {
    isFetchingToken = true;
    // トークンを再取得して最新トークンを更新
    const res = await axios.get("https://hogehoge/get_token");
    token = res.data.token;
    isFetchingToken = false;
  }
  // config内のトークンを最新トークンに書き換える
  err.config.headers.Authorization = `Bearer: ${token}`;
  // 再リクエストを発火
  return await axios(err.config);
};
  • 失敗したリクエストに用いたトークンと、最後に取得したトークンが同一なら再取得し、違うならトークンは並列実行中の他のリトライ処理で更新したとみなしてそれを使う。
  • すでにトークン取得中のフラグが立っていれば遅延ループさせてトークン取得終了を待つ。

といったちょっとしたややこしい処理を挟むことで余計なトークン再発行を防ぐことができる。

18
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
18