Övning – Lägga till användarautentisering

Slutförd

Webbappen för inköpslistan behöver användarautentisering. I den här övningen implementerar du inloggning och utloggning i appen och visar den aktuella användarinloggningsstatusen.

I den här övningen ska du:

  1. Installera Static Web Apps CLI för lokal utveckling.
  2. Kör appen och API:et lokalt med lokal autentiseringsemulering.
  3. Lägg till inloggningsknappar för flera autentiseringsprovidrar.
  4. Lägg till en utloggningsknapp om användaren är inloggad.
  5. Visa inloggningsstatus för användaren.
  6. Testa arbetsflödet för autentisering lokalt.
  7. Distribuera den uppdaterade appen.

Förbereda för lokal utveckling

Static Web Apps CLI, även kallat SWA CLI, är ett lokalt utvecklingsverktyg som gör att du kan köra webbappen och API:et lokalt och emulera autentiserings- och auktoriseringsservrar.

  1. Öppna en terminal på datorn.

  2. Installera SWA CLI som kör följande kommando.

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

Köra appen lokalt

Kör nu appen och API:et lokalt med en utvecklingsserver. På så sätt kan du se och testa dina ändringar, precis som du gör dem i koden.

  1. Öppna projektet i Visual Studio Code.

  2. Öppna kommandopaletten i Visual Studio Code genom att trycka på F1.

  3. Ange och välj Terminal: Skapa ny integrerad terminal.

  4. Gå till mappen för det klientramverk som du föredrar enligt följande:

    cd angular-app
    
    cd react-app
    
    cd svelte-app
    
    cd vue-app
    
  5. Kör klientprogrammet på klientsidan med hjälp av en utvecklingsserver.

    npm start
    
    npm start
    
    npm run dev
    
    npm run serve
    

    Låt den här servern vara igång i bakgrunden. Kör nu API:et och autentiseringsserveremulatorn med hjälp av SWA CLI.

  6. Öppna kommandopaletten i Visual Studio Code genom att trycka på F1.

  7. Ange och välj Terminal: Skapa ny integrerad terminal.

  8. Kör SWA CLI genom att köra följande kommando:

    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. Bläddra till http://localhost:4280.

Den sista porten som används av SWA CLI skiljer sig från den du har sett tidigare, eftersom den använder en omvänd proxy för att vidarebefordra begäranden till de tre olika komponenterna:

  • Din ramverksutvecklingsserver
  • Emulatorn för autentisering och auktorisering
  • API:et som körs av Functions-körningen

Skärmbild av CLI-arkitekturen för Static Web Apps.

Låt programmet fortsätta att köras medan du ändrar koden.

Hämta användarens inloggningsstatus

Först måste du komma åt användarens inloggningsstatus genom att göra en fråga till /.auth/me i klienten.

  1. Skapa filen angular-app/src/app/core/models/user-info.ts och lägg till följande kod för att representera gränssnittet för användarinformationen.

    export interface UserInfo {
      identityProvider: string;
      userId: string;
      userDetails: string;
      userRoles: string[];
    }
    
  2. Redigera filen angular-app/src/app/core/components/nav.component.tsoch lägg till följande metod i NavComponent klassen.

    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. Skapa en ny klassegenskap userInfooch lagra resultatet av funktionen getUserInfo() async när komponenten initieras. OnInit Implementera gränssnittet och uppdatera importinstruktionerna för import OnInit och UserInfo. Den här koden hämtar användarinformationen när komponenten initieras.

    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. Redigera filen react-app/src/components/NavBar.jsoch lägg till följande kod överst i funktionen. Den här koden hämtar användarinformationen när komponenten läses in och lagrar den i tillståndet.

    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. Redigera filen svelte-app/src/components/NavBar.svelteoch lägg till följande kod i skriptavsnittet. Den här koden hämtar användarinformationen när komponenten läses in.

    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. Redigera filen vue-app/src/components/nav-bar.vueoch lägg till userInfo i dataobjektet.

     data() {
       return {
         userInfo: {
           type: Object,
           default() {},
         },
       };
     },
    
  2. Lägg till metoden i getUserInfo()avsnittet metoder .

    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. created Lägg till livscykelkroken i komponenten.

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

    När komponenten skapas hämtas användarinformationen automatiskt.

Lägg till inloggnings- och utloggningsknappar

Användarinformationen blir undefined om de inte är inloggade, så ändringarna visas inte för tillfället. Det är dags att lägga till inloggningsknappar för de olika leverantörerna.

  1. Redigera filen angular-app/src/app/core/components/nav.component.ts för att lägga till en lista över providrar i NavComponent klassen.

    providers = ['x', 'github', 'aad'];
    
  2. Lägg till följande redirect egenskap för att avbilda den aktuella URL:en för omdirigeringen efter inloggningen.

    redirect = window.location.pathname;
    
  3. Lägg till följande kod i mallen efter det första </nav> elementet för att visa inloggnings- och utloggningsknapparna.

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

    Om användaren inte är inloggad visar du inloggningsknappen för varje provider. Varje knapp länkar till /.auth/login/<AUTH_PROVIDER>och anger omdirigerings-URL:en till den aktuella sidan.

    Om användaren redan är inloggad visas annars en utloggningsknapp som länkar till /.auth/logoutoch anger även omdirigerings-URL:en till den aktuella sidan.

Nu bör du se den här webbsidan i webbläsaren.

Skärmbild av Angular-webbappen med inloggningsknappar.

  1. Redigera filen react-app/src/components/NavBar.js för att lägga till en lista över leverantörer överst i funktionen.

    const providers = ['x', 'github', 'aad'];
    
  2. Lägg till följande redirect variabel under den första variabeln för att avbilda den aktuella URL:en för omdirigeringen efter inloggningen.

    const redirect = window.location.pathname;
    
  3. Lägg till följande kod i JSX-mallen efter det första </nav> elementet för att visa inloggnings- och utloggningsknapparna.

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

    Om användaren inte är inloggad visar du inloggningsknappen för varje provider. Varje knapp länkar till /.auth/login/<AUTH_PROVIDER>och anger omdirigerings-URL:en till den aktuella sidan.

    Om användaren redan är inloggad visar du annars en utloggningsknapp som länkar till /.auth/logoutoch anger även omdirigerings-URL:en till den aktuella sidan.

Nu bör du se den här webbsidan i webbläsaren.

Skärmbild av React-webbappen med inloggningsknappar.

  1. Redigera filen svelte-app/src/components/NavBar.svelte för att lägga till en lista över leverantörer överst i skriptet.

    const providers = ['x', 'github', 'aad'];
    
  2. Lägg till följande redirect variabel under den första variabeln för att avbilda den aktuella URL:en för omdirigeringen efter inloggningen.

    const redirect = window.location.pathname;
    
  3. Lägg till följande kod i mallen efter det första </nav> elementet för att visa inloggnings- och utloggningsknapparna.

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

    Om användaren inte är inloggad visar du inloggningsknappen för varje provider. Varje knapp länkar till /.auth/login/<AUTH_PROVIDER>och anger omdirigerings-URL:en till den aktuella sidan.

    Om användaren redan är inloggad visar du annars en utloggningsknapp som länkar till /.auth/logoutoch anger även omdirigerings-URL:en till den aktuella sidan.

Nu bör du se den här webbsidan i webbläsaren.

Skärmbild av Svelte-webbappen med inloggningsknappar.

  1. Redigera filen vue-app/src/components/nav-bar.vueoch lägg till en lista över providers i dataobjektet.

     providers: ['x', 'github', 'aad'],
    
  2. Lägg till följanderedirect egenskap för att avbilda den aktuella URL:en för omdirigeringen efter inloggningen.

     redirect: window.location.pathname,
    
  3. Lägg till följande kod i mallen efter det första </nav> elementet för att visa inloggnings- och utloggningsknapparna.

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

    Om användaren inte är inloggad visar du inloggningsknappen för varje provider. Varje knapp länkar till /.auth/login/<AUTH_PROVIDER>och anger omdirigerings-URL:en till den aktuella sidan.

    Om användaren redan är inloggad visar du annars en utloggningsknapp som länkar till /.auth/logoutoch anger även omdirigerings-URL:en till den aktuella sidan.

Nu bör du se den här webbsidan i webbläsaren.

Skärmbild av Vue-webbappen med inloggningsknappar.

Visa användarens inloggningsstatus

Innan du testar autentiseringsarbetsflödet ska vi visa användarinformation om den inloggade användaren.

Redigera filen angular-app/src/app/core/components/nav.component.tsoch lägg till den här koden längst ned i mallen efter den slutliga avslutande </nav> taggen.

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

Kommentar

Egenskapen userDetails kan vara antingen ett användarnamn eller en e-postadress, beroende på vilken identitet som angavs för att logga in.

Den färdiga filen bör nu se ut så här:

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

Redigera filen react-app/src/components/NavBar.jsoch lägg till den här koden längst ned i JSX-mallen efter den sista avslutande </nav> taggen för att visa inloggningsstatusen.

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

Kommentar

Egenskapen userDetails kan vara antingen ett användarnamn eller en e-postadress, beroende på vilken identitet som angavs för att logga in.

Den färdiga filen bör nu se ut så här:

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;

Redigera filen svelte-app/src/components/NavBar.svelteoch lägg till den här koden längst ned i mallen efter den sista avslutande </nav> taggen för att visa inloggningsstatusen.

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

Kommentar

Egenskapen userDetails kan vara antingen ett användarnamn eller en e-postadress, beroende på vilken identitet som angavs för att logga in.

Den färdiga filen bör nu se ut så här:

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

Redigera filen vue-app/src/components/nav-bar.vueoch lägg till den här koden längst ned i mallen efter den sista avslutande </nav> taggen för att visa inloggningsstatusen:

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

Kommentar

Egenskapen userDetails kan vara antingen ett användarnamn eller en e-postadress, beroende på vilken identitet som angavs för att logga in.

Den färdiga filen bör nu se ut så här:

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

Testa autentisering lokalt

Allt är nu på plats. Det sista steget är att testa om allt fungerar som förväntat.

  1. I webbappen väljer du en av identitetsprovidrar som ska loggas in.

  2. Du omdirigeras till den här sidan:

    Skärmbild som visar skärmen för falsk autentisering med SWA CLI.

    Det här är en skärm för falsk autentisering som tillhandahålls av SWA CLI, så att du kan testa autentisering lokalt genom att ange användarinformation.

  3. Ange mslearn som användarnamn och 1234 som användar-ID.

  4. Välj Logga in.

    Efter inloggningen omdirigeras du till föregående sida. Du kan se att inloggningsknapparna har ersatts av en utloggningsknapp. Du kan också se ditt användarnamn och den valda providern under utloggningsknappen.

    Nu när du har kontrollerat att allt fungerar som förväntat lokalt är det dags att distribuera ändringarna.

  5. Du kan stoppa appen och API:et som körs genom att trycka på Ctrl-C i båda terminalerna.

Distribuera dina ändringar

  1. Öppna kommandopaletten i Visual Studio Code genom att trycka på F1.

  2. Ange och välj Git: Checka in alla.

  3. Ange Add authentication som incheckningsmeddelande och tryck på Retur.

  4. Öppna kommandopaletten genom att trycka på F1.

  5. Ange och välj Git: Push och tryck på Retur.

När du har push-överfört ändringarna väntar du på att bygg- och distributionsprocessen ska köras. Ändringarna ska vara synliga i din distribuerade app efter det.

Nästa steg

Appen stöder nu användarautentisering och nästa steg är att begränsa vissa delar av appen till oautentiserade användare.