Exercice : ajouter l’authentification utilisateur

Effectué

Votre application web de liste d’achats a besoin d’une authentification utilisateur. Dans cet exercice, vous allez implémenter la connexion et la déconnexion dans votre application et afficher l’état de la connexion de l’utilisateur actuel.

Dans cet exercice, vous allez effectuer les étapes suivantes :

  1. Installer l’interface CLI Static Web Apps pour le développement local.
  2. Exécuter l’application et l’API localement avec l’émulation d’authentification locale.
  3. Ajouter des boutons de connexion pour plusieurs fournisseurs d’authentification.
  4. Ajouter un bouton de déconnexion si l’utilisateur est connecté.
  5. Afficher l’état de la connexion de l’utilisateur.
  6. Tester le workflow d’authentification localement.
  7. Déployer l’application mise à jour.

Se préparer pour le développement local

L’interface CLI Static Web Apps, également appelée interface CLI SWA, est un outil de développement local qui vous permet d’exécuter votre application web et votre API localement et d’émuler les serveurs d’authentification et d’autorisation.

  1. Ouvrez un terminal sur votre ordinateur.

  2. Installez l’interface CLI SWA en exécutant la commande suivante.

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

Exécutez l’application localement.

Nous exécutons maintenant l’application et l’API localement avec un serveur de développement. De cette façon, vous serez en mesure de voir et de tester vos modifications au fur et à mesure.

  1. Ouvrez le projet dans Visual Studio Code.

  2. Dans Visual Studio Code, ouvrez la palette de commandes en appuyant sur F1.

  3. Entrez et sélectionnez Terminal : Créer un terminal intégré.

  4. Accédez au dossier de votre framework front-end par défaut comme suit :

    cd angular-app
    
    cd react-app
    
    cd svelte-app
    
    cd vue-app
    
  5. Exécutez l’application cliente front-end à l’aide d’un serveur de développement.

    npm start
    
    npm start
    
    npm run dev
    
    npm run serve
    

    Laissez ce serveur en cours d’exécution en arrière-plan. Nous exécutons maintenant l’API et l’émulateur du serveur d’authentification à l’aide de l’interface CLI SWA.

  6. Dans Visual Studio Code, ouvrez la palette de commandes en appuyant sur F1.

  7. Entrez et sélectionnez Terminal : Créer un terminal intégré.

  8. Installez l’interface CLI SWA en exécutant la commande suivante :

    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. Accédez à http://localhost:4280.

Le port final utilisé par l’interface CLI SWA est différent de celui que vous avez vu précédemment, car il utilise un proxy inverse pour transférer des requêtes vers les trois composants différents :

  • votre serveur de développement d’infrastructure
  • l’émulateur d'authentification et d'autorisation
  • l’API hébergée par le runtime Functions

Capture d’écran de l’architecture de l’interface CLI Static Web Apps.

Laissez l’application s’exécuter pendant que vous modifiez le code.

Obtenir l’état de connexion de l’utilisateur

Tout d’abord, vous devez accéder à l’état de la connexion de l’utilisateur en exécutant une requête pour /.auth/me dans le client.

  1. Créez le fichier angular-app/src/app/core/models/user-info.ts et ajoutez le code suivant pour représenter l’interface des informations utilisateur.

    export interface UserInfo {
      identityProvider: string;
      userId: string;
      userDetails: string;
      userRoles: string[];
    }
    
  2. Ouvrez le fichier angular-app/src/app/core/components/nav.component.ts et ajoutez la méthode suivante dans la 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. Créez une propriété de classe userInfo et stockez le résultat de la fonction async getUserInfo() quand le composant est initialisé. Implémentez l’interface OnInit et mettez à jour les instructions d’importation pour importer OnInit et UserInfo. Ce code extrait les informations utilisateur quand le composant est initialisé.

    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. Modifiez le fichier react-app/src/components/NavBar.js et ajoutez le code suivant en haut de la fonction. Ce code extrait les informations utilisateur quand le composant se charge et le stocke dans l’état.

    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. Modifiez le fichier svelte-app/src/components/NavBar.svelte et ajoutez le code suivant dans la section de script. Ce code extrait les informations utilisateur quand le composant se charge.

    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. Ouvrez le fichier vue-app/src/components/nav-bar.vue et ajoutez userInfo à l’objet de données.

     data() {
       return {
         userInfo: {
           type: Object,
           default() {},
         },
       };
     },
    
  2. Ajoutez la méthode getUserInfo() à la section 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. Ajoutez le hook de cycle de vie created au composant.

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

    Une fois le composant créé, les informations utilisateur sont automatiquement récupérées.

Ajouter des boutons de connexion et de déconnexion

Les informations utilisateur sont undefined si vous n’êtes pas connecté : les changements ne sont donc pas visibles pour le moment. Il est temps d’ajouter des boutons de connexion pour les différents fournisseurs.

  1. Ouvrez le fichier angular-app/src/app/core/components/nav.component.ts pour ajouter une liste de fournisseurs dans la classe NavComponent.

    providers = ['x', 'github', 'aad'];
    
  2. Ajoutez la propriété redirect suivante afin de capturer l’URL actuelle pour la redirection après connexion.

    redirect = window.location.pathname;
    
  3. Ajoutez le code suivant dans le modèle après le premier élément </nav> pour afficher les boutons de connexion et de déconnexion.

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

    Si l’utilisateur n’est pas connecté, vous affichez le bouton de connexion pour chaque fournisseur. Chaque bouton est lié à /.auth/login/<AUTH_PROVIDER> et définit l’URL de redirection vers la page actuelle.

    Sinon, si l’utilisateur est déjà connecté, s’affiche un bouton de déconnexion qui est lié à /.auth/logout et qui définit également l’URL de redirection vers la page actuelle.

Vous devez maintenant voir cette page web dans votre navigateur.

Capture d’écran de l’application web Angular avec les boutons de connexion.

  1. Ouvrez le fichier react-app/src/components/NavBar.js pour ajouter une liste de fournisseurs en haut de la fonction.

    const providers = ['x', 'github', 'aad'];
    
  2. Ajoutez la variable redirect suivante en dessous de la première variable afin de capturer l’URL actuelle pour la redirection après connexion.

    const redirect = window.location.pathname;
    
  3. Ajoutez le code suivant dans le modèle JSX après le premier élément </nav> pour afficher les boutons de connexion et de déconnexion.

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

    Si l’utilisateur n’est pas connecté, vous affichez le bouton de connexion pour chaque fournisseur. Chaque bouton est lié à /.auth/login/<AUTH_PROVIDER> et définit l’URL de redirection vers la page actuelle.

    Sinon, si l’utilisateur est déjà connecté, vous affichez un bouton de déconnexion qui est lié à /.auth/logout et qui définit également l’URL de redirection vers la page actuelle.

Vous devez maintenant voir cette page web dans votre navigateur.

Capture d’écran de l’application web React avec les boutons de connexion.

  1. Ouvrez le fichier svelte-app/src/components/NavBar.svelte pour ajouter une liste de fournisseurs en haut du script.

    const providers = ['x', 'github', 'aad'];
    
  2. Ajoutez la variable redirect suivante en dessous de la première variable afin de capturer l’URL actuelle pour la redirection après connexion.

    const redirect = window.location.pathname;
    
  3. Ajoutez le code suivant dans le modèle après le premier élément </nav> pour afficher les boutons de connexion et de déconnexion.

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

    Si l’utilisateur n’est pas connecté, vous affichez le bouton de connexion pour chaque fournisseur. Chaque bouton est lié à /.auth/login/<AUTH_PROVIDER> et définit l’URL de redirection vers la page actuelle.

    Sinon, si l’utilisateur est déjà connecté, vous affichez un bouton de déconnexion qui est lié à /.auth/logout et qui définit également l’URL de redirection vers la page actuelle.

Vous devez maintenant voir cette page web dans votre navigateur.

Capture d’écran de l’application web Svelte avec les boutons de connexion.

  1. Ouvrez le fichier vue-app/src/components/nav-bar.vue et ajoutez une liste de fournisseurs à l’objet de données.

     providers: ['x', 'github', 'aad'],
    
  2. Ajoutez la propriété redirect suivante afin de capturer l’URL actuelle pour la redirection après connexion.

     redirect: window.location.pathname,
    
  3. Ajoutez le code suivant dans le modèle après le premier élément </nav> pour afficher les boutons de connexion et de déconnexion.

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

    Si l’utilisateur n’est pas connecté, vous affichez le bouton de connexion pour chaque fournisseur. Chaque bouton est lié à /.auth/login/<AUTH_PROVIDER> et définit l’URL de redirection vers la page actuelle.

    Sinon, si l’utilisateur est déjà connecté, vous affichez un bouton de déconnexion qui est lié à /.auth/logout et qui définit également l’URL de redirection vers la page actuelle.

Vous devez maintenant voir cette page web dans votre navigateur.

Capture d’écran de l’application web Vue avec les boutons de connexion.

Afficher l’état de connexion de l’utilisateur

Avant de tester le workflow d’authentification, affichons les détails de l’utilisateur connecté.

Modifiez le fichier angular-app/src/app/core/components/nav.component.ts et ajoutez ce code en bas du modèle après l’étiquette de fermeture finale </nav>.

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

Notes

La propriété userDetails peut être un nom d’utilisateur ou une adresse e-mail, en fonction de l’identité fournie utilisée pour la connexion.

Votre fichier terminé doit maintenant ressembler à ceci :

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 = ['x', '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;
    }
  }
}

Modifiez le fichier react-app/src/components/NavBar.js et ajoutez ce code en bas du modèle JSX, après l’étiquette de fermeture finale </nav>, pour afficher l’état de connexion.

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

Notes

La propriété userDetails peut être un nom d’utilisateur ou une adresse e-mail, en fonction de l’identité fournie utilisée pour la connexion.

Votre fichier terminé doit maintenant ressembler à ceci :

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

const NavBar = (props) => {
  const providers = ['x', '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;

Modifiez le fichier svelte-app/src/components/NavBar.svelte et ajoutez ce code en bas du modèle, après l’étiquette de fermeture finale </nav>, pour afficher l’état de connexion.

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

Notes

La propriété userDetails peut être un nom d’utilisateur ou une adresse e-mail, en fonction de l’identité fournie utilisée pour la connexion.

Votre fichier terminé doit maintenant ressembler à ceci :

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

  const providers = ['x', '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>

Modifiez le fichier vue-app/src/components/nav-bar.vue et ajoutez ce code en bas du modèle, après l’étiquette de fermeture finale </nav>, pour afficher l’état de connexion :

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

Notes

La propriété userDetails peut être un nom d’utilisateur ou une adresse e-mail, en fonction de l’identité fournie utilisée pour la connexion.

Votre fichier terminé doit maintenant ressembler à ceci :

<script>
  export default {
    name: 'NavBar',
    data() {
      return {
        userInfo: {
          type: Object,
          default() {},
        },
        providers: ['x', '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>

Tester l’authentification localement

Tout est maintenant en place. L’étape finale consiste à tester si tout fonctionne comme prévu.

  1. Dans votre application web, sélectionnez l’un des fournisseurs d’identité à connecter.

  2. Vous serez redirigé vers cette page :

    Capture d’écran montrant l’écran d’authentification fictif de l’interface CLI SWA.

    C’est un écran d’authentification fictif fourni par l’interface CLI SWA, qui vous permet de tester l’authentification localement en fournissant vous-même les détails de l’utilisateur.

  3. Entrez mslearn comme nom d'utilisateur et 1234 pour l’identifiant utilisateur.

  4. Sélectionnez Connexion.

    Après la connexion, vous êtes redirigé vers la page précédente. Vous pouvez voir que les boutons de connexion ont été remplacés par un bouton de déconnexion. Vous pouvez également voir votre nom d’utilisateur et le fournisseur sélectionné sous le bouton de déconnexion.

    Maintenant que vous avez vérifié que tout fonctionne comme prévu localement, il est temps de déployer vos modifications.

  5. Vous pouvez arrêter l’application en cours d’exécution et l’API en appuyant sur Ctrl-C dans les deux terminaux.

Déployez vos modifications

  1. Dans Visual Studio Code, ouvrez la palette de commandes en appuyant sur F1.

  2. Entrez et sélectionnez Git: Commit All.

  3. Entrez Add authentication comme message de commit, puis appuyez sur Entrée.

  4. Ouvrez la palette de commandes en appuyant sur F1.

  5. Entrez et sélectionnez Git: Push, puis appuyez sur Entrée.

Après avoir envoyé vos modifications, attendez que le processus de génération et de déploiement s’exécute. Par la suite, les modifications doivent être visibles sur votre application déployée.

Étapes suivantes

Votre application prend désormais en charge l’authentification utilisateur et l’étape suivante consiste à limiter certaines parties de l’application aux utilisateurs non authentifiés.