Exercício – Adicionar autenticação de usuário

Concluído

Seu aplicativo Web de lista de compras precisa de uma autenticação de usuário. Neste exercício, você implementará o logon e o logoff em seu aplicativo e exibirá o status de logon atual do usuário.

Neste exercício, você vai concluir as seguintes etapas:

  1. Instalar a CLI de Aplicativos Web Estáticos para desenvolvimento local.
  2. Executar o aplicativo e a API localmente com a emulação de autenticação local.
  3. Adicionar botões de logon para vários provedores de autenticação.
  4. Adicionar um botão de logoff se o usuário estiver conectado.
  5. Exibir o status de logon do usuário.
  6. Testar o fluxo de trabalho da autenticação localmente.
  7. Implantar o aplicativo atualizado.

Preparar-se para o desenvolvimento local

A CLI de Aplicativos Web Estáticos, também conhecida como CLI do SWA, é uma ferramenta de desenvolvimento local que permite executar seu aplicativo Web e a API localmente e emular servidores de autenticação e autorização.

  1. Abra um terminal no seu computador.

  2. Instale a CLI do SWA executando o comando a seguir.

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

Executar o aplicativo localmente

Agora, execute o aplicativo e a API localmente com um servidor de desenvolvimento. Dessa forma, você poderá ver e testar suas alterações à medida que implementá-las no código.

  1. Abra o projeto no Visual Studio Code.

  2. No Visual Studio Code, abra a paleta de comandos pressionando F1.

  3. Insira e selecione Terminal: criar terminal integrado.

  4. Vá para a pasta da sua estrutura de front-end preferida, conforme o seguinte:

    cd angular-app
    
    cd react-app
    
    cd svelte-app
    
    cd vue-app
    
  5. Execute o aplicativo cliente de front-end usando um servidor de desenvolvimento.

    npm start
    
    npm start
    
    npm run dev
    
    npm run serve
    

    Deixe esse servidor em execução em segundo plano. Agora, execute a API e o emulador do servidor de autenticação usando a CLI do SWA.

  6. No Visual Studio Code, abra a paleta de comandos pressionando F1.

  7. Insira e selecione Terminal: criar terminal integrado.

  8. Execute a CLI do SWA usando o seguinte 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. Navegue até http://localhost:4280.

A porta final usada pela CLI do SWA é diferente da que você já viu antes, porque ela usa um proxy reverso para encaminhar solicitações aos três componentes diferentes:

  • Seu servidor de desenvolvimento de estrutura
  • O emulador de autenticação e autorização
  • A API hospedada pelo runtime do Functions

Captura de tela da arquitetura da CLI dos Aplicativos Web Estáticos.

Deixe o aplicativo em execução enquanto você modifica o código.

Obter o status de logon do usuário

Primeiro, você precisa acessar o status de logon do usuário fazendo uma consulta em /.auth/me no cliente.

  1. Crie o arquivo angular-app/src/app/core/models/user-info.ts e adicione o código a seguir para representar a interface para as informações do usuário.

    export interface UserInfo {
      identityProvider: string;
      userId: string;
      userDetails: string;
      userRoles: string[];
    }
    
  2. Edite o arquivo angular-app/src/app/core/components/nav.component.ts e adicione o método a seguir à 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. Crie uma propriedade de classe userInfo e armazene o resultado da função assíncrona getUserInfo() quando o componente for inicializado. Implemente a interface OnInit e atualize as instruções de importação para importar OnInit e UserInfo. Esse código busca as informações do usuário quando o componente é inicializado.

    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. Edite o arquivo react-app/src/components/NavBar.js e adicione o código a seguir ao início da função. Esse código busca as informações do usuário quando o componente é carregado e as armazena no estado.

    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. Edite o arquivo svelte-app/src/components/NavBar.svelte e adicione o código a seguir à seção do script. Esse código busca as informações do usuário quando o componente é carregado.

    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. Edite o arquivo vue-app/src/components/nav-bar.vue e adicione userInfo ao objeto de dados.

     data() {
       return {
         userInfo: {
           type: Object,
           default() {},
         },
       };
     },
    
  2. Adicione o método getUserInfo() à seção 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. Adicione o gancho do ciclo de vida created ao componente.

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

    Quando o componente é criado, as informações do usuário são buscadas automaticamente.

Adicionar botões de logon e logoff

As informações do usuário serão undefined se ele não estiver conectado. Portanto, as alterações não ficarão visíveis por enquanto. É hora de adicionar botões de logon para os diferentes provedores.

  1. Edite o arquivo angular-app/src/app/core/components/nav.component.ts para adicionar uma lista de provedores à classe NavComponent.

    providers = ['x', 'github', 'aad'];
    
  2. Adicione a propriedade redirect a seguir a fim de capturar a URL atual para o redirecionamento após o logon.

    redirect = window.location.pathname;
    
  3. Adicione o código a seguir ao modelo após o primeiro elemento </nav> para exibir os botões de logon e de logoff.

    <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 o usuário não estiver conectado, você exibirá o botão de logon de cada provedor. Cada botão vincula a /.auth/login/<AUTH_PROVIDER> e define a URL de redirecionamento para a página atual.

    Caso contrário, se o usuário já estiver conectado, será exibido um botão de logoff que está vinculado a /.auth/logout e que define a URL de redirecionamento como a página atual.

Agora esta página da Web deverá ser exibida no seu navegador.

Captura de tela do aplicativo Web Angular com botões de logon.

  1. Edite o arquivo react-app/src/components/NavBar.js para adicionar uma lista de provedores à parte superior da função.

    const providers = ['x', 'github', 'aad'];
    
  2. Adicione a variável redirect a seguir abaixo da primeira variável a fim de capturar a URL atual para o redirecionamento após o logon.

    const redirect = window.location.pathname;
    
  3. Adicione o código a seguir ao modelo JSX após o primeiro elemento </nav> para exibir os botões de logon e de logoff.

    <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 o usuário não estiver conectado, você exibirá o botão de logon de cada provedor. Cada botão vincula a /.auth/login/<AUTH_PROVIDER> e define a URL de redirecionamento para a página atual.

    Caso contrário, se o usuário já estiver conectado, você exibirá um botão de logoff que está vinculado a /.auth/logout e que define a URL de redirecionamento como a página atual.

Agora esta página da Web deverá ser exibida no seu navegador.

Captura de tela do aplicativo Web React com botões de logon.

  1. Edite o arquivo svelte-app/src/components/NavBar.svelte para adicionar uma lista de provedores à parte superior do script.

    const providers = ['x', 'github', 'aad'];
    
  2. Adicione a variável redirect a seguir abaixo da primeira variável a fim de capturar a URL atual para o redirecionamento após o logon.

    const redirect = window.location.pathname;
    
  3. Adicione o código a seguir ao modelo após o primeiro elemento </nav> para exibir os botões de logon e de logoff.

     <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 o usuário não estiver conectado, você exibirá o botão de logon de cada provedor. Cada botão vincula a /.auth/login/<AUTH_PROVIDER> e define a URL de redirecionamento para a página atual.

    Caso contrário, se o usuário já estiver conectado, você exibirá um botão de logoff que está vinculado a /.auth/logout e que define a URL de redirecionamento como a página atual.

Agora esta página da Web deverá ser exibida no seu navegador.

Captura de tela do aplicativo Web Svelte com botões de logon.

  1. Edite o arquivo vue-app/src/components/nav-bar.vue e adicione uma lista de provedores ao objeto de dados.

     providers: ['x', 'github', 'aad'],
    
  2. Adicione a propriedade redirect a seguir a fim de capturar a URL atual para o redirecionamento após o logon.

     redirect: window.location.pathname,
    
  3. Adicione o código a seguir ao modelo após o primeiro elemento </nav> para exibir os botões de logon e de logoff.

    <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 o usuário não estiver conectado, você exibirá o botão de logon de cada provedor. Cada botão vincula a /.auth/login/<AUTH_PROVIDER> e define a URL de redirecionamento para a página atual.

    Caso contrário, se o usuário já estiver conectado, você exibirá um botão de logoff que está vinculado a /.auth/logout e que define a URL de redirecionamento como a página atual.

Agora esta página da Web deverá ser exibida no seu navegador.

Captura de tela do aplicativo Web Vue com botões de logon.

Exibir o status de logon do usuário

Antes de testar o fluxo de trabalho de autenticação, vamos exibir os detalhes do usuário conectado.

Edite o arquivo angular-app/src/app/core/components/nav.component.ts e adicione este código à parte inferior do modelo após a marca </nav> de fechamento final.

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

Observação

A propriedade userDetails pode ser um nome de usuário ou endereço de email, dependendo da identidade fornecida usada para fazer logon.

O seu arquivo completo deverá estar como o seguinte:

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

Edite o arquivo react-app/src/components/NavBar.js e adicione este código à parte inferior do modelo JSX após a marca </nav> de fechamento final para exibir o status de logon.

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

Observação

A propriedade userDetails pode ser um nome de usuário ou endereço de email, dependendo da identidade fornecida usada para fazer logon.

O seu arquivo completo deverá estar como o seguinte:

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;

Edite o arquivo svelte-app/src/components/NavBar.svelte e adicione este código à parte inferior do modelo após a marca </nav> de fechamento final para exibir o status de logon.

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

Observação

A propriedade userDetails pode ser um nome de usuário ou endereço de email, dependendo da identidade fornecida usada para fazer logon.

O seu arquivo completo deverá estar como o seguinte:

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

Edite o arquivo vue-app/src/components/nav-bar.vue e adicione este código à parte inferior do modelo após a marca </nav> de fechamento final para exibir o status de logon:

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

Observação

A propriedade userDetails pode ser um nome de usuário ou endereço de email, dependendo da identidade fornecida usada para fazer logon.

O seu arquivo completo deverá estar como o seguinte:

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

Testar a autenticação localmente

Agora está tudo em seu devido lugar. A etapa final é testar se tudo está funcionando conforme o esperado.

  1. Em seu aplicativo Web, selecione um dos provedores de identidade para fazer logon.

  2. Você será redirecionado para esta página:

    Captura de tela mostra a tela falsa de autenticação da CLI do SWA.

    Essa é uma tela de autenticação falsa fornecida pela CLI do SWA, permitindo que você teste a autenticação localmente simulando o fornecimento de detalhes de usuário.

  3. Insira mslearn como nome de usuário e 1234 para a ID de usuário.

  4. Selecione Fazer logon.

    Após o logon, você será redirecionado para a página anterior. Você pode ver que os botões de logon foram substituídos por um botão de logoff. Você também pode ver seu nome de usuário e o provedor selecionado abaixo do botão de logoff.

    Agora que você verificou se tudo funciona conforme o esperado localmente, é hora de implantar suas alterações.

  5. Você pode interromper o aplicativo e a API em execução pressionando Ctrl-C em ambos os terminais.

Implantar suas alterações

  1. No Visual Studio Code, abra a paleta de comandos pressionando F1.

  2. Insira e selecione Git: confirmar tudo.

  3. Insira Add authentication como a mensagem de confirmação e pressione Enter.

  4. Abra a paleta de comandos pressionando F1.

  5. Insira e selecione Git: push e pressione Enter.

Depois de enviar as alterações, aguarde até que o processo de compilação e implantação seja executado. As alterações devem ficar visíveis no aplicativo implantado depois disso.

Próximas etapas

Seu aplicativo agora dá suporte à autenticação de usuário, e a próxima etapa é restringir algumas partes do aplicativo a usuários não autenticados.