# 前端 @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"
}
```