Auth0 の React x Express サンプルアプリを動かしてみる
はじめに
この記事では、Auth0 を用いて認証・認可を導入した React + Express アプリケーションの作成方法について説明します。Auto0 の設定から、アプリケーションの作成、認可の設定を画面キャプチャを多用しながら解説します。
想定
Auth0 のアカウントは作成済みとします。特につまづくポイントはないと思います。
また、React や Express はある程度知っていることを前提にしています。
OAuth2.0 や Auth0 の仕組みは本記事だけではわかりにくいと思うので、下記サイト等を参照ください。
- 【連載】世界一わかりみの深い OAuth 入門 〜 その 1:OAuth ってなに? 〜 | SIOS Tech. Lab
- 一番分かりやすい OAuth の説明 - Qiita
- Auth0 導入編 | フューチャー技術ブログ
- Auth0 概要 - Qiita
アプリ作成
Auth0 設定
まず、Auth0 のダッシュボードから Applications
を作成します。
次に、Quick Start
ガイドの React を選択し React サンプルアプリケーションをダウンロードします。
作成したサンプルアプリの Settings
に必要な設定を追加します。
Connections
の設定は、デフォルトのままにして、ユーザー名・パスワードの認証と、google-oauth2 の認証の二つにしました。
次に、 APIs
を作成します。
今回のサンプルアプリは、http://localhost:3001
で API サーバーを動かすので、identifier
はhttp://localhost:3001
を記載しました。
認可の確認もしたいので RBAC (Role-Based Access Control) と Permissions の設定を有効にします。
アプリケーション立ち上げ
そして、ダウンロードしている React コード の src/auth_config.json
ファイルを以下のように修正します。
domain
とclientId
はコードをダウンロードした際にすでに記載されています。
audience
は、APIs のところで設定したidentifier
を記載します。
{
"domain": "DOMAIN.us.auth0.com",
"clientId": "CLIENTID",
"audience": "http://localhost:3001"
}
Auth0 の画面の下記項目に相当します。
次に、React アプリケーションを立ち上げ、ユーザーがサインアップ、ログイン、ログアウトできることを確認します。
npm start
package.json
をみると、SPA と API Server が起動することがわかります。
"scripts": {
"start": "npm-run-all --parallel spa api-server",
},
起動画面は以下の通りで、とてもシンプルなものになっています。
右上のログインボタンを押すと Auth0 のログイン画面に遷移します。
はじめは、サインアップのリンクをクリックして、ユーザーを作成します。
ユーザー名・パスワードか、Google アカウントを用いたサインアップが可能です。
ログインすると、以下のような画面になります。
Profile をクリックすると、json のユーザー情報を確認できます。
API アクセス確認のための画面もあります。
また、デベロッパーツールを用いて、送信しているトークンを確認できます。
ブラウザから API に直接アクセスすると、 token を送信していないためUnauthorizedError
が表示されます。
最後に User Management
からユーザーが正しく作成されていることを確認します。
React のコードを確認
React のコードについても確認を行います。具体的には、以下の認証認可に関わる各コードについて確認を行います。
index.js
: Auth0 との接続
Auth0Provider
でApp
を囲むことで、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>
);
NavBar.js
: ログイン機能
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!",
});
});
参考
認可の確認
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 を使わないにしても、基本的な認証認可の仕組みを知ることができるので、たくさんの人に試してもらいたいと思います。
Discussion