Samouczek: logowanie użytkowników i wywoływanie interfejsu API programu Microsoft Graph z aplikacji jednostronicowej angular (SPA) przy użyciu przepływu kodu uwierzytelniania
W tym samouczku utworzysz jednostronicową aplikację angular (SPA), która loguje użytkowników i wywołuje interfejs API programu Microsoft Graph przy użyciu przepływu kodu autoryzacji za pomocą protokołu PKCE. Utworzone spa używa biblioteki Microsoft Authentication Library (MSAL) dla platformy Angular w wersji 2.
W tym samouczku:
- Rejestrowanie aplikacji w centrum administracyjnym firmy Microsoft Entra
- Tworzenie projektu Angular za pomocą polecenia
npm
- Dodawanie kodu do obsługi logowania użytkownika i wylogowywanie się
- Dodawanie kodu w celu wywołania interfejsu API programu Microsoft Graph
- Testowanie aplikacji
Biblioteka MSAL Angular w wersji 2 ulepsza usługę MSAL Angular w wersji 1, obsługując przepływ kodu autoryzacji w przeglądarce zamiast niejawnego przepływu udzielania. Biblioteka MSAL Angular v2 nie obsługuje niejawnego przepływu.
Wymagania wstępne
- Node.js do uruchamiania lokalnego serwera internetowego.
- Program Visual Studio Code lub inny edytor do modyfikowania plików projektu.
Jak działa przykładowa aplikacja
Przykładowa aplikacja utworzona w tym samouczku umożliwia usłudze Angular SPA wykonywanie zapytań względem interfejsu API programu Microsoft Graph lub internetowego interfejsu API, który akceptuje tokeny wystawione przez Platforma tożsamości Microsoft. Używa biblioteki Microsoft Authentication Library (MSAL) dla platformy Angular w wersji 2, otoki biblioteki MSAL.js w wersji 2. Usługa MSAL Angular umożliwia aplikacjom Angular 9+ uwierzytelnianie użytkowników przedsiębiorstwa przy użyciu identyfikatora Entra firmy Microsoft, a także użytkowników z kontami Microsoft i tożsamościami społecznościowymi, takimi jak Facebook, Google i LinkedIn. Biblioteka umożliwia również aplikacjom uzyskiwanie dostępu do usług w chmurze firmy Microsoft i programu Microsoft Graph.
W tym scenariuszu po zalogowaniu użytkownika jest wymagany token dostępu i dodawany do żądań HTTP za pośrednictwem nagłówka autoryzacji. Pozyskiwanie i odnawianie tokenów jest obsługiwane przez bibliotekę MSAL.
Biblioteki
W tym samouczku są używane następujące biblioteki:
Biblioteka | opis |
---|---|
MSAL Angular | Biblioteka uwierzytelniania firmy Microsoft dla otoki angular języka JavaScript |
Przeglądarka MSAL | Biblioteka uwierzytelniania firmy Microsoft dla pakietu przeglądarki JavaScript w wersji 2 |
Kod źródłowy dla wszystkich bibliotek MSAL.js można znaleźć w repozytorium w witrynie microsoft-authentication-library-for-js
GitHub.
Pobieranie ukończonego przykładu kodu
Czy wolisz pobrać ukończony przykładowy projekt dla tego samouczka? Klonowanie ms-identity-javascript-angular-spa
git clone https://github.com/Azure-Samples/ms-identity-javascript-angular-spa.git
Aby kontynuować pracę z samouczkiem i utworzyć aplikację samodzielnie, przejdź do następnej sekcji Rejestrowanie aplikacji i identyfikatorów rekordów.
Rejestrowanie aplikacji i identyfikatorów rekordów
Napiwek
Kroki opisane w tym artykule mogą się nieznacznie różnić w zależności od portalu, od którego zaczynasz.
Aby ukończyć rejestrację, podaj nazwę aplikacji, określ obsługiwane typy kont i dodaj identyfikator URI przekierowania. Po zarejestrowaniu okienko Przegląd aplikacji wyświetla identyfikatory wymagane w kodzie źródłowym aplikacji.
- Zaloguj się do centrum administracyjnego firmy Microsoft Entra co najmniej jako deweloper aplikacji.
- Jeśli masz dostęp do wielu dzierżaw, użyj ikonyUstawienia w górnym menu, aby przełączyć się do dzierżawy, w której chcesz zarejestrować aplikację z menu Katalogi i subskrypcje.
- Przejdź do aplikacji tożsamości>> Rejestracje aplikacji.
- Wybierz opcjęNowa rejestracja.
- Wprowadź nazwę aplikacji, na przykład Angular-SPA-auth-code.
- W obszarze Obsługiwane typy kont wybierz pozycję Konta tylko w tym katalogu organizacyjnym. Aby uzyskać informacje o różnych typach kont, wybierz opcję Pomoc dla mnie .
- W obszarze Identyfikator URI przekierowania (opcjonalnie) użyj menu rozwijanego, aby wybrać pozycję Aplikacja jednostronicowa (SPA) i wprowadzić
http://localhost:4200
w polu tekstowym. - Wybierz pozycję Zarejestruj.
- Po zakończeniu rejestracji zostanie wyświetlone okienko Przegląd aplikacji. Zapisz identyfikator katalogu (dzierżawy) i identyfikator aplikacji (klienta) do użycia w kodzie źródłowym aplikacji.
Tworzenie projektu
Otwórz program Visual Studio Code, wybierz pozycję Plik>Otwórz folder.... Przejdź do i wybierz lokalizację, w której chcesz utworzyć projekt.
Otwórz nowy terminal, wybierając pozycję Terminal>Nowy terminal.
- Może być konieczne przełączenie typów terminali. Wybierz strzałkę w dół obok + ikony w terminalu i wybierz pozycję Wiersz polecenia.
Uruchom następujące polecenia, aby utworzyć nowy projekt Angular o nazwie
msal-angular-tutorial
, zainstaluj biblioteki składników Angular Material, przeglądarkę MSAL, bibliotekę MSAL Angular i wygeneruj składniki domu i profilu.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
Konfigurowanie aplikacji i edytowanie podstawowego interfejsu użytkownika
Otwórz plik src/app/app.module.ts. Element
MsalModule
iMsalInterceptor
należy dodać doimports
elementu wraz ze stałąisIE
. Dodasz również moduły materiałowe. Zastąp całą zawartość pliku następującym fragmentem kodu: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 {}
Zastąp następujące wartości wartości wartościami uzyskanymi z centrum administracyjnego firmy Microsoft Entra. Aby uzyskać więcej informacji na temat dostępnych opcji konfigurowalnych, zobacz Inicjowanie aplikacji klienckich.
clientId
— Identyfikator aplikacji, nazywany również klientem. ZastąpEnter_the_Application_Id_Here
ciąg wartością Identyfikator aplikacji (klienta), która została zarejestrowana wcześniej na stronie przeglądu zarejestrowanej aplikacji.authority
- Składa się z dwóch części:- Wystąpienie jest punktem końcowym dostawcy usług w chmurze. W przypadku głównej lub globalnej chmury platformy Azure wprowadź .
https://login.microsoftonline.com
Sprawdź różne dostępne punkty końcowe w chmurach krajowych. - Identyfikator dzierżawy to identyfikator dzierżawy, w której zarejestrowano aplikację. Zastąp wartość
_Enter_the_Tenant_Info_Here
Identyfikator katalogu (dzierżawy), która została zarejestrowana wcześniej na stronie przeglądu zarejestrowanej aplikacji.
- Wystąpienie jest punktem końcowym dostawcy usług w chmurze. W przypadku głównej lub globalnej chmury platformy Azure wprowadź .
redirectUri
— lokalizacja, w której serwer autoryzacji wysyła użytkownika, gdy aplikacja została pomyślnie autoryzowana i przyznała kod autoryzacji lub token dostępu. ZamieńEnter_the_Redirect_Uri_Here
nahttp://localhost:4200
.
Otwórz plik src/app/app-routing.module.ts i dodaj trasy do składników domu i profilu . Zastąp całą zawartość pliku następującym fragmentem kodu:
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 {}
Otwórz src/app/app.component.html i zastąp istniejący kod następującym kodem:
<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>
Otwórz plik src/style.css , aby zdefiniować arkusz 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%; }
Otwórz plik src/app/app.component.css , aby dodać styl CSS do aplikacji:
.toolbar-spacer { flex: 1 1 auto; } a.title { color: white; }
Logowanie przy użyciu wyskakujących okienek
Otwórz plik src/app/app.component.ts i zastąp zawartość pliku następującym kodem, aby zalogować użytkownika przy użyciu okna podręcznego:
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; } }
Logowanie przy użyciu przekierowań
Zaktualizuj plik src/app/app.module.ts , aby uruchomić plik
MsalRedirectComponent
. Jest to dedykowany składnik przekierowania, który obsługuje przekierowania.MsalModule
Zmień import iAppComponent
bootstrap w następujący sposób:... import { MsalModule, MsalRedirectComponent } from '@azure/msal-angular'; // Updated import ... bootstrap: [AppComponent, MsalRedirectComponent] // MsalRedirectComponent bootstrapped here ...
Otwórz src/index.html i zastąp całą zawartość pliku następującym fragmentem kodu, który dodaje
<app-redirect>
selektor:<!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>
Otwórz plik src/app/app.component.ts i zastąp kod następującym kodem, aby zalogować użytkownika przy użyciu przekierowania w pełnej ramce:
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; } }
Przejdź do pliku src/app/home/home.component.ts i zastąp całą zawartość pliku następującym fragmentem kodu, aby zasubskrybować
LOGIN_SUCCESS
zdarzenie: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); }); } }
Renderowanie warunkowe
Aby renderować określony interfejs użytkownika tylko dla uwierzytelnionych użytkowników, składniki muszą subskrybować MsalBroadcastService
, aby sprawdzić, czy użytkownicy zostali zalogowani, a interakcja została ukończona.
Dodaj element
MsalBroadcastService
do pliku src/app/app.component.ts i zasubskrybuj go,inProgress$
aby sprawdzić, czy interakcja została ukończona, a konto jest zalogowane przed renderowaniem interfejsu użytkownika. Kod powinien teraz wyglądać następująco: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(); } }
Zaktualizuj kod w pliku src/app/home/home.component.ts , aby również sprawdzić, czy interakcja ma zostać ukończona przed zaktualizowaniem interfejsu użytkownika. Kod powinien teraz wyglądać następująco:
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; } }
Zastąp kod w src/app/home/home.component.html następującymi ekranami warunkowymi:
<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>
Implementowanie usługi Angular Guard
Klasa MsalGuard
jest używana do ochrony tras i wymagania uwierzytelniania przed uzyskaniem dostępu do chronionej trasy. Poniższe kroki dodają element MsalGuard
do Profile
trasy. Profile
Ochrona trasy oznacza, że nawet jeśli użytkownik nie zaloguje się przy użyciu Login
przycisku, jeśli spróbuje uzyskać dostęp Profile
do trasy lub wybierze Profile
przycisk, MsalGuard
monituje użytkownika o uwierzytelnienie za pomocą wyskakującego lub przekierowania przed wyświetleniem Profile
strony.
MsalGuard
to klasa wygody, której można użyć, aby poprawić środowisko użytkownika, ale nie powinna polegać na zabezpieczeniach. Osoby atakujące mogą potencjalnie obejść zabezpieczenia po stronie klienta i należy upewnić się, że serwer nie zwraca żadnych danych, do których użytkownik nie powinien uzyskiwać dostępu.
Dodaj klasę
MsalGuard
jako dostawcę w aplikacji w pliku src/app/app.module.ts i dodaj konfiguracje dla elementuMsalGuard
. Zakresy potrzebne do uzyskiwania tokenów można później podać w elemecieauthRequest
, a typ interakcji dla funkcji Guard można ustawić naRedirect
lubPopup
. Kod powinien wyglądać następująco: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
Ustaw wartość na trasach, które chcesz chronić w pliku 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 {}
Dostosuj wywołania logowania w pliku src/app/app.component.ts , aby uwzględnić
authRequest
zestaw w konfiguracjach funkcji guard. Kod powinien teraz wyglądać następująco: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(); } }
Uzyskiwanie tokenu
Przechwytnika angular
Biblioteka MSAL Angular udostępnia klasę Interceptor
, która automatycznie uzyskuje tokeny dla żądań wychodzących, które używają klienta Angular http
do znanych chronionych zasobów.
Dodaj klasę
Interceptor
jako dostawcę do aplikacji w pliku src/app/app.module.ts z jego konfiguracjami. Kod powinien teraz wyglądać następująco: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 {}
Chronione zasoby są udostępniane jako
protectedResourceMap
. Adresy URL podane w kolekcji są uwzględniane wprotectedResourceMap
wielkości liter. Dla każdego zasobu dodaj żądane zakresy do zwrócenia w tokenie dostępu.Na przykład:
["user.read"]
dla programu Microsoft Graph["<Application ID URL>/scope"]
dla niestandardowych internetowych interfejsów API (czyliapi://<Application ID>/access_as_user
)
Zmodyfikuj wartości w obiekcie zgodnie z
protectedResourceMap
opisem w tym miejscu:Enter_the_Graph_Endpoint_Here
to wystąpienie interfejsu API programu Microsoft Graph, z którymi aplikacja powinna się komunikować. W przypadku globalnego punktu końcowego interfejsu API programu Microsoft Graph zastąp ten ciągiemhttps://graph.microsoft.com
. Aby uzyskać informacje o punktach końcowych we wdrożeniach chmury krajowej, zobacz Wdrożenia chmury krajowej w dokumentacji programu Microsoft Graph.
Zastąp kod w pliku src/app/profile/profile.component.ts , aby pobrać profil użytkownika z żądaniem HTTP, a następnie zastąp element
GRAPH_ENDPOINT
punktem końcowym programu Microsoft Graph: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; }); } }
Zastąp interfejs użytkownika w src/app/profile/profile.component.html , aby wyświetlić informacje o profilu:
<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>
Wyloguj się
Zaktualizuj kod w src/app/app.component.html , aby warunkowo wyświetlić
Logout
przycisk:<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>
Wyloguj się przy użyciu przekierowań
Zaktualizuj kod w pliku src/app/app.component.ts , aby wylogować użytkownika przy użyciu przekierowań:
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(); } }
Wyloguj się przy użyciu wyskakujących okienek
Zaktualizuj kod w pliku src/app/app.component.ts , aby wylogować użytkownika przy użyciu wyskakujących okienek:
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(); } }
testowanie kodu
Uruchom serwer internetowy, aby nasłuchiwać portu, uruchamiając następujące polecenia w wierszu polecenia z folderu aplikacji:
npm install npm start
W przeglądarce wprowadź ciąg
http://localhost:4200
, a powinna zostać wyświetlona strona podobna do poniższej.Wybierz pozycję Akceptuj , aby przyznać uprawnienia aplikacji do profilu. Stanie się to przy pierwszym rozpoczęciu logowania.
Po wyrażeniu zgody w przypadku wyrażenia zgody na żądane uprawnienia aplikacja internetowa wyświetli pomyślną stronę logowania.
Wybierz pozycję Profil , aby wyświetlić informacje o profilu użytkownika zwrócone w odpowiedzi z wywołania interfejsu API programu Microsoft Graph:
Dodawanie zakresów i uprawnień delegowanych
Interfejs API programu Microsoft Graph wymaga zakresu User.Read w celu odczytania profilu użytkownika. Zakres User.Read jest dodawany automatycznie do każdej rejestracji aplikacji. Inne interfejsy API dla programu Microsoft Graph i niestandardowe interfejsy API dla serwera zaplecza mogą wymagać innych zakresów. Na przykład interfejs API programu Microsoft Graph wymaga zakresu Mail.Read , aby wyświetlić listę wiadomości e-mail użytkownika.
Podczas dodawania zakresów użytkownicy mogą być monitowani o udzielenie dodatkowej zgody dla dodanych zakresów.
Uwaga
Użytkownik może zostać poproszony o dodatkowe zgody w miarę zwiększania liczby zakresów.
Pomoc i obsługa techniczna
Jeśli potrzebujesz pomocy, chcesz zgłosić problem lub poznać opcje pomocy technicznej, zobacz Pomoc i obsługa techniczna dla deweloperów.
Następne kroki
- Dowiedz się więcej, tworząc aplikację jednostronicową React (SPA), która loguje użytkowników w poniższej serii samouczków wieloczęściowych.