연습 - 사용자 인증 추가

완료됨

쇼핑 목록 웹앱에는 사용자 인증이 필요합니다. 이 연습에서는 앱에서 로그인 및 로그아웃을 구현하고 현재 사용자 로그인 상태를 표시합니다.

이 연습에서는 다음 단계를 완료합니다.

  1. 로컬 개발을 위해 Static Web Apps CLI를 설치합니다.
  2. 로컬 인증 에뮬레이션을 통해 로컬로 앱 및 API를 실행합니다.
  3. 여러 인증 공급자에 대한 로그인 단추를 추가합니다.
  4. 사용자가 로그인한 경우 로그아웃 단추를 추가합니다.
  5. 사용자의 로그인 상태를 표시합니다.
  6. 로컬로 인증 워크플로를 테스트합니다.
  7. 업데이트된 앱을 배포합니다.

로컬 개발을 위한 준비

SWA CLI라고도 하는 Static Web Apps CLI는 웹앱 및 API를 로컬로 실행하고, 인증 및 권한 부여 서버를 에뮬레이트할 수 있는 로컬 개발 도구입니다.

  1. 컴퓨터에서 터미널을 엽니다.

  2. 다음 명령을 실행하여 SWA CLI를 설치합니다.

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

로컬에서 앱 실행하기

이제 개발 서버를 사용하여 앱 및 API를 로컬로 실행합니다. 이렇게 하면 코드를 변경하면서 변경 내용을 확인하고 테스트할 수 있습니다.

  1. Visual Studio Code에서 프로젝트를 엽니다.

  2. Visual Studio Code에서 F1 키를 눌러 명령 팔레트를 엽니다.

  3. 터미널: 새 통합 터미널 만들기를 입력하고 선택합니다.

  4. 다음과 같이 원하는 프런트 엔드 프레임워크의 폴더로 이동합니다.

    cd angular-app
    
    cd react-app
    
    cd svelte-app
    
    cd vue-app
    
  5. 개발 서버를 사용하여 프런트 엔드 클라이언트 애플리케이션을 실행합니다.

    npm start
    
    npm start
    
    npm run dev
    
    npm run serve
    

    이 서버를 백그라운드 실행 상태로 둡니다. 이제 SWA CLI를 사용하여 API 및 인증 서버 에뮬레이터를 실행해 보겠습니다.

  6. Visual Studio Code에서 F1 키를 눌러 명령 팔레트를 엽니다.

  7. 터미널: 새 통합 터미널 만들기를 입력하고 선택합니다.

  8. 다음 명령을 실행하여 SWA CLI를 실행합니다.

    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. [https://www.microsoft.com]\(http://localhost:4280) 로 이동합니다.

SWA CLI에서 사용하는 최종 포트는 역방향 프록시를 사용하여 세 가지 구성 요소에 요청을 전달하기 때문에 앞서 본 것과 다릅니다.

  • 프레임워크 개발 서버
  • 인증 및 권한 부여 에뮬레이터
  • Functions 런타임에서 호스팅하는 API

Static Web Apps CLI 아키텍처의 스크린샷

코드를 수정하는 동안 애플리케이션이 계속 실행되도록 합니다.

사용자 로그인 상태 가져오기

먼저 클라이언트에서 /.auth/me를 대상으로 하는 쿼리를 만들어 사용자 로그인 상태에 액세스합니다.

  1. angular-app/src/app/core/models/user-info.ts 파일을 만들고 다음 코드를 추가하여 사용자 정보에 대한 인터페이스를 나타냅니다.

    export interface UserInfo {
      identityProvider: string;
      userId: string;
      userDetails: string;
      userRoles: string[];
    }
    
  2. 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;
      }
    }
    
  3. 새 클래스 속성 userInfo를 만들고 구성 요소가 초기화될 때 비동기 함수 getUserInfo()의 결과를 저장합니다. OnInit 인터페이스를 구현하고 가져오기 문을 업데이트하여 OnInitUserInfo를 가져옵니다. 이 코드는 구성 요소가 초기화될 때 사용자 정보를 가져옵니다.

    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. 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 (
      // ...
    
  1. 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;
      }
    }
    
  1. vue-app/src/components/nav-bar.vue 파일을 편집하여 데이터 개체에 userInfo를 추가합니다.

     data() {
       return {
         userInfo: {
           type: Object,
           default() {},
         },
       };
     },
    
  2. methods 섹션에 getUserInfo() 메서드를 추가합니다.

    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 수명 주기 후크를 추가합니다.

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

    구성 요소가 만들어지면 사용자 정보는 자동으로 페치됩니다.

로그인 및 로그아웃 단추 추가

로그인하지 않은 경우 사용자 정보는 undefined입니다. 따라서 지금은 변경 사항이 표시되지 않습니다. 다른 공급자에 대한 로그인 단추를 추가할 때입니다.

  1. angular-app/src/app/core/components/nav.component.ts 파일을 편집하여 NavComponent 클래스에 공급자 목록을 추가합니다.

    providers = ['x', 'github', 'aad'];
    
  2. 다음 redirect 속성을 추가하여 로그인 후 리디렉션을 위해 현재 URL을 캡처합니다.

    redirect = window.location.pathname;
    
  3. 첫 번째 </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도 현재 페이지로 설정합니다.

이제 브라우저에서 이 웹 페이지가 표시됩니다.

로그인 버튼이 있는 Angular 웹앱의 스크린샷

  1. react-app/src/components/NavBar.js 파일을 편집하여 함수 맨 위에 공급자 목록을 추가합니다.

    const providers = ['x', 'github', 'aad'];
    
  2. 첫 번째 변수 아래에 다음 redirect 변수를 추가하여 로그인 후 리디렉션을 위해 현재 URL을 캡처합니다.

    const redirect = window.location.pathname;
    
  3. 첫 번째 </nav> 요소 뒤에 있는 JSX 템플릿에 다음 코드를 추가하여 로그인 및 로그아웃 단추를 표시합니다.

    <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도 현재 페이지로 설정합니다.

이제 브라우저에서 이 웹 페이지가 표시됩니다.

로그인 버튼이 있는 React 웹앱의 스크린샷

  1. svelte-app/src/components/NavBar.svelte 파일을 편집하여 스크립트 맨 위에 공급자 목록을 추가합니다.

    const providers = ['x', 'github', 'aad'];
    
  2. 첫 번째 변수 아래에 다음 redirect 변수를 추가하여 로그인 후 리디렉션을 위해 현재 URL을 캡처합니다.

    const redirect = window.location.pathname;
    
  3. 첫 번째 </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도 현재 페이지로 설정합니다.

이제 브라우저에서 이 웹 페이지가 표시됩니다.

로그인 버튼이 있는 Svelte 웹앱의 스크린샷

  1. vue-app/src/components/nav-bar.vue 파일을 편집하여 데이터 개체에 공급자 목록을 추가합니다.

     providers: ['x', 'github', 'aad'],
    
  2. 다음 redirect 속성을 추가하여 로그인 후 리디렉션을 위해 현재 URL을 캡처합니다.

     redirect: window.location.pathname,
    
  3. 첫 번째 </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도 현재 페이지로 설정합니다.

이제 브라우저에서 이 웹 페이지가 표시됩니다.

로그인 버튼이 있는 Vue 웹앱의 스크린샷

사용자 로그인 상태 표시

인증 워크플로를 테스트하기 전에 로그인한 사용자의 사용자 세부 정보를 표시해 보겠습니다.

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 속성은 로그인에 사용된 ID에 따라 사용자 이름 또는 이메일 주소일 수 있습니다.

이제 완료된 파일이 다음과 같이 표시됩니다.

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 파일을 편집하고 마지막으로 닫는 </nav> 태그 뒤에 있는 JSX 템플릿 맨 아래에 해당 코드를 추가하여 로그인 상태를 표시합니다.

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

참고

userDetails 속성은 로그인에 사용된 ID에 따라 사용자 이름 또는 이메일 주소일 수 있습니다.

이제 완료된 파일이 다음과 같이 표시됩니다.

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 속성은 로그인에 사용된 ID에 따라 사용자 이름 또는 이메일 주소일 수 있습니다.

이제 완료된 파일이 다음과 같이 표시됩니다.

<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 속성은 로그인에 사용된 ID에 따라 사용자 이름 또는 이메일 주소일 수 있습니다.

이제 완료된 파일이 다음과 같이 표시됩니다.

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

로컬로 인증 테스트

이제 모든 것이 한 곳에 있습니다. 마지막 단계는 모든 것이 예상대로 작동하는지 테스트하는 것입니다.

  1. 웹앱에서 로그인할 ID 공급자 중 하나를 선택합니다.

  2. 다음 페이지로 리디렉션됩니다.

    SWA CLI 가짜 인증 화면을 보여 주는 스크린샷

    이 화면은 SWA CLI에서 제공하는 가짜 인증 화면으로, 사용자 세부 정보를 제공하여 로컬로 인증을 테스트할 수 있습니다.

  3. 사용자 이름으로 mslearn을 입력하고 사용자 ID로 1234를 입력합니다.

  4. 로그인을 선택합니다.

    로그인한 후 이전 페이지로 리디렉션됩니다. 로그인 단추가 로그아웃 단추로 바뀐 것을 볼 수 있습니다. 로그아웃 단추 아래에 사용자 이름 및 선택한 공급자가 나타난 것도 확인할 수 있습니다.

    로컬에서 모든 것이 예상대로 작동하는지 확인했으므로 이제 변경 내용을 배포할 차례입니다.

  5. 두 터미널에서 Ctrl+C를 눌러 실행 중인 앱과 API를 중지할 수 있습니다.

변경 내용 배포

  1. Visual Studio Code에서 F1 키를 눌러 명령 팔레트를 엽니다.

  2. Git: Commit All을 입력하고 선택합니다.

  3. 커밋 메시지로 Add authentication을 입력하고 Enter 키를 누릅니다.

  4. F1 키를 눌러 명령 팔레트를 엽니다.

  5. Git: Push를 입력하고 선택한 다음 Enter 키를 누릅니다.

변경 내용을 푸시한 후 빌드 및 배포 프로세스가 실행될 때까지 기다립니다. 그러면 배포된 앱에 변경 내용이 표시됩니다.

다음 단계

이제 앱에서 사용자 인증을 지원하며 다음 단계는 앱의 일부 부분을 인증되지 않은 사용자로 제한하는 것입니다.