チュートリアル: 認証コード フローを使用して、ユーザーをサインインさせて React シングルページ アプリ (SPA) から Microsoft Graph API を呼び出す

このチュートリアルでは、ユーザーのサインインを行い、PKCE による承認コード フローを使用して Microsoft Graph を呼び出す React シングルページ アプリケーション (SPA) を構築します。 構築する SPA では、React 用の Microsoft Authentication Library (MSAL) を使用します。

このチュートリアルでは、次の作業を行います。

  • npm を使用して React プロジェクトを作成する
  • Azure portal でアプリケーションを登録する
  • ユーザーのサインインとサインアウトをサポートするコードを追加する
  • Microsoft Graph API を呼び出すコードを追加する
  • アプリケーションをテストする

MSAL React では、暗黙的な許可のフローではなくブラウザーでの承認コード フローがサポートされています。 MSAL React では、暗黙的フローはサポートされていません

前提条件

チュートリアル アプリの動作

Diagram showing the authorization code flow in a single-page application

このチュートリアルで作成するアプリケーションにより、React SPA では、Microsoft ID プラットフォームからセキュリティ トークンを取得して、Microsoft Graph API に対してクエリを実行できるようになります。 React 用の MSAL である、MSAL.js v2 ライブラリのラッパーを使用します。 MSAL React では、React (16 以降) のアプリケーションで Azure Active Directory (Azure AD) を使用してエンタープライズ ユーザーを認証できるほか、Microsoft アカウントを持つユーザーとソーシャル ID (Facebook、Google、LinkedIn など) を認証することができます。 また、アプリケーションは、このライブラリを通じて、Microsoft クラウド サービスや Microsoft Graph にアクセスすることができます。

このシナリオでは、ユーザーのサインイン後に、アクセス トークンが要求され、HTTP 要求の Authorization ヘッダーに追加されます。 トークンの取得と更新は、React 用の MSAL によって処理されます。

ライブラリ

このチュートリアルでは、次のライブラリを使用します。

ライブラリ 説明
MSAL React JavaScript React Wrapper 用の Microsoft Authentication Library
MSAL ブラウザー JavaScript v2 ブラウザー パッケージ用の Microsoft Authentication Library

完成したコード サンプルを入手する

代わりに、このチュートリアルの完成したサンプル プロジェクトをダウンロードすることもできます。 Node.js などのローカル Web サーバーを使用してプロジェクトを実行するには、ms-identity-javascript-react-spa リポジトリをクローンします。

git clone https://github.com/Azure-Samples/ms-identity-javascript-react-spa

その後、コード サンプルを実行前に構成するために、構成手順に進みます。

チュートリアルを続行してアプリケーションを自分でビルドする場合は、次のセクション「プロジェクトを作成する」に進みます。

プロジェクトを作成する

Node.js をインストールしたら、ターミナル ウィンドウを開いて次のコマンドを実行します。

npx create-react-app msal-react-tutorial # Create a new React app
cd msal-react-tutorial # Change to the app directory
npm install @azure/msal-browser @azure/msal-react # Install the MSAL packages
npm install react-bootstrap bootstrap # Install Bootstrap for styling

Create React App を使用して、小型の React プロジェクトをブートストラップしました。 これが、このチュートリアルの残りの作業を進めるうえでの出発点になります。 このチュートリアルの作業でアプリに加えられる変更を確認したいときは、次のコマンドを実行できます。

npm start

ブラウザー ウィンドウが開いて自動的にアプリに移動するはずです。 そうならない場合は、ブラウザーを開いて http://localhost:3000. に移動します。 更新したコードでファイルを保存するたびに、ページが再度読み込まれて変更が反映されます。

アプリケーションを登録する

シングルページ アプリケーション: アプリの登録」の手順に従って、Azure portal を使用して SPA のアプリ登録を作成します。

リダイレクト URI: 承認コード フローを使用した MSAL.js 2.0」の手順で、「http://localhost:3000」(create-react-app によってアプリケーションが生成される既定の場所) を入力します。

JavaScript SPA の構成

  1. src フォルダー内に authConfig.js という名前のファイルを作成し、認証用の構成パラメーターを記述します。さらに、次のコードを追加します。

    export const msalConfig = {
      auth: {
        clientId: "Enter_the_Application_Id_Here",
        authority: "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here", // This is a URL (e.g. https://login.microsoftonline.com/{your tenant ID})
        redirectUri: "Enter_the_Redirect_Uri_Here",
      },
      cache: {
        cacheLocation: "sessionStorage", // This configures where your cache will be stored
        storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
      }
    };
    
    // Add scopes here for ID token to be used at Microsoft identity platform endpoints.
    export const loginRequest = {
     scopes: ["User.Read"]
    };
    
    // Add the endpoints here for Microsoft Graph API services you'd like to use.
    export const graphConfig = {
        graphMeEndpoint: "Enter_the_Graph_Endpoint_Here/v1.0/me"
    };
    
  2. 以下の説明に従って、msalConfig セクションの値を変更します。

    値の名前 概要
    Enter_the_Application_Id_Here 登録したアプリケーションのアプリケーション (クライアント) ID
    Enter_the_Cloud_Instance_Id_Here アプリケーションが登録されている Azure クラウド インスタンス。 メイン ("グローバル") Azure クラウドの場合は、「https://login.microsoftonline.com」と入力します。 各国のクラウド (中国など) の場合は、「各国のクラウド」に適切な値が記載されています。
    Enter_the_Tenant_Info_Here 次のいずれかのオプションに設定します。1) お使いのアプリケーションで "この組織のディレクトリ内のアカウント" がサポートされる場合は、この値をディレクトリ (テナント) ID またはテナント名 (例: "contoso.microsoft.com") に置き換えます。 アプリケーションで "任意の組織のディレクトリ内のアカウント" がサポートされる場合は、この値を organizations に置き換えます。 アプリケーションで "任意の組織のディレクトリ内のアカウントと、個人用の Microsoft アカウント" がサポートされる場合は、この値を common に置き換えます。 "個人用の Microsoft アカウントのみ" にサポートを制限するには、この値を consumers に置き換えます。
    Enter_the_Redirect_Uri_Here http://localhost:3000 に置き換えます。
    Enter_the_Graph_Endpoint_Here アプリケーションが通信する必要がある、Microsoft Graph API のインスタンス。 グローバル Microsoft Graph API エンドポイントの場合は、この文字列の両方のインスタンスを https://graph.microsoft.com に置き換えます。 各国のクラウドのデプロイにおけるエンドポイントの場合は、Microsoft Graph のドキュメントで「各国のクラウドでのデプロイ」を参照してください。

    使用できる構成オプションの詳細については、クライアント アプリケーションの初期化に関する記事を参照してください。

  3. src/index.js ファイルを開いて、次の import を追加します。

    import "bootstrap/dist/css/bootstrap.min.css";
    import { PublicClientApplication } from "@azure/msal-browser";
    import { MsalProvider } from "@azure/msal-react";
    import { msalConfig } from "./authConfig";
    
  4. 手順 1 の構成を使用して、src/index.js の import の下に PublicClientApplication インスタンスを作成します。

    const msalInstance = new PublicClientApplication(msalConfig);
    
  5. src/index.js にある <App /> コンポーネントを見つけて、MsalProvider コンポーネントでそれをラップします。 render 関数が次のようになります。

    root.render(
        <React.StrictMode>
            <MsalProvider instance={msalInstance}>
                <App />
            </MsalProvider>
        </React.StrictMode>
    );
    

ユーザーのサインイン

srccomponents というフォルダーを作成し、そのフォルダー内に SignInButton.jsx という名前のファイルを作成します。 次のいずれかのセクションからコードを追加し、ポップアップ ウィンドウまたはフルフレーム リダイレクトを使用したログインを呼び出します。

ポップアップを使用したサインイン

選択時にポップアップ ログインを呼び出すボタン コンポーネントを作成するには、次のコードを src/components/SignInButton.jsx に追加します。

import React from "react";
import { useMsal } from "@azure/msal-react";
import { loginRequest } from "../authConfig";
import Button from "react-bootstrap/Button";


/**
 * Renders a button which, when selected, will open a popup for login
 */
export const SignInButton = () => {
    const { instance } = useMsal();

    const handleLogin = (loginType) => {
        if (loginType === "popup") {
            instance.loginPopup(loginRequest).catch(e => {
                console.log(e);
            });
        }
    }
    return (
        <Button variant="secondary" className="ml-auto" onClick={() => handleLogin("popup")}>Sign in using Popup</Button>
    );
}

リダイレクトを使用したサインイン

選択時にリダイレクト ログインを呼び出すボタン コンポーネントを作成するには、次のコードを src/components/SignInButton.jsx に追加します。

import React from "react";
import { useMsal } from "@azure/msal-react";
import { loginRequest } from "../authConfig";
import Button from "react-bootstrap/Button";


/**
 * Renders a button which, when selected, will redirect the page to the login prompt
 */
export const SignInButton = () => {
    const { instance } = useMsal();

    const handleLogin = (loginType) => {
        if (loginType === "redirect") {
            instance.loginRedirect(loginRequest).catch(e => {
                console.log(e);
            });
        }
    }
    return (
        <Button variant="secondary" className="ml-auto" onClick={() => handleLogin("redirect")}>Sign in using Redirect</Button>
    );
}

サインイン ボタンの追加

  1. components フォルダーに PageLayout.jsx という名前の別のフォルダーを作成し、次のコードを追加して、作成したばかりのサインイン ボタンが含まれる navbar コンポーネントを作成します。

    import React from "react";
    import Navbar from "react-bootstrap/Navbar";
    import { useIsAuthenticated } from "@azure/msal-react";
    import { SignInButton } from "./SignInButton";
    
    /**
     * Renders the navbar component with a sign-in button if a user is not authenticated
     */
    export const PageLayout = (props) => {
        const isAuthenticated = useIsAuthenticated();
    
        return (
            <>
                <Navbar bg="primary" variant="dark">
                    <a className="navbar-brand" href="/">MSAL React Tutorial</a>
                    { isAuthenticated ? <span>Signed In</span> : <SignInButton /> }
                </Navbar>
                <h5><center>Welcome to the Microsoft Authentication Library For React Tutorial</center></h5>
                <br />
                <br />
                {props.children}
            </>
        );
    };
    
  2. ここで src/App.js を開いて、既存の内容を次のコードに置き換えます。

    import React from "react";
    import { PageLayout } from "./components/PageLayout";
    
    function App() {
      return (
          <PageLayout>
              <p>This is the main app content!</p>
          </PageLayout>
      );
    }
    
    export default App;
    

これで、未認証のユーザーにのみ表示されるサインイン ボタンがアプリに追加されました。

[Sign in using Popup] または [Sign in using Redirect] のボタンをユーザーが初めて選択するとき、onClick ハンドラーによって loginPopup (または loginRedirect) が呼び出され、ユーザーのサインインが行われます。 loginPopup メソッドによって、"Microsoft ID プラットフォーム エンドポイント" でポップアップ ウィンドウが開き、ユーザーの資格情報が要求されて検証が行われます。 サインインに成功すると、msal.js によって認証コード フローが開始されます。

この時点で、PKCE で保護された認証コードが CORS によって保護されたトークン エンドポイントに送信され、トークンと交換されます。 アプリケーションによって ID トークン、アクセス トークン、および更新トークンが受信され、msal.js によって処理されて、トークンに含まれる情報がキャッシュされます。

ユーザーのサインアウト

src/componentsSignOutButton.jsx という名前のファイルを作成します。 次のいずれかのセクションからコードを追加し、ポップアップ ウィンドウまたはフルフレーム リダイレクトを使用したログアウトを呼び出します。

ポップアップを使用したサインアウト

選択時にポップアップ ログアウトを呼び出すボタン コンポーネントを作成するには、次のコードを src/components/SignOutButton.jsx に追加します。

import React from "react";
import { useMsal } from "@azure/msal-react";
import Button from "react-bootstrap/Button";

/**
 * Renders a button which, when selected, will open a popup for logout
 */
export const SignOutButton = () => {
    const { instance } = useMsal();

    const handleLogout = (logoutType) => {
        if (logoutType === "popup") {
            instance.logoutPopup({
                postLogoutRedirectUri: "/",
                mainWindowRedirectUri: "/" // redirects the top level app after logout
            });
        }
    }

    return (
        <Button variant="secondary" className="ml-auto" onClick={() => handleLogout("popup")}>Sign out using Popup</Button>
    );
}

リダイレクトを使用したサインアウト

選択時にリダイレクト ログアウトを呼び出すボタン コンポーネントを作成するには、次のコードを src/components/SignOutButton.jsx に追加します。

import React from "react";
import { useMsal } from "@azure/msal-react";
import Button from "react-bootstrap/Button";

/**
 * Renders a button which, when selected, will redirect the page to the logout prompt
 */
export const SignOutButton = () => {
    const { instance } = useMsal();
    
    const handleLogout = (logoutType) => {
        if (logoutType === "redirect") {
           instance.logoutRedirect({
                postLogoutRedirectUri: "/",
            });
        }
    }

    return (
        <Button variant="secondary" className="ml-auto" onClick={() => handleLogout("redirect")}>Sign out using Redirect</Button>
    );
}

サインアウト ボタンの追加

認証済みのユーザーに新しい SignOutButton コンポーネントが表示されるように、src/components/PageLayout.jsxPageLayout コンポーネントを更新します。 コードは次のようになります。

import React from "react";
import Navbar from "react-bootstrap/Navbar";
import { useIsAuthenticated } from "@azure/msal-react";
import { SignInButton } from "./SignInButton";
import { SignOutButton } from "./SignOutButton";

/**
 * Renders the navbar component with a sign-in button if a user is not authenticated
 */
export const PageLayout = (props) => {
    const isAuthenticated = useIsAuthenticated();

    return (
        <>
            <Navbar bg="primary" variant="dark">
                <a className="navbar-brand" href="/">MSAL React Tutorial</a>
                { isAuthenticated ? <SignOutButton /> : <SignInButton /> }
            </Navbar>
            <h5><center>Welcome to the Microsoft Authentication Library For React Tutorial</center></h5>
            <br />
            <br />
            {props.children}
        </>
    );
};

コンポーネントの条件付き表示

認証済みまたは未認証のユーザーにのみ特定のコンポーネントを表示するには、以下に示すとおり AuthenticateTemplateUnauthenticatedTemplate、またはその両方を使用します。

  1. src/App.js に次の import を追加します。

    import { AuthenticatedTemplate, UnauthenticatedTemplate } from "@azure/msal-react";
    
  2. 特定のコンポーネントを認証済みのユーザーにのみ表示するには、src/App.jsApp 関数を次のコードで更新します。

    function App() {
        return (
            <PageLayout>
                <AuthenticatedTemplate>
                    <p>You are signed in!</p>
                </AuthenticatedTemplate>
            </PageLayout>
        );
    }
    
  3. 特定のコンポーネント (ログインの提案など) を未認証のユーザーにのみ表示するには、src/App.jsApp 関数を次のコードで更新します。

    function App() {
        return (
            <PageLayout>
                <AuthenticatedTemplate>
                    <p>You are signed in!</p>
                </AuthenticatedTemplate>
                <UnauthenticatedTemplate>
                    <p>You are not signed in! Please sign in.</p>
                </UnauthenticatedTemplate>
            </PageLayout>
        );
    }
    

トークンを取得する

  1. Microsoft Graph などの API を呼び出す前に、アクセス トークンを取得する必要があります。 次のコードで、ProfileContent という新しいコンポーネントを src/App.js に追加します。

    function ProfileContent() {
        const { instance, accounts, inProgress } = useMsal();
        const [accessToken, setAccessToken] = useState(null);
    
        const name = accounts[0] && accounts[0].name;
    
        function RequestAccessToken() {
            const request = {
                ...loginRequest,
                account: accounts[0]
            };
    
            // Silently acquires an access token which is then attached to a request for Microsoft Graph data
            instance.acquireTokenSilent(request).then((response) => {
                setAccessToken(response.accessToken);
            }).catch((e) => {
                instance.acquireTokenPopup(request).then((response) => {
                    setAccessToken(response.accessToken);
                });
            });
        }
    
        return (
            <>
                <h5 className="card-title">Welcome {name}</h5>
                {accessToken ? 
                    <p>Access Token Acquired!</p>
                    :
                    <Button variant="secondary" onClick={RequestAccessToken}>Request Access Token</Button>
                }
            </>
        );
    };
    
  2. src/App.js の import を、次のスニペットに一致するように更新します。

    import React, { useState } from "react";
    import { PageLayout } from "./components/PageLayout";
    import { AuthenticatedTemplate, UnauthenticatedTemplate, useMsal } from "@azure/msal-react";
    import { loginRequest } from "./authConfig";
    import Button from "react-bootstrap/Button";
    
  3. さらに、src/App.jsApp コンポーネントの AuthenticatedTemplate の子として新しい ProfileContent コンポーネントを追加します。 App コンポーネントは次のようになります。

    function App() {
      return (
          <PageLayout>
              <AuthenticatedTemplate>
                  <ProfileContent />
              </AuthenticatedTemplate>
              <UnauthenticatedTemplate>
                  <p>You are not signed in! Please sign in.</p>
              </UnauthenticatedTemplate>
          </PageLayout>
      );
    }
    

上記のコードにより、サインイン済みのユーザーにボタンが表示され、ボタンの選択時に Microsoft Graph のアクセス トークンを要求できるようになります。

ユーザーがサインインした後は、保護されたリソースへのアクセスが必要になるたびに、アプリがユーザーに再認証を行うように要求する (つまり、トークンを要求する) べきではありません。 そのような再認証の要求を防ぐには、acquireTokenSilent を呼び出します。これは、キャッシュされている期限切れでないアクセス トークンを最初に検索し、必要な場合に更新トークンを使用して新しいアクセス トークンを取得します。 ただし、ユーザーに Microsoft ID プラットフォームとのやり取りを強制しなければならない場合があります。 次に例を示します。

  • セッションの有効期限が切れているため、ユーザーは資格情報を再入力する必要がある。
  • 更新トークンの有効期限が切れている。
  • アプリケーションがリソースへのアクセスを要求し、ユーザーの同意が必要である。
  • 2 要素認証が必須である。

acquireTokenPopup を呼び出すと、ポップアップ ウィンドウが表示されます (または、acquireTokenRedirect によってユーザーが Microsoft ID プラットフォームにリダイレクトされます)。 ユーザーはそのウィンドウ内で、自分の資格情報の確認、必要なリソースへの同意、2 要素認証の完了のいずれかの方法で操作を行う必要があります。

Internet Explorer を使用している場合は、Internet Explorer とポップアップ ウィンドウに既知の問題があるため、loginRedirect および acquireTokenRedirect メソッドを使用することをお勧めします。

Microsoft Graph API を呼び出す

  1. src フォルダーに graph.js という名前のファイルを作成し、Microsoft Graph API への REST 呼び出しを行うために、次のコードを追加します。

    import { graphConfig } from "./authConfig";
    
    /**
     * Attaches a given access token to a Microsoft Graph API call. Returns information about the user
     */
    export async function callMsGraph(accessToken) {
        const headers = new Headers();
        const bearer = `Bearer ${accessToken}`;
    
        headers.append("Authorization", bearer);
    
        const options = {
            method: "GET",
            headers: headers
        };
    
        return fetch(graphConfig.graphMeEndpoint, options)
            .then(response => response.json())
            .catch(error => console.log(error));
    }
    
  2. 次に、src/componentsProfileData.jsx という名前のファイルを作成し、次のコードを追加します。

    import React from "react";
    
    /**
     * Renders information about the user obtained from Microsoft Graph
     */
    export const ProfileData = (props) => {
        return (
            <div id="profile-div">
                <p><strong>First Name: </strong> {props.graphData.givenName}</p>
                <p><strong>Last Name: </strong> {props.graphData.surname}</p>
                <p><strong>Email: </strong> {props.graphData.userPrincipalName}</p>
                <p><strong>Id: </strong> {props.graphData.id}</p>
            </div>
        );
    };
    
  3. 次に、src/App.js を開いて、次の import を追加します。

    import { ProfileData } from "./components/ProfileData";
    import { callMsGraph } from "./graph";
    
  4. さらに、src/App.jsProfileContent コンポーネントを更新して、Microsoft Graph を呼び出してトークンの取得後にプロファイル データを表示するようにします。 ProfileContent コンポーネントは次のようになります。

    function ProfileContent() {
        const { instance, accounts } = useMsal();
        const [graphData, setGraphData] = useState(null);
    
        const name = accounts[0] && accounts[0].name;
    
        function RequestProfileData() {
            const request = {
                ...loginRequest,
                account: accounts[0]
            };
    
            // Silently acquires an access token which is then attached to a request for Microsoft Graph data
            instance.acquireTokenSilent(request).then((response) => {
                callMsGraph(response.accessToken).then(response => setGraphData(response));
            }).catch((e) => {
                instance.acquireTokenPopup(request).then((response) => {
                    callMsGraph(response.accessToken).then(response => setGraphData(response));
                });
            });
        }
    
        return (
            <>
                <h5 className="card-title">Welcome {name}</h5>
                {graphData ? 
                    <ProfileData graphData={graphData} />
                    :
                    <Button variant="secondary" onClick={RequestProfileData}>Request Profile Information</Button>
                }
            </>
        );
    };
    

上記で行った変更では、callMSGraph() メソッドを使用して、トークンが必要な保護されたリソースに対して HTTP GET 要求を実行します。 その後、この要求からその内容が呼び出し元に返されます。 このメソッドは、取得したトークンを HTTP Authorization ヘッダーに追加します。 このチュートリアルで作成したサンプル アプリケーションでは、保護されているリソースは、サインイン ユーザーのプロファイル情報を表示する Microsoft Graph API me エンドポイントです。

アプリケーションのテスト

これでアプリケーションの作成が完了し、Web サーバーを起動して、アプリの機能をテストする準備ができました。

  1. プロジェクト フォルダーのルート内から次のコマンドを実行して、アプリを起動します。

    npm start
    
  2. ブラウザー ウィンドウが開いて自動的にアプリに移動するはずです。 そうならない場合は、ブラウザーを開いて http://localhost:3000 に移動します。 次のようなページが表示されます。

    Web browser displaying sign-in dialog

  3. サインイン ボタンを選択して、サインインします。

アプリケーションへの初回サインイン時には、お使いのプロファイルへのアクセスを許可してサインインすることを求められます。

Content dialog displayed in web browser

要求されたアクセス許可に同意すると、Web アプリケーションに自分の名前が表示されます。これは、ログインが成功したことを示しています。

Results of a successful sign-in in the web browser

Graph API を呼び出す

サインインした後、 [See Profile] を選択して、Microsoft Graph API への呼び出しからの応答で返されるユーザー プロファイル情報を表示します。

Profile information from Microsoft Graph displayed in the browser

スコープと委任されたアクセス許可の詳細

Microsoft Graph API には、ユーザーのプロファイルを読み取るための user.read スコープが必要です。 既定では、このスコープは、Azure portal に登録されているすべてのアプリケーションに自動的に追加されます。 Microsoft Graph の他の API や、バックエンド サーバーのカスタム API には、追加のスコープが必要な場合があります。 たとえば、Microsoft Graph API では、ユーザーのメールを一覧表示するために Mail.Read スコープが必要です。

スコープを追加すると、追加したスコープに対して追加の同意を求めるメッセージがユーザーに表示される場合があります。

ヘルプとサポート

サポートが必要な場合、問題をレポートする場合、またはサポート オプションについて知りたい場合は、開発者向けのヘルプとサポートに関するページを参照してください。

次のステップ

Microsoft ID プラットフォームでの JavaScript シングルページ アプリケーションの開発についてさらに詳しく知りたい場合は、複数のパートで構成される次のシナリオ シリーズを参照してください。