はまったりひらめいたり…とか…

Angularや.NETやAzureやその他色々。

MSAL.js+Azure AD v2を触ってみる

はじめに

使用ライブラリは下記のバージョンでお送ります。

msal.js: 1.0.1

SPAっぽいなにかを作りたかったのでAngularも使用しています。

@angular/cli: 8.0.0

ただ、記述するソースコードの殆どはmsal.jsのヴァニラなものを使用しているので

他のフレームワークやバニラJSでも問題なく動作すると思います。

Azureは2019/6時点の画面をキャプチャしています。

それぞれの内容について記事をご覧になるタイミングによっては、画面、仕様が変更されている可能性があるのでご留意くださいませ。

あらまし

マイクロソフトのアカウント認証に便利なJavascriptのライブラリ、msal.js

github.com

長らく0.x.xとβ?みたいな扱いだったのですが、いつの間にか(5/4)に1.0.0とメジャーバージョンアップしていたので改めて使い方を復習してみようと思います。

この記事ではmsal.jsの使い方にのみフォーカスを当てます。

Azure AD アプリケーションの作り方とかADのスコープの設定とか

その周りの話については割愛していきます。

Azure AD v2については流れ上触る機会があったので簡単に。。。

単純にサインイン

msal.jsのREADMEにある通り、まずはClientIdのみを指定してログイン処理を実行してみます。

import * as msal from 'msal';

export class SampleAuth {
  private msalClient: msal.UserAgentApplication;

  login() {
    const con: msal.Configuration = {
      auth: { clientId: 'd476053b-7f9a-4c9a-9241-cbd54714266b' }
    };
    this.msalClient = new msal.UserAgentApplication(con);
    this.msalClient.loginPopup({scopes: ['openid']}).then(
      res => { console.log(res); }
    );
  }
}

単純に書くとこんなところですね。

何はともあれ実行してみましょう。

f:id:TakasDev:20190614222439p:plain

いつものログイン画面が表示されました。

ログイン後、バッチリアカウント情報も取得できているようです。

f:id:TakasDev:20190614222623p:plain

注意点-LoginPopup

LoginPopupを行う場合、ポップアップされた画面で認証画面からのリダイレクトが発生します。

しかし、リダイレクト先でUserAgentApplicationインスタンスが生成されていないと

ポップアップ画面が閉じられず残りっぱなしになってしまうようです。

loginPopup doesn't properly close itself · Issue #174 · AzureAD/microsoft-authentication-library-for-js · GitHub

ログイン用のボタン押下でインスタンス生成する処理になっている場合はこの部分で嵌りそうです(ハマった)。

リダイレクトページのコンストラクタなどでインスタンスを生成するようにしておく必要があります。

トークンを取得する

何はともあれトークンを取得しないことには始まりません。

ログインができたのであれば、トークンを取得してみましょう。

acquireTokenSilentを使用し、openid のスコープでトークンを取得してみます。

login() {
    const con: msal.Configuration = {
        auth: { clientId: 'd476053b-7f9a-4c9a-9241-cbd54714266b' }
    };
        this.msalClient = new msal.UserAgentApplication(con);
        this.msalClient.loginPopup({scopes: ['openid']}).then(
        res => {
            console.log(res);
            this.msalClient.acquireTokenSilent({ scopes: ['openid' ] }).then(token => {
                console.log('get token');
                console.log(token);
            });
        }
    );
}

f:id:TakasDev:20190614224640p:plain

トークンが取得できました。

The provided value for the input parameter 'response_type' is not allowed for this client....

上記のようなエラーが出る場合は認証に使用しているADアプリケーションの暗黙的フローの許可がされていない場合があるので確認してみてください。

生成されたトークンがどのようなものか

JSON Web Tokens - jwt.io で確認してみます。

f:id:TakasDev:20190614225026p:plain

issでADのテナントID、audで使用したADアプリケーションIDが設定されていることが確認できます。

取得したトークンで、ASP.net Coreで構築した認証付きのWebAPIにアクセスしてみます。

f:id:TakasDev:20190615194201p:plain

しかし、APIにアクセスはできませんでした。

それは当然で、生成されたJWTのaudは認証用に使用しているAzure ADアプリケーションのClientIdで

APIの認証で使用されているAzure ADアプリケーションのClientIdとは異なるからです。

WebAPIのClientIdは "ClientId": "792dd3ef-a306-4288-b12d-5aff71f16193" が指定されています。

WebAPI側のClientIdを変更してもいいのですが、それも芸がないので、別の方法を使用してみます。

AzureADアプリケーションの設定変更

認証で使用しているAzureADアプリケーションで、作成したWebAPIのPermissionを通しておきます。

f:id:TakasDev:20190615195221p:plain

こんな状態ですね。

スコープの変更

追加されたAPI認証にしているアプリケーションのスコープは下図で取得できます。

f:id:TakasDev:20190616162533p:plain

早速、スコープを指定し、トークンを取得してみます。

this.msalClient.acquireTokenSilent({ scopes: [ 'user.read' ] })

となっていたものを

this.msalClient.acquireTokenSilent({ scopes: [ '<APIのスコープ>' ] })

のように変更してみます。

これでトークンが取得できました。

注意点-Authorityの指定

Authorityを未指定のままでトークン取得処理を実行すると下記のようなエラーが発生しました。

AADSTS501941: Resource '792dd3ef-a306-4288-b12d-5aff71f16193'(WebApplication1) is not configured as a multi-tenant application. Usage of the /common endpoint is not supported for such applications created after '10/15/2018'. Use a tenant-specific endpoint or configure the application to be multi-tenant.

標準で使用されている認証のURL https://login.microsoft.com/common は使用できず、テナントの指定を促されました。

その場合は、msal.jsのインスタンス生成時にauthorityを指定することで回避できます。

    const con: msal.Configuration = {
      auth: {
        clientId: 'd476053b-7f9a-4c9a-9241-cbd54714266b',
        authority: 'https://login.microsoftonline.com/<テナントのGUID>'
      }
    };
    this.msalClient = new msal.UserAgentApplication(con);

注意点 - トークンの指定

ADのトークンは仕様として、複数のリソースのトークンを一挙に取得できないようです。

複数のリソースの承認を取得する (Microsoft Authentication Library for .NET) | Microsoft Docs

なので、スコープ指定時に、 scopes: [ 'user.read', '<APIのスコープURI>' ] のようにリソースをまたがる設定にすると

AADSTS28000: Provided value for the input parameter scope is not valid because it contains more than one resource.

上記のようなエラーとなります。トークンの取得は単一リソースごとに取得が基本ということですね。

Azure AD アプリケーションのエンドポイントを v2の設定に

さて、取得できたトークンを解析してみると、aud は下図のような状態でした。

f:id:TakasDev:20190616211920p:plain

ほしいのはClientIdであって、スコープではありません。

Azure AD v2であればClientIDを取得できそうなので(情報元は失念。。。)Azure AD アプリケーションの設定を変更します。

f:id:TakasDev:20190616171548p:plain

accessTokenAcceptedVersionnull2 に変更しました。

これで再度トークンを取得してみます。

f:id:TakasDev:20190616212403p:plain

お望みのトークンを取得できました。

ASP.net Core WebAPIの実装

さて、トークンは帰ってきましたが、Azure AD v2を使用することでissのエンドポイントが変わってしまいました。

(URLの末尾に/v2.0がついた)

GitHubにサンプルが転がっていたのでそれと同じように実装してみましょう。

GitHub - Azure-Samples/active-directory-dotnet-native-aspnetcore-v2: Calling a ASP.NET Core Web API from a WPF application using Azure AD v2.0

と、いってもやっていることは非常に単純で 上記GitHubで管理されているMicrosoft.Identity.WebAPIプロジェクトで参照して

startup.cs の認証部分を少し書き換えるだけですね。appsettings.jsonも書き換え必要ありません。

(Microsoft.Identity.Web はNuGetで公開されていないのですね…)

f:id:TakasDev:20190616214438p:plain

startup.cs

- services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
-   .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
+ services.AddProtectWebApiWithMicrosoftIdentityPlatformV2(Configuration);

msal.jsで取得したトークンで、APIを使用できるようになりました!

f:id:TakasDev:20190616222634p:plain

まとめ

msal.jsの基本的な使用方法と派生してAzure AD v2の認証方法を抑えることができました。

ただ、Azure ADv2は各所みるにまだまだ開発中の匂いがプンプンしますし

AzureADアプリケーションを使用して認証を行う場合は

v1エンドポイントを使用して、adal.jsを使用するほうがまだまだ無難なようです。

力尽きたので、Azure AD B2Cとmsal.jsの認証は別の機会に記事にしようと思います。。。