はじめに
.NET 6.0から.NET 8.0へバージョンアップ後、JWTで認証を行っているWebAPIが常に401で終了する事象に遭遇しました。.NET 8.0への移行は基本的にはライブラリを.NET8.0に対応した最新版へ置き換えるだけで問題なく作業できましたが、こいつだけ対応に調査が必要だったのでメモを残しておきます。
2024/07/17追記
「現象1:invalid_tokenと表示されJWTの検証が失敗する」の対応方法に問題がありました。以前の実装ではJWTの署名検証が行われなくなるので注意してください。
原因は破壊的変更 セキュリティ トークン イベントが JsonWebToken を返すにある通り.NET8.0の破壊的変更が原因で、JWTを検証する内部の実装がJwtSecurityTokenからJsonWebTokensに置き換えられたためでした。
.NET6.0から8.0の破壊的変更は次のページにまとまっています。
現象1:invalid_tokenと表示されJWTの検証が失敗する
正しい署名がされたJWTであってもJWTの検証が失敗し、401が返却されます。
curl -X 'GET' \
'http://localhost:5100/api/v1/Hello' \
-H 'accept: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjNBQTdFQ...略...
HTTP/1.1 401 Unauthorized
Content-Length: 0
Date: Thu, 21 Mar 2024 00:29:22 GMT
Server: Kestrel
WWW-Authenticate: Bearer error="invalid_token", error_description="The signature key was not found"
ログには下記のようにセキュリティーキーが見つからないために、署名の検証が失敗したというメッセージが表示されます。
[09:19:27 INF] Failed to validate the token.
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10500: Signature validation failed. No security keys were provided to validate the signature.
at Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateSignature(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
at Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateSignatureAndIssuerSecurityKey(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
at Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateJWSAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
[09:19:27 INF] Bearer was not authenticated. Failure message: IDX10500: Signature validation failed. No security keys were provided to validate the signature.
ブレーキングチェンジには、SecurityTokenの実装がJwtSecurityTokenからJsonWebTokenに変更されたため、コード内でJwtSecurityTokenをダウンキャストして使っていたり、SignatureValidatorを差し替えている場合に問題が発生するとあります。
2024/07/17追記
以前のJwonWebTokenを単純に返却する方法では、JWTの検証がスキップされJWTの偽装されても検知できません。以下の対応を検討してください。
今回はどちらにも当てはまらない状況でエラーが表示されました。調べていくと、Microsoft.IdentityModel
から始まるライブラリが推移的に解決され、依存するライブラリのバージョンが崩れてしまったことが問題でした。
StackOverflowにコメントがありました。
Microsoft.IdentityModel
から始まる複数のライブラリで異なるバージョンが選択されていたので、プロジェクトに明示的に参照したり、不要な参照を削除したりして同じバージョンを参照するように調整して対応しました。
また、Microsoft.IdentityModel.Tokens
とSystem.IdentityModel.Tokens.Jwt
の両方が参照されていたので、System.IdentityModel.Tokens.Jwt
の参照を削除したところ正しく署名の検証がおこなわれつつ、JWTのエラーも発生しなくなりました。
現象2:JwtSecurityTokenHandlerでクレームのマッピングをクリアしている場合は、JsonWebTokenHandlerに置き換える
subクレーム
を直に参照して値を確認しているコードで、subクレーム
を参照できない問題が発生しました。
例えば、次のデコードされたJWTの場合、
{
"iss": "https://*********",
"aud": [ "https://****/resources" ],
"scope": [ "openid", "profile" ],
"amr": [ "pwd" ],
"client_id": "****",
"sub": "testuser@example.com",
"idp": "local",
... 以下略 ...
}
クレームを.NETでウォッチすると次のように表示されました。
subクレーム
や、armクレーム
、idpクレーム
などがnameidentifierなどに変換されています。
Program.cs内でJwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()
を呼び出してマッピングを構成している部分があったので、JwtSecurityTokenHandler
からJsonWebTokenHandler
に変更しました。
-JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
+JsonWebTokenHandler.DefaultInboundClaimTypeMap.Clear();
おわりに
バージョンアップ時は破壊的変更をちゃんと読もう