エラーメッセージ
token has invalid claims: token is expired
別の表記
- Token is expired
- token has invalid claims: token is not valid yet
- token has invalid claims: token used before issued
golang-jwt v4 は「Token is expired」(先頭が大文字の T)と出力します。v5 のほかのクレームエラーも、同じ「token has invalid claims:」というプレフィックスを使います。
golang-jwt/jwt (Go) — jwt.ErrTokenInvalidClaims joined with jwt.ErrTokenExpired
このエラーの意味
トークンの署名は検証に成功しましたが、`exp` クレームが過去の時刻です(設定した許容誤差を考慮しても過ぎています)。golang-jwt v5 はこれを、`ErrTokenInvalidClaims` と `ErrTokenExpired` を結合したエラーとして報告します。同じ系統のエラーとして、`nbf` が未来の時刻なら「token is not valid yet」、`iat` が未来の時刻なら「token used before issued」が返されます。解析済みのトークンはエラーと一緒に返されますが、信頼してはいけません。
発生する理由
トークンが有効期間を過ぎて使われている
よくあるアクセストークンの有効期間は通常 5〜60 分です。トークンをキャッシュするクライアント、かなり時間が経ってからリクエストを再試行するクライアント、長時間実行されるジョブにトークンを保持するクライアントは、`exp` を過ぎてからトークンを提示することになります。
検証者の時計が進んでいる
よくある時計が進んでいるサーバーでは、すべてのトークンが本来より早く期限切れになります。コンテナーはホストの時計を引き継ぐため、NTP を使っていないホストが 1 台あるだけで、そのホスト上のすべてのサービスが影響を受けます。
クライアントにトークンを更新する手段がない
ときどきアクセストークンを保存してもリフレッシュトークンを一切使わない(または 401 を処理しない)シングルページアプリケーションや CLI は、最初のトークンが期限切れになった時点から失敗し続けます。
exp が誤った単位で書き込まれている
まれ`exp` はエポックからの秒数です。ミリ秒から秒への変換を二重に行ったり、ローカル時刻への変換を挟んだりする発行者は、発行した時点ですでに期限切れのトークンを生成することがあります。
修正方法
- 1.
期限切れを検出し、クライアントにトークンを更新させる
`ErrTokenExpired` は、`invalid_token` というヒントを付けて 401 にマッピングしてください。そうすればクライアントはリフレッシュトークンを使い(または再認証し)、リクエストを再試行します。
go token, err := jwt.Parse(raw, keyfunc, jwt.WithValidMethods([]string{"ES256"})) if errors.Is(err, jwt.ErrTokenExpired) { w.Header().Set("WWW-Authenticate", `Bearer error="invalid_token", error_description="token expired"`) http.Error(w, "token expired", http.StatusUnauthorized) // client refreshes and retries return } - 2.
わずかな時刻のずれを許容し、exp を必須にする
数秒の許容誤差を設ければ、マシン間の時計のずれを吸収できます。`WithExpirationRequired` は逆方向の抜け穴をふさぎます。これを指定しないと、`exp` のないトークンはいつまでも期限切れになりません。
go parser := jwt.NewParser( jwt.WithValidMethods([]string{"ES256"}), jwt.WithLeeway(30*time.Second), // absorbs clock skew for exp, nbf and iat jwt.WithExpirationRequired(), // tokens without exp are rejected too )