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

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

<img width="1033" alt="Screen Shot 2017-11-01 at 12.50.24 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/aaa74137-46af-b0e6-7d55-f9569944f74b.png">

# SPA と Azure Functions 間の認証方式

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

<img width="1000" alt="Slide1.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/3470/be2a7cb2-4b8b-8a3c-2060-26f9d0f3a966.jpeg">

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

* [Microsoft Authentication Library Preview for JavaScript (MSAL.js)](https://github.com/AzureAD/microsoft-authentication-library-for-js) 

# SPA を実装する。

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

* [A single page application (SPA) calling a Web API. Authentication is done with Azure AD B2C by leveraging MSAL.js](https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp)

これの設定をすれば良い感じです。じゃあ、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 の識別子になっているようです。

<img width="862" alt="Screen Shot 2017-11-01 at 1.03.12 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/9db34ac2-5b84-97c6-3bc6-adf47b19dc8c.png">

_Scope_ 

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

<img width="1108" alt="Screen Shot 2017-11-01 at 1.03.31 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/d5996865-d993-0b15-79e8-01a24af19385.png">

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

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

<img width="928" alt="Screen Shot 2017-11-01 at 1.03.48 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/3a93733e-8151-7da2-1542-2b4287fc5046.png">

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

<img width="1131" alt="Screen Shot 2017-11-01 at 1.04.10 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/0f6e88c5-4541-3508-e382-93a60bb0f67b.png">

## Sign up / Sign in policy

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

<img width="956" alt="Screen Shot 2017-11-01 at 1.06.03 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/d84dd6d4-5d55-607b-64e0-4587e9674ad4.png">

この後は、個別の設定を載せておきます。これらは、サインインに必要な属性の設定だったり、認証済みのリソースに渡される内容の設定だったりしています。
<img width="578" alt="Screen Shot 2017-11-01 at 1.06.17 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/53d4f8d8-0c2c-4bbc-b185-34ede4c75ce1.png">
<img width="844" alt="Screen Shot 2017-11-01 at 1.06.26 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/fa72ed60-95b1-cab9-bfe5-19f61a77d4cd.png">
<img width="848" alt="Screen Shot 2017-11-01 at 1.06.36 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/ae3aa1e0-72ad-23e0-af5d-73af515be6fb.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の設定が悪いと思った。）実際は、ログインすると、アクセスできるリソースの情報と権限が渡されるので、ログイン時にすでに査定されているので、間違えているとログイン時に落ちます。そのケースだと、下記のエラーになります。

<img width="604" alt="Screen Shot 2017-11-01 at 1.37.21 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/7b9662ad-8215-cbcd-ead0-ce04618ef22f.png">

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

# Azure Functions 側の認証

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

<img width="581" alt="Screen Shot 2017-11-01 at 1.40.30 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/48e47789-4283-377f-e705-2399ba6a87a7.png">

_CLIENT_ID_: Function アプリケーションの Application ID
_Issuer Url_: Sign up / Sign in policy で出てきたメタ情報
_ALLOWED TOKEN AUDIENCE_: SPA アプリケーションの Application ID

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

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

<img width="1152" alt="Screen Shot 2017-11-01 at 12.17.21 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/ecde0bd4-829e-0f7a-d199-d01e2fc6a347.png">

# おまけ

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

```js

        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` を取ってきて、下の方で、トークンにつけて渡しています。

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

* [SPA authenticate by Azure Active Directory B2C sample with Redirect](https://github.com/TsuyoshiUshio/SPAB2CRedirect)

* [ユーザーのサインインに Microsoft Authentication Library (MSAL) を使用する
](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/guidedsetups/active-directory-singlepageapp-javascriptspa-use)

# 実行

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

<img width="1033" alt="Screen Shot 2017-11-01 at 12.50.24 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/6d849bcb-6c89-91df-eda6-36fb94fcb57c.png">


# Resource

* [Microsoft Authentication Library Preview for JavaScript (MSAL.js)](https://github.com/AzureAD/microsoft-authentication-library-for-js) 
* [A single page application (SPA) calling a Web API. Authentication is done with Azure AD B2C by leveraging MSAL.js](https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp)
* [Azure AD B2C: Single-page app sign-in by using OAuth 2.0 implicit flow](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-reference-spa)
*[Azure active directory - Allow token audiences](https://stackoverflow.com/questions/42640158/azure-active-directory-allow-token-audiences)
* [Node.js Web API with Azure AD B2C](https://github.com/Azure-Samples/active-directory-b2c-javascript-nodejs-webapi)