Упражнение. Добавление проверки подлинности пользователя
Вашему веб-приложению со списком покупок требуется проверка подлинности пользователей. В этом упражнении вы реализуете вход в систему и выход из нее в приложении, а также отобразите состояние входа текущего пользователя.
В этом упражнении вы выполните следующие действия.
- Установите интерфейс командной строки для Статических веб-приложений для локальной разработки.
- Локально запустите приложение и API с помощью эмуляции локальной проверки подлинности.
- Добавите кнопки входа в систему для нескольких поставщиков проверки подлинности.
- Добавите кнопку выхода из системы, если пользователь вошел в систему.
- Отобразите состояние входа пользователя.
- Локально протестируете рабочий процесс проверки подлинности.
- Развернете обновленное приложение.
Подготовка к локальной разработке
Интерфейс командной строки для Статических веб-приложений, известный также как SWA CLI, — это локальное средство разработки, которое позволяет запускать веб-приложение и API на локальном компьютере и эмулировать серверы проверки подлинности и авторизации.
Откройте терминал на своем компьютере.
Установите SWA CLI, выполнив приведенную ниже команду.
npm install -g @azure/static-web-apps-cli
Локальный запуск приложения
Теперь запустите приложение и API локально на сервере разработки. Таким образом вы сможете просматривать и тестировать изменения по мере их внесения в код.
Откройте проект в Visual Studio Code.
Откройте палитру команд Visual Studio Code, нажав клавишу F1.
Введите и выберите Терминал: создать встроенный терминал.
Перейдите в папку предпочтительной интерфейсной платформы, как показано ниже.
cd angular-app
cd react-app
cd svelte-app
cd vue-app
Запустите интерфейсное клиентское приложение с помощью сервера разработки.
npm start
npm start
npm run dev
npm run serve
Оставьте этот сервер работать в фоновом режиме. Теперь запустите API и эмулятор сервера проверки подлинности с помощью SWA CLI.
Откройте палитру команд Visual Studio Code, нажав клавишу F1.
Введите и выберите Терминал: создать встроенный терминал.
Запустите интерфейс командной строки SWA, выполнив следующую команду.
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
Перейдите в
http://localhost:4280
.
Последний порт, используемый интерфейсом командной строки SWA, отличается от того, который вы видели ранее, так как он использует обратный прокси-сервер для пересылки запросов в три различных приведенных ниже компонента.
- Сервер разработки платформы.
- Эмулятор проверки подлинности и авторизации.
- API, размещенный в среде выполнения функций.
Пусть приложение работает, пока вы будете изменять код.
Получение состояния входа пользователя
Сначала вам необходимо получить доступ к состоянию входа пользователя, выполнив запрос /.auth/me
в клиенте.
Создайте файл
angular-app/src/app/core/models/user-info.ts
и добавьте следующий код, который представляет интерфейс для сведений о пользователе.export interface UserInfo { identityProvider: string; userId: string; userDetails: string; userRoles: string[]; }
Измените файл
angular-app/src/app/core/components/nav.component.ts
и добавьте следующий метод в класс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; } }
Создайте свойство класса
userInfo
и сохраните результат асинхронной функцииgetUserInfo()
при инициализации компонента. Реализуйте интерфейсOnInit
и обновите инструкции импорта для импортаOnInit
иUserInfo
. Этот код получает сведения о пользователе при инициализации компонента.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(); } // ... }
Измените файл
react-app/src/components/NavBar.js
и добавьте следующий код в начало функции. Этот код получает сведения о пользователе при загрузке компонента и сохраняет их в состоянии.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 ( // ...
Измените файл
svelte-app/src/components/NavBar.svelte
и добавьте следующий код в раздел скрипта. Этот код получает сведения о пользователе при загрузке компонента.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; } }
Измените файл
vue-app/src/components/nav-bar.vue
и добавьтеuserInfo
в объект данных.data() { return { userInfo: { type: Object, default() {}, }, }; },
Добавьте метод
getUserInfo()
в раздел 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; } }, },
Добавьте в компонент обработчик жизненного цикла
created
.async created() { this.userInfo = await this.getUserInfo(); },
После создания компонента сведения о пользователе извлекаются автоматически.
Добавление кнопок входа и выхода
Сведения о пользователе будут иметь свойство undefined
, если вы не вошли в систему, поэтому сейчас изменения не будут видны. Пора добавить кнопки входа для разных поставщиков.
Измените файл
angular-app/src/app/core/components/nav.component.ts
, добавив список поставщиков в классNavComponent
.providers = ['x', 'github', 'aad'];
Добавьте следующее свойство
redirect
, чтобы записать текущий URL-адрес для перенаправления после входа.redirect = window.location.pathname;
Добавьте следующий код в шаблон после первого элемента
</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>
Если пользователь не вошел в систему, отобразится кнопка входа для каждого поставщика. Каждая кнопка ссылается на
/.auth/login/<AUTH_PROVIDER>
и задает URL-адрес перенаправления для текущей страницы.В противном случае, если пользователь уже вошел в систему, отобразится кнопка выхода, которая ссылается на
/.auth/logout
, а также URL-адрес перенаправления на текущую страницу.
Вы увидите следующую страницу в браузере.
Измените файл
react-app/src/components/NavBar.js
, добавив список поставщиков в верхней части функции.const providers = ['x', 'github', 'aad'];
Добавьте следующую переменную
redirect
ниже первой переменной, чтобы записать текущий URL-адрес для перенаправления после входа.const redirect = window.location.pathname;
Добавьте следующий код в шаблон JSX после первого элемента
</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>
Если пользователь не вошел в систему, отобразится кнопка входа для каждого поставщика. Каждая кнопка ссылается на
/.auth/login/<AUTH_PROVIDER>
и задает URL-адрес перенаправления для текущей страницы.В противном случае, если пользователь уже вошел в систему, отобразится кнопка выхода, которая ссылается на
/.auth/logout
, а также URL-адрес перенаправления на текущую страницу.
Вы увидите следующую страницу в браузере.
Измените файл
svelte-app/src/components/NavBar.svelte
, добавив список поставщиков в верхней части скрипта.const providers = ['x', 'github', 'aad'];
Добавьте следующую переменную
redirect
ниже первой переменной, чтобы записать текущий URL-адрес для перенаправления после входа.const redirect = window.location.pathname;
Добавьте следующий код в шаблон после первого элемента
</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>
Если пользователь не вошел в систему, отобразится кнопка входа для каждого поставщика. Каждая кнопка ссылается на
/.auth/login/<AUTH_PROVIDER>
и задает URL-адрес перенаправления для текущей страницы.В противном случае, если пользователь уже вошел в систему, отобразится кнопка выхода, которая ссылается на
/.auth/logout
, а также URL-адрес перенаправления на текущую страницу.
Вы увидите следующую страницу в браузере.
Измените файл
vue-app/src/components/nav-bar.vue
и добавьте список поставщиков в объект данных.providers: ['x', 'github', 'aad'],
Добавьте следующее свойство
redirect
, чтобы записать текущий URL-адрес для перенаправления после входа.redirect: window.location.pathname,
Добавьте следующий код в шаблон после первого элемента
</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/login/${provider}?post_login_redirect_uri=${redirect}`"> Logout </a> </div> </nav>
Если пользователь не вошел в систему, отобразится кнопка входа для каждого поставщика. Каждая кнопка ссылается на
/.auth/login/<AUTH_PROVIDER>
и задает URL-адрес перенаправления для текущей страницы.В противном случае, если пользователь уже вошел в систему, отобразится кнопка выхода, которая ссылается на
/.auth/logout
, а также URL-адрес перенаправления на текущую страницу.
Вы увидите следующую страницу в браузере.
Отображение состояния входа пользователя
Перед тестированием рабочего процесса проверки подлинности давайте отобразим сведения о пользователе, вошедшем в систему.
Измените файл angular-app/src/app/core/components/nav.component.ts
и добавьте следующий код в нижнюю часть шаблона после последнего закрывающего тега </nav>
.
<div class="user" *ngIf="userInfo">
<p>Welcome</p>
<p>{{ userInfo?.userDetails }}</p>
<p>{{ userInfo?.identityProvider }}</p>
</div>
Примечание.
Свойство userDetails
может быть либо именем пользователя, либо адресом электронной почты в зависимости от удостоверения, используемого для входа.
Готовый файл будет выглядеть следующим образом.
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;
}
}
}
Измените файл react-app/src/components/NavBar.js
и добавьте следующий код в нижнюю часть шаблона JSX после последнего закрывающего тега </nav>
, чтобы отобразить состояние входа.
{
userInfo && (
<div>
<div className="user">
<p>Welcome</p>
<p>{userInfo && userInfo.userDetails}</p>
<p>{userInfo && userInfo.identityProvider}</p>
</div>
</div>
)
}
Примечание.
Свойство userDetails
может быть либо именем пользователя, либо адресом электронной почты в зависимости от удостоверения, используемого для входа.
Готовый файл будет выглядеть следующим образом.
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;
Измените файл svelte-app/src/components/NavBar.svelte
и добавьте следующий код в нижнюю часть шаблона после последнего закрывающего тега </nav>
, чтобы отобразить состояние входа.
{#if userInfo}
<div class="user">
<p>Welcome</p>
<p>{userInfo && userInfo.userDetails}</p>
<p>{userInfo && userInfo.identityProvider}</p>
</div>
{/if}
Примечание.
Свойство userDetails
может быть либо именем пользователя, либо адресом электронной почты в зависимости от удостоверения, используемого для входа.
Готовый файл будет выглядеть следующим образом.
<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>
Измените файл vue-app/src/components/nav-bar.vue
и добавьте следующий код в нижнюю часть шаблона после последнего закрывающего тега </nav>
, чтобы отобразить состояние входа:
<div class="user" v-if="userInfo">
<p>Welcome</p>
<p>{{ userInfo.userDetails }}</p>
<p>{{ userInfo.identityProvider }}</p>
</div>
Примечание.
Свойство userDetails
может быть либо именем пользователя, либо адресом электронной почты в зависимости от удостоверения, используемого для входа.
Готовый файл будет выглядеть следующим образом.
<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>
Тестирование проверки подлинности локально
Теперь все готово. Последний шаг — проверить, все ли работает должным образом.
В веб-приложении выберите одного из поставщиков удостоверений для входа.
Вы будете перенаправлены на эту страницу:
Это фиктивный экран проверки подлинности, предоставленный интерфейсом командной строки SWA. Он позволяет протестировать проверку подлинности локально, предоставив свои сведения о пользователе.
Введите
mslearn
в качестве имени пользователя и1234
в качестве его идентификатора.Выберите Войти.
После входа вы будете перенаправлены на предыдущую страницу. Вы можете видеть, что кнопки входа были заменены кнопкой выхода. Кроме того, под кнопкой выхода отображаются имя и выбранный поставщик.
Теперь, когда вы локально проверили, что все работает правильно, пришло время развернуть изменения.
Вы можете отключить работающее приложение и API, нажав клавиши CTRL+C на обоих терминалах.
Развертывание изменений
Откройте палитру команд Visual Studio Code, нажав клавишу F1.
Введите и выберите Git: зафиксировать в.
Введите сообщение фиксации
Add authentication
и нажмите клавишу ВВОД.Откройте палитру команд, нажав клавишу F1.
Введите и выберите Git: отправить и нажмите клавишу ВВОД.
После того как изменения отправлены, дождитесь выполнения процесса сборки и развертывания. После этого изменения должны быть видны в развернутом приложении.
Следующие шаги
Теперь приложение поддерживает проверку подлинности пользователей. Следующим шагом является ограничение некоторых частей приложения для пользователей, не прошедших проверку подлинности.