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

Diagram showing the authorization code flow in a single-page application

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.

  1. Zaloguj się do centrum administracyjnego firmy Microsoft Entra co najmniej jako deweloper aplikacji.
  2. 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.
  3. Przejdź do aplikacji tożsamości>> Rejestracje aplikacji.
  4. Wybierz opcjęNowa rejestracja.
  5. Wprowadź nazwę aplikacji, na przykład Angular-SPA-auth-code.
  6. 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 .
  7. W obszarze Identyfikator URI przekierowania (opcjonalnie) użyj menu rozwijanego, aby wybrać pozycję Aplikacja jednostronicowa (SPA) i wprowadzić http://localhost:4200 w polu tekstowym.
  8. Wybierz pozycję Zarejestruj.
  9. 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

  1. Otwórz program Visual Studio Code, wybierz pozycję Plik>Otwórz folder.... Przejdź do i wybierz lokalizację, w której chcesz utworzyć projekt.

  2. Otwórz nowy terminal, wybierając pozycję Terminal>Nowy terminal.

    1. Może być konieczne przełączenie typów terminali. Wybierz strzałkę w dół obok + ikony w terminalu i wybierz pozycję Wiersz polecenia.
  3. 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

  1. Otwórz plik src/app/app.module.ts. Element MsalModule i MsalInterceptor należy dodać do imports 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 {}
    
  2. 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ąp Enter_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.
    • 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 na http://localhost:4200.
  3. 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 {}
    
  4. 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>
    
  5. 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%;
    }
    
  6. 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

  1. 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ń

  1. 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 i AppComponent bootstrap w następujący sposób:

    ...
    import { MsalModule, MsalRedirectComponent } from '@azure/msal-angular'; // Updated import
    ...
      bootstrap: [AppComponent, MsalRedirectComponent] // MsalRedirectComponent bootstrapped here
    ...
    
  2. 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>
    
  3. 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;
      }
    }
    
  4. 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.

  1. 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();
      }
    }
    
  2. 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;
      }
    }
    
  3. 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.

  1. Dodaj klasę MsalGuard jako dostawcę w aplikacji w pliku src/app/app.module.ts i dodaj konfiguracje dla elementu MsalGuard. Zakresy potrzebne do uzyskiwania tokenów można później podać w elemecie authRequest, a typ interakcji dla funkcji Guard można ustawić na Redirect lub Popup. 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 {}
    
  2. 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 {}
    
  3. 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.

  1. 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 w protectedResourceMap 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 (czyli api://<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ągiem https://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.
  2. 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;
          });
      }
    }
    
  3. 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ę

  1. 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ń

  1. 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

  1. 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

  1. Uruchom serwer internetowy, aby nasłuchiwać portu, uruchamiając następujące polecenia w wierszu polecenia z folderu aplikacji:

    npm install
    npm start
    
  2. W przeglądarce wprowadź ciąg http://localhost:4200, a powinna zostać wyświetlona strona podobna do poniższej.

    Web browser displaying sign-in dialog

  3. Wybierz pozycję Akceptuj , aby przyznać uprawnienia aplikacji do profilu. Stanie się to przy pierwszym rozpoczęciu logowania.

    Content dialog displayed in web browser

  4. Po wyrażeniu zgody w przypadku wyrażenia zgody na żądane uprawnienia aplikacja internetowa wyświetli pomyślną stronę logowania.

    Results of a successful sign-in in the web browser

  5. Wybierz pozycję Profil , aby wyświetlić informacje o profilu użytkownika zwrócone w odpowiedzi z wywołania interfejsu API programu Microsoft Graph:

    Profile information from Microsoft Graph displayed in the browser

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