Azure Static Web AppsにてCustom AAD認証を行う

Azure Static Web AppsにてCustom AAD認証を行う

Azureにて格安で静的WebページをホストするAzure Static Web Appsというサービスがある。ここでは簡単にAAD認証ができる……はずだったのだが、非常に苦労したお話。(そもそも管理者による制限下というのも大きいが)

結論としては、Azure Static Web Appsは十分優秀で、キャッシュ的な問題だった……。

静的Webページ

そもそも静的Webページとは何かという話をしておこう。
すなわち、HTML + CSS + JavaScriptのみで構成されたWebページのことだ。

そうでないWebページに触れたことがない人は、「他の要素なんてあるのか」と思っているかもしれない。
PHPにせよ、JavaScriptにせよ、Javaにせよ、Pythonにせよ、サーバー側で言語を走らせている、動的なWebページというものがあるのだ。

静的Webページは、サーバー側で何も走らせず、ブラウザ側で全ての処理を行う。これは管理者側からするとサーバーサイドの負担が非常に少なく、費用面でも優れる。
ただ、ユーザーにすべての情報を開示する設計であり、取り扱う内容によっては選択肢に入りえない。少なくともDBが絡むような処理は不可能だ。
また、クライアント側(利用者側)のPCにて動的な処理をすべて行うため、パフォーマンス面での制約もある。データ処理や描画にかかる時間が、そのままユーザーを待たせる時間になるわけなのだから。

静的Webページの作成にあたり

念のためのべておくと、「HTML + CSS + JavaScript」のみからなるWebページの作成の際、JavaScriptのライブラリやフレームワークを利用することは一般的だ。
HTMLなどのファイルがすべて手動作成されるわけではない。
不可能ではないが、手を動かす量と成果物の比を見れば、軍配が上がってしまう。

静的Webページホストサービス

とはいえ、用途を選べば静的Webページは非常に有用だ。例えばこのサイトのようなブログであったり、小さなサービスであったり。端的に言えばDBが不要なレベルの規模とセキュリティであれば、デメリットはさほどなく、ただただ費用面のメリットが大きい。

今回は主題でもないので詳細は割愛するが、以下のサービスが有名だ。

  • GitHub Pages
  • Netlify
  • Vercel
  • Azure Static Web Apps
  • Firebase Hosting
  • AWS Amplify
  • Cloudflare Pages

alt text

特にGitHub Pagesの登場は大きな衝撃を伴った。実際、ソースコード管理から直接Webページホストにつながるというのは、快適なユーザー体験だ。しかも、この手のサービスには珍しくドメインが<アカウント名>.github.ioであり、きれいなのだ。(一般にはランダムなURLが割り当てられる)

alt text

他にも無料であったり、そうでなくとも非常に廉価に提供されているサービスばかり。VMを一か月間稼働させるとなれば、低スペックなものでもどれほどの費用がかかるものか……。(RAM 3.5GBで他はすべて妥協しても57USD per monthだ。)

alt text

ちなみに、このブログは現在、Cloudflare Pagesでホストされている。ドメインの購入先として優れていると聞いたCloudflareでそのままWebホストまで行ったというわけだ。(こういうサービスの結合性は、利用者にとって非常に魅力的だ)

Azure Static Web Appsのアクセス制限

さて、本題に近づこう。
静的Webページは、基本的にあまり複雑な処理は行わない。その需要はすなわち、DBが必要な規模とセキュリティを持つことになる。
それでもアクセス制限をしたい状況はあるもので、例えば社内の情報を公開するためのWebページであったり、特定のユーザーにのみ公開するためのWebページであったり。

GitHub EnterpriseにおけるPagesは、GitHub Enterpriseのアクセス制限を継承しているので、実質的に社内限定公開となる。
Azure Static Web Appsは、Azureのサービスであることもあり、Microsoft Entra ID (旧Azure AD, AAD) などを利用してアクセス制限をかけることができる。

認証とアクセス制限は一致しない

Azure Static Web Appsは、非常に簡単にGitHubやAADアカウントによって認証をかけることができる。
ただ、注意しなければならない。認証とは、アカウントのIDを確認するだけだ。仮にGitHubアカウントによる認証を選択したとしても、特定のGitHubアカウントへの制限を行っているわけではない。GitHubアカウントを持っているかどうかを見ているだけだ。単なる認証では、アカウントさえあれば素通りなのだ。

パスワード制限

Standardプラン以上であれば、Azure Portalからささっとパスワード制限をかけることができる。ただし、これは非常に簡易な制限であり、全員に対して共通のパスワードを設定するだけだ。
もともと内部限定で公開するサイトに念のための制限をかける程度のもので、セキュリティを求める場合には向かない。

alt text

カスタムAAD制限

まずはAzure PortalでMicrosoft Entra IDのページを開き、アプリケーションを登録する。
その際、アクセス許可のスコープを同一テナント(社内)限定に設定できる。これが大きい。というかこれしかない。

Microsoft Entra IDの設定

参照: https://dev.classmethod.jp/articles/azure-static-web-apps-auth-custom-aad/

概要の欄から、アプリケーションIDとテナントIDをコピーできる。
さらに、クライアントシークレットを生成する。これは一度しか表示されないので、コピーしておくこと。

OAuth2.0認証を行うので、続けてリダイレクトURLを設定する。
ここではAzure Static Web AppsのURLを利用するので、状況によっては後回しにしてもよい。
プラットフォームとしてWebを選択し、リダイレクトURLにhttps://<サイト名>.<数字>.azurestaticapps.net/.auth/login/aad/callbackを設定する。
なお、Azure Static Web Appsの最大の魅力でもあるプレビュー環境を利用する場合は、プレビュー環境のURLも設定する必要がある。
そのURLはhttps://<サイト名>-<Branch名>.<地域名>.<数字>.azurestaticapps.net/.auth/login/aad/callbackとなる。
ただ、プレビューBranch名が固定とも限らないので、ワイルドカードを使ってhttps://*.<地域名>.<数字>.azurestaticapps.net/.auth/login/aad/callbackとしておくとよい。
その際は、サイドバーのマニフェストから、jsonファイルを直接編集する。

参照先サイトいわく、ワイルドカードの利用は本来望ましいものでもないので、このように面倒な仕様になっているのだろうとのこと。僕も同意見だ。

参照: https://blog.shibayan.jp/entry/20240108/1704679165

Azure Static Web Appsの設定

続けて、Azure Static Web Apps側の設定を行う。
なお、ページの単純なホストはすでにできているものとする。

サイドバーから環境変数を開き、以下の環境変数を設定する。

  • AAD_CLIENT_ID: Microsoft Entra IDのアプリケーションID
  • AAD_CLIENT_SECRET: Microsoft Entra IDのクライアントシークレット

残りのテナントIDは、設定jsonファイルにべた書きする。
興味深い点として、置換される環境変数は、そのまま文字列として記載するのだ。いつものように$で囲んだりしない。
うっかり短かいフレーズを登録すると事故が起きそうだ……。環境変数らしい名前をせいぜい心がけよう。

staticwebapp.config.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"navigationFallback": {
"rewrite": "/index.html"
},
"routes": [
{
"route": "/*",
"allowedRoles": [
"authenticated"
]
}
],
"responseOverrides": {
"401": {
"statusCode": 302,
"redirect": "/.auth/login/aad"
}
},
"auth": {
"identityProviders": {
"azureActiveDirectory": {
"registration": {
"clientIdSettingName": "AAD_CLIENT_ID",
"clientSecretSettingName": "AAD_CLIENT_SECRET",
"openIdIssuer": "https://login.microsoftonline.com/<テナントID>/v2.0"
}
}
}
}
}

(これも全部参照先に書かれているのだが)設定している内容としては、以下の通りだ。

  • Fallbackをindex.htmlに設定している。
  • すべてのページに対して「認証済み」を要求している。
    • なお、今回は同一テナントのみを許可するカスタムAADによる認証なので、別テナントの無関係なMicrosoft Entra IDではアクセスできない。安心だ。
  • 401エラー、すなわち認証なしの場合はログイン画面へリダイレクトする。
  • 認証は事前に作成したカスタムAADを利用する。

これで、今回の設定は完了だ。

障害と解決

さて、実は今回僕はこのカスタムAAD認証で失敗してかなり時間を溶かしている。
ここまでスムーズだったのにどこで失敗したのだろうか、と思われることだろう。

まず、最初にAzure Static Web Appsの環境変数設定において、別のAADのアプリケーションIDを設定してしまっていた。
これに気付くの自体が遅くなったが、むしろそこからが本番だった。

気付くまでは管理人の承諾が必要なんだろうなとうっすらとあきらめていた。
だが、環境変数設定が誤っていたということは、解決の見込みがぐっと出てくる。

なにより、クイックスタートのローカルテストでは、User Profileの取得までは成功している。
(Email取得は管理人の承諾が必要ではじかれた)

そして環境変数を更新したのに、認証画面には一向に反映されない。
commitして、pushして、buildしても、反映されない。何度も何度何度も何度も試したが、だめだった。
こういう状況で何がつらいかといえば、常に複数の間違いの可能性があり、そして実際どれもが間違っていることが多い。
さらに解決に近づいたとしても、見た目に反映されるとは限らない。遠ざかったとしてすら、分からない。

ただただTryするしかない。状況の変化を起こしていくしかない。
というわけで、環境変数の変更が認識されていない可能性を考え、プレビュー環境を新しく作成してみた。

そして……認証が通った……やったぞ!!!!
結論としては、環境変数の反映はいつでもスムーズに行われるとは限らないようだ。
これがいつ悪さをするのかは分からないが、頭の片隅に置き続ける必要はあるのだろう。

オマケ: Azure Static Web Appsのプレビュー環境

以下はAzure ReposからAzure Static Web AppsにCI/CDするためのyamlファイルの一部だ。
自動で初期生成されたものを少しだけ書き換えている。
triger > branches > includeに記載されており、かつproduction_branchに指定されていないBranchはプレビュー環境として扱われる。
develop_environmentを指定しなければ、上記の条件を満たすBranchごとにプレビュー環境が作成される。
これは非常に便利だ。
僕の場合はAzure側で確認したい場合は「review/」配下にBranchを作成している。
Git側の操作だけでプレビュー環境を新規作成できるというのは、あまりにありがたい。

なお、プレビュー環境のBranch名の部分は、「Branch名のうち/で区切られた最後の部分から記号を取り除いた部分」がプレビュー環境の名前として扱われる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
name: Azure Static Web Apps CI/CD

pr:
branches:
include:
- main
trigger:
branches:
include:
- main
- develop
- review/*

jobs:
- job: build_and_deploy_job
# 中略
- task: AzureStaticWebApp@0
inputs:
# 中略
###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
app_location: "/" # App source code path
api_location: "" # Api source code path - optional
output_location: "docs" # Built app content directory - optional
production_branch: "main" # The branch to which code will be deployed
# develop_environment: "develop" # The name of the environment to deploy to
###### End of Repository/Build Configurations ######

コメント