背景
前回の記事でNuxt.jsでKeycloakと連携したログイン機能を実装してみて、一通り動作するところまでは確認できたけど、一部動作に問題があったので対応してみる。
問題の動作について
問題の動作は、ブラウザが保持しているトークンの有効期限が切れてもkeycloak側にアクセスしにいかないこと。
keycloakのデフォルトでは、セッションのタイムアウトは30分に設定されていて、トークンの有効期限は15分に設定されているっぽい。
ここで、Implicitフローの場合、アクセストークンの有効期限が切れて新しいトークンが必要になった場合、リフレッシュトークンで新しいアクセストークンを取得するのではなく、再度サーバー側にAuthorizationリクエストを送信する方法が普通らしいけど、nuxtのauth-moduleがトークンが切れても、サーバー側にリクエストを投げてくれないので、セッションタイムアウトしてもアクセスできてしまう。
下記のauth-moduleのmiddlewareの実装を見ると、下記のようになっている。
(nuxt.config.jsでmiddlewareにauthを指定することで呼び出されるコード)
...
// 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というファイルを追加して下記のように実装する。
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に設定。
export default {
...
router: {
middleware: ["token-check", "auth"]
},
...
一応、これでトークンの有効期限が切れると、keycloakサーバー側にアクセスしてトークンが更新されるようになり、
keycloak側のセッションタイムアウト後にアクセスすると、ログイン画面が再表示されるようになった。
2020/09/07追記
期限切れチェックには**$auth.check(true)**をそのまま使っても大丈夫なようなので、token-check.tsはもっとシンプルに書ける模様。
export default function(store: any) {
const { tokenExpired } = store.$auth.check(true);
if (tokenExpired) {
store.$auth.reset();
}
}