教學課程:使用驗證碼流程從 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 藉由在瀏覽器中支援授權碼流程,而不是隱含授與流程,改善 MSAL Angular v1。 MSAL Angular v2 不支援隱含流程。
必要條件
- Node.js執行本機網頁伺服器。
- Visual Studio Code 或其他編輯器,用於修改項目檔。
範例應用程式的運作方式
本教學課程中建立的範例應用程式可讓 Angular SPA 查詢 Microsoft Graph API 或 Web API,以接受 Microsoft 身分識別平台 所簽發的令牌。 它會使用 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 處理。
Libraries
本教學課程使用下列連結庫:
程式庫 | 描述 |
---|---|
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) 以在下列多部分 教學課程系列中登入使用者,以深入瞭解。