Tutorial: Inicio de sesión de usuarios y llamada a Microsoft Graph API desde una aplicación de página única (SPA) de Angular mediante el uso del flujo de código de autenticación

En este tutorial, creará una aplicación de página única (SPA) de Angular que inicia la sesión de los usuarios y llama a Microsoft Graph API mediante el flujo de código de autorización con PKCE. La aplicación de página única que cree usa la biblioteca de autenticación de Microsoft (MSAL) para Angular v2.

En este tutorial, aprenderá a:

  • Registro de la aplicación en el centro de administración de Microsoft Entra
  • Crear un proyecto de Angular con npm
  • Agregar código para admitir el inicio y el cierre de sesión de usuario
  • Agregar código para llamar a Microsoft Graph API
  • Prueba de la aplicación

MSAL Angular v2 mejora MSALAngular v1 al ser compatible con el flujo de código de autorización en el explorador, en lugar de utilizar el flujo de concesión implícita. MSAL Angular v2 no admite el flujo implícito.

Prerrequisitos

Funcionamiento de la aplicación de ejemplo

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

La aplicación de ejemplo que se crea con este tutorial permite que una aplicación de página única de Angular haga consultas a Microsoft Graph API o a una API web que acepte los tokens que emite la Plataforma de identidad de Microsoft. Usa la Biblioteca de autenticación de Microsoft (MSAL) para Angular v2, un contenedor de la biblioteca principal de MSAL.js v2. MSAL Angular permite que las aplicaciones de Angular 9 y versiones posteriores autentiquen a usuarios empresariales mediante Microsoft Entra ID, a usuarios de cuentas de Microsoft y a usuarios de identidades de redes sociales como Facebook, Google y LinkedIn. La biblioteca también permite que las aplicaciones obtengan acceso a los servicios en la nube de Microsoft y a Microsoft Graph.

En este escenario, después de que un usuario inicia sesión, se solicita un token de acceso y se agrega a las solicitudes HTTP mediante el encabezado de autorización. MSAL controla la adquisición y la renovación de tokens.

Bibliotecas

En este tutorial se usan las siguientes bibliotecas:

Biblioteca Descripción
MSAL Angular Biblioteca de autenticación de Microsoft para el contenedor de Angular de JavaScript
Explorador MSAL Biblioteca de autenticación de Microsoft para el paquete del explorador de JavaScript v2

Puede encontrar el código fuente de todas las bibliotecas MSAL.js en el repositorio microsoft-authentication-library-for-js en GitHub.

Obtención del ejemplo de código completado

¿Prefiere descargar el proyecto de muestra completado para este tutorial? Clonación del ms-identity-javascript-angular-spa

git clone https://github.com/Azure-Samples/ms-identity-javascript-angular-spa.git

Para continuar con el tutorial y crear la aplicación, vaya a la siguiente sección, Registro de la aplicación y registro de los identificadores .

Registro de la aplicación y los identificadores de registro

Sugerencia

Los pasos de este artículo pueden variar ligeramente en función del portal desde donde comienza.

Para completar el registro, proporcione un nombre a la aplicación, especifique los tipos de cuenta admitidos y agregue un URI de redireccionamiento. Una vez registrada, el panel Información general de la aplicación muestra los identificadores necesarios en el código fuente de la aplicación.

  1. Inicie sesión en el Centro de administración de Microsoft Entra al menos como Desarrollador de aplicaciones.
  2. Si tiene acceso a varios inquilinos, use el icono Configuración del menú superior para cambiar al inquilino en el que desea registrar la aplicación desde el menú Directorios y suscripciones.
  3. Vaya aIdentidad>Aplicaciones>Registros de aplicaciones.
  4. Seleccione Nuevo registro.
  5. Escriba un nombre para la aplicación, como Angular-SPA-auth-code.
  6. Para la opción Tipos de cuenta admitidos, seleccione Solo las cuentas de este directorio organizativo. Para obtener información sobre los distintos tipos de cuenta, selecciona la opción Ayudarme a elegir.
  7. En URI de redireccionamiento (opcional), use el menú desplegable para seleccionar Aplicación de página única (SPA) y escriba http://localhost:4200 en el cuadro de texto.
  8. Seleccione Registrar.
  9. El panel Información general de la aplicación se muestra cuando se completa el registro. Registrar el id. de directorio (inquilino) y el id. de aplicación (cliente) que se usará en el código fuente de la aplicación.

Creación del proyecto

  1. Abra Visual Studio Code, seleccione Archivo >Abrir carpeta.... Navegue y seleccione la ubicación en la que se va a crear el proyecto.

  2. Abra una terminal nueva seleccionando Terminal >Crear terminal.

    1. Es posible que tenga que modificar los tipos de terminal. Seleccione la flecha abajo situada junto al icono + del terminal y seleccione Símbolo del sistema.
  3. Ejecute los siguientes comandos para crear un nuevo proyecto de Angular con el nombre msal-angular-tutorial, instale bibliotecas de componentes de material de Angular, MSAL Browser, MSAL Angular y genere componentes de inicio y perfil.

    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
    

Configuración de la aplicación y edición de la interfaz de usuario base

  1. Abra src/app/app.module.ts. El MsalModule y MsalInterceptor deberán agregarse a imports junto con la constante isIE. También agregará los módulos de material. Reemplace todo el contenido del archivo de código por el siguiente fragmento de código:

    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. Reemplace los siguientes valores por los valores obtenidos del Centro de administración de Microsoft Entra. Para más información acerca de las opciones configurables disponibles, consulte Inicializar aplicaciones cliente.

    • clientId - El identificador de la aplicación, también denominado cliente. Reemplace Enter_the_Application_Id_Here por el valor de Id. de la aplicación (cliente) que se registró anteriormente en la página de resumen de la aplicación registrada.
    • authority - Se compone de dos partes:
      • La instancia es el punto de conexión del proveedor de nube. En el caso de la nube principal o global de Azure, escriba https://login.microsoftonline.com. Consulte los diferentes puntos de conexión disponibles en nubes nacionales.
      • El Id. de inquilino es el identificador del inquilino en el que está registrada la solicitud. Reemplace el _Enter_the_Tenant_Info_Here con el valor de Id. de directorio (inquilino) que se registró anteriormente en la página de resumen de la aplicación registrada.
    • redirectUri: la ubicación a la que el servidor de autorización envía al usuario una vez que se haya autorizado correctamente a la aplicación y se haya concedido un código de autorización o un token de acceso. Reemplace Enter_the_Redirect_Uri_Here por http://localhost:4200.
  3. Abra src/app/app-routing.module.ts y agregue rutas a los componentes de inicio y perfil. Reemplace todo el contenido del archivo de código por el siguiente fragmento de código:

    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. Abra src/app/app.component.html y reemplace el código existente por lo siguiente:

    <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. Abra src/style.css para definir el 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. Abra src/app/app.component.css para agregar estilos CSS a la aplicación:

    .toolbar-spacer {
      flex: 1 1 auto;
    }
    
    a.title {
      color: white;
    }
    

Inicio de sesión mediante elementos emergentes

  1. Abra src/app/app.component.ts y reemplace el contenido del archivo por lo siguiente para iniciar sesión a un usuario mediante una ventana emergente:

    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;
      }
    }
    

Inicio de sesión mediante redireccionamientos

  1. Actualice src/app/app.module.ts para arrancar MsalRedirectComponent. Se trata de un componente de redireccionamiento dedicado, el cual controla las redirecciones. Cambie la importación MsalModule y el arranque AppComponent para que se parezcan a lo siguiente:

    ...
    import { MsalModule, MsalRedirectComponent } from '@azure/msal-angular'; // Updated import
    ...
      bootstrap: [AppComponent, MsalRedirectComponent] // MsalRedirectComponent bootstrapped here
    ...
    
  2. Abra src/index.html y reemplace todo el contenido del archivo por el siguiente fragmento de código, el cual agrega el selector <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>
    
  3. Abra src/app/app.component.ts y reemplace el código por lo siguiente para iniciar sesión en un usuario mediante una redirección de marco completo:

    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. Vaya a src/app/home/home.component.ts y reemplace todo el contenido del archivo por el siguiente fragmento de código para suscribirse al evento 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);
          });
      }
    }
    

Representación condicional

Para representar cierta interfaz de usuario (UI) solo para los usuarios autenticados, los componentes tienen que suscribirse a MsalBroadcastService para ver si los usuarios han iniciado sesión y la interacción se ha completado.

  1. Agregue MsalBroadcastService a src/app/app.component.ts y suscríbase al objeto observable inProgress$ para comprobar si la interacción se ha completado y una cuenta ha iniciado sesión antes de representar la interfaz de usuario. Ahora el código debería ser similar a este:

    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. Actualice el código de src/app/home/home.component.ts para comprobar también si la interacción se completará antes de actualizar la interfaz de usuario. Ahora el código debería ser similar a este:

    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. Reemplace el código de src/app/home/home.component.html por las siguientes pantallas condicionales:

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

Implementación de Angular Guard

La clase MsalGuard es una que puede usar para proteger las rutas y requerir autenticación antes de acceder a la ruta protegida. En los pasos siguientes, agregue MsalGuard a la ruta Profile. Proteger la ruta Profile significa que, aunque un usuario no inicie sesión con el botón Login, si intenta acceder a la ruta Profile o seleccionar el botón Profile, MsalGuard le solicitará que se autentique mediante un elemento emergente o un redireccionamiento antes de mostrar la página Profile.

MsalGuard es una clase de utilidad que puede usar para mejorar la experiencia del usuario, pero no debería basarse en ella por seguridad. Los atacantes podría moverse por las protecciones del lado cliente y usted debería asegurarse de que el servidor no devuelva ningún dato al que el usuario no debiera acceder.

  1. Agregue la clase MsalGuard como proveedor en la aplicación en src/app/app.module.tsy agregue las configuraciones de MsalGuard. Los ámbitos necesarios para adquirir tokens más adelante se pueden proporcionar en authRequest y el tipo de interacción para la protección se puede establecer en Redirect o Popup. El código debe tener un aspecto parecido al siguiente:

    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. Establezca MsalGuard las rutas que desea proteger en 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. Ajuste las llamadas de inicio de sesión en src/app/app.component.ts para tener en cuenta el conjunto de authRequest en las configuraciones de protección. El código debe tener un aspecto parecido al siguiente.

    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();
      }
    }
    

Adquisición de un token

Interceptor de Angular

MSAL Angular proporciona una clase Interceptor que adquiere automáticamente tokens para las solicitudes salientes que usan el cliente http de Angular con los recursos protegidos conocidos.

  1. Agregue la clase Interceptor como proveedor a la aplicación en src/app/app.module.ts, con sus configuraciones. El código debe tener un aspecto parecido al siguiente.

    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 {}
    

    Los recursos protegidos se proporcionan como protectedResourceMap. Las direcciones URL que proporciona en la colección protectedResourceMap distinguen mayúsculas de minúsculas. Para cada recurso, agregue los ámbitos cuya devolución se solicita en el token de acceso.

    Por ejemplo:

    • ["user.read"] para Microsoft Graph
    • ["<Application ID URL>/scope"] para las API web personalizadas (es decir, api://<Application ID>/access_as_user)

    Modifique los valores en la sección protectedResourceMap, como se describe aquí:

    • Enter_the_Graph_Endpoint_Here es la instancia de Microsoft Graph API con la que la aplicación debe comunicarse. En el caso del punto de conexión de Microsoft Graph API global, puede reemplazar esta cadena por https://graph.microsoft.com. Para obtener información sobre los puntos de conexión en las implementaciones de nubes nacionales, consulte Implementaciones de nube nacionales en la documentación de Microsoft Graph.
  2. Reemplace el código de src/app/profile/profile.component.ts para recuperar el perfil de un usuario con una solicitud HTTP, y reemplace GRAPH_ENDPOINT por el punto de conexión de 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. Reemplace la interfaz de usuario de src/app/profile/profile.component.html para mostrar la información del perfil:

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

Cerrar sesión

  1. Actualice el código de src/app/app.component.html para mostrar condicionalmente un botón 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>
    

Cierre de sesión mediante redireccionamientos

  1. Actualice el código de src/app/app.component.ts para cerrar la sesión de un usuario mediante redirecciones:

    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();
      }
    }
    

Cierre de sesión mediante elementos emergentes

  1. Actualice el código de src/app/app.component.ts para cerrar la sesión de un usuario mediante elementos emergentes:

    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();
      }
    }
    

Prueba del código

  1. Inicie el servidor web para que escuche el puerto; para ello, ejecute los siguientes comandos desde la carpeta de la aplicación en un símbolo del sistema de la línea de comandos:

    npm install
    npm start
    
  2. En el explorador, escriba http://localhost:4200 y, a continuación, debería ver una página similar a la siguiente.

    Web browser displaying sign-in dialog

  3. Seleccione Aceptar para conceder los permisos de aplicación al perfil. Esto ocurrirá la primera vez que empiece a iniciar sesión.

    Content dialog displayed in web browser

  4. Después de dar su consentimiento, verá lo siguiente: Si da su consentimiento a los permisos solicitados, la aplicación web mostrará una página indicando que el inicio de sesión se ha realizado correctamente.

    Results of a successful sign-in in the web browser

  5. Seleccione Perfil para ver la información de perfil de usuario devuelta en la respuesta de la llamada a Microsoft Graph API:

    Profile information from Microsoft Graph displayed in the browser

Incorporación de ámbitos y permisos delegados

Microsoft Graph API requiere el ámbito User.Read para leer el perfil de un usuario. El ámbito User.Read se agrega automáticamente a cada registro de aplicación. Otras API de Microsoft Graph, así como las API personalizadas para el servidor back-end, podrían requerir otros ámbitos. Por ejemplo, Microsoft Graph API requiere el ámbito Mail.Read para mostrar el correo electrónico del usuario.

A medida que se agreguen ámbitos, puede que se pida a los usuarios que proporcionen consentimiento adicional para los ámbitos agregados.

Nota:

Es posible que se pida al usuario algún consentimiento adicional a medida que aumente el número de ámbitos.

Ayuda y soporte técnico

Si necesita ayuda, desea informar de un problema o desea obtener información sobre las opciones de soporte técnico, consulte Opciones de ayuda y soporte técnico para desarrolladores.

Pasos siguientes

  • Para obtener más información, cree una aplicación de página única (SPA) de React que inicie sesión de los usuarios en la siguiente serie de tutoriales de varias partes.