다음을 통해 공유


자습서: JavaScript 단일 페이지 앱에 로그인 및 로그아웃 흐름 추가

적용 대상: 워크포스 테넌트 흰색 확인 표시가 있는 녹색 원 외부 테넌트(Green circle with a white check mark symbol.자세히 알아보기)

이 자습서에서는 인증을 위해 JavaScript SPA(단일 페이지 애플리케이션)를 구성합니다. 이 시리즈 1부에서는 JavaScript SPA를 만들고 인증을 위해 준비했습니다. 이 자습서에서는 앱에 msAL(Microsoft 인증 라이브러리) 구성 요소를 추가하고 앱에 대한 반응형 UI(사용자 인터페이스)를 빌드하여 인증 흐름을 추가하는 방법을 알아봅니다.

이 자습서에서는 다음을 수행합니다.

  • auth.js 인증 흐름을 처리하는 코드 추가
  • 애플리케이션에 대한 사용자 인터페이스 빌드

필수 구성 요소

리디렉션 파일에 코드 추가

인증 흐름은 애플리케이션이 사용자를 인증하는 데 걸리는 일련의 단계입니다. Auth.js 리디렉션 또는 팝업 방법을 사용하여 로그인 및 로그아웃을 포함하여 인증 흐름을 처리하는 데 사용되는 함수를 포함합니다.

  1. public/auth.js 열고 다음 코드를 추가합니다.

    // Browser check variables: If you support IE, our recommendation is to sign-in using Redirect APIs. If you are testing using Edge InPrivate mode, please add "isEdge" to the if check.
    const ua = window.navigator.userAgent;
    const msie = ua.indexOf("MSIE ");
    const msie11 = ua.indexOf("Trident/");
    const msedge = ua.indexOf("Edge/");
    const isIE = msie > 0 || msie11 > 0;
    const isEdge = msedge > 0;
    
    let signInType;
    let accountId = "";
    
    // myMSALObj instance - configuration parameters are located at authConfig.js
    const myMSALObj = new msal.PublicClientApplication(msalConfig);
    
    myMSALObj.initialize().then(() => {
        // Redirect: once login is successful and redirects with tokens, call Graph API
        myMSALObj.handleRedirectPromise().then(handleResponse).catch(err => {
            console.error(err);
        });
    })
    
    function selectAccount() {
    
        /**
        * See here for more info on account retrieval: 
        * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
        */
    
        const currentAccounts = myMSALObj.getAllAccounts();
    
        if (!currentAccounts) {
            return;
        } else if (currentAccounts.length > 1) {
            // Add your account choosing logic here
            console.warn("Multiple accounts detected.");
        } else if (currentAccounts.length === 1) {
            username = currentAccounts[0].username
            showWelcomeMessage(currentAccounts[0].username);
            updateTable(currentAccounts[0]);
        }
    }
    
    function handleResponse(resp) {
        if (resp !== null) {
            accountId = resp.account.homeAccountId;
            myMSALObj.setActiveAccount(resp.account);
            showWelcomeMessage(resp.account);
        } else {
                selectAccount();
            } 
        }
    
    async function signIn(method) {
        signInType = isIE ? "redirect" : method;
        if (signInType === "popup") {
            return myMSALObj.loginPopup({
                ...loginRequest,
                redirectUri: "/redirect"
            }).then(handleResponse).catch(function (error) {
                console.log(error);
            });
        } else if (signInType === "redirect") {
            return myMSALObj.loginRedirect(loginRequest)
        }
    }
    
    function signOut(interactionType) {
        const logoutRequest = {
            account: myMSALObj.getAccountByHomeId(accountId)
        };
    
        if (interactionType === "popup") {
            myMSALObj.logoutPopup(logoutRequest).then(() => {
                window.location.reload();
            });
        } else {
            myMSALObj.logoutRedirect(logoutRequest);
        }
    }
    
    // This function can be removed if you do not need to support IE
    async function getTokenRedirect(request, account) {
        return await myMSALObj.acquireTokenSilent(request).catch(async (error) => {
            console.log("silent token acquisition fails.");
            if (error instanceof msal.InteractionRequiredAuthError) {
                // fallback to interaction when silent call fails
                console.log("acquiring token using redirect");
                myMSALObj.acquireTokenRedirect(request);
            } else {
                console.error(error);
            }
        });
    }
    
  2. 파일을 저장합니다.

애플리케이션에 대한 사용자 인터페이스 빌드

권한 부여가 구성되면 프로젝트를 실행할 때 애플리케이션과 상호 작용하도록 UI를 만들 수 있습니다. 부트스트랩로그인로그아웃 단추가 포함된 반응형 UI를 만드는 데 사용됩니다. 또한 UI에는 토큰의 클레임을 표시하는 테이블이 포함되어 있으며, 이 테이블은 자습서의 뒷부분에서 추가됩니다.

index.html 파일에 코드 추가

SPA의 기본 페이지인 index.html애플리케이션이 시작될 때 로드되는 첫 번째 페이지입니다. 또한 사용자가 로그아웃 단추를 선택할 때 로드되는 페이지이기도 합니다. 페이지에는 탐색 모음, 사용자 전자 메일이 포함된 환영 메시지 및 토큰의 클레임을 표시하는 테이블이 포함되어 있습니다.

  1. public/index.html 열고 다음 코드 조각을 추가합니다.

     <!DOCTYPE html>
     <html lang="en">
    
     <head>
         <meta charset="UTF-8">
         <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
         <title>Microsoft identity platform</title>
         <link rel="SHORTCUT ICON" href="./favicon.svg" type="image/x-icon">
         <link rel="stylesheet" href="./styles.css">
    
         <!-- adding Bootstrap 5 for UI components  -->
         <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet"
             integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
         <!-- msal.min.js can be used in the place of msal-browser.js -->
         <script src="/msal-browser.min.js"></script>
     </head>
    
     <!DOCTYPE html>
     <html lang="en">
     <head>
     <meta charset="UTF-8">
     <title>Bootstrap 5 Navbar</title>
     <!-- Bootstrap 5 CSS -->
     <link 
         href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" 
         rel="stylesheet" 
         integrity="sha384-Zenh87qX5JnK2Rl3l94faQlfdS3b8SpxyDkgOn+Y5Qu3og6JpNZnN9LfX9k8wAI5" 
         crossorigin="anonymous">
     </head>
    
     <body>
     <!-- Navbar -->
     <nav class="navbar navbar-expand-sm navbar-dark bg-primary navbarStyle">
         <a class="navbar-brand" href="/">Microsoft identity platform</a>
    
         <div class="ms-auto d-flex align-items-center">
         <!-- Dropdown group (Bootstrap 5 uses dropstart instead of dropleft) -->
         <div class="btn-group dropstart">
             <!-- Toggle button for dropdown -->
             <button
             id="signIn"
             type="button"
             class="btn btn-primary dropdown-toggle"
             data-bs-toggle="dropdown"
             aria-expanded="false"
             >
             Sign In
             </button>
    
             <!-- Dropdown menu -->
             <div class="dropdown-menu">
             <button class="dropdown-item" id="popup" onclick="signIn(this.id)">
                 Sign in using Popup
             </button>
             <button class="dropdown-item" id="redirect" onclick="signIn(this.id)">
                 Sign in using Redirect
             </button>
             </div>
         </div>
    
         <!-- Sign Out button -->
         <button class="btn btn-secondary ms-2" id="signOut" onclick="signOut()">
             Sign Out
         </button>
         </div>
     </nav>
    
    
     <br />
     <div class="container">
         <div class="row">
         <h5 id="title-div" class="card-header text-center">
             JavaScript single-page application secured with MSAL.js
         </h5>
         <br />
         <h5 id="welcome-div" class="card-header text-center d-none"></h5>
    
         <table class="table table-striped table-bordered d-none" id="table-div">
             <thead>
             <tr>
                 <th>Claim Type</th>
                 <th>Value</th>
                 <th>Description</th>
             </tr>
             </thead>
             <tbody id="table-body-div"></tbody>
         </table>
         </div>
     </div>
    
     <script 
         src="https://code.jquery.com/jquery-3.3.1.slim.min.js" 
         integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" 
         crossorigin="anonymous">
     </script>
    
     <!-- Bootstrap 5 JS bundle (includes Popper) -->
     <script 
         src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" 
         integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" 
         crossorigin="anonymous">
     </script>
    
     <!-- Your custom scripts -->
     <script type="text/javascript" src="./authConfig.js"></script>
     <script type="text/javascript" src="./ui.js"></script>
     <script type="text/javascript" src="./claimUtils.js"></script>
     <script type="text/javascript" src="./auth.js"></script>
     </body>
     </html>
    
  2. 파일을 저장합니다.

ui.js 파일에 코드 추가

애플리케이션을 대화형으로 만들기 위해 ui.js 파일을 사용하여 애플리케이션의 UI 요소를 처리합니다. 파일에는 로그인할 때 사용자의 이름을 업데이트하고 토큰의 클레임으로 테이블을 업데이트하는 데 사용되는 함수가 포함되어 있습니다.

  1. public/ui.js 열고 다음 코드 조각을 추가합니다.

    // Select DOM elements to work with
    const signInButton = document.getElementById('signIn');
    const signOutButton = document.getElementById('signOut');
    const titleDiv = document.getElementById('title-div');
    const welcomeDiv = document.getElementById('welcome-div');
    const tableDiv = document.getElementById('table-div');
    const tableBody = document.getElementById('table-body-div');
    
    function showWelcomeMessage(account) {
        signInButton.classList.add('d-none');
        signOutButton.classList.remove('d-none');
        titleDiv.classList.add('d-none');
        welcomeDiv.classList.remove('d-none');
        welcomeDiv.innerHTML = `Welcome ${account.username}!`;
        updateTable(account);
    };
    
    function updateTable(account) {
        tableDiv.classList.remove('d-none');
    
        const tokenClaims = createClaimsTable(account.idTokenClaims);
    
        Object.keys(tokenClaims).forEach((key) => {
            let row = tableBody.insertRow(0);
            let cell1 = row.insertCell(0);
            let cell2 = row.insertCell(1);
            let cell3 = row.insertCell(2);
            cell1.innerHTML = tokenClaims[key][0];
            cell2.innerHTML = tokenClaims[key][1];
            cell3.innerHTML = tokenClaims[key][2];
        });
    };
    
  2. 파일을 저장합니다.

signout.html 파일에 코드 추가

signout.html 파일은 애플리케이션에서 로그아웃할 때 사용자에게 메시지를 표시하는 데 사용됩니다.

  1. public/signout.html 열고 다음 코드 조각을 추가합니다.

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Microsoft Entra ID | JavaScript SPA</title>
        <link rel="SHORTCUT ICON" href="./favicon.svg" type="image/x-icon">
    
        <!-- adding Bootstrap 4 for UI components  -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    </head>
    <body>
        <div class="jumbotron" style="margin: 10%">
            <h1>Goodbye!</h1>
            <p>You have signed out and your cache has been cleared.</p>
            <a class="btn btn-primary" href="/" role="button">Take me back</a>
        </div>
    </body>
    </html>
    
  2. 파일을 저장합니다.

앱에 스타일 추가

마지막으로 애플리케이션에 스타일을 추가하여 더 매력적으로 보이게 합니다. 스타일은 styles.css 파일에 추가되며 필요에 맞게 사용자 지정할 수 있습니다.

  1. public/styles.css 열고 다음 코드 조각을 추가합니다.

    .navbarStyle {
        padding: .5rem 1rem !important;
    }
    
    .table-responsive-ms {
        max-height: 39rem !important;
        padding-left: 10%;
        padding-right: 10%;
    }
    
  2. 파일을 저장합니다.

다음 단계