- 記事一覧 >
- ブログ記事
Azure Static Web AppsのアプリにAzure ADカスタム認証機能を追加
はじめに
Azure Static Web Apps アプリに Azure AD(Azure Active Directory)を使った認証機能を追加しました。
別記事「MSAL React を使用してサインインする SPA を Azure Static Web Apps にデプロイ」でも認証機能を実現していますが、この時は、MSAL を使って自力で実装したパターンです。
今回は、それと異なり、Azure Static Web Apps の機能を利用しています。
https://learn.microsoft.com/ja-jp/azure/static-web-apps/authentication-custom?tabs=aad
に
「Azure Static Web Apps が提供するマネージド認証では、Azure が管理するプロバイダー登録が使用されます。
登録の柔軟性を高めるため、既定値をカスタム登録でオーバーライドできます。」
とあり、最初、??...何をおっしゃっておられるのかな?
と思いましたが、
いろいろやっている内に、
マネージド認証: ほとんど何も実装しなくても認証機能を追加できる。しかも、Azure AD、GitHub、Twitter のアカウントを使える。Azure AD テナントは絞り込めない。
カスタム認証: Azure AD 側で仕込みが必要。認証対象を一つのテナントに絞り込める。Azure AD に限らず、OpenID Connect (OIDC)プロバイダー等も利用可。有料の Standard プラン必要。
ということが分かってきました。
今回、両方ともの手順と検証結果を書きたいと思います。
検証用プログラム
本記事で使用するソースコードサンプルは、GitHub にアップしました。https://github.com/itc-lab/azure-devops-custom-auth-example
フロントエンド: React TypeScript
バックエンド API: Python (Azure Static Function用)
フロントエンドの実装については、
別記事「React TypeScript ESLint Prettier VSCode のプロジェクト作成」
で create-react-app
を使って作成したプロジェクトをベースにしています。(記事の通り、ESLint、Prettier に対応しています。)
肉付けした実装は、
「7. Web アプリに簡単な認証を追加する」(https://learn.microsoft.com/ja-jp/azure/developer/javascript/how-to/with-web-app/static-web-app-with-swa-cli/add-authentication
)
ほぼそのままです。
Python の Azure Static Function 用 API 作成 については、別記事「【swa】Azure Static Web Apps をローカル環境でデバッグ」で説明しています。
これを /api/hello
とし、{ "message": "メッセージ内容" }
の JSON を返すようにしました。
メッセージ内容は、X-MS-CLIENT-PRINCIPAL-ID
X-MS-CLIENT-PRINCIPAL-NAME
X-MS-CLIENT-PRINCIPAL-IDP
X-MS-CLIENT-PRINCIPAL
X-MS-TOKEN-AAD-ID-TOKEN
X-MS-TOKEN-AAD-ACCESS-TOKEN
X-MS-TOKEN-AAD-REFRESH-TOKEN
ヘッダの内容です。
以降、前提として、API(https://**********.azurestaticapps.net/api/hello
)は、認証された人からしか呼び出せないようにするものとします。
トークンについて
後になって、知ると困る事になりそうなので、話を進める前に書いておきますが、この記事の方法で認証を実現してもトークンは渡されないようです。
トークンを使って、本人の代わりに Microsoft サービスを呼び出すようなことはできません。
(https://stackoverflow.com/questions/71728993/how-to-get-azure-ad-access-token-in-static-web-app)
つまり、以下のようなことはできないということです。
2番目の On-Behalf-Of (OBO) フローについては、チュートリアルをやってみた記事を
「MSAL React を使用してサインインする SPA を Azure Static Web Apps にデプロイ」
に書きました。Microsoft Authentication Library(MSAL)を使って実現する必要があります。
デプロイについて
別記事「Azure DevOps から React Python アプリを Azure Static Web Apps にデプロイ」に従って、sample-project
が Azure Static Web Apps にデプロイ済みとします。
マネージド認証
承認プロバイダー | ログイン ルート |
---|---|
Azure Active Directory | /.auth/login/aad |
GitHub | /.auth/login/github |
/.auth/login/twitter |
とあり、何もしなくても
https://[デプロイしたAzure Static Web Apps URL]/.auth/login/aad
で Azure AD 認証https://[デプロイしたAzure Static Web Apps URL]/.auth/login/github
で GitHub 認証、https://[デプロイしたAzure Static Web Apps URL]/.auth/login/twitter
で Twitter 認証
となります。
かなりありがたい機能です。
以上です。
...で終わると、重大な欠陥を持ったアプリになります!
https://**********.azurestaticapps.net/api/hello
と直接 API を呼び出すことが可能で、何もブロックしていません。
そこで、staticwebapp.config.json
をリポジトリに置いて、routes
を設定します。
一部、本物のAzureで検証すると辛いため、
swa
コマンドを使ってローカルで検証しています。画面キャプチャは、ローカルで検証中の画面ですが、本物でも結果は同じです。
swa
は、ダミーの認証画面が表示されて、今回のような認証動作も検証できます。
swa
コマンドの詳細は、別記事「【swa】Azure Static Web Appsをローカル環境でデバッグ」を参照してください。 ←この記事の時は、承認と認証のエミュレーションの検証はスキップしています。
認証をかけるには、
{
"route": "/*", //保護したいパス
"allowedRoles": ["authenticated"]
}
のように、"allowedRoles": ["authenticated"]
を設定します。"authenticated"
の部分は、ロール(全員じゃなくてあらかじめ選ばれた人々に限定)にすることもできますが、ロールの話は、今回スルーします。
{
"routes": [
{
"route": "/*",
"allowedRoles": ["authenticated"]
}
]
}
を設定して、
https://**********.azurestaticapps.net/
にアクセスしてみます。
単純にアクセス拒否されました。あらゆるアクセスが拒否されて、AD 認証画面にたどり着けないので、何もできません。
URL 欄に直接https://**********.azurestaticapps.net/.auth/login/aad
を入力して、ログインできます。
これで、アクセス可能になって、ログイン後の画面になりましたが、この操作方法を知らないと、使えません。
ログアウトすると、また同じ状況になります。
今度は、401: Unauthorized
の時、/.auth/login/aad
にリダイレクトされるようにします。
{
"routes": [
{
"route": "/*",
"allowedRoles": ["authenticated"]
}
],
"responseOverrides": {
"401": {
"statusCode": 302,
"redirect": "/.auth/login/aad"
}
}
}
とします。
今度はアクセスした途端、AD 認証画面になります。
↓
これはこれで、良いのですが、最初の画面(twitter, github, aad 選択画面)を無認証で表示したいです。
無認証は、"allowedRoles": ["anonymous"]
を設定すれば良いので、
{
"routes": [
{
"route": "/",
"allowedRoles": ["anonymous"]
},
{
"route": "/index.html",
"allowedRoles": ["anonymous"]
},
{
"route": "/*",
"allowedRoles": ["authenticated"]
}
],
"responseOverrides": {
"401": {
"statusCode": 302,
"redirect": "/.auth/login/aad"
}
}
}
とします。
アクセスすると、画面が真っ白になりました。
これは、なぜかというと、/static/js/*
、/static/css/*
、/static/media/*
、/manifest.json
、/favicon.ico
にアクセスが行って、リダイレクトされているからです。
/static/js/*
、/static/css/*
、/static/media/*
、/manifest.json
、/favicon.ico
へのアクセスで 401 が返らないようにします。
{
"routes": [
{
"route": "/",
"allowedRoles": ["anonymous"]
},
{
"route": "/index.html",
"allowedRoles": ["anonymous"]
},
{
"route": "/manifest.json",
"allowedRoles": ["anonymous"]
},
{
"route": "/favicon.ico",
"allowedRoles": ["anonymous"]
},
{
"route": "/static/js/*",
"allowedRoles": ["anonymous"]
},
{
"route": "/static/css/*",
"allowedRoles": ["anonymous"]
},
{
"route": "/static/media/*",
"allowedRoles": ["anonymous"]
},
{
"route": "/*",
"allowedRoles": ["authenticated"]
}
],
"responseOverrides": {
"401": {
"statusCode": 302,
"redirect": "/.auth/login/aad"
}
}
}
とします。
OK!
未認証で、/api/hello
にアクセスすると、/.auth/login/aad
にリダイレクトされます。
↓
ログイン用のリンクがあるのだから、リダイレクトではなく、普通に拒否で良いやということで、リダイレクトをやめてみます。
{
"routes": [
{
"route": "/",
"allowedRoles": ["anonymous"]
},
{
"route": "/index.html",
"allowedRoles": ["anonymous"]
},
{
"route": "/manifest.json",
"allowedRoles": ["anonymous"]
},
{
"route": "/favicon.ico",
"allowedRoles": ["anonymous"]
},
{
"route": "/static/js/*",
"allowedRoles": ["anonymous"]
},
{
"route": "/static/css/*",
"allowedRoles": ["anonymous"]
},
{
"route": "/static/media/*",
"allowedRoles": ["anonymous"]
},
{
"route": "/*",
"allowedRoles": ["authenticated"]
}
]
}
未認証で、/api/hello
にアクセスすると、401: Unauthorized
になりました。
目的は達成です。
ちなみに、ここまでやっておいてなんですが、結果、API が保護されれば良いので、以下の設定でも目的は達成されます。
{
"routes": [
{
"route": "/api/*",
"allowedRoles": ["authenticated"]
}
]
}
続いて、twitter と github を無効にして、Azure AD 認証だけにします。
{
"routes": [
{
"route": "/api/*",
"allowedRoles": ["authenticated"]
},
{
"route": "/.auth/login/twitter",
"statusCode": 404
},
{
"route": "/.auth/login/github",
"statusCode": 404
}
]
}
成功です。
しかし、この状態では、Azure AD のどのテナントでもログインできてしまいます。
どういうことかと言うと、A 社、B 社、C 社、謎の組織...と Azure AD のアカウントであれば、サービスを提供する予定の無い会社でも謎の組織でも、誰でもログインできるということです。
正確には、ロールで絞り込めますが、25アカウントまでという情報があります。
【
https://learn.microsoft.com/ja-jp/azure/static-web-apps/faq
】
それで問題無い場合は、ここまでの対応で良いですが、A 社だけログインできるようにしたい場合、カスタム認証の対応が必要になります。
次節でカスタム認証の対応をやっていきます。
カスタム認証
カスタム認証を行うには、3つの対応が必要になります。
・Azure AD のテナントでアプリの登録
・staticwebapp.config.json
に "auth":
設定追加
・Azure Static Web Apps のアプリケーション設定で環境変数設定
Azure Static Web Apps のアプリのホスティング プランを有料のStandardプランに切り替える必要があります。
Freeプランのままの場合、ここで行う作業はことごとく無視されます。
アプリの登録
・Azure AD のテナントでアプリの登録
からやっていきます。
Azure ポータル(https://portal.azure.com/
)にアクセスして、Azure Active Directory をクリックします。
アプリ情報を入力します。
名前: sample-project
(任意)
サポートされているアカウントの種類: この組織ディレクトリのみに含まれるアカウント (<テナント名> のみ - シングル テナント)
リダイレクト URI: Web
https://**********.azurestaticapps.net/.auth/login/aad/callback
アプリは、SPAですが、
Web
が正解です。
シングルページアプリケーション (SPA)
で登録すると、AADSTS9002325: Proof Key for Code Exchange is required for cross-origin authorization code redemption.
エラーになります。
OAuth 2.0 authorization code flow で動作するのに、
Web
の選択が必要です。
https://**********.azurestaticapps.net
はアプリのURLです。
/.auth/login/aad/callback
は決め打ちです。
登録 をクリックします。
アプリケーション (クライアント) ID、ディレクトリ (テナント) ID が表示されます。これは、後で設定するところで必要になります。この アプリの概要 画面で何度でも確認できます。
アプリの概要 画面のまま、左側にある 証明書とシークレット をクリックします。
クライアントシークレット を選択して、新しいクライアントシークレット をクリックします。
説明:sample-project secret
有効期限:12 ヵ月
(任意)
とし、追加 をクリックします。
追加し終わると、値 と シークレット ID が表示されます。これは、他の画面に移ると、二度と表示されませんので、どこかへコピーペーストしておく必要があります。
後で必要になるのは、値 の方です。
左側にある 認証 をクリックします。
フロントチャネルのログアウト URL にhttps://**********.azurestaticapps.net/.auth/logout/aad/callback
を入力します。
**********.azurestaticapps.net
はアプリのURLです。
/.auth/logout/aad/callback
は決め打ちです。
承認エンドポイントによって発行してほしいトークンを選択してください。 のところのID トークン (暗黙的およびハイブリッド フローに使用)
にチェックを入れます。
チェックを入れずに登録すると、
AADSTS700054: response_type 'id_token' is not enabled for the application.
エラーになります。
カスタム認証は、ID トークンを使用するからです。
保存 をクリックします。
staticwebapp.config.json 設定追加
・staticwebapp.config.json
に "auth":
設定追加
を行います。
{
"auth": {
"identityProviders": {
"azureActiveDirectory": {
"registration": {
"openIdIssuer": "https://login.microsoftonline.com/<テナントID>/v2.0",
"clientIdSettingName": "AZURE_CLIENT_ID",
"clientSecretSettingName": "AZURE_CLIENT_SECRET"
}
}
}
},
"routes": [
{
"route": "/api/*",
"allowedRoles": ["authenticated"]
},
]
}
と書き換えてデプロイしておきます。
<テナントID>
部分は、先ほどアプリの登録で確認した ディレクトリ (テナント) ID です。
AZURE_CLIENT_ID
、AZURE_CLIENT_SECRET
は、置き換えず、本当にこのまま記述しておきます。
なお、
{
"route": "/.auth/login/twitter",
"statusCode": 404
},
{
"route": "/.auth/login/github",
"statusCode": 404
}
部分は無くなりましたが、カスタム認証設定により、twitter, github は自動的に 404: Not Found
になり、404 設定は、不要になるからです。
アプリケーション設定
・Azure Static Web Apps のアプリケーション設定で環境変数設定
を行います。
Azure ポータル(https://portal.azure.com/
)にアクセスして、静的 Web アプリ をクリックします。
作成したアプリ sample-project をクリックします。
左側から 構成 をクリックして、アプリケーション設定 → 追加 にて、以下の環境変数を追加します。
AZURE_CLIENT_ID: a04dbd00-****-****-****-************
アプリの登録 → sample-project → 概要の アプリケーション (クライアント) ID です。
AZURE_CLIENT_SECRET: 1n78Q~V*********************************
クライアントシークレットで表示された 値 です。(シークレット ID ではありません。)
追加したら、保存 をクリックします。
保存を押さないと反映されません。保存をクリックし忘れて動作確認したとき、
404: Not Found
になりました。
動作確認
OK!
参考
Azure Static Web Apps の認証とアクセス制御まわりで気になったところ
https://ayuina.github.io/ainaba-csa-blog/auth-static-webapp/
Azure Static Web Apps で事前構成済みのプロバイダーで認証設定をしてみた
https://dev.classmethod.jp/articles/azure-static-web-apps-auth-pre-configured-providers/
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。