照理說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直接給你就會了解的.