React & Next.js に Azure Active Directory B2C (AADB2C) で超手軽に認証機能を実装しよう

Next.js に Azure Active Directory B2C (AADB2C) で超手軽に認証機能を実装しよう

はじめに

fwywd(フュード) でもバックエンドとして利用している Microsoft Azure に用意されている認証・認可機能などを兼ね備えた Azure Acitve Directory B2C (AADB2C) がめちゃくちゃ良いのです。その一方で、まだ情報が少ないからか使われていない印象を受けます。初期設定に関する情報が少ないだけでなく、実際に React や Next.js での実装にまで踏み込んでいる記事となるとほとんどありません。
そこで、今回はそんな非常に使いやすい AADB2C をみなさまに使っていただけるように、ログインの認証機能をバッチリと使っていただけるところまで紹介していきます。この記事を読んでいただいた後に、なんで今まで自前で認証や認可を実装していたのだろうと後悔間違いなしです。

前提

今回は以下の前提の方にピッタリの記事となるように書いています。
  • React & Next.js (TypeScript) を触ったことがある
  • Azure のアカウントを持っている
  • Azure Active Directory B2C のリソースを作成している(参考記事
参考になる記事を3つ紹介します。

IDaaS の導入

Web アプリケーションフレームワークに標準で搭載されている認証・認可の機能を IDaaS として提供されている外部サービスを利用する理由を紹介しています。

AADB2C の導入

AADB2C の導入はすでに以下の記事で紹介していますので、興味を持っていただけた方はぜひこちらからご覧ください。

Microsoft Azure を選択する理由

また、Microsot Azure をパブリッククラウドとして採用していますが、その理由も以前紹介していますので、気になった方はぜひこちらもご覧ください。

AADB2C の設定手順

すでに前提として紹介したとおり、AADB2C のリソース作成に関して初めての方は、Azure Active Directory B2C でユーザー認証を超簡単に実装しよう で手順を詳細に紹介しておりますので、まずはこちらから始めてください。

認証情報の設定

今回は React & Next.js で組んでいき、主に認証はクライアントサイドで行います。サーバーサイドでの処理ではない点に注意が必要です。AADB2C を利用する際の最も大きな落とし穴が、どこで処理をするかわかっていないことです。
登録したアプリケーションからサイドバーにある認証を開き、プラットフォームとしてシングルページアプリケーションを追加してください。注意として、Web もそれらしい名前がついているのですが、これはサーバーサイドでの場合の認証であり、クライアントサイドでの認証では動きません。ここの設定を忘れるとうまく動作しない理由が全くわからずに、永遠にさまようことになるので注意してください...。
04
05
06
07
08

ユーザーフローの作成

ユーザーフローに関しては前回の記事で紹介していました。
前回は新規登録とログインができるようになっていましたが、今回は違う知見もつけるために、ログインのみのユーザーフローを作ってみましょう。アプリケーション側からは新規登録できず、Azure のポータル画面からユーザーを登録するような管理方法ですね。社員がブログを操作するときなど限られた人間のみの登録を受け付ける場合には有効なユーザーフローです。
09
10
11
今回はログイン後にユーザーの情報として、メールアドレスも渡されるように、要求を返すにメールアドレスも追加します。
12
13
これで準備完了です。基本的に GUI だけで操作できるので本当に簡単ですよね。

ユーザーの作成

ログインができるユーザーを Azure のポータル画面から設定しましょう。ブログの編集ができる社員を登録するようなイメージですね。こちらも当然、簡単です。
14
15
16
17
これでユーザーが追加されていれば OK です。
Azure 側での設定は以上です。簡単すぎます!

Next.js のプロジェクト準備

次はログインを行うアプリケーション側を進めていきましょう。

バージョン情報

  • Node.js:16.13.1
  • React:17.0.2
  • Next.js:12.0.7
  • @azure
    • msal-react:1.1.2
    • msal-browser:2.20.0

プロジェクト新規作成

今回は多くの方に再現しやすいように Next.js のプロジェクトを新規で作成するところから紹介します。基本的に yarn を用いますが、npm でももちろんできますので、必要に応じて読み替えていただけると助かります。
# プロジェクト nextjs-aadb2c の作成(TypeScript) yarn create next-app --typescript nextjs-aadb2c
これで Next.js のプロジェクトが用意できたため、ディレクトリを移り、サーバーを立ち上げて動作を確認しましょう。
cd nextjs-aadb2c yarn dev
01
これで最初の準備が完了です。

src ディレクトリの作成

Next.js では初期に pages がルートにありますが、この後の編集では lib なども作っていきたいので、src ディレクトリを作成します。src の中に pages, styles を格納し、lib ディレクトリもその中に作っておくと良いでしょう。

ルートページのリセット

この後のログインの機能がわかりやすくなるように、ルートでアクセスする画面に必要な文字は削除しておきます。
// src/pages/index.tsx import type { NextPage } from 'next'; const Home: NextPage = () => { return <h1>React & Next.js と AADB2C の連携</h1>; }; export default Home;
02
さて、ここから始めていきましょう。

MSAL の導入

Azure を利用した IDaaS の良い点は、クライアントサイド側の認証に必要なライブラリが用意されていることです。Next-Auth といった外部製のライブラリを使用することもできますが、これが非常に厄介な上に情報が少ないのです。
今回は Microsoft 製でセキュリティ対策もバッチリな Microsoft Authentication Library (MSAL) を利用して実装を進めます。MSAL は JavaScript 向けにもありますが、さらに React に特化したバージョンもあるため、Next.js など React 製のアプリケーションでは開発者体験が最高です
msal-react の情報は現状で少なく、こちら のパッケージ配布用に書かれている簡単な導入と、GitHub で公開されている導入記事 を見ながら進めました。ただ、結構設定周りで苦労したので、今回の記事は他の方にとってかなり重宝するのでは無いかと思っています。
# msal-react のインストール yarn add @azure/msal-react @azure/msal-browser
これでインストール完了です。ここまでは特に問題ないと思います。

Config  の設定

今回作成した AADB2C やユーザーフローの情報を記載します。基本的には以下の内容を編集していただければ OK です。
クライアント ID は Azure のポータル画面から確認できます。
18
19
authority で登場する kikagakuapps は私が AADB2C で登録した名前です。b2c_1_signin はユーザーフローで作成した名前でしたね。これらを合わせて設定してみましょう。詳細な情報に困ったときは、以下のようにユーザーフローで定義されているページを見に行くと、情報のヒントがあるはずです。
20
// src/lib/auth/config.ts import { Configuration } from '@azure/msal-browser'; export const msalConfig: Configuration = { auth: { clientId: '34d76626-.............', authority: 'https://kikagakuapps.b2clogin.com/kikagakuapps.onmicrosoft.com/b2c_1_signin', knownAuthorities: ['kikagakuapps.b2clogin.com'], redirectUri: '/', postLogoutRedirectUri: '/', }, cache: { cacheLocation: 'localStorage', }, }; export const loginRequest = { scopes: ['openid', 'offline_access'], };
ここで、注意点として cacheLocationlocalStorage に設定しています。デフォルトでは sessionStorage です。違いはブラウザを閉じたときに記憶している (localStorage) か否 (sessionStorage) かだと考えるとわかりやすいと思います。セキュリティ的には、情報を短く持つ sessionStorage が推奨されています。この辺りは、ユーザー体験とバランスを取ると良いでしょう。

Provider の設定

Recoil や Chakra UI でおなじみの Provider の設定です。先述した設定を反映するように、以下のように MsalProvide を追加しましょう。
// src/pages/_app.tsx import { PublicClientApplication } from '@azure/msal-browser'; import { MsalProvider } from '@azure/msal-react'; import { msalConfig } from '../lib/auth/config'; import '../styles/globals.css'; import type { AppProps } from 'next/app'; function MyApp({ Component, pageProps }: AppProps) { const pca = new PublicClientApplication(msalConfig); return ( <MsalProvider instance={pca}> <Component {...pageProps} /> </MsalProvider> ); } export default MyApp;
これで準備完了です。

ログインボタンの設定

それでは、AADB2C にログインさせるボタンを配置しましょう。話をシンプルにするために特にスタイリングは行っていません。
// src/components/SigninBtn.tsx import { useMsal } from '@azure/msal-react'; import { loginRequest } from '../lib/auth/config'; export default function SigninBtn(): JSX.Element { const { instance } = useMsal(); return ( <div> <button onClick={() => instance.loginRedirect(loginRequest)}>ログイン</button> </div> ); }
// src/pages/index.tsx import type { NextPage } from 'next'; import SigninBtn from '../components/SigninBtn'; const Home: NextPage = () => { return ( <> <h1>React & Next.js と AADB2C の連携</h1> <SigninBtn /> </> ); }; export default Home;
以下のように、ログイン用のボタンを押して、AADB2C でのログイン画面に遷移すれば成功です。msal-react があることで、神がかって実装を楽にしてくれました。
21
22

ユーザーの状態を取得

ログインが完了しても見た目は特に変わりませんが、実はブラウザ上のローカルストレージにしっかりとプロフィール情報を含んだトークンのがしっかりと保存されています。Google Chrome の場合、開発者ツールで確認することができます。
23
このトークンから情報を読み込む方法も react-msal では当然サポートされています(当たり前ですが神!)。以下のように、useCurrentUser という Hook を作っておくと、色々なページから利用できて便利です。
// src/hooks/useCurrentUser.ts import { AccountInfo } from '@azure/msal-browser'; import { useMsal } from '@azure/msal-react'; interface Account extends AccountInfo { idTokenClaims: { aud: string; auth_time: number; family_name: string; given_name: string; emails: string[]; iss: string; nbf: number; nonce: string; sub: string; tfp: string; ver: string; }; } export interface User { sub: string; familyName: string; givenName: string; email: string; } const useCurrentUser = (): User | null | undefined => { const { accounts } = useMsal(); if (accounts.length > 0) { const account = accounts[0] as Account; const user: User = { sub: account.idTokenClaims?.sub, familyName: account.idTokenClaims?.family_name, givenName: account.idTokenClaims?.given_name, email: account.idTokenClaims?.emails[0], }; return user; } return null; }; export default useCurrentUser;
この useCurrentUser を利用して、まずはターミナルで取得したユーザー情報を表示してみましょう。
// src/pages/index.tsx import type { NextPage } from 'next'; import SigninBtn from '../components/SigninBtn'; import useCurrentUser from '../hooks/useCurrentUser'; const Home: NextPage = () => { const user = useCurrentUser(); console.log(user); return ( <> <h1>React & Next.js と AADB2C の連携</h1> <SigninBtn /> </> ); }; export default Home;
24
めっちゃ簡単にユーザー情報にアクセスできて感動的です!

ログイン時にユーザー情報を表示

それでは取得したユーザー情報を文字として表示するように設定していきましょう。ここで、ログイン時にはユーザー情報が存在して表示しても良いですが、ログインしていない状況では表示しない情報の出し分けが必要となります。これを実装するのは意外と面倒なのです(情報が一時的に表示されるチラツキを抑えるなど)。
この点も msal-react はもちろん対応しています。ログイン時の情報は AuthenticatedTemplate のブロック内に、非ログイン時の情報は UnauthenticatedTemplate に記載するだけと超簡単です。
// src/pages/index.tsx import type { NextPage } from 'next'; import { AuthenticatedTemplate, UnauthenticatedTemplate } from '@azure/msal-react'; import SigninBtn from '../components/SigninBtn'; import useCurrentUser from '../hooks/useCurrentUser'; const Home: NextPage = () => { const user = useCurrentUser(); return ( <> <h1>React & Next.js と AADB2C の連携</h1> <UnauthenticatedTemplate> <SigninBtn /> </UnauthenticatedTemplate> <AuthenticatedTemplate> <p> こんにちは、{user?.familyName} {user?.givenName} さん </p> <p>メールアドレス:{user?.email}</p> </AuthenticatedTemplate> </> ); }; export default Home;
25
これも超簡単です!最高!

ログアウトボタン

最後はログアウトのボタンを作れば完成です。こちらもログインのボタンとほぼ同じです。
// src/components/SignoutBtn.tsx import { useMsal } from '@azure/msal-react'; export default function SigninBtn(): JSX.Element { const { instance } = useMsal(); return ( <div> <button onClick={() => instance.logout()}>ログアウト</button> </div> ); }
// src/pages/index.tsx import type { NextPage } from 'next'; import { AuthenticatedTemplate, UnauthenticatedTemplate } from '@azure/msal-react'; import SigninBtn from '../components/SigninBtn'; import SignoutBtn from '../components/SignoutBtn'; import useCurrentUser from '../hooks/useCurrentUser'; const Home: NextPage = () => { const user = useCurrentUser(); return ( <> <h1>React & Next.js と AADB2C の連携</h1> <UnauthenticatedTemplate> <SigninBtn /> </UnauthenticatedTemplate> <AuthenticatedTemplate> <p> こんにちは、{user?.familyName} {user?.givenName} さん </p> <p>メールアドレス:{user?.email}</p> <SignoutBtn /> </AuthenticatedTemplate> </> ); }; export default Home;
これでログアウトしてみると、以下のように UnauthenticatedTemplate のブロック内で書かれたログインボタンのみが表示されると成功です。
26
27
これで認証に必要な一連の機能をすべて紹介できました!
AADB2C と msal-react の組み合わせの開発者体験は最高です!!

おわりに

認証という機能を作るためには、セキュリティなど非機能要件も含めた考慮が必要となりますが、AADB2C を利用することで、この点の問題を実装コスト 0 でクリアすることができています。また、クライアント側での実装も msal-react を利用することで、めちゃくちゃ簡単に実装を進められます。
今回の記事で IDaaS は難しいんじゃない?と疑問に思われえていた方も見方が変わっていれば嬉しいです。ぜひ、みなさん AADB2C を使った認証を利用することで、より本質的な独自性のある機能の開発に時間を割いていきましょう!

著者

株式会社キカガク 代表取締役会長
吉崎 亮介
twitter: @yoshizaki_91