とりあえず API Management + Azure AD B2C による認証をトライしてみようと思います。
あ、注意点としては、この手順を実施しても Azure Functions 自体は認証キーによる認証で保護してるだけなので、認証キーばれたら叩かれる感じではあります。
Azure AD B2C 作ろう
Azure ポータルから Azure Active Directory B2C を作ります。
まず 新しい Azure AD B2C テナントを作成します
から作成します。
作成時に国とリージョン選びますが日本はないので、ここらへんのドキュメントを参考に…
私は、とりあえず米国を選びました。
テナントが出来たら 既存の Azure AD B2C テナントを Azure サブスクリプションにリンクする
から適当なリソースグループに紐づけます。
ID プロバイダーを登録しよう
Twitter, Facebook, etc... 色々対応しています。ここでは Microsoft Account を追加します。
普通の Azure AD に移動してアプリ登録をします。任意の組織のディレクトリ内のアカウントと、個人用の Microsoft アカウント (Skype、Xbox、Outlook.com など)
を選択してコ ールバック URL に以下のような Azure AD のテナント ID を持ったコールバック URL を設定します。
https://azureadsampledomain.b2clogin.com/azureadsampledomain.onmicrosoft.com/oauth2/authresp
参考:
Azure AD にアプリを登録しよう
リソースグループを開くと Azure AD B2C のテナントがいるので選択した先の画面で移動を押して Azure AD B2C の画面にいきます。
この手順とは、関係ありませんが Azure AD B2C 使うとここらへんのアカウント使ってログイン出来るようになるので便利。
今後 Apple ID にも対応するらしいので、Sign with Apple にも割と簡単に対応できるようになるのでは…?という甘い期待。
さて、API 用のアプリを作ります。さくっとね
とりあえず適当なスコープを 1 個用意しました。
次に、Azure AD B2C のほうにクライアントアプリケーション用のアプリを登録します。新しいアプリケーションで以下のようにアプリを作ります。
今回は Vue.js でアプリ作ろうと思うので npm run serve
すると 8080 ポートで動くという前提で、リダイレクト URL に http://localhost:8080
を設定しています。
アプリが Azure AD B2C に作成されたら API アクセスを選んで API 用のアプリへのアクセスを追加します。
そして、Azure AD B2C のユーザーフロー(ポリシー)に新しいユーザーフローを追加します。
追加するのは サインアップとサインイン
です。
適当に名前をつけてサインインできる ID プロバイダーを設定します。(Twitterとかをちゃんと構成したらここに出てくるはず)要求を返すところに表示名というのがあるので、それにチェックを入れておきます。
ユーザーフローが出来たら、ユーザー フローを実行しますボタンを押して出てくる画面の一番上の URL をコピーしておきましょう。
ここでアプリを選択してログインの確認も出来ます。
API を作ろう
とりあえず Azure に Function App を作ります。従量課金プランでいいでしょう。 ポータル上から適当に HTTP トリガーの関数を作ります。
とりあえずデフォルトの名前の HttpTrigger1 を作りました。
続けて API Management を作ります。これもコンサンプションプランでいきましょう。
API Management が出来たら Azure Functions に行ってプラットフォーム機能の API Management を選びます。 さっき作った API Management を選んで API のリンクをします。
HttpTrigger1 が選ばれてるのを確認してサクッと作りましょう。
API Management で API を選ぶと Function App の名前の API があります。今回はサブスクリプションキーによる認証はしないで純粋に OAuth 2.0 での認証にしたいので、All APIs から Function App の名前の API を選んで Settings を選んで Subscription required のチェックを外して Save します。
そして、All operations のポリシーの inbound に validate-jwt を追加します。
openid_config に先ほどのサインインポリシーで取得した URL になります。そして audience に アプリ ID を設定します。Azure AD B2C に登録したアプリ ID が 98852140-8e30-47dd-abd2-06a5b200bb7d
だったので、以下のような感じになりました。
<policies><inbound><base /><validate-jwt header-name="Authorization"failed-validation-httpcode="401"failed-validation-error-message="Unauthorized. Access token is missing or invalid."><openid-config url="https://azureadsampledomain.b2clogin.com/azureadsampledomain.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_signin" /><audiences><audience>98852140-8e30-47dd-abd2-06a5b200bb7d</audience></audiences></validate-jwt></inbound><backend><base /></backend><outbound><base /></outbound><on-error><base /></on-error></policies>
API を叩いてみました。私は Visual Studio Code の REST Client を使ってます。認証を何もしない状態なので以下のように 401 が返ってきますね!!素敵!
この後 Vue.js で作ったアプリから叩いてみたいので、CORS の設定もしておきましょう。
CORS は、とりあえず localhost:8080 だけ許す感じでいきます。ということで最終的に API Management の API の All operations のポリシーは以下のようになりました。
<policies><inbound><base /><cors allow-credentials="true"><allowed-origins><origin>http://localhost:8080</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."><openid-config url="https://azureadsampledomain.b2clogin.com/azureadsampledomain.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_signin" /><audiences><audience>98852140-8e30-47dd-abd2-06a5b200bb7d</audience></audiences></validate-jwt></inbound><backend><base /></backend><outbound><base /></outbound><on-error><base /></on-error></policies>
本当は、アプリ ID とか openid-config のところの url とかは名前付きの値として定義しておいたほうが行儀がいいのでしょうが今回はハードコードします。
クライアントアプリの作成
とりあえず個人的に一番手軽な Vue.js 使って作ります。Vue CLI を使ってサクッと作ります。
$ vue create sample
で TypeScript と Linter を選んだ状態で作成します。クラススタイルではないものを使うようにしました。 認証ライブラリの msal を入れます。あとで Web API も呼ぶので axios も入れておきます。
$ npm i msal $ npm i axios
そして、とりあえず今回は App.vue
に全部コードを書きます。こんな感じで。
<template><div><button @click="signIn">Sign in</button><button @click="callApi">Call API</button><br/><span>{{ message }}</span></div></template><scriptlang="ts">import Vue from 'vue';import * as Msal from 'msal';import axios from 'axios';const redirectUri = window.location.origin;const appConfig = { clientId: '6a8798cd-a9cd-4d06-86d3-32847d293cbc', redirectUri, scopes: ['https://azureadsampledomain.onmicrosoft.com/myapi/api.read'], // api 用のアプリに定義したスコープ};const app = new Msal.UserAgentApplication({ auth: { clientId: appConfig.clientId, authority: 'https://azureadsampledomain.b2clogin.com/azureadsampledomain.onmicrosoft.com/B2C_1_signin', // URL の最後はユーザーフローの名前 validateAuthority: false,}, cache: { cacheLocation: 'localStorage', storeAuthStateInCookie: true,},});interface AppData { message: string; authResult: Msal.AuthResponse | undefined;}exportdefault Vue.extend({ name: 'app', data(){return{ message: '',} as AppData;}, methods: { async signIn(){try{const result = await app.loginPopup({ scopes: appConfig.scopes });this.message = `ログイン成功 ${result.account.name}`;}catch(e){this.message = e.message;}}, async callApi(){try{const result = await app.acquireTokenSilent({ scopes: appConfig.scopes });const r = await axios.get('https://spaauthtest.azure-api.net/authapitest222/HttpTrigger1?name=okazuki', { headers: { Authorization: `Bearer ${result.accessToken}`,},});this.message = r.data as string;}catch(e){this.message = e.message;}},},});</script>
npm run serve
で開発サーバー立ち上げて http://localhost:8080
にアクセスして Sign in
ボタンを押すと以下のようにログイン画面になります。
ログインに成功すると以下のように名前が出てきます。
Call api
ボタンを押すと API を呼んで結果が表示されます。
感想
疲れた…。けどとりあえず動きますね。