はじめに
こんにちは、ACS事業部の奥山です。
今回は Azure AD B2C を利用した認証・認可(認可コードフロー+PKCE)についての検証を行いました。 AppService/Functionsの認証(EasyAuth)を利用して工数をかけずに導入できることを検証しましたので備忘録としてブログにしておきます。
※今回、Azure B2C を利用した認証・認可をSPAアプリケーションで実施する方法を調査していたのですが、関連のドキュメントが多く正直なかなか苦戦しました。(正直、今も苦戦中。。。)
AD B2C と アプリケーションの関係
今回の検証ではSPAとWebAPIの2つのアプリケーションが登場します。 AD B2C にそれぞれのアプリを登録してSPAアプリのTokenにWebAPIへアクセスできる権限(scope)を付けます。 Azure AD B2C と アプリケーションの関係は以下の図のようになります。
検証環境について
以下のような環境を構築して Azure AD B2C で発行されたtokenの検証を行いました。
※SPA は (Vue + msal-browser.js)
tokenの検証は
・AppService、Functions の 認証 (EasyAuth)
・API Management の Policy(validate-jwt)
で行います。
APIに到達するまでの処理の流れ
① BrowserがまずSPAをダウンロード
② ログイン処理 (AccessTokenの取得) ※SPA用の認可コードフロー(+PKCE)
③ AccessTokenを使ってAPIを呼び出す
④ EasyAuth, API Management で Token の検証
⑤ AppService/Functions へ API 到達することを確認する。
検証環境の構築
手順自体はこの記事の内容になるので、作業してポイントとなったところを記載します。
Azure B2C
Azure B2C でやらなければいけないことは
「1. ユーザーフローを構成」
「2. アプリの登録」
の2つになります。
1. Azure AD B2C で ユーザーフロー を構成する
サインインと サインアップ, パスワードのリセット の2つを構成しました。 サインインと サインアップ(susi)の「ユーザーフローを実行します」のところに表示されているOpenID構成URLは認証を実施するサーバー(今回の場合は EasyAuth, API Management)で必要になるのでメモしておく。
2. アプリ(SPA, API)登録
図の赤枠で囲った部分を登録します。SPA用とWEB API用の2つを登録します。 以下のように2つ登録しました。
アプリ登録(SPA)
認証 のプラットフォームで「シングルページ アプリケーション」を選択します。
リダイレクトURIにはSPAが配信されるURLを設定します。※http://localhost:6420 はローカルでの確認用。
※今回は SPA で利用するので暗黙的な許可のフローは有効にしない
APIのアクセス許可でWeb APIのScopeを許可します。 ※アプリ登録(Web API)のScope設定を行った後で実施
アプリ登録(Web API)
認証の設定は不要。Scope設定のみ行います。
SPA アプリを準備
msal-browser.js を利用した簡単なSPAアプリを作成します。
msal-browser.jsの利用について
利用する msal-browser.js は GitHubのリポジトリに公開されています。
MSAL.jsライブラリは以下の構成になっており今回は Vue.js を利用したかったので @azure/msal-browser を利用しました。
GitHubのリポジトリに含まれているsampleのソースコードを参考にしてテスト用アプリを作成しました。
テスト用に作成したアプリ
msal-browser.js関連の設定では、設定箇所がやや多いのですがアプリ登録(SPA)で登録したSPAの内容と要求するWebAPIのScopeを設定します。redirectUri には BrowserがSPAへアクセスするときのURLを設定します。
Azure Blob Storage($web) へデプロイしBrowserでアクセスすると自分の作成したUIが確認できます。
(Blob Storageのweb hostingを利用)
login start popup ボタン(msalのloginPopupが呼ばれる)を押しします。正しく設定されていれば Sign in 画面が表示されます。
下段に Sign up now が表示されているのでここから新規にユーザーを登録をして、ログインします。
ログイン後にSession Storageを確認すると accesstoken が確認できます。
Web API アプリを準備
AppService, Functionsで動作する簡単なAPIアプリを作成しました。
App Service
以下のように "Hello World! from azure xx on hostname:c06bfc77d9c2" と簡単なメッセージとhostnameを返します。
curl https://xxxxx.azurewebsites.net/ Hello World! from azure xx on hostname:c06bfc77d9c2
Functions
以下のように "hello is working in AZ Functions (ee1620004fcf)" と簡単なメッセージとhostnameを返します。
curl https://xxxxx.azurewebsites.net/api/hello hello is working in AZ Functions (ee1620004fcf)
(認証設定) EasyAuth (App Service/Functions)
App Service/Functions の認証ブレードから IDプロバイダーに「Microsoft」を選択して設定を行います。
・アプリ登録(Web API) で登録したアプリのIDとシークレット
・発行者のURLにはユーザーフロー(susi)の作成で確認したopenid構成URLを設定します。
(認証設定) API Management
API Managementのポリシー設定で AccessToken のバリデーションを行ってみます。
こちらの手順に従い設定を行います。
※SPAなのでCORSの設定が必要
(設定内容) ・アプリ登録(Web API) で登録したアプリのID ・ユーザーフローで確認したOpenID構成エンドポイント(susi)を設定します。
(API Mnagemnet の policyの例)
<policies> <inbound> <cors allow-credentials="false"> <allowed-origins> <origin>*</origin> </allowed-origins> <allowed-methods> <method>*</method> </allowed-methods> <allowed-headers> <header>*</header> </allowed-headers> </cors> <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid." require-expiration-time="true" require-signed-tokens="true" clock-skew="300"> <openid-config url="https://xxxxxxxx.b2clogin.com/xxxxxxxx.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_susi" /> <required-claims> <claim name="aud"> <value>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</value> </claim> </required-claims> </validate-jwt> </inbound> <backend> <forward-request /> </backend> <outbound /> <on-error /> </policies>
AccessTokenなしでAPIにアクセスすると、 401 Unauthorized
AppService via APIM
curl https://xxxxx.azure-api.net/appservice/ -H 'Ocp-Apim-Subscription-Key:<key> ' -i HTTP/1.1 401 Unauthorized
AppService via APIM
curl https://xxxxx.azure-api.net/func-app1-78935/hello -H 'Ocp-Apim-Subscription-Key:<key>' -i HTTP/1.1 401 Unauthorized
SPA で取得した AccessToken 使ってAPIへアクセス
※AccessTokenをブラウザの開発者ツールからコピーして利用
AppService
curl https://xxxxx.azure-api.net/appservice/ -H 'Ocp-Apim-Subscription-Key:<key>' -H 'Authorization: Bearer <access token>' Hello World! from azure xx on hostname:e68320d209cb
Functions
curl https://xxxxx.azure-api.net/func-app1-78935/hello -H 'Ocp-Apim-Subscription-Key:<key>' -H 'Authorization: Bearer <token>' hello is working in AZ Functions (2366e1b954f9)
APIM の jwt-validation が正しく動いていることが確認できました。
(動作確認) SPAからToken付でAPIを呼び出す
最終的な確認はこちらのサンプル vue3-sample-appにボタンを追加して動作確認をおこないました。 (このサンプルはvueで作成されたSPAになります。)
赤いボタンを4つ追加して、押されたときに各APIを呼び出すように修正して確認しました。左からFunction, AppService, Function via APIM, AppService via APIM の4通りです。
Azure B2C 向けの設定
参考にしたサンプル自体は Azure AD を対象にしていたので Azure AD B2C の場合は以下を変更します。
authConfig.ts
export const msalConfig = { auth: { clientId: '<CLIENT_ID>', authority: b2cPolicies.authorities.signUpSignIn.authority, // ★Azure B2C用に変更 knownAuthorities: [b2cPolicies.authorityDomain], // ★Azure B2C用に変更 redirectUri: 'http://localhost:3000', // Must be registered as a SPA redirectURI on your app registration postLogoutRedirectUri: 'http://localhost:3000' // Must be registered as a SPA redirectURI on your app registration }, cache: { //cacheLocation: 'localStorage' cacheLocation: 'sessionStorage' }, : export const loginRequest = { //scopes: ['User.Read'], scopes: ["openid", ...apiConfig.b2cScopes], // ★Azure B2C用に変更 };
まとめ
Azure AD B2C を利用した、認証・認可の基本的な動作確認ができました。今回はフロントエンド側を自分で少し作成してみたのですが、「MSAL.jsライブラリの利用方法」がなかなか難しいと感じました。Azureでの認証・認可、まだまだ理解が足りない部分もあるので継続調査を行いまたブログを書きたいと思います。
最後に
私達のチームでは、Azure・AKSを活用したシステムのSIや内製化のお手伝いをさせていただいております。 Azureやコンテナ技術の知見を持つエンジニアが対応いたします。ご相談等ありましたらぜひご連絡ください。