Esercizio: Aggiungere l'autenticazione utente

Completato

L'app Web per la lista della spesa richiede l'autenticazione utente. In questo esercizio si implementerà l'accesso e la disconnessione nell'app e si visualizzerà lo stato di accesso dell'utente corrente.

In questo esercizio verranno completati i passaggi seguenti:

  1. Installare l'interfaccia della riga di comando di App Web statiche per lo sviluppo locale.
  2. Eseguire l'app e l'API in locale con l'emulazione di autenticazione locale.
  3. Aggiungere i pulsanti di accesso per più provider di autenticazione.
  4. Aggiungere un pulsante di disconnessione se l'utente è connesso.
  5. Visualizzare lo stato di accesso dell'utente.
  6. Testare il flusso di lavoro di autenticazione in locale.
  7. Distribuire l'app aggiornata.

Prepararsi per lo sviluppo locale

L'interfaccia della riga di comando di App Web statiche, chiamata anche interfaccia della riga di comando di SWA, è uno strumento di sviluppo locale che consente di eseguire l'app Web e l'API in locale e di emulare i server di autenticazione e autorizzazione.

  1. Aprire un terminale nel computer.

  2. Installare l'interfaccia della riga di comando di SWA eseguendo questo comando.

    npm install -g @azure/static-web-apps-cli
    

Eseguire l'app in locale

Eseguire ora l'app e l'API in locale con un server di sviluppo. In questo modo, sarà possibile visualizzare e testare le modifiche apportate nel codice.

  1. Aprire il progetto in Visual Studio Code.

  2. In Visual Studio Code aprire il riquadro comandi premendo F1.

  3. Immettere e selezionare Terminale: Crea nuovo terminale integrato.

  4. Passare alla cartella del framework front-end preferito, come mostrato di seguito:

    cd angular-app
    
    cd react-app
    
    cd svelte-app
    
    cd vue-app
    
  5. Eseguire l'applicazione client front-end usando un server di sviluppo.

    npm start
    
    npm start
    
    npm run dev
    
    npm run serve
    

    Lasciare il server in esecuzione in background. Eseguire ora l'API e l'emulatore del server di autenticazione usando l'interfaccia della riga di comando di SWA.

  6. In Visual Studio Code aprire il riquadro comandi premendo F1.

  7. Immettere e selezionare Terminale: Crea nuovo terminale integrato.

  8. Eseguire l'interfaccia della riga di comando di SWA eseguendo questo comando:

    swa start http://localhost:4200 --api-location ./api
    
    swa start http://localhost:3000 --api-location ./api
    
    swa start http://localhost:5000 --api-location ./api
    
    swa start http://localhost:8080 --api-location ./api
    
  9. Passa a http://localhost:4280.

La porta finale usata dall'interfaccia della riga di comando di SWA è diversa da quella descritta in precedenza poiché usa un proxy inverso per inoltrare le richieste ai tre diversi componenti:

  • Server di sviluppo del framework
  • Emulatore di autenticazione e autorizzazione
  • API ospitata dal runtime di Funzioni

Screenshot of the Static Web Apps CLI architecture.

Lasciare l'applicazione in esecuzione mentre si modifica il codice.

Ottenere lo stato di accesso dell'utente

È prima di tutto necessario accedere allo stato di accesso dell'utente effettuando una query in /.auth/me nel client.

  1. Creare il file angular-app/src/app/core/models/user-info.ts e aggiungere il codice seguente per rappresentare l'interfaccia per le informazioni utente.

    export interface UserInfo {
      identityProvider: string;
      userId: string;
      userDetails: string;
      userRoles: string[];
    }
    
  2. Modificare il file angular-app/src/app/core/components/nav.component.ts e aggiungere il metodo seguente nella classe NavComponent.

    async getUserInfo() {
      try {
        const response = await fetch('/.auth/me');
        const payload = await response.json();
        const { clientPrincipal } = payload;
        return clientPrincipal;
      } catch (error) {
        console.error('No profile could be found');
        return undefined;
      }
    }
    
  3. Creare una nuova proprietà della classe userInfo e archiviare il risultato della funzione asincrona getUserInfo() quando il componente viene inizializzato. Implementare l'interfaccia OnInit e aggiornare le istruzioni di importazione per importare OnInit e UserInfo. Questo codice recupera le informazioni utente quando il componente viene inizializzato.

    import { Component, OnInit } from '@angular/core';
    import { UserInfo } from '../model/user-info';
    
    export class NavComponent implements OnInit {
      userInfo: UserInfo;
    
      async ngOnInit() {
        this.userInfo = await this.getUserInfo();
      }
      // ...
    }
    
  1. Modificare il file react-app/src/components/NavBar.js e aggiungere il codice seguente all'inizio della funzione. Questo codice recupera le informazioni utente quando il componente viene caricato e le archivia nello stato.

    import React, { useState, useEffect } from 'react';
    import { NavLink } from 'react-router-dom';
    
    const NavBar = (props) => {
      const [userInfo, setUserInfo] = useState();
    
      useEffect(() => {
        (async () => {
          setUserInfo(await getUserInfo());
        })();
      }, []);
    
      async function getUserInfo() {
        try {
          const response = await fetch('/.auth/me');
          const payload = await response.json();
          const { clientPrincipal } = payload;
          return clientPrincipal;
        } catch (error) {
          console.error('No profile could be found');
          return undefined;
        }
      }
    
      return (
      // ...
    
  1. Modificare il file svelte-app/src/components/NavBar.svelte e aggiungere il codice seguente nella sezione dello script. Questo codice recupera le informazioni utente quando il componente viene caricato.

    import { onMount } from 'svelte';
    
    let userInfo = undefined;
    
    onMount(async () => (userInfo = await getUserInfo()));
    
    async function getUserInfo() {
      try {
        const response = await fetch('/.auth/me');
        const payload = await response.json();
        const { clientPrincipal } = payload;
        return clientPrincipal;
      } catch (error) {
        console.error('No profile could be found');
        return undefined;
      }
    }
    
  1. Modificare il file vue-app/src/components/nav-bar.vue e aggiungere userInfo all'oggetto dati.

     data() {
       return {
         userInfo: {
           type: Object,
           default() {},
         },
       };
     },
    
  2. Aggiungere il metodo getUserInfo() nella sezione methods.

    methods: {
      async getUserInfo() {
        try {
          const response = await fetch('/.auth/me');
          const payload = await response.json();
          const { clientPrincipal } = payload;
          return clientPrincipal;
        } catch (error) {
          console.error('No profile could be found');
          return undefined;
        }
      },
    },
    
  3. Aggiungere l'hook del ciclo di vita created al componente.

    async created() {
      this.userInfo = await this.getUserInfo();
    },
    

    Quando il componente viene creato, le informazioni utente vengono recuperate automaticamente.

Aggiungere pulsanti di accesso e disconnessione

Le informazioni utente saranno undefined se non è stato eseguito l'accesso, quindi le modifiche non saranno visibili per il momento. È possibile aggiungere pulsanti di accesso per i diversi provider.

  1. Modificare il file angular-app/src/app/core/components/nav.component.ts per aggiungere un elenco di provider nella classe NavComponent.

    providers = ['twitter', 'github', 'aad'];
    
  2. Aggiungere la proprietà redirect seguente per acquisire l'URL corrente per il reindirizzamento successivo all'accesso.

    redirect = window.location.pathname;
    
  3. Aggiungere il codice seguente al modello dopo il primo elemento </nav> per visualizzare i pulsanti di accesso e disconnessione.

    <nav class="menu auth">
      <p class="menu-label">Auth</p>
      <div class="menu-list auth">
        <ng-container *ngIf="!userInfo; else logout">
          <ng-container *ngFor="let provider of providers">
            <a href="/.auth/login/{{provider}}?post_login_redirect_uri={{redirect}}">{{provider}}</a>
          </ng-container>
        </ng-container>
        <ng-template #logout>
          <a href="/.auth/logout?post_logout_redirect_uri={{redirect}}">Logout</a>
        </ng-template>
      </div>
    </nav>
    

    Se l'utente non è connesso, viene visualizzato il pulsante di accesso per ogni provider. Ogni pulsante collega a /.auth/login/<AUTH_PROVIDER> e imposta l'URL di reindirizzamento sulla pagina corrente.

    In caso contrario, se l'utente è già connesso, viene visualizzato un pulsante di disconnessione che collega a /.auth/logout e imposta anche l'URL di reindirizzamento sulla pagina corrente.

Nel browser verrà visualizzata questa pagina Web.

Screenshot of the Angular web app with login buttons.

  1. Modificare il file react-app/src/components/NavBar.js per aggiungere un elenco di provider all'inizio della funzione.

    const providers = ['twitter', 'github', 'aad'];
    
  2. Aggiungere la variabile redirect seguente sotto la prima variabile per acquisire l'URL corrente per il reindirizzamento successivo all'accesso.

    const redirect = window.location.pathname;
    
  3. Aggiungere il codice seguente al modello JSX dopo il primo elemento </nav> per visualizzare i pulsanti di accesso e disconnessione.

    <nav className="menu auth">
      <p className="menu-label">Auth</p>
      <div className="menu-list auth">
        {!userInfo &&
          providers.map((provider) => (
            <a key={provider} href={`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`}>
              {provider}
            </a>
          ))}
        {userInfo && <a href={`/.auth/logout?post_logout_redirect_uri=${redirect}`}>Logout</a>}
      </div>
    </nav>
    

    Se l'utente non è connesso, viene visualizzato il pulsante di accesso per ogni provider. Ogni pulsante collega a /.auth/login/<AUTH_PROVIDER> e imposta l'URL di reindirizzamento sulla pagina corrente.

    In caso contrario, se l'utente è già connesso, viene visualizzato un pulsante di disconnessione che collega a /.auth/logout e imposta anche l'URL di reindirizzamento sulla pagina corrente.

Nel browser verrà visualizzata questa pagina Web.

Screenshot of the React web app with login buttons.

  1. Modificare il file svelte-app/src/components/NavBar.svelte per aggiungere un elenco di provider all'inizio dello script.

    const providers = ['twitter', 'github', 'aad'];
    
  2. Aggiungere la variabile redirect seguente sotto la prima variabile per acquisire l'URL corrente per il reindirizzamento successivo all'accesso.

    const redirect = window.location.pathname;
    
  3. Aggiungere il codice seguente al modello dopo il primo elemento </nav> per visualizzare i pulsanti di accesso e disconnessione.

     <nav class="menu auth">
       <p class="menu-label">Auth</p>
       <div class="menu-list auth">
         {#if !userInfo}
           {#each providers as provider (provider)}
             <a href={`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`}>
               {provider}
             </a>
           {/each}
         {/if}
         {#if userInfo}
           <a href={`/.auth/logout?post_logout_redirect_uri=${redirect}`}>
             Logout
           </a>
         {/if}
       </div>
     </nav>
    

    Se l'utente non è connesso, viene visualizzato il pulsante di accesso per ogni provider. Ogni pulsante collega a /.auth/login/<AUTH_PROVIDER> e imposta l'URL di reindirizzamento sulla pagina corrente.

    In caso contrario, se l'utente è già connesso, viene visualizzato un pulsante di disconnessione che collega a /.auth/logout e imposta anche l'URL di reindirizzamento sulla pagina corrente.

Nel browser verrà visualizzata questa pagina Web.

Screenshot of the Svelte web app with login buttons.

  1. Modificare il file vue-app/src/components/nav-bar.vue e aggiungere un elenco di provider all'oggetto dati.

     providers: ['twitter', 'github', 'aad'],
    
  2. Aggiungere la proprietà redirect seguente per acquisire l'URL corrente per il reindirizzamento successivo all'accesso.

     redirect: window.location.pathname,
    
  3. Aggiungere il codice seguente al modello dopo il primo elemento </nav> per visualizzare i pulsanti di accesso e disconnessione.

    <nav class="menu auth">
      <p class="menu-label">Auth</p>
      <div class="menu-list auth">
        <template v-if="!userInfo">
          <template v-for="provider in providers">
            <a :key="provider" :href="`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`">
              {{ provider }}
            </a>
          </template>
        </template>
        <a v-if="userInfo" :href="`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`"> Logout </a>
      </div>
    </nav>
    

    Se l'utente non è connesso, viene visualizzato il pulsante di accesso per ogni provider. Ogni pulsante collega a /.auth/login/<AUTH_PROVIDER> e imposta l'URL di reindirizzamento sulla pagina corrente.

    In caso contrario, se l'utente è già connesso, viene visualizzato un pulsante di disconnessione che collega a /.auth/logout e imposta anche l'URL di reindirizzamento sulla pagina corrente.

Nel browser verrà visualizzata questa pagina Web.

Screenshot of the Vue web app with login buttons.

Visualizzare lo stato di accesso dell'utente

Prima di testare il flusso di lavoro di autenticazione, visualizzare i dettagli dell'utente connesso.

Modificare il file angular-app/src/app/core/components/nav.component.ts e aggiungere questo codice nella parte inferiore del modello dopo il tag </nav> di chiusura finale.

<div class="user" *ngIf="userInfo">
  <p>Welcome</p>
  <p>{{ userInfo?.userDetails }}</p>
  <p>{{ userInfo?.identityProvider }}</p>
</div>

Nota

La proprietà userDetails può essere un nome utente o un indirizzo di posta elettronica, a seconda dell'identità usata per l'accesso.

Il file completato dovrà essere simile al seguente:

import { Component, OnInit } from '@angular/core';
import { UserInfo } from '../model/user-info';

@Component({
  selector: 'app-nav',
  template: `
    <nav class="menu">
      <p class="menu-label">Menu</p>
      <ul class="menu-list">
        <a routerLink="/products" routerLinkActive="router-link-active">
          <span>Products</span>
        </a>
        <a routerLink="/about" routerLinkActive="router-link-active">
          <span>About</span>
        </a>
      </ul>
    </nav>
    <nav class="menu auth">
      <p class="menu-label">Auth</p>
      <div class="menu-list auth">
        <ng-container *ngIf="!userInfo; else logout">
          <ng-container *ngFor="let provider of providers">
            <a href="/.auth/login/{{ provider }}?post_login_redirect_uri={{ redirect }}">{{ provider }}</a>
          </ng-container>
        </ng-container>
        <ng-template #logout>
          <a href="/.auth/logout?post_logout_redirect_uri={{ redirect }}">Logout</a>
        </ng-template>
      </div>
    </nav>
    <div class="user" *ngIf="userInfo">
      <p>Welcome</p>
      <p>{{ userInfo?.userDetails }}</p>
      <p>{{ userInfo?.identityProvider }}</p>
    </div>
  `,
})
export class NavComponent implements OnInit {
  providers = ['twitter', 'github', 'aad'];
  redirect = window.location.pathname;
  userInfo: UserInfo;

  async ngOnInit() {
    this.userInfo = await this.getUserInfo();
  }

  async getUserInfo() {
    try {
      const response = await fetch('/.auth/me');
      const payload = await response.json();
      const { clientPrincipal } = payload;
      return clientPrincipal;
    } catch (error) {
      console.error('No profile could be found');
      return undefined;
    }
  }
}

Modificare il file react-app/src/components/NavBar.js e aggiungere questo codice nella parte inferiore del modello JSX dopo il tag </nav> di chiusura finale per visualizzare lo stato di accesso.

{
  userInfo && (
    <div>
      <div className="user">
        <p>Welcome</p>
        <p>{userInfo && userInfo.userDetails}</p>
        <p>{userInfo && userInfo.identityProvider}</p>
      </div>
    </div>
  )
}

Nota

La proprietà userDetails può essere un nome utente o un indirizzo di posta elettronica, a seconda dell'identità usata per l'accesso.

Il file completato dovrà essere simile al seguente:

import React, { useState, useEffect } from 'react';
import { NavLink } from 'react-router-dom';

const NavBar = (props) => {
  const providers = ['twitter', 'github', 'aad'];
  const redirect = window.location.pathname;
  const [userInfo, setUserInfo] = useState();

  useEffect(() => {
    (async () => {
      setUserInfo(await getUserInfo());
    })();
  }, []);

  async function getUserInfo() {
    try {
      const response = await fetch('/.auth/me');
      const payload = await response.json();
      const { clientPrincipal } = payload;
      return clientPrincipal;
    } catch (error) {
      console.error('No profile could be found');
      return undefined;
    }
  }

  return (
    <div className="column is-2">
      <nav className="menu">
        <p className="menu-label">Menu</p>
        <ul className="menu-list">
          <NavLink to="/products" activeClassName="active-link">
            Products
          </NavLink>
          <NavLink to="/about" activeClassName="active-link">
            About
          </NavLink>
        </ul>
        {props.children}
      </nav>
      <nav className="menu auth">
        <p className="menu-label">Auth</p>
        <div className="menu-list auth">
          {!userInfo &&
            providers.map((provider) => (
              <a key={provider} href={`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`}>
                {provider}
              </a>
            ))}
          {userInfo && <a href={`/.auth/logout?post_logout_redirect_uri=${redirect}`}>Logout</a>}
        </div>
      </nav>
      {userInfo && (
        <div>
          <div className="user">
            <p>Welcome</p>
            <p>{userInfo && userInfo.userDetails}</p>
            <p>{userInfo && userInfo.identityProvider}</p>
          </div>
        </div>
      )}
    </div>
  );
};

export default NavBar;

Modificare il file svelte-app/src/components/NavBar.svelte e aggiungere questo codice nella parte inferiore del modello dopo il tag </nav> di chiusura finale per visualizzare lo stato di accesso.

{#if userInfo}
<div class="user">
  <p>Welcome</p>
  <p>{userInfo && userInfo.userDetails}</p>
  <p>{userInfo && userInfo.identityProvider}</p>
</div>
{/if}

Nota

La proprietà userDetails può essere un nome utente o un indirizzo di posta elettronica, a seconda dell'identità usata per l'accesso.

Il file completato dovrà essere simile al seguente:

<script>
  import { onMount } from 'svelte';
  import { Link } from 'svelte-routing';

  const providers = ['twitter', 'github', 'aad'];
  const redirect = window.location.pathname;
  let userInfo = undefined;

  onMount(async () => (userInfo = await getUserInfo()));

  async function getUserInfo() {
    try {
      const response = await fetch('/.auth/me');
      const payload = await response.json();
      const { clientPrincipal } = payload;
      return clientPrincipal;
    } catch (error) {
      console.error('No profile could be found');
      return undefined;
    }
  }

  function getProps({ href, isPartiallyCurrent, isCurrent }) {
    const isActive = href === '/' ? isCurrent : isPartiallyCurrent || isCurrent;

    // The object returned here is spread on the anchor element's attributes
    if (isActive) {
      return { class: 'router-link-active' };
    }
    return {};
  }
</script>

<div class="column is-2">
  <nav class="menu">
    <p class="menu-label">Menu</p>
    <ul class="menu-list">
      <Link to="/products" {getProps}>Products</Link>
      <Link to="/about" {getProps}>About</Link>
    </ul>
  </nav>
  <nav class="menu auth">
    <p class="menu-label">Auth</p>
    <div class="menu-list auth">
      {#if !userInfo}
        {#each providers as provider (provider)}
          <a href={`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`}>
            {provider}
          </a>
        {/each}
      {/if}
      {#if userInfo}
        <a href={`/.auth/logout?post_logout_redirect_uri=${redirect}`}>
          Logout
        </a>
      {/if}
    </div>
  </nav>
  {#if userInfo}
    <div class="user">
      <p>Welcome</p>
      <p>{userInfo && userInfo.userDetails}</p>
      <p>{userInfo && userInfo.identityProvider}</p>
    </div>
  {/if}
</div>

Modificare il file vue-app/src/components/nav-bar.vue e aggiungere questo codice nella parte inferiore del modello dopo il tag </nav> di chiusura finale per visualizzare lo stato di accesso:

<div class="user" v-if="userInfo">
  <p>Welcome</p>
  <p>{{ userInfo.userDetails }}</p>
  <p>{{ userInfo.identityProvider }}</p>
</div>

Nota

La proprietà userDetails può essere un nome utente o un indirizzo di posta elettronica, a seconda dell'identità usata per l'accesso.

Il file completato dovrà essere simile al seguente:

<script>
  export default {
    name: 'NavBar',
    data() {
      return {
        userInfo: {
          type: Object,
          default() {},
        },
        providers: ['twitter', 'github', 'aad'],
        redirect: window.location.pathname,
      };
    },
    methods: {
      async getUserInfo() {
        try {
          const response = await fetch('/.auth/me');
          const payload = await response.json();
          const { clientPrincipal } = payload;
          return clientPrincipal;
        } catch (error) {
          console.error('No profile could be found');
          return undefined;
        }
      },
    },
    async created() {
      this.userInfo = await this.getUserInfo();
    },
  };
</script>

<template>
  <div column is-2>
    <nav class="menu">
      <p class="menu-label">Menu</p>
      <ul class="menu-list">
        <router-link to="/products">Products</router-link>
        <router-link to="/about">About</router-link>
      </ul>
    </nav>
    <nav class="menu auth">
      <p class="menu-label">Auth</p>
      <div class="menu-list auth">
        <template v-if="!userInfo">
          <template v-for="provider in providers">
            <a :key="provider" :href="`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`">{{ provider }}</a>
          </template>
        </template>
        <a v-if="userInfo" :href="`/.auth/logout?post_logout_redirect_uri=${redirect}`">Logout</a>
      </div>
    </nav>
    <div class="user" v-if="userInfo">
      <p>Welcome</p>
      <p>{{ userInfo.userDetails }}</p>
      <p>{{ userInfo.identityProvider }}</p>
    </div>
  </div>
</template>

Testare l'autenticazione in locale

Sono state eseguite tutte le operazioni necessarie. Il passaggio finale consiste nel testare se tutto funziona come previsto.

  1. Nell'app Web selezionare uno dei provider di identità per eseguire l'accesso.

  2. Si verrà reindirizzati a questa pagina:

    Screenshot showing SWA CLI fake authentication screen.

    Si tratta di una schermata di autenticazione fittizia, fornita dall'interfaccia della riga di comando di SWA che consente di testare l'autenticazione in locale specificando i dettagli dell'utente.

  3. Immettere mslearn come nome utente e 1234 come ID utente.

  4. Seleziona Accedi.

    Dopo l'accesso, si viene reindirizzati alla pagina precedente. È possibile osservare che i pulsanti di accesso sono stati sostituiti da un pulsante di disconnessione. Inoltre, il nome utente e il provider selezionato sono indicati sotto il pulsante di disconnessione.

    Dopo aver verificato che tutto funziona come previsto in locale, è possibile distribuire le modifiche.

  5. È possibile arrestare l'app e l'API in esecuzione premendo CTRL+C in entrambi i terminali.

Distribuire le modifiche

  1. In Visual Studio Code aprire il riquadro comandi premendo F1.

  2. Digitare e selezionare Git: Esegui commit di tutto.

  3. Immettere Add authentication come messaggio di commit e premere INVIO.

  4. Aprire il riquadro comandi premendo F1.

  5. Immettere e selezionare Git: Push e premere INVIO.

Dopo aver eseguito il push delle modifiche, attendere l'esecuzione del processo di compilazione e distribuzione. Al termine, le modifiche dovrebbero essere visibili nell'app distribuita.

Passaggi successivi

L'app ora supporta l'autenticazione utente e il passaggio successivo consiste nel limitare l'accesso ad alcune parti dell'app agli utenti non autenticati.