🔑

Auth0 の React x Express サンプルアプリを動かしてみる

2023/07/11に公開

はじめに

この記事では、Auth0 を用いて認証・認可を導入した React + Express アプリケーションの作成方法について説明します。Auto0 の設定から、アプリケーションの作成、認可の設定を画面キャプチャを多用しながら解説します。

想定

Auth0 のアカウントは作成済みとします。特につまづくポイントはないと思います。
また、React や Express はある程度知っていることを前提にしています。
OAuth2.0 や Auth0 の仕組みは本記事だけではわかりにくいと思うので、下記サイト等を参照ください。

アプリ作成

Auth0 設定

まず、Auth0 のダッシュボードから Applications を作成します。

次に、Quick Start ガイドの React を選択し React サンプルアプリケーションをダウンロードします。

作成したサンプルアプリの Settings に必要な設定を追加します。

Connectionsの設定は、デフォルトのままにして、ユーザー名・パスワードの認証と、google-oauth2 の認証の二つにしました。

次に、 APIs を作成します。

今回のサンプルアプリは、http://localhost:3001で API サーバーを動かすので、identifierhttp://localhost:3001を記載しました。

認可の確認もしたいので RBAC (Role-Based Access Control) と Permissions の設定を有効にします。

アプリケーション立ち上げ

そして、ダウンロードしている React コード の src/auth_config.json ファイルを以下のように修正します。
domainclientIdはコードをダウンロードした際にすでに記載されています。
audienceは、APIs のところで設定したidentifierを記載します。

auth_config.json
{
  "domain": "DOMAIN.us.auth0.com",
  "clientId": "CLIENTID",
  "audience": "http://localhost:3001"
}

Auth0 の画面の下記項目に相当します。

次に、React アプリケーションを立ち上げ、ユーザーがサインアップ、ログイン、ログアウトできることを確認します。

npm start

package.jsonをみると、SPA と API Server が起動することがわかります。

package.json
  "scripts": {
    "start": "npm-run-all --parallel spa api-server",
  },

起動画面は以下の通りで、とてもシンプルなものになっています。

右上のログインボタンを押すと Auth0 のログイン画面に遷移します。

はじめは、サインアップのリンクをクリックして、ユーザーを作成します。
ユーザー名・パスワードか、Google アカウントを用いたサインアップが可能です。

ログインすると、以下のような画面になります。

Profile をクリックすると、json のユーザー情報を確認できます。

API アクセス確認のための画面もあります。

また、デベロッパーツールを用いて、送信しているトークンを確認できます。

ブラウザから API に直接アクセスすると、 token を送信していないためUnauthorizedErrorが表示されます。

最後に User Management からユーザーが正しく作成されていることを確認します。

React のコードを確認

React のコードについても確認を行います。具体的には、以下の認証認可に関わる各コードについて確認を行います。

index.js : Auth0 との接続

Auth0ProviderAppを囲むことで、App以下で認証認可の機能を使用できます。

const config = getConfig();

const providerConfig = {
  domain: config.domain,
  clientId: config.clientId,
  onRedirectCallback,
  authorizationParams: {
    redirect_uri: window.location.origin,
    ...(config.audience ? { audience: config.audience } : null),
  },
};

const root = createRoot(document.getElementById("root"));
root.render(
  <Auth0Provider {...providerConfig}>
    <App />
  </Auth0Provider>
);

auth0-reactにあるuseAuth0からログインに関わる関数や変数を取得できます。

import { useAuth0 } from "@auth0/auth0-react";

const NavBar = () => {
  const [isOpen, setIsOpen] = useState(false);
  const {
    user,
    isAuthenticated,
    loginWithRedirect,
    logout,
  } = useAuth0();

isAuthenticatedに従って、表示を切り替えます。また、loginWithRedirect()によってログインしたり、user情報を表示したりできます。

{
  !isAuthenticated && (
    <NavItem>
      <Button
        id="qsLoginBtn"
        color="primary"
        className="btn-margin"
        onClick={() => loginWithRedirect()}
      >
        Log in
      </Button>
    </NavItem>
  );
}
{
  isAuthenticated && (
    <UncontrolledDropdown nav inNavbar>
      <DropdownToggle nav caret id="profileDropDown">
        <img
          src={user.picture}
          alt="Profile"
          className="nav-user-profile rounded-circle"
          width="50"
        />
      </DropdownToggle>
      <DropdownMenu>
        <DropdownItem header>{user.name}</DropdownItem>
        <DropdownItem
          tag={RouterNavLink}
          to="/profile"
          className="dropdown-profile"
          activeClassName="router-link-exact-active"
        >
          <FontAwesomeIcon icon="user" className="mr-3" /> Profile
        </DropdownItem>
        <DropdownItem id="qsLogoutBtn" onClick={() => logoutWithRedirect()}>
          <FontAwesomeIcon icon="power-off" className="mr-3" /> Log out
        </DropdownItem>
      </DropdownMenu>
    </UncontrolledDropdown>
  );
}

Profile.js : ユーザー情報

useAuth0()により、ユーザー情報を取得して表示しています。

export const ProfileComponent = () => {
  const { user } = useAuth0();

  return (
    <Container className="mb-5">
      <Row className="align-items-center profile-header mb-5 text-center text-md-left">
        <Col md={2}>
          <img
            src={user.picture}
            alt="Profile"
            className="rounded-circle img-fluid profile-picture mb-3 mb-md-0"
          />
        </Col>
        <Col md>
          <h2>{user.name}</h2>
          <p className="lead text-muted">{user.email}</p>
        </Col>
      </Row>
      <Row>
        <Highlight>{JSON.stringify(user, null, 2)}</Highlight>
      </Row>
    </Container>
  );
};

ExternalApi.js : API アクセス

getAccessTokenSilentLy()でトークンを取得し、fetch()で API にアクセスしています。

const callApi = async () => {
  try {
    const token = await getAccessTokenSilently();

    const response = await fetch(`${apiOrigin}/api/external`, {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });

    const responseData = await response.json();

    setState({
      ...state,
      showResult: true,
      apiMessage: responseData,
    });
  } catch (error) {
    setState({
      ...state,
      error: error.error,
    });
  }
};

api-server.js : API サーバー

Express を用いて、API サーバーが構築されています。express-oauth2-jwt-bearerにあるauth()を用いてトークンを検証します。

const checkJwt = auth({
  audience: authConfig.audience,
  issuerBaseURL: `https://${authConfig.domain}/`,
  algorithms: ["RS256"],
});

app.get("/api/external", checkJwt, (req, res) => {
  res.send({
    msg: "Your access token was successfully validated!",
  });
});

参考

node.js - Auth0 - Insufficient Scope Error in Express.js using 'express-oauth2-jwt-bearer' Package - Stack Overflow

認可の確認

Auth0 設定

認可設定は APIs の RBAC (Role-Based Access Control)、Permission、Roles の設定を追加することで行います。User Management から各ユーザーに対する Role/Permission を設定します。

Auth0 のAPIsの画面から、Permissionsのタブでパーミッションを追加します。

認可を実装するために、Permission、Roles についても設定します。
ロールを作成します。

ロールにパーミッションを追加します。

ロールにユーザーを追加します。

実際のアプリでは、Auth0 の API(Assign Roles to Users) を使ってユーザーにロールを割り当てます。ここでは、簡単のため、Auth0 のダッシュボードからロールを割り当てました。

React アプリケーションを操作して token を取得し、この token を jwt.io でデコードしてみます。デコード結果から permissions が正しく設定されていることを確認します。


最後に、api-server.js を修正し、permissions があるときだけ API にアクセス可能になるようにします。

サンプルのコードはアクセストークンの検証しかしてませんが、ペイロード(ロールやパーミッション)によってアクセスを制御したい場合、以下のようなコードを使用します。requiredScopes()によってアクセスを制御する場合もありますが、今回のペイロードの構造的にclaimCheck()を使いました。

const checkClaims = claimCheck((claims) => {
  return claims.permissions.includes("read:appointments");
});
app.get("/api/external", checkJwt, checkClaims, (req, res) => {
  res.send({
    msg: "Your access token was successfully validated!",
  });
});

動作確認

permissions を紐付けたユーザーと紐づけていないユーザーを作成し、 アプリの External API の Ping API をクリックすることで、認可動作を確認できます。
パーミッションがなくデータを取得できない場合、API から401 Unauthorizedのステータスコードが返されます。デベロッパーツールを通して確認できます。

おわりに

以上の手順により、React アプリケーションおよび Express による API サーバーに Auth0 を用いた認証・認可を設定することができました。これにより、セキュアなアプリケーションの開発が可能となります。Auth0 は高度な認証・認可機能を提供しており、開発者のニーズに対応する柔軟性を持っています。

試す前は敷居が高いように感じていましたが、情報も多いので比較的スムーズに試せました。Auth0 を使わないにしても、基本的な認証認可の仕組みを知ることができるので、たくさんの人に試してもらいたいと思います。

GitHubで編集を提案

Discussion