教學課程:使用驗證碼流程從 Angular 單頁應用程式登入使用者並呼叫 Microsoft Graph API
在本教學課程中,您將建置 Angular 單頁應用程式 (SPA),以使用授權碼流程搭配 PKCE 來登入使用者並呼叫 Microsoft Graph API。 您所建置的 SPA 會使用適用於 Angular v2 的 Microsoft 驗證連結庫 (MSAL)。
在本教學課程中:
- 在 Microsoft Entra 系統管理中心註冊應用程式
- 使用建立 Angular 專案
npm
- 新增程式代碼以支援使用者登入和註銷
- 新增程式代碼以呼叫 Microsoft Graph API
- 測試應用程式
MSAL Angular v2 藉由在瀏覽器中透過 PKCE 支援授權碼流程,而不是隱含授與流程,藉此改善 MSAL Angular v1。 我們建議針對單頁應用程式使用授權碼流程搭配 PKCE,因為它比隱含流程更安全。 MSAL Angular v2 不支援隱含流程。
必要條件
- Node.js執行本機網頁伺服器。
- Visual Studio Code 或其他編輯器,用於修改項目檔。
範例應用程式的運作方式
本教學課程中建立的範例應用程式可讓 Angular SPA 查詢 Microsoft Graph API 或接受 Microsoft 身分識別平台 所簽發之令牌的 Web API。 它會使用 Microsoft Authentication Library (MSAL) for Angular v2,這是 MSAL.js v2 連結庫的包裝函式。 MSAL Angular 可讓 Angular 9+ 應用程式使用 Microsoft Entra ID 來驗證企業使用者,以及具有 Facebook、Google 和 LinkedIn 等 Microsoft 帳戶和社交身分識別的使用者。 連結庫也可讓應用程式存取 Microsoft 雲端服務和 Microsoft Graph。
在此案例中,在使用者登入之後,會要求存取令牌,並透過授權標頭新增至 HTTP 要求。 令牌擷取和更新是由 MSAL 處理。
程式庫
本教學課程使用下列連結庫:
程式庫 | 描述 |
---|---|
MSAL Angular | 適用於 JavaScript Angular 包裝函式的 Microsoft 驗證連結庫 |
MSAL 瀏覽器 | 適用於 JavaScript v2 瀏覽器套件的 Microsoft 驗證連結庫 |
您可以在 GitHub 上的存放庫中找到所有MSAL.js連結庫的 microsoft-authentication-library-for-js
原始程式碼。
取得已完成的程式代碼範例
您偏好改為下載本教學課程的已完成範例專案嗎? 複製 ms-identity-javascript-angular-spa
git clone https://github.com/Azure-Samples/ms-identity-javascript-angular-spa.git
若要繼續進行本教學課程並自行建置應用程式,請繼續進行下一節註冊 應用程式和記錄標識符。
註冊應用程式和記錄標識碼
提示
本文中的步驟可能會根據您從開始的入口網站稍有不同。
若要完成註冊,請提供應用程式名稱、指定支援的帳戶類型,以及新增重新導向 URI。 註冊之後,應用程式 [概觀] 窗格會顯示應用程式原始程式碼中所需的標識碼。
- 以至少應用程式開發人員身分登入 Microsoft Entra 系統管理中心。
- 如果您有多個租使用者的存取權,請使用頂端功能表中的 [設定] 圖示,切換至您要從 [目錄 + 訂用帳戶] 功能表註冊應用程式的租使用者。
- 流覽至 [身分>識別應用程式> 應用程式註冊。
- 選取新增註冊。
- 輸入 應用程式的 [名稱 ],例如 Angular-SPA-auth-code。
- 在 [支援的帳戶類型] 區段中,選取 [僅限此組織目錄中的帳戶]。 如需不同帳戶類型的資訊,請選取 [ 協助我選擇] 選項。
- 在 [ 重新導向 URI(選擇性)] 底下,使用下拉菜單來選取 [單一頁面應用程式][SPA] ,然後輸入
http://localhost:4200
到文本框中。 - 選取註冊。
- 註冊完成時,會顯示應用程式的 [ 概觀 ] 窗格。 記錄要用於應用程式原始碼的目錄(租使用者)標識碼和應用程式(用戶端)標識碼。
建立專案
開啟 Visual Studio Code,選取 [檔案>開啟資料夾...]。流覽至 並選取要在其中建立專案的位置。
選取 [終端>機新終端機] 以開啟新的終端機。
- 您可能需要切換終端機類型。 選取終端機中圖示旁的 + 向下箭號,然後選取 [ 命令提示字元]。
執行下列命令以建立名稱為
msal-angular-tutorial
的新 Angular 專案、安裝 Angular Material 元件庫、MSAL Browser、MSAL Angular,併產生首頁和配置檔元件。npm install -g @angular/cli ng new msal-angular-tutorial --routing=true --style=css --strict=false cd msal-angular-tutorial npm install @angular/material @angular/cdk npm install @azure/msal-browser @azure/msal-angular ng generate component home ng generate component profile
設定應用程式並編輯基底UI
開啟 src/app/app.module.ts。 與
MsalModule
MsalInterceptor
必須與 常數一起isIE
新增至imports
。 您也會新增材料模組。 以下欄代碼段取代檔案的整個內容:import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { NgModule } from "@angular/core"; import { MatButtonModule } from "@angular/material/button"; import { MatToolbarModule } from "@angular/material/toolbar"; import { MatListModule } from "@angular/material/list"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { HomeComponent } from "./home/home.component"; import { ProfileComponent } from "./profile/profile.component"; import { MsalModule, MsalRedirectComponent } from "@azure/msal-angular"; import { PublicClientApplication } from "@azure/msal-browser"; const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1; @NgModule({ declarations: [AppComponent, HomeComponent, ProfileComponent], imports: [ BrowserModule, BrowserAnimationsModule, AppRoutingModule, MatButtonModule, MatToolbarModule, MatListModule, MsalModule.forRoot( new PublicClientApplication({ auth: { clientId: "Enter_the_Application_Id_here", // Application (client) ID from the app registration authority: "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here", // The Azure cloud instance and the app's sign-in audience (tenant ID, common, organizations, or consumers) redirectUri: "Enter_the_Redirect_Uri_Here", // This is your redirect URI }, cache: { cacheLocation: "localStorage", storeAuthStateInCookie: isIE, // Set to true for Internet Explorer 11 }, }), null, null ), ], providers: [], bootstrap: [AppComponent, MsalRedirectComponent], }) export class AppModule {}
將下列值取代為從 Microsoft Entra 系統管理中心取得的值。 如需可用可設定選項的詳細資訊,請參閱 初始化用戶端應用程式。
clientId
- 應用程式的識別碼,也稱為用戶端。 將取代Enter_the_Application_Id_Here
為 先前從已註冊應用程式概觀頁面記錄的應用程式 (client) 識別碼 值。authority
- 這由兩個部分組成:- 實例是雲端提供者的端點。 針對主要或全域 Azure 雲端,輸入
https://login.microsoftonline.com
。 在國家雲端中檢查不同的可用端點。 - 租 用戶標識碼 是註冊應用程式之租用戶的標識碼。
_Enter_the_Tenant_Info_Here
將 取代為先前從已註冊應用程式概觀頁面記錄的 Directory (tenant) 識別碼值。
- 實例是雲端提供者的端點。 針對主要或全域 Azure 雲端,輸入
redirectUri
- 授權伺服器在應用程式成功授權並授與授權碼或存取令牌后,傳送使用者的位置。 把Enter_the_Redirect_Uri_Here
替換為http://localhost:4200
。
開啟 src/app/app-routing.module.ts ,並將路由新增至 首頁 和 配置檔 元件。 以下欄代碼段取代檔案的整個內容:
import { NgModule } from "@angular/core"; import { Routes, RouterModule } from "@angular/router"; import { BrowserUtils } from "@azure/msal-browser"; import { HomeComponent } from "./home/home.component"; import { ProfileComponent } from "./profile/profile.component"; const routes: Routes = [ { path: "profile", component: ProfileComponent, }, { path: "", component: HomeComponent, }, ]; const isIframe = window !== window.parent && !window.opener; @NgModule({ imports: [ RouterModule.forRoot(routes, { // Don't perform initial navigation in iframes or popups initialNavigation: !BrowserUtils.isInIframe() && !BrowserUtils.isInPopup() ? "enabledNonBlocking" : "disabled", // Set to enabledBlocking to use Angular Universal }), ], exports: [RouterModule], }) export class AppRoutingModule {}
開啟 src/app/app.component.html ,並以下列程式代碼取代現有的程式代碼:
<mat-toolbar color="primary"> <a class="title" href="/">{{ title }}</a> <div class="toolbar-spacer"></div> <a mat-button [routerLink]="['profile']">Profile</a> <button mat-raised-button *ngIf="!loginDisplay" (click)="login()">Login</button> </mat-toolbar> <div class="container"> <!--This is to avoid reload during acquireTokenSilent() because of hidden iframe --> <router-outlet *ngIf="!isIframe"></router-outlet> </div>
開啟 src/style.css 以定義 CSS:
@import "~@angular/material/prebuilt-themes/deeppurple-amber.css"; html, body { height: 100%; } body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } .container { margin: 1%; }
開啟 src/app/app.component.css ,將 CSS 樣式新增至應用程式:
.toolbar-spacer { flex: 1 1 auto; } a.title { color: white; }
使用快顯登入
開啟 src/app/app.component.ts ,並將檔案的內容取代為下列內容,以使用彈出視窗登入使用者:
import { MsalService } from '@azure/msal-angular'; import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'msal-angular-tutorial'; isIframe = false; loginDisplay = false; constructor(private authService: MsalService) { } ngOnInit() { this.isIframe = window !== window.parent && !window.opener; } login() { this.authService.loginPopup() .subscribe({ next: (result) => { console.log(result); this.setLoginDisplay(); }, error: (error) => console.log(error) }); } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } }
使用重新導向登入
更新 src/app/app.module.ts 以啟動
MsalRedirectComponent
。 這是處理重新導向的專用重新導向元件。 將匯入MsalModule
與AppComponent
啟動程式變更為類似下列內容:... import { MsalModule, MsalRedirectComponent } from '@azure/msal-angular'; // Updated import ... bootstrap: [AppComponent, MsalRedirectComponent] // MsalRedirectComponent bootstrapped here ...
開啟 src/index.html ,並以下列代碼段取代檔案的整個內容,以新增
<app-redirect>
選取器:<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>msal-angular-tutorial</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <app-root></app-root> <app-redirect></app-redirect> </body> </html>
開啟 src/app/app.component.ts ,並將程式代碼取代為下列程式代碼,以使用完整畫面重新導向登入使用者:
import { MsalService } from '@azure/msal-angular'; import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'msal-angular-tutorial'; isIframe = false; loginDisplay = false; constructor(private authService: MsalService) { } ngOnInit() { this.isIframe = window !== window.parent && !window.opener; } login() { this.authService.loginRedirect(); } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } }
流覽至 src/app/home/home.component.ts ,並以下列代碼段取代檔案的整個內容,以訂閱
LOGIN_SUCCESS
事件:import { Component, OnInit } from '@angular/core'; import { MsalBroadcastService, MsalService } from '@azure/msal-angular'; import { EventMessage, EventType, InteractionStatus } from '@azure/msal-browser'; import { filter } from 'rxjs/operators'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit { constructor(private authService: MsalService, private msalBroadcastService: MsalBroadcastService) { } ngOnInit(): void { this.msalBroadcastService.msalSubject$ .pipe( filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS), ) .subscribe((result: EventMessage) => { console.log(result); }); } }
條件式轉譯
為了只針對已驗證的用戶轉譯特定使用者介面(UI),元件必須訂閱 , MsalBroadcastService
才能查看使用者是否已登入,且互動已完成。
MsalBroadcastService
將 新增至 src/app/app.component.ts並訂閱inProgress$
可觀察的 ,以檢查互動是否已完成,且帳戶已在轉譯 UI 之前登入。 您的程式碼現在看起來應該像這樣:import { Component, OnInit, OnDestroy } from '@angular/core'; import { MsalService, MsalBroadcastService } from '@azure/msal-angular'; import { InteractionStatus } from '@azure/msal-browser'; import { Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { title = 'msal-angular-tutorial'; isIframe = false; loginDisplay = false; private readonly _destroying$ = new Subject<void>(); constructor(private broadcastService: MsalBroadcastService, private authService: MsalService) { } ngOnInit() { this.isIframe = window !== window.parent && !window.opener; this.broadcastService.inProgress$ .pipe( filter((status: InteractionStatus) => status === InteractionStatus.None), takeUntil(this._destroying$) ) .subscribe(() => { this.setLoginDisplay(); }) } login() { this.authService.loginRedirect(); } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } ngOnDestroy(): void { this._destroying$.next(undefined); this._destroying$.complete(); } }
更新 src/app/home/home.component.ts 中的程式代碼,在更新 UI 之前也會檢查是否已完成互動。 您的程式碼現在看起來應該像這樣:
import { Component, OnInit } from '@angular/core'; import { MsalBroadcastService, MsalService } from '@azure/msal-angular'; import { EventMessage, EventType, InteractionStatus } from '@azure/msal-browser'; import { filter } from 'rxjs/operators'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit { loginDisplay = false; constructor(private authService: MsalService, private msalBroadcastService: MsalBroadcastService) { } ngOnInit(): void { this.msalBroadcastService.msalSubject$ .pipe( filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS), ) .subscribe((result: EventMessage) => { console.log(result); }); this.msalBroadcastService.inProgress$ .pipe( filter((status: InteractionStatus) => status === InteractionStatus.None) ) .subscribe(() => { this.setLoginDisplay(); }) } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } }
將 src/app/home/home.component.html 中的程式代碼取代為下列條件式顯示:
<div *ngIf="!loginDisplay"> <p>Please sign-in to see your profile information.</p> </div> <div *ngIf="loginDisplay"> <p>Login successful!</p> <p>Request your profile information by clicking Profile above.</p> </div>
實作 Angular Guard
類別 MsalGuard
是您可以用來保護路由的類別,而且在存取受保護的路由之前需要驗證。 下列步驟會將 新增 MsalGuard
至 Profile
路由。 Profile
保護路由表示,即使使用者未使用Login
按鈕登入,如果他們嘗試存取Profile
路由或選取Profile
該按鈕,則會MsalGuard
提示使用者在顯示Profile
頁面之前先透過快顯或重新導向進行驗證。
MsalGuard
是一個方便的類別,可用來改善用戶體驗,但不應該依賴它的安全性。 攻擊者可能會繞過用戶端防護,您應該確保伺服器不會傳回使用者不應該存取的任何數據。
在 src/app/app.module.ts 中,將
MsalGuard
類別新增為應用程式中的提供者,並新增的MsalGuard
組態。 稍後可以在 中authRequest
提供取得權杖所需的範圍,而 Guard 的互動類型可以設定為Redirect
或Popup
。 您的程式代碼看起來應該如下所示:import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { NgModule } from "@angular/core"; import { MatButtonModule } from "@angular/material/button"; import { MatToolbarModule } from "@angular/material/toolbar"; import { MatListModule } from "@angular/material/list"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { HomeComponent } from "./home/home.component"; import { ProfileComponent } from "./profile/profile.component"; import { MsalModule, MsalRedirectComponent, MsalGuard, } from "@azure/msal-angular"; // MsalGuard added to imports import { PublicClientApplication, InteractionType, } from "@azure/msal-browser"; // InteractionType added to imports const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1; @NgModule({ declarations: [AppComponent, HomeComponent, ProfileComponent], imports: [ BrowserModule, BrowserAnimationsModule, AppRoutingModule, MatButtonModule, MatToolbarModule, MatListModule, MsalModule.forRoot( new PublicClientApplication({ auth: { clientId: "Enter_the_Application_Id_here", authority: "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here", redirectUri: "Enter_the_Redirect_Uri_Here", }, cache: { cacheLocation: "localStorage", storeAuthStateInCookie: isIE, }, }), { interactionType: InteractionType.Redirect, // MSAL Guard Configuration authRequest: { scopes: ["user.read"], }, }, null ), ], providers: [ MsalGuard, // MsalGuard added as provider here ], bootstrap: [AppComponent, MsalRedirectComponent], }) export class AppModule {}
MsalGuard
在您要在 src/app/app-routing.module.ts 中保護的路由上設定 :import { NgModule } from "@angular/core"; import { Routes, RouterModule } from "@angular/router"; import { BrowserUtils } from "@azure/msal-browser"; import { HomeComponent } from "./home/home.component"; import { ProfileComponent } from "./profile/profile.component"; import { MsalGuard } from "@azure/msal-angular"; const routes: Routes = [ { path: "profile", component: ProfileComponent, canActivate: [MsalGuard], }, { path: "", component: HomeComponent, }, ]; const isIframe = window !== window.parent && !window.opener; @NgModule({ imports: [ RouterModule.forRoot(routes, { // Don't perform initial navigation in iframes or popups initialNavigation: !BrowserUtils.isInIframe() && !BrowserUtils.isInPopup() ? "enabledNonBlocking" : "disabled", // Set to enabledBlocking to use Angular Universal }), ], exports: [RouterModule], }) export class AppRoutingModule {}
調整 src/app/app.component.ts 中的登入呼叫,以
authRequest
將防護組態中的設定納入考慮。 您的程式碼現在應該看起來類似下列範例:import { Component, OnInit, OnDestroy, Inject } from '@angular/core'; import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular'; import { InteractionStatus, RedirectRequest } from '@azure/msal-browser'; import { Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { title = 'msal-angular-tutorial'; isIframe = false; loginDisplay = false; private readonly _destroying$ = new Subject<void>(); constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { } ngOnInit() { this.isIframe = window !== window.parent && !window.opener; this.broadcastService.inProgress$ .pipe( filter((status: InteractionStatus) => status === InteractionStatus.None), takeUntil(this._destroying$) ) .subscribe(() => { this.setLoginDisplay(); }) } login() { if (this.msalGuardConfig.authRequest){ this.authService.loginRedirect({...this.msalGuardConfig.authRequest} as RedirectRequest); } else { this.authService.loginRedirect(); } } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } ngOnDestroy(): void { this._destroying$.next(undefined); this._destroying$.complete(); } }
取得權杖
Angular 攔截器
MSAL Angular 提供類別 Interceptor
,可針對使用 Angular http
用戶端已知受保護資源的傳出要求自動取得令牌。
在 src/app/app.module.ts 中,將
Interceptor
類別新增為提供者至您的應用程式,並設定其組態。 您的程式碼現在應該看起來類似下列範例:import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { NgModule } from "@angular/core"; import { HTTP_INTERCEPTORS, HttpClientModule } from "@angular/common/http"; // Import import { MatButtonModule } from "@angular/material/button"; import { MatToolbarModule } from "@angular/material/toolbar"; import { MatListModule } from "@angular/material/list"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { HomeComponent } from "./home/home.component"; import { ProfileComponent } from "./profile/profile.component"; import { MsalModule, MsalRedirectComponent, MsalGuard, MsalInterceptor, } from "@azure/msal-angular"; // Import MsalInterceptor import { InteractionType, PublicClientApplication, } from "@azure/msal-browser"; const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1; @NgModule({ declarations: [AppComponent, HomeComponent, ProfileComponent], imports: [ BrowserModule, BrowserAnimationsModule, AppRoutingModule, MatButtonModule, MatToolbarModule, MatListModule, HttpClientModule, MsalModule.forRoot( new PublicClientApplication({ auth: { clientId: "Enter_the_Application_Id_Here", authority: "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here", redirectUri: "Enter_the_Redirect_Uri_Here", }, cache: { cacheLocation: "localStorage", storeAuthStateInCookie: isIE, }, }), { interactionType: InteractionType.Redirect, authRequest: { scopes: ["user.read"], }, }, { interactionType: InteractionType.Redirect, // MSAL Interceptor Configuration protectedResourceMap: new Map([ ["Enter_the_Graph_Endpoint_Here/v1.0/me", ["user.read"]], ]), } ), ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: MsalInterceptor, multi: true, }, MsalGuard, ], bootstrap: [AppComponent, MsalRedirectComponent], }) export class AppModule {}
受保護的資源會以的形式
protectedResourceMap
提供。 您在集合中protectedResourceMap
提供的 URL 會區分大小寫。 針對每個資源,請在存取令牌中新增要求傳回的範圍。例如:
["user.read"]
for Microsoft Graph["<Application ID URL>/scope"]
適用於自訂 Web API(也就是api://<Application ID>/access_as_user
)
修改中的
protectedResourceMap
值,如這裡所述:Enter_the_Graph_Endpoint_Here
是應用程式應該與其通訊的 Microsoft Graph API 實例。 針對全域 Microsoft Graph API 端點,請將此字串取代為https://graph.microsoft.com
。 如需國家雲端部署中的端點,請參閱 Microsoft Graph 檔中的國家雲端部署。
取代 src/app/profile/profile.component.ts 中的程式代碼,以 HTTP 要求擷取使用者配置檔,並以 Microsoft Graph 端點取代
GRAPH_ENDPOINT
:import { Component, OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http'; const GRAPH_ENDPOINT = 'Enter_the_Graph_Endpoint_Here/v1.0/me'; type ProfileType = { givenName?: string, surname?: string, userPrincipalName?: string, id?: string }; @Component({ selector: 'app-profile', templateUrl: './profile.component.html', styleUrls: ['./profile.component.css'] }) export class ProfileComponent implements OnInit { profile!: ProfileType; constructor( private http: HttpClient ) { } ngOnInit() { this.getProfile(); } getProfile() { this.http.get(GRAPH_ENDPOINT) .subscribe(profile => { this.profile = profile; }); } }
取代 src/app/profile/profile.component.html 中的 UI 以顯示設定檔資訊:
<div> <p><strong>First Name: </strong> {{profile?.givenName}}</p> <p><strong>Last Name: </strong> {{profile?.surname}}</p> <p><strong>Email: </strong> {{profile?.userPrincipalName}}</p> <p><strong>Id: </strong> {{profile?.id}}</p> </div>
登出
更新 src/app/app.component.html 中的程式代碼,以有條件地顯示
Logout
按鈕:<mat-toolbar color="primary"> <a class="title" href="/">{{ title }}</a> <div class="toolbar-spacer"></div> <a mat-button [routerLink]="['profile']">Profile</a> <button mat-raised-button *ngIf="!loginDisplay" (click)="login()">Login</button> <button mat-raised-button *ngIf="loginDisplay" (click)="logout()">Logout</button> </mat-toolbar> <div class="container"> <!--This is to avoid reload during acquireTokenSilent() because of hidden iframe --> <router-outlet *ngIf="!isIframe"></router-outlet> </div>
使用重新導向註銷
更新 src/app/app.component.ts 中的程式代碼,以使用重新導向註銷使用者:
import { Component, OnInit, OnDestroy, Inject } from '@angular/core'; import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular'; import { InteractionStatus, RedirectRequest } from '@azure/msal-browser'; import { Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { title = 'msal-angular-tutorial'; isIframe = false; loginDisplay = false; private readonly _destroying$ = new Subject<void>(); constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { } ngOnInit() { this.isIframe = window !== window.parent && !window.opener; this.broadcastService.inProgress$ .pipe( filter((status: InteractionStatus) => status === InteractionStatus.None), takeUntil(this._destroying$) ) .subscribe(() => { this.setLoginDisplay(); }) } login() { if (this.msalGuardConfig.authRequest){ this.authService.loginRedirect({...this.msalGuardConfig.authRequest} as RedirectRequest); } else { this.authService.loginRedirect(); } } logout() { // Add log out function here this.authService.logoutRedirect({ postLogoutRedirectUri: 'http://localhost:4200' }); } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } ngOnDestroy(): void { this._destroying$.next(undefined); this._destroying$.complete(); } }
使用快顯註銷
更新 src/app/app.component.ts 中的程式代碼,以使用快顯註銷使用者:
import { Component, OnInit, OnDestroy, Inject } from '@angular/core'; import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular'; import { InteractionStatus, PopupRequest } from '@azure/msal-browser'; import { Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { title = 'msal-angular-tutorial'; isIframe = false; loginDisplay = false; private readonly _destroying$ = new Subject<void>(); constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { } ngOnInit() { this.isIframe = window !== window.parent && !window.opener; this.broadcastService.inProgress$ .pipe( filter((status: InteractionStatus) => status === InteractionStatus.None), takeUntil(this._destroying$) ) .subscribe(() => { this.setLoginDisplay(); }) } login() { if (this.msalGuardConfig.authRequest){ this.authService.loginPopup({...this.msalGuardConfig.authRequest} as PopupRequest) .subscribe({ next: (result) => { console.log(result); this.setLoginDisplay(); }, error: (error) => console.log(error) }); } else { this.authService.loginPopup() .subscribe({ next: (result) => { console.log(result); this.setLoginDisplay(); }, error: (error) => console.log(error) }); } } logout() { // Add log out function here this.authService.logoutPopup({ mainWindowRedirectUri: "/" }); } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } ngOnDestroy(): void { this._destroying$.next(undefined); this._destroying$.complete(); } }
測試您的程式碼
在應用程式資料夾的命令列提示字元中執行下列命令,以啟動 Web 伺服器以接聽埠:
npm install npm start
在瀏覽器中,輸入
http://localhost:4200
,您應該會看到如下所示的頁面。選取 [ 接受 ] 將應用程式許可權授與配置檔。 這會在您第一次開始登入時發生。
同意之後,下列內容:如果您同意要求的許可權,Web 應用程式會顯示成功的登入頁面。
選取 [設定檔 ] 以檢視從 Microsoft Graph API 呼叫回應中傳回的使用者設定檔資訊:
新增範圍和委派的許可權
Microsoft Graph API 需要 User.Read 範圍才能讀取使用者配置檔。 User.Read 範圍會自動新增至每個應用程式註冊。 Microsoft Graph 的其他 API,以及後端伺服器的自定義 API,可能需要其他範圍。 例如,Microsoft Graph API 需要 Mail.Read 範圍,才能列出使用者的電子郵件。
當您新增範圍時,系統可能會提示使用者為新增的範圍提供額外的同意。
注意
當您增加範圍數目時,可能會提示使用者輸入其他同意。
說明與支援
如果您需要協助、想要回報問題,或想要了解支援選項,請參閱 開發人員的說明和支援。
下一步
- 透過建置 React 單頁應用程式 (SPA) 以在下列多部分 教學課程系列中登入使用者,以深入瞭解。
意見反映
https://aka.ms/ContentUserFeedback。
即將推出:我們會在 2024 年淘汰 GitHub 問題,並以全新的意見反應系統取代並作為內容意見反應的渠道。 如需更多資訊,請參閱:提交及檢視以下的意見反映: