JWT(JSON Web Token)有効期限の自動延長


509

新しいREST APIにJWTベースの認証を実装したいと思います。しかし、有効期限はトークンに設定されているので、自動的に延長することは可能ですか?ユーザーがその期間にアプリケーションをアクティブに使用している場合、X分ごとにサインインする必要がないようにしたい。それは大きなUXの失敗です。

ただし、有効期限を延長すると新しいトークンが作成されます(古いトークンは有効期限が切れるまで有効です)。そして、各リクエストの後に新しいトークンを生成することは、私にはばかげているように思えます。複数のトークンが同時に有効な場合、セキュリティの問題のように聞こえます。もちろん、ブラックリストを使用して古いものを無効にすることもできますが、トークンを保存する必要があります。また、JWTの利点の1つはストレージがないことです。

Auth0がそれを解決する方法を見つけました。JWTトークンだけでなく更新トークンも使用します:https : //docs.auth0.com/refresh-token

しかし、これも(Auth0なしで)これを実装するには、リフレッシュトークンを保存し、有効期限を維持する必要があります。では、本当のメリットは何ですか?(JWTではなく)トークンを1つだけ使用して、サーバーで有効期限を維持しないのはなぜですか?

他のオプションはありますか?JWTの使用はこのシナリオに適していませんか?


1
実際には、一度に多くの有効なトークンを使用しても、セキュリティの問題はおそらくないでしょう...実際には無制限の数の有効なトークンがあります...では、なぜ更新トークンがあるのでしょうか?リクエストごとに再生成しますが、実際には問題にはなりません。
maryo 2014年

1
SPAについては、私のブログ投稿をチェックアウトしてください:blog.wong2.me/2017/02/20/refresh-auth0-token-in-spa
wong2

2
@maryoいつでも(潜在的に)数百または数千の未使用の有効なJWTが存在すると、攻撃の足跡が増え、セキュリティリスクになると思います。私の考えでは、JWTはある方法で城へのキーを持つアクセストークンなので、慎重に発行する必要があります。
java-addict301

回答:


590

私はAuth0で働いており、リフレッシュトークン機能の設計に携わっていました。

それはすべて、アプリケーションのタイプによって異なります。推奨されるアプローチを次に示します。

Webアプリケーション

有効なパターンは、トークンが期限切れになる前に更新することです。

トークンの有効期限を1週間に設定し、ユーザーがWebアプリケーションを開くたび、および1時間ごとにトークンを更新します。ユーザーが1週間以上アプリケーションを開かない場合、ユーザーは再度ログインする必要があり、これは許容できるWebアプリケーションUXです。

トークンを更新するには、APIに有効な有効期限のないJWTを受信し、新しい有効期限フィールドで同じ署名付きJWTを返す新しいエンドポイントが必要です。次に、Webアプリケーションはトークンをどこかに保存します。

モバイル/ネイティブアプリケーション

ほとんどのネイティブアプリケーションは、一度だけログインします。

この考え方は、更新トークンが期限切れになることはなく、常に有効なJWTと交換できるということです。

トークンが期限切れにならないという問題は、決して期限がないことを意味します。スマートフォンを紛失した場合はどうしますか?したがって、それは何らかの方法でユーザーが識別できる必要があり、アプリケーションはアクセスを取り消す方法を提供する必要があります。「maryo's iPad」などのデバイス名を使用することにしました。次に、ユーザーはアプリケーションに移動して、「maryo's iPad」へのアクセスを取り消すことができます。

別のアプローチは、特定のイベントで更新トークンを取り消すことです。興味深いイベントは、パスワードの変更です。

JWTはこれらの使用例には役に立たないと考えているため、ランダムに生成された文字列を使用して、それを自分の側に格納します。


42
Webアプリケーションが推奨するアプローチの場合、トークンが1週間有効である場合、誰かがトークンを傍受し、そのトークンを長期間使用できるかどうかは関係ありませんか?免責事項:私は何を話しているのかよくわかりません。
user12121234 2015年

30
@wbeangeはいcookieを使用した場合でも、傍受が問題になります。httpsを使用する必要があります。
ホセ・F. Romaniello

15
@JoséF.RomanielloWebアプリケーションの例では、トークンを保存する必要があることを除いて、すべてが私にとって意味があります。JWTの美しさはステートレス認証だと思いました。つまり、署名されているため、Webアプリケーションはトークンを保存する必要がありません。サーバーはトークンの有効性を確認し、それが有効期限内であることを確認してから、更新されたJWTトークンを発行できると思います。これについて詳しく説明してもらえますか?多分私はまだJWTを十分に理解していません。
Lo-Tan

7
2つの質問/懸念事項:1- Webアプリケーションのケース:期限切れのトークンを更新できないのはなぜですか?短い有効期限(1時間)を設定し、トークンの有効期限が切れたときにバックエンドサーバーに更新呼び出しを行うとします。2-ハッシュ化された(無作為なソルトを使用した)パスワードをトークンに格納することにセキュリティ上の懸念はありますか?そこにある場合、更新が要求されたときにバックエンドサーバーがDBに保存されているパスワードをチェックし、パスワードが一致しない場合は要求を拒否することができます。これはモバイル/ネイティブアプリのパスワード変更をカバーし、ソリューションをモバイルのユースケースに拡張できます。
psamaan 2015年

8
-1検証期間を延長するためにトークンを盲目的に再署名するパブリックAPIを公開することは悪いことです。これで、すべてのトークンに有効な無限の有効期限があります。トークンに署名する行為には、署名時にそのトークンで行われたすべての要求に対する適切な認証チェックが含まれている必要があります。
Phil

69

authを自分で処理する場合(つまり、Auth0のようなプロバイダーを使用しない場合)、以下が機能する可能性があります。

  1. 有効期限が比較的短い(15分など)とJWTトークンを発行します。
  2. アプリケーションは、トークンを必要とするトランザクションの前にトークンの有効期限をチェックします(トークンには有効期限が含まれます)。トークンの有効期限が切れている場合は、最初にAPIにトークンの「更新」を要求します(これはUXに対して透過的に行われます)。
  3. APIはトークンの更新リクエストを取得しますが、まずユーザーデータベースをチェックして、そのユーザープロファイルに対して 'reauth'フラグが設定されているかどうかを確認します(トークンにはユーザーIDを含めることができます)。フラグが存在する場合、トークンの更新は拒否されます。それ以外の場合、新しいトークンが発行されます。
  4. 繰り返す。

データベースバックエンドの「reauth」フラグは、たとえば、ユーザーがパスワードをリセットしたときに設定されます。このフラグは、ユーザーが次回ログインしたときに削除されます。

さらに、ユーザーが少なくとも72時間ごとに1回ログインする必要があるというポリシーがあるとします。その場合、APIトークンの更新ロジックは、ユーザーデータベースからのユーザーの最終ログイン日付もチェックし、それに基づいてトークンの更新を拒否または許可します。


7
これは安全だとは思いません。私が攻撃者であり、トークンを盗んでサーバーに送信した場合、サーバーはフラグをチェックしてtrueに設定されていることを確認します。これは、更新をブロックするのに最適です。問題は、被害者がパスワードを変更した場合、フラグがfalseに設定され、攻撃者がその元のトークンを使用して更新できるようになることです。
user2924127

6
@ user2924127完璧な認証ソリューションはなく、常にトレードオフがあります。攻撃者が「トークンを盗む」立場にある場合、心配するべきより大きな問題がある可能性があります。トークンの最大有効期間を設定することは、上記の調整に役立つでしょう。
IanB 2015年

27
データベースに別のフィールド、reauthフラグを設定する代わりに、ハッシュにハッシュ(bcrypt_password_hash)をトークンに含めることができます。次に、トークンを更新するときに、hash(bcrypt_password_hash)がトークンの値と等しいかどうかを確認します。トークンの更新を拒否するには、パスワードハッシュを更新する必要があります。
bas 2015年

4
@basは、最適化とパフォーマンスについて考えると、パスワードハッシュの検証は冗長であり、サーバーへの影響が大きいと思います。トークンのサイズを大きくして、署名会社/検証に時間がかかるようにします。パスワード用のサーバーの追加のハッシュ計算。追加のフィールドアプローチでは、単純なブール値を使用して再計算を検証するだけです。Dbの更新は、追加のフィールドではそれほど頻繁ではありませんが、より頻繁にトークンが更新されます。また、既存のセッション(モバイル、Webなど)に対して個別に再ログインを強制するオプションサービスを利用できます。
le0diaz

6
user2924127による最初のコメントは実際には間違っていると思います。パスワードが変更されると、アカウントは再認証を要求するものとしてマークされるため、既存の期限切れのトークンは無効になります。
ラルフ

15

アプリケーションをバックエンドでRESTful APIを使用してHTML5に移行するときに、いじくり回っていました。私が思いついた解決策は:

  1. ログインが成功すると、クライアントにはセッション時間30分(または通常のサーバー側セッション時間)のトークンが発行されます。
  2. 有効期限が切れる前にトークンを更新するサービスを呼び出すクライアント側タイマーが作成されます。新しいトークンは、将来の呼び出しで既存のものを置き換えます。

ご覧のとおり、これにより、頻繁な更新トークン要求が減少します。トークンの更新呼び出しがトリガーされる前にユーザーがブラウザー/アプリを閉じると、前のトークンは期限切れになり、ユーザーは再ログインする必要があります。

より複雑な戦略を実装して、ユーザーの非アクティブ(たとえば、開いているブラウザータブを無視)に対応できます。その場合、更新トークンの呼び出しには、定義されたセッション時間を超えてはならない、予想される有効期限が含まれている必要があります。アプリケーションは、最後のユーザーインタラクションを適宜追跡する必要があります。

有効期限を長く設定するのは好きではないので、このアプローチはそれほど頻繁に認証を必要としないネイティブアプリケーションではうまく機能しない可能性があります。


1
コンピュータがサスペンド/スリープ状態の場合はどうなりますか。タイマーは有効期限が切れるまでカウントされますが、トークンは実際にはすでに有効期限が切れています。タイマーはこの状況では機能しません
Alex Parij

:@AlexParijあなたは、一定の時間に対して、このような何かを比較でしょう stackoverflow.com/a/35182296/1038456
Aparajita

2
クライアントが優先有効期限のある新しいトークンをリクエストできるようにすると、セキュリティリスクのように感じられます。
java-addict301

14

バックエンドに安全なストレージを追加せずにJWTを無効にする別のソリューションはjwt_version、usersテーブルに新しい整数列を実装することです。ユーザーがログアウトするか、既存のトークンを期限切れにしたい場合は、単にjwt_versionフィールドを増分します。

新しいJWTを生成するときjwt_version、JWTペイロードにをエンコードします。新しいJWTが他のすべてを置き換える必要がある場合は、オプションで事前に値を増分します。

JWTを検証するとき、jwt_versionフィールドはと比較され、user_id一致する場合にのみ承認が付与されます。


1
これには複数のデバイスで問題があります。基本的に、1つのデバイスからログアウトすると、どこからでもログアウトします。正しい?
Sam Washburn

4
ねえ、それはあなたの要件によっては「問題」ではないかもしれませんが、あなたは正しいです。これは、デバイスごとのセッション管理をサポートしていません。
Ollieベネット

これは、認証方式が「セッションのように」なり、JWTの基本的な目的を無効にするように、jwt_versionをサーバー側に格納する必要があることを意味しませんか?
ChetPrickles

8

よい質問です。質問自体には豊富な情報があります。

記事「トークンを更新する:トークンを使用するタイミングとトークンJWTとどのように相互作用するか」は、このシナリオに適しています。いくつかのポイントは:-

  • 更新トークンには、新しいアクセストークンを取得するために必要な情報が含まれています。
  • 更新トークンも期限切れになる可能性がありますが、かなり長持ちします。
  • 更新トークンは通常、厳格なストレージ要件の影響を受け、リークが発生しないようにします。
  • また、承認サーバーによってブラックリストに登録される場合もあります。

また、見てください auth0 /アンギュラJWT angularjsを

Web APIの場合。ASP .NET Web API 2を使用してAngularJSアプリでOAuth更新トークンを有効にする、およびOwinをお読みください


多分私はそれを間違って読んだかもしれません...しかし、「Refresh Tokens ...」で始まるタイトルの記事には、ここで言及したものを除いて、更新トークンに関する情報は何も含まれていません。
Ievgen Martynov

8

実際にGuzzleクライアントを使用してこれをPHPに実装し、APIのクライアントライブラリを作成しましたが、この概念は他のプラットフォームでも機能するはずです。

基本的に、短い(5分)トークンと、1週間後に期限切れになる長いトークンの2つのトークンを発行します。クライアントライブラリは、ミドルウェアを使用して、リクエストに対する401応答を受信した場合に短いトークンの1回の更新を試みます。次に、元の要求を再試行し、更新できた場合は、ユーザーに対して透過的に正しい応答を取得します。失敗した場合は、401をユーザーに送信します。

短いトークンの有効期限が切れても、長いトークンが有効で本物である場合、長いトークンが認証するサービスの特別なエンドポイントを使用して短いトークンを更新します(これは、トークンが使用できる唯一の方法です)。次に、短いトークンを使用して新しい長いトークンを取得し、短いトークンを更新するたびに、トークンをさらに1週間延長します。

このアプローチでは、最大5分以内にアクセスを取り消すこともできます。これは、トークンのブラックリストを保存する必要がなく、使用に適しています。

後期の編集:頭の中で新鮮になってから数か月後にもう一度読みますが、短いトークンを更新するときにアクセスを取り消すことができることを指摘しておきます。禁止されています)サービスへの呼び出しごとに支払う必要はありません。


8

JWTアクセストークンを取り消す手順は次のとおりです。

1)ログインするとき、クライアントに応答して2つのトークン(アクセストークン、更新トークン)を送信します。
2)アクセストークンの有効期限は短くなり、更新の有効期限は長くなります。
3)クライアント(フロントエンド)は更新トークンをローカルストレージに保存し、アクセストークンをCookieに保存します。
4)クライアントはAPIを呼び出すためにアクセストークンを使用します。ただし、有効期限が切れたら、ローカルストレージから更新トークンを選択し、認証サーバーAPIを呼び出して新しいトークンを取得します。
5)認証サーバーには、更新トークンを受け入れてその有効性をチェックし、新しいアクセストークンを返すAPIが公開されます。
6)更新トークンの有効期限が切れると、ユーザーはログアウトされます。

詳細が必要な場合はお知らせください。コード(Java + Springブート)も共有できます。


GitHubにプロジェクトリンクがある場合は、それを共有していただけませんか?
アルンクマールN


6

jwt-autorefresh

ノード(React / Redux / Universal JS)を使用している場合は、をインストールできますnpm i -S jwt-autorefresh

このライブラリは、(トークンにエンコードされたexpクレームに基づいて)アクセストークンが期限切れになる前にユーザーが計算した秒数でJWTトークンの更新をスケジュールします。広範なテストスイートがあり、かなりの数の条件をチェックして、奇妙なアクティビティに環境の設定ミスに関する説明メッセージが付随していることを確認します。

完全な実装例

import autorefresh from 'jwt-autorefresh'

/** Events in your app that are triggered when your user becomes authorized or deauthorized. */
import { onAuthorize, onDeauthorize } from './events'

/** Your refresh token mechanism, returning a promise that resolves to the new access tokenFunction (library does not care about your method of persisting tokens) */
const refresh = () => {
  const init =  { method: 'POST'
                , headers: { 'Content-Type': `application/x-www-form-urlencoded` }
                , body: `refresh_token=${localStorage.refresh_token}&grant_type=refresh_token`
                }
  return fetch('/oauth/token', init)
    .then(res => res.json())
    .then(({ token_type, access_token, expires_in, refresh_token }) => {
      localStorage.access_token = access_token
      localStorage.refresh_token = refresh_token
      return access_token
    })
}

/** You supply a leadSeconds number or function that generates a number of seconds that the refresh should occur prior to the access token expiring */
const leadSeconds = () => {
  /** Generate random additional seconds (up to 30 in this case) to append to the lead time to ensure multiple clients dont schedule simultaneous refresh */
  const jitter = Math.floor(Math.random() * 30)

  /** Schedule autorefresh to occur 60 to 90 seconds prior to token expiration */
  return 60 + jitter
}

let start = autorefresh({ refresh, leadSeconds })
let cancel = () => {}
onAuthorize(access_token => {
  cancel()
  cancel = start(access_token)
})

onDeauthorize(() => cancel())

免責事項:私はメンテナーです


これについての質問、私はそれが使用するデコード機能を見ました。シークレットを使用せずにJWTをデコードできると想定していますか?シークレットで署名されたJWTで動作しますか?
ジャンフランコザバリーノ2016

3
はい、デコードはクライアントのみのデコードであり、秘密を知ってはなりません。シークレットはJWTトークンのサーバー側に署名するために使用され、署名が元々JWTを生成するために使用され、クライアントから使用されるべきではないことを確認します。JWTの魔法は、ペイロードをクライアント側でデコードでき、内部のクレームを使用して秘密なしでUIを構築できることです。jwt-autorefreshそれをデコードする唯一のものは、expクレームを抽出することです。これにより、次の更新をスケジュールするまでの期間を決定できます。
チェンバレン

1
わかりました。意味がありませんでしたが、今は意味があります。答えてくれてありがとう。
Gian Franco Zabarino、2016

4

トークンデータに変数を追加して、この問題を解決しました。

softexp - I set this to 5 mins (300 seconds)

expiresInユーザーに再度ログインを強制する前に、オプションを希望の時間に設定しました。鉱山は30分に設定されています。これはの値より大きい必要がありますsoftexp

クライアント側のアプリがサーバーAPI(顧客リストページなどのトークンが必要な場所)にリクエストを送信すると、サーバーは送信されたトークンがまだ有効かどうかを元の有効期限(expiresIn)の値に基づいてチェックします。有効でない場合、サーバーはこのエラーに固有のステータスで応答します。INVALID_TOKEN

トークンはexpiredIn値に基づいてまだ有効であるが、すでにsoftexp値を超えている場合、サーバーはこのエラーに対して別のステータスで応答します。EXPIRED_TOKEN

(Math.floor(Date.now() / 1000) > decoded.softexp)

クライアント側で、受け取った場合 EXPIRED_TOKEN応答を、更新要求をサーバーに送信することにより、トークンを自動的に更新する必要があります。これはユーザーに対して透過的で、自動的にクライアントアプリを処理します。

サーバーの更新メソッドは、トークンがまだ有効かどうかを確認する必要があります。

jwt.verify(token, secret, (err, decoded) => {})

上記の方法が失敗した場合、サーバーはトークンの更新を拒否します。


この戦略はよさそうです。しかし、ユーザーのセッションは永遠に存続する可能性があるので、「更新の最大量」の種類で補完する必要があると思います。
ファンイグナシオバリシッチ

1
トークンデータにhardExp変数を設定して、最大期限を設定してトークンを強制的に期限切れにするか、またはトークンが更新されるたびに減少して、トークンの合計更新量を制限するカウンターを設定できます。
ジェームズA

1
そのとおりです。これは「必須」だと思います。
ファンイグナシオバリシッチ

2

このアプローチはどうですか:

  • すべてのクライアント要求について、サーバーはトークンのexpirationTimeを(currentTime-lastAccessTime)と比較します
  • もしexpirationTime <( - lastAccessedTime CURRENTTIME)、それはCURRENTTIMEへの最後のlastAccessedTimeを変更します。
  • 有効期限を超えてブラウザで非アクティブの場合、またはブラウザウィンドウが閉じられ、expirationTime>(currentTime-lastAccessedTime)の場合、サーバーはトークンを期限切れにして、ユーザーに再度ログインするように要求できます。

この場合、トークンを更新するためにエンドポイントを追加する必要はありません。フィードアックをいただければ幸いです。


それはこの日の良い選択ですか、実装はかなり簡単に見えます。
b.ben 2016年

4
この場合、lastAccessedTimeはどこに保存しますか?バックエンドとリクエストごとに行う必要があるため、望ましい状態のないソリューションになります。
antgar9 2016

2

今日、多くの人々は、彼らがのためにあきらめているかを意識することなくJWTsでセッション管理を行うことを選ぶの知覚シンプル。私の回答は、質問の2番目の部分について詳しく述べています。

では、本当のメリットは何ですか?(JWTではなく)トークンを1つだけ使用して、サーバーで有効期限を維持しないのはなぜですか?

他のオプションはありますか?JWTの使用はこのシナリオに適していませんか?

JWTは、いくつかの制限付きで基本的なセッション管理をサポートできます。自己記述型トークンであるため、サーバー側で状態を必要としません。これは彼らを魅力的にします。たとえば、サービスに永続化レイヤーがない場合は、セッション管理のために永続化レイヤーを組み込む必要はありません。

しかし、無国籍も彼らの欠点の主な原因です。これらは、固定されたコンテンツと有効期限で1回だけ発行されるため、通常のセッション管理設定では希望どおりの操作を実行できません。

つまり、オンデマンドでそれらを無効にすることはできません。これはすでに発行されたトークンを期限切れにする方法がないため、安全なログアウト実装できないことを意味します。同じ理由でアイドルタイムアウト実装することもできません。1つの解決策はブラックリストを維持することですが、それは状態をもたらします。

私はこれらの欠点をより詳細に説明する投稿を書きました。明確にするために、複雑さを追加することでこれらを回避できます(スライドセッション、更新トークンなど)。

その他のオプションについては、クライアントがブラウザー経由でのみサービスとやり取りする場合は、Cookieベースのセッション管理ソリューションを使用することを強くお勧めします。また現在Webで広く使用されているリスト認証方法をまとめました

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.