2026年5月4日 星期一

沒事把node.js簽發的jwt拿來呼叫.net core web API時踩了一堆坑的故事

 照理說dot net 的 web api與nextjs整合應該是前端用Browser -->next.js --> .net web api (讓dot net的microservice放在另一個網段)
不過如果直接讓node.js簽發給browser client的jwt拿來call .net web api會發生什麼事 (為什麼要這樣做?不為什麼,就只是想知道罷了)

這一路走來我遇到了狀況,因為很有趣,所以把它做個記錄.

狀況一.401錯誤--起源是錯用了AOT,而不是傳統的Web API專案
原因: VS 2026 建專案時選到了 "Web API (Native AOT)" 模板,使用 CreateSlimBuilder + PublishAot。AOT 的 trimmer 會把 JWT 需要的 crypto provider 砍掉。



所以最初在設計Web API時,一定要選對專案型態 ,不是AOT,而是最上面那種傳統Web API型態的專案
AOT與一般web API專案的差別:

                    Regular              AOT
─────────────────────────────────────────────────
Builder           CreateBuilder        CreateSlimBuilder
編譯方式           IL → JIT             直接編譯成機器碼
啟動速度           ~500ms+              ~50ms
發布大小           需要 Runtime          單一檔案 (~10MB)
Reflection         ✅ 完整支援           ❌ 受限
JSON 序列化        System.Text.Json      需要 Source Generator
JWT/Crypto         ✅ 全部可用           ⚠️ 可能被 Trim 掉
NuGet 相容性       幾乎全部              部分不支援
適用場景           一般 API、企業應用     微服務、Serverless

-----------------------------------------------------------------------
(小的沒在用AOT,如有先進有補充建議的亦請不吝賜教)

重新增加一個web api project,修改program.cs
-----------------------------

...
...
var
key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(Configuration["JWT_SECRET"]) );
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = key, ValidateIssuer = false, ValidateAudience = false, }; });
....
...
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
-----------------------------
Action in Sample controller
-------------------------
[Authorize]
[HttpGet]
public IActionResult Get() {
    var username = User.FindFirst("username")?.Value;
    var role = User.FindFirst("role")?.Value;
    return Ok();
}
----------------------------
之後....
狀況二.錯誤訊息 IDX10517: Signature validation failed. The token's kid is missing.
------------------------------------------
原因: .NET 10 的 Microsoft.IdentityModel 套件收緊了驗證規則,要求 token header 中必須有 kid(Key ID)。而 Node.js 的 jose 預設不產生 kid
解法:
這要在nextjs 簽發JWT及解析端(.net core web API)都要再加一個kid
------JWT.ts-----------
export async function signJWT(user:AuthUser):Promise<string> {
    return new SignJWT(
       {
        id:user.id,
        username:user.username,
        role:user.role
       }
    ).setProtectedHeader({alg:'HS256') <--Before
    ).setProtectedHeader({alg:'HS256', kid: 'MyKidKey'}) <--after.
    .setIssuedAt()
    .setExpirationTime('8h')
    .sign(SECRET); <---SECRET是從.node 設定檔來的,跟program.cs的 Configuration["JWT_SECRET"]相呼應
}
------Program.cs------
var key = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes(Configuration["JWT_SECRET"])
);
key.KeyId = "MyKidKey";  <----KID set here.
--------------------------
這下子,總該可以了吧?
再次用postman設定好bearer token後,試著request controller 時
出現新的錯誤錯誤--
狀況三:「The signature is invalid」錯誤
這東西真的是超難解的,最後是把兩邊的code都送給AI去看的結果才知是「長度」問題:

原因: HS256 要求 key 至少 256 bits(32 bytes)。原本的 secret 只有 27 字元,jose 和 .NET 對不足長度的 key 可能有不同的 padding 行為。

解法: Secret 改成 32 字元以上,兩邊同步更新。

最後,終於在controller的GET action中,可以讀取到User資訊了
但是「User.FindFirst("role")?.Value」回傳null,這樣還是不完成,
改成「User.FindFirst(ClaimTypes.Role)?.Value」,就會得到role值了

別忘了,在[Authorize...]指定需要的角色權限.ex  [Authorize(Roles = "manager,admin")]

有些知識真的是靠踩坑得來的,不是AI直接給你就會了解的.





0 個意見:

張貼留言

訂閱 張貼留言 [Atom]

<< 首頁