# 前端 @azure/msal-browser 使用方式 > [GitHub](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev) ## 1. 安裝 `npm install @azure/msal-browser` (Microsoft Learn 的安裝與定位說明) ## 2 必填設定(建議集中在 auth.js / auth.ts) MSAL 的 auth 設定核心通常是: clientId:App Registration 的 Application (client) ID authority:https://login.microsoftonline.com/<TENANT_ID>(建議用 tenant-specific authority) redirectUri:必須與 Entra ID「單頁應用程式」平台的 URI 完全一致 (設定選項與 authority 形式說明) ```javascript= import { PublicClientApplication } from "@azure/msal-browser"; export const msalInstance = new PublicClientApplication({ auth: { clientId: import.meta.env.VITE_ENTRA_CLIENT_ID, authority: `https://login.microsoftonline.com/${import.meta.env.VITE_ENTRA_TENANT_ID}`, redirectUri: "http://localhost:5173", }, cache: { // 視需求:sessionStorage / localStorage cacheLocation: "sessionStorage", }, }); ``` ## 3. 取得 Access Token 規則:先 silent,再互動。(官方建議模式) ```javascript= import { InteractionRequiredAuthError } from "@azure/msal-browser"; import { msalInstance } from "./auth"; const scopes = ["api://<API_CLIENT_ID>/access_as_user"]; // 依實際設定替換 export async function getAccessToken() { const accounts = msalInstance.getAllAccounts(); const account = accounts[0]; if (!account) { // 尚未登入:導向登入(或改用 loginPopup) await msalInstance.loginRedirect({ scopes }); return null; // redirect 會離開頁面 } try { const result = await msalInstance.acquireTokenSilent({ scopes, account }); return result.accessToken; } catch (e) { if (e instanceof InteractionRequiredAuthError) { await msalInstance.acquireTokenRedirect({ scopes }); return null; } throw e; } } ``` ## 4. 呼叫後端 API(統一寫法) ```javascript= export async function callApi() { const token = await getAccessToken(); if (!token) return; const res = await fetch(import.meta.env.VITE_API_BASE_URL + "/todos", { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) throw new Error(`API error: ${res.status}`); return await res.json(); } ``` # 後端:Microsoft.Identity.Web 使用方式 [GitHub](https://github.com/AzureAD/microsoft-identity-web) ### 1. 安裝(NuGet) 在 ASP.NET Core Web API 專案: `dotnet add package Microsoft.Identity.Web` ### 2. appsettings.json ```json= { "AzureAd": { "Instance": "https://login.microsoftonline.com/", "TenantId": "<TENANT_ID>", "ClientId": "<API_CLIENT_ID>" } } ``` ### 3. Program.cs 啟用 JWT 驗證 ```csharp= using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Identity.Web; var builder = WebApplication.CreateBuilder(args); builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")); builder.Services.AddAuthorization(); builder.Services.AddControllers(); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run(); ``` ## 4.在 Controller/Action 上要求 Scope ```csharp= using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Identity.Web.Resource; namespace BackendApi.Controllers; [ApiController] [Route("api/[controller]")] public class ProfileController : ControllerBase { [HttpGet("me")] [Authorize] [RequiredScope("access_as_user")] public IActionResult Me() { return Ok(new { Scopes = User.FindFirst("scp")?.Value }); } } ``` >[RequiredScope] 會檢查 token 的 scp(或等價的 scope claim)是否包含宣告的值。 HealthController 提供一個不需要登入的健康檢查端點,用來確認後端服務「是否活著、是否能正常回應 HTTP」 ```csharp= using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace BackendApi.Controllers; [ApiController] [Route("api/[controller]")] public class HealthController : ControllerBase { [HttpGet] [AllowAnonymous] public IActionResult Get() { return Ok(new { Status = "OK" }); } } ``` --- ## 前後端整合步驟 ### 一 正確的前後端責任分工 ```markdown= 使用者 ↓ 前端(SPA + MSAL) ↓ ① 前往 Entra ID 登入 ↓ ② 取得「發給『某個 API』的 access token」 ↓ 後端(任何 Web API) ↓ ③ 驗證 token 是否合法(JWT 驗證) ↓ ④ 驗證 token 是否允許存取此 API(scope / role) ↓ 回傳資料 ``` ### 二 vue3-sample-app修改成能用的範例 [Git](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/msal-browser-samples/vue3-sample-app) 官方 vue3-sample-app ,它預設示範的是 Microsoft Graph: ```markdown= 使用者 ↓ 前端(Vue + MSAL) ↓ ① 登入 Entra ↓ ② 拿到「給 Microsoft Graph 用的 token」 ❌ 沒有你的 API ❌ token 不是發給你後端的 ❌ 無法保護你的資料 ``` ### 三 要對齊任何後端 API,前端一定要改的 4 件事(通用) #### 1. 一定要對齊「後端定義的權限」 前端在向 Entra 要 token 時,必須指定「後端 API 的 scope」: ```vue= scopes: ["api://<API_CLIENT_ID>/access_as_user"] // api://<API_CLIENT_ID> 表示「這張 token 是發給哪一個 API(audience)」 // access_as_user 表示「允許以使用者身分呼叫該 API」 ``` #### 2. API 呼叫:token 只能拿來呼叫「對應的 API」 ```vue= fetch("https://api.example.com/profile/me", { headers: { Authorization: `Bearer ${accessToken}` } }); // 拿這張 token 去呼叫其他 API 一定會拒絕不是發給它的 token ``` #### 3. MSAL 初始化:確保 token 流程穩定 在 SPA 啟動時,一定要先完成 MSAL 初始化與 redirect 處理: ```vue= await msalInstance.initialize(); await msalInstance.handleRedirectPromise(); // 保證:redirect 登入回來時,token 能被正確處理 // 不會發生重複登入 / token 卡住的情況 ``` #### 4.API 請求一定要帶 Bearer Token ```vue= headers: { Authorization: `Bearer ${accessToken}` } // 沒有這一行:後端會視為「未登入」、JWT 驗證會失敗 ``` ### 四 後端「一定會做的事」 後端 API 一定都會做這些檢查: | 檢查項目 | 說明 | | ------------------ | --------------------- | | 驗簽章 | token 是否由 Entra ID 發出 | | 驗 audience (`aud`) | token 是否「發給我這個 API」 | | 驗 scope / role | token 是否允許存取此資源 | | 驗過期時間 | token 是否已失效 | ### 五 前後端對照表 | 後端的責任 | 前端必須做到 | | -------------------- | -------------------- | | API 需要驗 JWT | 前端要帶 Bearer token | | API 定義了 scope / role | 前端要用對的 scope 取 token | | API 檢查 audience | 前端不能拿錯 API 的 token | | API 保護資料 | 前端不能假設「登入就安全」 | ### 六 如何驗證是否成功 呼叫一個需要驗證的 API(例如 /profile/me)只要後端回傳的資訊符合以下條件,代表成功: ```json= { "isAuthenticated": true, "audience": "api://<API_CLIENT_ID>", "scopes": "access_as_user" } ```