ちょっと必要に駆られたのでメモ書きです。Azure App Service(Web Apps/Functions)には通称EasyAuth(Authentication/Authorization)という便利な機能がありますが、今のところ簡単に設定できるのはAzure ADやTwitterなどのソーシャル系のみです。
このPostではEasyAuthでAzure ADではなくAzure AD B2Cを指定して、かつフロントエンドのWeb Appsと裏側のAPIとなるFunctionsをそれぞれ保護するためのTipsを纏めておきます。
想定環境としては以下のような感じ。
認証・認可はAzure AD B2C側で行うようにWeb AppsとFunctionsのEasyAuthを構成します。
Web Appsで提供されるWebアプリはJavaScriptとかで直接Functionsに通信するけどその際に本人のアクセストークンを使わないとAPIが呼べないようにしたい、というのが主旨ですね。
Azure AD B2Cの構成
さてまずはAAD B2C側で最低限の設定を行います。管理側がすることは
- ユーザーフロー(ポリシー)またはIdentity Experience Framework(IEF)の作成
- アプリケーションの登録
の大きく2つです。要件によってユーザーフローかIEFでカスタムポリシーを作ります。IDプロバイダーの追加やユーザーの作成などは適宜どうぞ。AAD B2Cのユーザーフローは最低限サインアップ/サインインフローがあればとりあえずEasyAuthは構成できます。
次にアプリケーションをAAD B2Cに登録します。Webアプリ用とBackendのFunctions用に2つ登録します。この時の注意点は2つほど。1つは応答URLはEasyAuthの場合、以下のように /.auth/login/aad/callback を付与したURLにすること、2つ目はアプリケーション登録(プレビューじゃないほう)で作るアプリキー(Secret)は期限が100年というところでしょうか。
応答URLはAzure App Serviceでカスタムドメインを指定していない場合は以下のようになります。
https://<app name>.azurewebsites.net/.auth/login/aad/callback
それぞれ作ってアプリケーションIDとアプリキーを控えておきます。(EasyAuth構成時に使用します)
EasyAuthの構成
EasyAuthの構成(Web/Functionsとも)はそんなに難しくありません。が、ちょっと罠があるのでその辺は後程。
さて設定する際はAzure Active Directoryを選んでAdvancedを選択し、必要な情報を設定していきます。
- Client ID … それぞれ用に作ったAzure AD B2CのアプリケーションID
- Client Secret … それぞれ用に作ったAzure AD B2Cのアプリキー
- Allowed Token Audiences … それぞれのFQDN
さてIssuer Urlですが、わかりにくいですがAzure AD B2Cの対象のユーザーフローのopenid-configurationを指定します。このURLはユーザーフローの実行時に表示される値をコピーすれば楽です。
https://<aad b2c tenantname>.b2clogin.com/<b2c domain>/v2.0/.well-known/openid-configuration?p=B2C_1_<user flow name>
という形式のURLなので分かってしまえばどうということはないです。(ユーザーフローを指定するのを忘れずに)

さてこれでWeb/Functionsは適切なアクセストークンなりが無いとアクセスできないように保護されました。めでたしめでたし…と言いたいところですがこのままでは思ったことはできません。(とりあえずWebにアクセスすると問題なければAAD B2CでログインしてID_Tokenを取得したりはできます)
具体的にはWebでログインした際にAccess_Tokenが含まれてないのでAPI(Functions)を呼ぶ際に困る、Refresh_Tokenもないというところでしょうか。
ということで次はそのあたりを構成します。
アプリケーションのAPI公開と同意
先ほどの問題に戻りますが、アクセストークンを得るためには使うAPI(リソース)とスコープにも権限があるトークンをくださいと認証時に教えてあげないとAAD B2C(IdP)側は提供してくれません。なのでそのように指示するわけですが、その前にこんなスコープがありますというのをアプリ側が持っていないとダメなので、AAD B2Cテナントに登録したアプリケーションに情報を追加します。
AAD B2C側で最初にBackend(Functions)用として登録したアプリケーションを「アプリの登録(プレビュー)」のほうで表示します。「APIの公開」でスコープを追加しましょう。
スコープ(アプリケーションIDのURL+スコープ名なURL)は後で使うので控えておきます。
これでバックエンド側にはこんなスコープがありますよというのが定義できました。
次にFrontend(Web)側のアプリの登録(プレビュー)でそのAPIを使うようにアクセス許可を行います。
アクセス許可の追加を行い自組織内からBackend用のアプリを選べば先ほど追加したスコープが出てくると思うので追加して管理者の同意を与えます(管理者アカウントで同意をしておかないとアクセス時に利用者に同意を求めてくる(と思う)、が権限が無いのでエラーになる)。最終的に状態が「<テナント名>に付与されました」になればOKです。

アクセストークンをもらうように構成
次はWebアプリ側です。Webアプリにアクセスがあって認証する際に、BackendのAPI(リソース)に対するトークンもくださいと指示する必要があります。EasyAuthを使わずに自前でAzure AD B2Cのエンドポイントを叩いたりする場合はOIDCなどのお作法に従えばいいのですがEasyAuthで行う場合はちょっとコツが必要になります。
具体的には resources.azure.com などを使ってフロントのWeb Appsの config/authsettings に additionalLoginParams を構成する必要があります。(ポータルのブレード内にある「Resource Explorer」から開けます)
このプロパティは認証時に渡すいろんなパラメーターを指定できます。
今回修正が必要なのはresponse_typeとresource、scopeの3つになります。response_typeはcode id_tokenを、resourceにはアクセス対象となるAAD B2Cに登録したBackend用のアプリのIDを、scopeには openid offline_access とBackendアプリに登録したスコープ(URL)を指定します。

"additionalLoginParams": [
"response_type=code id_token",
"resource=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"scope=openid profile email offline_access https://xxxx.onmicrosoft.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/user_impersonation"
]
余談ですがこのauthsettingsを追加すればEasyAuthの構成ができるので自動展開する際は参考にしましょう。
以上で構成は終了です。ざっくり纏めるなら以下のような感じでしょうか?(雑な図だ)
実際にWebアプリにアクセスすると指定したAAD B2Cのユーザーフローが実行され、認証後はTokenStore(Httpリクエストヘッダー)を見たり /.auth/me とかにアクセスするとaccess_tokenやrefresh_tokenが含まれていることがかります。あとはWebアプリ内でこのaccess_tokenをBearerトークンとしてAuthorizationヘッダーに付与すればFunctionsのAPIでも問題なくアクセスできます。
Functions側でもEasyAuthを構成してますので、アクセストークンの検証は自動的にしてくれますし、TokenStoreやHTTPリクエストヘッダー経由で必要な情報を取得し、適切に処理することができるようになります。
その他
最低限の構成はこれでOKですが、Webアプリ側やバックエンドのAPI側ではスコープを見てアクセスの可否を判断したり、アクセストークンの管理や更新、サインアウトなど適切に処理するようにちゃんと開発しましょうね。
またサインアウト時のページなど一部URLはEasyAuthで認証必須にしたくない(匿名でもOK)といった要件は以下のリンクにあるようにアプリルートにauthorization.json(またはYaml)を置いてルールを構成すれば対応可能です。
というわけでEasyAuthは(ちょっと闇が多いけど)非常に便利で素晴らしい機能だと思うのでうまく活用しましょう。