4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Nuxt.jsでKeycloakと連携した際のアクセストークンの有効期限切れに対応

Last updated at Posted at 2020-09-05

背景

前回の記事でNuxt.jsでKeycloakと連携したログイン機能を実装してみて、一通り動作するところまでは確認できたけど、一部動作に問題があったので対応してみる。

問題の動作について

問題の動作は、ブラウザが保持しているトークンの有効期限が切れてもkeycloak側にアクセスしにいかないこと。

keycloakのデフォルトでは、セッションのタイムアウトは30分に設定されていて、トークンの有効期限は15分に設定されているっぽい。

ここで、Implicitフローの場合、アクセストークンの有効期限が切れて新しいトークンが必要になった場合、リフレッシュトークンで新しいアクセストークンを取得するのではなく、再度サーバー側にAuthorizationリクエストを送信する方法が普通らしいけど、nuxtのauth-moduleがトークンが切れても、サーバー側にリクエストを投げてくれないので、セッションタイムアウトしてもアクセスできてしまう。

下記のauth-moduleのmiddlewareの実装を見ると、下記のようになっている。
(nuxt.config.jsでmiddlewareにauthを指定することで呼び出されるコード)

middleware.ts
    ...
    // Perform scheme checks.
    const { tokenExpired, refreshTokenExpired, isRefreshable } = ctx.$auth.check(true)

    // Refresh token has expired. There is no way to refresh. Force reset.
    if (refreshTokenExpired) {
      ctx.$auth.reset()
    } else if (tokenExpired) {
      // Token has expired. Check if refresh token is available.
      if (isRefreshable) {
        // Refresh token is available. Attempt refresh.
        await ctx.$auth.refreshTokens()
      } else {
        // Refresh token is not available. Force reset.
        ctx.$auth.reset()
      }
    }
    ...

nuxt.config.jsでストラテジーのschemeに'oauth2'を指定すると、
最初のctx.$auth.check(true)で取得されるisRefreshableは(現時点のコードでは)常にtrueを返す実装になっていて、
アクセストークンの有効期限が切れていれば、tokenExpiredがtrueのときのif文の中には入るけど、
isRefreshableがfalseのときのelseに入らないので、トークンの有効期限が切れていても保持しているトークンがリセットされない模様。

リフレッシュトークンが利用されない場合の実装が漏れているのか、開発者の意図通りなのか分からないけど(そもそも、schemeに'oauth2'を指定するやり方自体、間違っている可能性もある)、なんとか対応したい。

対応

方針としては、できる限りauth-moduleの実装を利用しつつ、問題の部分にだけ対応したい。

ということで、auth-moduleの実装を見ながら、なるべくロジックが崩れないように有効期限切れチェック用のmiddlewareを追加してみた。

middleware/の下にtoken-check.tsというファイルを追加して下記のように実装する。

token-check.ts
export default function(store: any) {
  const tokenExpireAt =
    store.$auth.$storage._state["_token_expiration.keycloak"];
  if (isExpired(tokenExpireAt)) {
    store.$auth.reset();
  }
}

function isExpired(tokenExpireAt: number) {
  const now = Date.now();
  if (!tokenExpireAt) {
    return true;
  }
  const timeSlackMillis = 500;
  tokenExpireAt -= timeSlackMillis;
  if (now < tokenExpireAt) {
    return false;
  }
  return true;
}

トークンの有効期限は、storeを探っていたら、
store.$auth.$storage._state["_token_expiration.keycloak"]
に入っていたので、ここから取得(もっと良い取り出し方があるかも)。

有効期限切れの判定は、auth-moduleのロジックに合わせつつ、isExpiredというfunctionで実装。

期限が切れていたら、トークンをリセットするようにしてみた。

このmiddlewareがauth-moduleのデフォルトのmiddlewareの前に実行されるように、
下記のようにnuxt.config.jsに設定。

nuxt.config.js
export default {
  ...
  
  router: {
    middleware: ["token-check", "auth"]
  },
  
  ...

一応、これでトークンの有効期限が切れると、keycloakサーバー側にアクセスしてトークンが更新されるようになり、
keycloak側のセッションタイムアウト後にアクセスすると、ログイン画面が再表示されるようになった。

2020/09/07追記

期限切れチェックには**$auth.check(true)**をそのまま使っても大丈夫なようなので、token-check.tsはもっとシンプルに書ける模様。

token-check.ts
export default function(store: any) {
  const { tokenExpired } = store.$auth.check(true);
  if (tokenExpired) {
    store.$auth.reset();
  }
}
4
5
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
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?