---
# System prepended metadata

title: 前端 @azure/msal-browser 使用方式
tags: [Microsoft Entra ID]

---

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