Help us understand the problem. What is going on with this article?

SPA から Azure Functions を使う時のActive Directory B2C 認証設定方法

More than 3 years have passed since last update.

SPA -> Azure Functions にアクセスするときに、Azure Active Directory B2C を使って認証をしたいときにどのように設計するべきか?と言うのを考えて実装してみました。

Screen Shot 2017-11-01 at 12.50.24 AM.png

SPA と Azure Functions 間の認証方式

SPA には様々な制約があります。例えば環境変数を受け取れないなどの制約です。SPAに埋め込んだエンドポイントのURLは全てユーザ側にわかってしまいます。と言うことは、どうやって、認証・認可を行えばいいでしょう?一つの方法は、Azure Active Directory B2C を使って認証します。イメージ図は次の通りです。

Slide1.jpeg

大まかな方式としては、SPA の JavaScriptのADの認証ライブラリを使って認証し、アクセストークンを取得します。そのアクセストークンをつけて、Azure Functions にリクエストを送信し、Azure Functions 側では、そのアクセストークンが妥当かをAzure Active Directory B2C に問い合わせて認証すると言うものです。この方式であれば、エンドポイントがわかっても、認証しなければ、API にアクセスすることはできません。

SPA を実装する。

SPA のログイン実装をするためのサンプルが存在します。

これの設定をすれば良い感じです。じゃあ、Azure Active Directory B2C の設定からみていきましょう。

AD B2C を設定する

Functions アプリケーション の設定

Application ID

これは、CLIENT_ID とか言われたりするものです。アプリケーションのIDです。

Reply URL
一般的にAD で認証された後、リダイレクトされるURLです。このシナリオでは、Azure Functions 側からリダイレクトすることはないのですがAzure Functions のURlを書いておきます。

App ID URL
なにそれ?と言う感じですが、Azure Functions のURLの最初のサブディレクトリを書いておきます。ここでは、api になっています。これが、Functions Application の識別子になっているようです。

Screen Shot 2017-11-01 at 1.03.12 AM.png

Scope

公開するスコープです。ここでは、新しいスコープとして、func.read と言うスコープを設定してみました。

Screen Shot 2017-11-01 at 1.03.31 AM.png

SPA アプリケーションの設定

SPA 側のアプリケーションの登録です。App ID URI は不要です。Reply URL は、認証後、サンプルアプリはローカルで動くので、このURL にしています。こちらにリダイレクトされます。

Screen Shot 2017-11-01 at 1.03.48 AM.png

また、SPA 側のアプリケーションでは、API access の設定が必要です。これは、先ほど Function アプリケーションと、そちらで設定したスコープが見えるようになりますので、それを選択してセーブします。

Screen Shot 2017-11-01 at 1.04.10 AM.png

Sign up / Sign in policy

ここでは、赤枠のところを保存しておきましょう。メタ情報です。

Screen Shot 2017-11-01 at 1.06.03 AM.png

この後は、個別の設定を載せておきます。これらは、サインインに必要な属性の設定だったり、認証済みのリソースに渡される内容の設定だったりしています。
Screen Shot 2017-11-01 at 1.06.17 AM.png
Screen Shot 2017-11-01 at 1.06.26 AM.png
Screen Shot 2017-11-01 at 1.06.36 AM.png

さて、準備環境

SPA Application の設定

先ほどのサンプルをClone したら次の箇所を変更します。

CLIENT_ID SPA アプリケーションの Application ID
authority "https://login.microsoftonline.com/tfp/{tenant}.onmicrosoft.com/{Sign UP/ Sign in Policy Name}"
b2cScopes "{Function App ID URL}/{Function App Policy name}"
webApi "認証後、アクセスする Azure Functions の URL"

        var applicationConfig = {
            clientID: '84270d3d-1c67-4d18-8436-f3a121b1ebef',
            authority: "https://login.microsoftonline.com/tfp/someorganication.onmicrosoft.com/B2C_1_SPA_Local",
            b2cScopes: ["https://someorganication.onmicrosoft.com/api/func.read"],
            webApi: 'https://removebackend.azurewebsites.net/api/HttpTriggerCSharp1?name=Azure',
        };

ここでポイントは、b2cScopes です。CLIENT_IDがSPA のIDであるため、ここも、SPA側の設定をしそうなものですが、ここは、Functions アプリケーション側の設定です。SPA からみて、どのリソースにアクセスするか?と言うリストです。(これをみて、AD B2C の方で認証して、アクセスできるリソースを取ってきます。

私は当初、ここを間違えていて、ずっと悩んでいました。なぜかと言うと、ここで、間違えるとバックエンドにアクセス時ではなく、ログイン時にエラーになります。(だから、SPAの設定が悪いと思った。)実際は、ログインすると、アクセスできるリソースの情報と権限が渡されるので、ログイン時にすでに査定されているので、間違えているとログイン時に落ちます。そのケースだと、下記のエラーになります。

Screen Shot 2017-11-01 at 1.37.21 AM.png

このケースは、b2cScopes が間違っていると思われます。これで SPAは準備おっけー

Azure Functions 側の認証

前回のポストで、Azure Function に直接 B2C の認証をかける方式は試しましたが、トークンの確認だけのパターンはまだなので、やってみます。前回と同じく Authentication /Authorization > Active Directory Settings を設定します。

Screen Shot 2017-11-01 at 1.40.30 AM.png

CLIENT_ID: Function アプリケーションの Application ID
Issuer Url: Sign up / Sign in policy で出てきたメタ情報
ALLOWED TOKEN AUDIENCE: SPA アプリケーションの Application ID

と言う感じで設定します。

この設定をしてから、Azure Functions の画面からテストをすると、401 になります。準備完了。

Screen Shot 2017-11-01 at 12.17.21 AM.png

おまけ

これですでに実行していいのですが、サンプルのアプリケーションは、ログイン時にポップアップが出てきて、認証します。しかし、ポップアップじゃなくしたい場合は、次のようにします。

        var clientApplication = new Msal.UserAgentApplication(applicationConfig.clientID, applicationConfig.authority, function (errorDesc, token, error, tokenType) {

        });
        logMessage("here");
        window.onload = function() {
            if (!clientApplication.isCallback(window.location.hash) && window.parent == window && !window.opener) {
                clientApplication.acquireTokenSilent(applicationConfig.b2cScopes).then(function(accessToken) {
                    logMessage("accessToken" + accessToken);
                    updateUI();
                });
                var user = clientApplication.getUser();
            }
        }
        function login() {
            logMessage("step1");
            clientApplication.loginRedirect(applicationConfig.b2cScopes).then(function (idToken) {

login の中身を、loginPopup ではなく、loginRedirectにして、リダイレクトで戻ってきたときに、clientApplication オブジェクトが生成されたあとに、 acquireTokenSilent を取ってきて、下の方で、トークンにつけて渡しています。

全ソースコードを上げておきました。

実行

クライアントアプリケーションを起動し、操作すると、無事ログインして、バックエンドのAzure Functions が呼ばれたことが確認できました。簡単!

Screen Shot 2017-11-01 at 12.50.24 AM.png

Resource

TsuyoshiUshio@github
プログラマ。自分の学習用のブログです。内容は会社とは一切関係ありません。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away