Next.jsとMicrosoft Entra External IDでログイン機能を持つWebサイトを作る
はじめに
今回はMicrosoft EntraIDとNext.jsを使って、ログイン機能を実装してみたいと思います。
以下の記事を見てみると、Azure AD B2CよりMicrosoft Entra External IDを使ったログインの方がデファクトスタンダードになるようです。
今回作成するアプリのアーキテクチャは以下のようになります。
リポジトリは以下になります。
ご参考までに!
環境構築
Githubリポジトリの作成
まずはリポジトリを作成します。
サクサク進めていきます。
コードの箇所からURLをコピーしてローカルへcloneします。
これでリポジトリの作成は完了です。
Next.jsプロジェクトの作成
準備できたディレクトリに以下のコマンドを実行してください。
npx create-next-app . --ts
色々質問されるので、全部Yesで回答
✔ Would you like to use ESLint with this project? … No / Yes
✔ Would you like to use Tailwind CSS with this project? … No / Yes
✔ Would you like to use `src/` directory with this project? … No / Yes
✔ Use App Router (recommended)? … No / Yes
✔ Would you like to customize the default import alias? … No / Yes
✔ What import alias would you like configured? … @/*
Creating a new Next.js app in /Users/s.y/prj/04.webapp/sat-web-repo.
出来上がったプロジェクトをVSCodeで開いてください。
まずは必要なモジュールをインストールします。
npm install @azure/msal-browser @azure/msal-react
次に、src/lib/config.tsを作成してください。
export const msalConfig = {
auth: {
clientId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', // Azure ポータルで取得したクライアントID
authority:
'https://login.microsoftonline.com/xxxxxxxxx-xxxxxxxxxxx-xxxxxxxxxxx', // テナントID
redirectUri: 'http://localhost:3000', // リダイレクトURI
},
cache: {
cacheLocation: 'sessionStorage', // キャッシュの場所
storeAuthStateInCookie: false, // IE11やEdgeをサポートする場合はtrueに設定
},
};
export const loginRequest = {
scopes: ['User.Read'],
};
xxxxの部分は後ほどリソース作成後に置き換えるイメージです。
次にsrc/components/Providers.tsxを以下のように実装してください。
'use client';
import { msalConfig } from '@/lib/msalConfig';
import { PublicClientApplication } from '@azure/msal-browser';
import { MsalProvider } from '@azure/msal-react';
import React from 'react';
const msalInstance = new PublicClientApplication(msalConfig);
export function Providers({ children }: { children: React.ReactNode }) {
return <MsalProvider instance={msalInstance}>{children}</MsalProvider>;
}
次にユーザー取得のためのカスタムフックを作成します。
src/hooks/useCurrentUser.tsを作成し、以下のように実装してください。
import { AccountInfo } from '@azure/msal-browser';
import { useMsal } from '@azure/msal-react';
interface Account extends AccountInfo {
idTokenClaims: {
aud: string;
iss: string;
iat: number;
nbf: number;
exp: number;
idp: string;
name: string;
nonce: string;
oid: string;
preferred_username: string;
rh: string;
sub: string;
tid: string;
uti: string;
ver: string;
};
}
export interface User {
sub: string;
user_name: 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?.aud,
user_name: account.idTokenClaims?.name,
email: account.idTokenClaims?.preferred_username,
};
return user;
}
return null;
};
export default useCurrentUser;
次にsrc/app/page.tsxを以下のように実装してください。
'use client';
import useCurrentUser from '@/hooks/useCurrentUser';
import { loginRequest } from '@/lib/msalConfig';
import { useMsal } from '@azure/msal-react';
import { useEffect } from 'react';
export default function Home() {
const { instance, accounts } = useMsal();
const user = useCurrentUser();
useEffect(() => {
console.log('🚀 ~ SideMenu ~ user:', user);
console.log('🚀 ~ SideMenu ~ accounts:', accounts);
}, [accounts]);
return (
<main>
<h1
onClick={() =>
user === null
? instance.loginRedirect(loginRequest)
: instance.logout()
}
className="flex justify-center px-4 text-2xl font-bold cursor-pointer"
>
{user === null ? 'login' : 'logout'}
</h1>
<h1>
{user === null
? 'Please login to see your profile'
: `Welcome ${user?.user_name}! 🚀 Warm Welcome 🚀 Your email: ${user?.email} && ${user?.sub}`}
</h1>
</main>
);
}
次にsrc/app/layout.tsxを以下のように実装してください。
import { Providers } from '@/components/Providers'
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'External ID',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="ja">
<body className={inter.className}>
<Providers>{children}</Providers>
</body>
</html>
);
}
次に、node.jsのversionを指定しておきます。
以下を一番下に追記してください。
"engines": {
"node": "20.9.0"
}
次に、next.config.jsをoutput: "standalone"を追記してください
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone"
}
module.exports = nextConfig
ここまで記載したら一回リポジトリへpushしましょう。
Azure Static Web Appsの作成
Azureポータルにログインして、リソースグループを作成します。
次にStatic Web Appsを作成します。
Static Web AppsのDeployの設定で先ほど作成したGithubを指定します。
あとはデフォルトの設定で作成します。
作成ボタンをクリックして、static web appsをが作成されたことを確認します。
これでAzure Static Web Appsの作成は完了です。
Github Actionsを使ってNext.jsをStaticWebAppsにデプロイ
では、Githubのリポジトリへ画面を戻ってください。
Actionsタブに行くと、Azure Static Web Apps CI/CDのワークフローが作成されていることを確認できます。
しばらく待ってエラーなくDeployされているか確認しましょう。
こんな感じになっていればOKです。
これで下準備は完了です。
肝心のログイン機能を作っていきましょう!
Microsoft Entra External IDの設定
まずはEntra管理センターへログインして、外部テナントを作成します。
概要→テナントの管理の画面で作成をクリックしてください。
外部を選択して、テナントを選択して作成してください。
テナント情報を入力...
これで外部テナントのAzureサブスクリプションの作成は完了です。
元のAzure PortalからExternal IDのテナントが作成出来ていればOKです!
アプリケーションの登録
Microsoft 管理センターから画面左側のアプリケーション→アプリの登録をクリック
アプリケーション登録に際して、必要な情報を入力してください。
今回はNext.jsで実装するので、シングルページアプリケーションを選択してください。
xxxxに管理者の同意を与えます
という箇所があるので、クリックして同意を与えてください。
こんな感じに表示されていればOKです!
ユーザーフローの作成
次はユーザーフローを作成します。
画面左の箇所からExternal Identitiesのユーザーフローを選択してください。
新しいユーザーフローをクリック
ユーザーフローを作成する画面にて、ユーザーフローの名前とパスワードを含むメールを選択し、
ユーザー属性には以下3つのチェックを入れて作成をクリックしてください。
- Given Name
- Surname
- Display Name
ユーザーフローをEntraID portalから実行して、リダイレクトされることを確認し、無事にユーザーが登録されていればOKです!
config.tsの設定
先ほど記載した以下のmsalConfigファイルを修正します。
export const msalConfig = {
auth: {
clientId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', // Azure ポータルで取得したクライアントID
authority:
'https://login.microsoftonline.com/xxxxxxxxx-xxxxxxxxxxx-xxxxxxxxxxx', // テナントID
redirectUri: 'http://localhost:3000', // リダイレクトURI
},
cache: {
cacheLocation: 'sessionStorage', // キャッシュの場所
storeAuthStateInCookie: false, // IE11やEdgeをサポートする場合はtrueに設定
},
};
export const loginRequest = {
scopes: ['User.Read'],
};
clientIdはEntra portalのアプリケーション→アプリの登録→全てのアプリケーションの箇所から、作成したアプリケーションを選択し、クライアントIDの箇所を貼り付けます。
authorityはURL末尾にテナントIDをくっつけます。xxxxの箇所に記載してください。
これで設定は完了です。
動作確認
では、今回はローカルで動かしてみましょう。
Next.jsのアプリケーションを起動してください。
npm run dev
これ以上ないぐらいシンプルな画面になってますね。
loginをクリックすると、こんな感じのカスタマイズされたログイン画面が表示されます。
別のアカウントを使用するをクリックするとこんな画面が表示されます。
メールアドレスにコードを送ってくれます。
コードを入力すると、パスワードの設定画面が表示されます!
もろもろ登録を完了すると、Microsoft EntraID 管理センターにユーザーが登録されていればOKです!
最後に、こんな感じでユーザー情報が取得できれいればOKです!!
これにて実装は完了です!
お疲れ様でした!
参考資料
Discussion