web-dev-qa-db-ja.com

Adal.jsが外部APIエンドポイントリソースのトークンを取得しない

Angular SPA(Single Page Application)Webサイトでadal.jsを試してみます。外部Web APIサイト(別のドメイン)からデータを取得します。SPAに対する認証はadalで簡単でした.jsですが、ベアラートークンが必要な場合、APIとの通信はまったく機能しません。私は https://github.com/AzureAD/Azure-activedirectory-library-for-js を使用しました=無数のブログに加えてテンプレートとして。

問題は、adal.jsの開始時にエンドポイントを設定すると、adal.jsがすべての発信エンドポイントトラフィックをmicrosoftsログインサービスにリダイレクトするように見えることです。

観察:

  • Adal.jsセッションストレージには、2つのadal.access.token.keyエントリが含まれています。 1つはSPA Azure ADアプリケーションのクライアントID用、もう1つは外部API用です。値を持つのはSPAトークンだけです。
  • $ httpProviderをadal.jsに注入しない場合、呼び出しは外部APIに送信され、代わりに401が返されます。
  • SPAトークンを手動でhttpヘッダーに追加した場合(承認:ベアラー「トークン値」)、代わりに401が返されます。

私の理論は、adal.jsがエンドポイントのトークンを取得できず(おそらくSPAで何か間違っているため)、必要なトークンを取得できないため、エンドポイントへのトラフィックを停止します。 SPAトークンには必要な権限が含まれていないため、APIに対して使用できません。 adal.jsがエンドポイントのトークンを取得しないのはなぜですか?どうすれば修正できますか?

追加情報:

  • クライアントAzure ADアプリケーションは、APIに対する委任されたアクセス許可を使用するように構成されており、アプリマニフェストでoauth2AllowImplicitFlow = trueです。
  • API Azure ADアプリケーションは偽装用に構成されており、oauth2AllowImplicitFlow = true(それが必要であるとは考えていませんが、試しました)。マルチテナントです。
  • APIは、すべてのCORSオリジンを許可するように構成されており、偽装を使用する別のWebアプリ(ハイブリッドMVC(Adal.net)+ Angular)で使用すると、正しく機能します。

セッションストレージ:

key (for the SPA application): adal.access.token.keyxxxxx-b7ab-4d1c-8cc8-xxx value: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1u...

key (for API application): adal.access.token.keyxxxxx-bae6-4760-b434-xxx
value:

app.js(Angularおよびadal構成ファイル)

(function () {
    'use strict';

    var app = angular.module('app', [
        // Angular modules 
        'ngRoute',

        // Custom modules 

        // 3rd Party Modules
        'AdalAngular'

    ]);

    app.config(['$routeProvider', '$locationProvider',
        function ($routeProvider, $locationProvider) {
        $routeProvider           

            // route for the home page
            .when('/home', {
                templateUrl: 'App/Features/Test1/home.html',
                controller: 'home'
            })

            // route for the about page
            .when('/about', {
                templateUrl: 'App/Features/Test2/about.html',
                controller: 'about',
                requireADLogin: true
            })

            .otherwise({
                redirectTo: '/home'
            })

        //$locationProvider.html5Mode(true).hashPrefix('!');

        }]);

    app.config(['$httpProvider', 'adalAuthenticationServiceProvider',
        function ($httpProvider, adalAuthenticationServiceProvider) {
            // endpoint to resource mapping(optional)
            var endpoints = {
                "https://localhost/Api/": "xxx-bae6-4760-b434-xxx",
            };

            adalAuthenticationServiceProvider.init(
                    {                        
                        // Config to specify endpoints and similar for your app
                        clientId: "xxx-b7ab-4d1c-8cc8-xxx", // Required
                        //localLoginUrl: "/login",  // optional
                        //redirectUri : "your site", optional
                        extraQueryParameter: 'domain_hint=mydomain.com',
                        endpoints: endpoints  // If you need to send CORS api requests.
                    },
                    $httpProvider   // pass http provider to inject request interceptor to attach tokens
                    );
        }]);
})();

エンドポイントを呼び出すための角度コード:

$scope.getItems = function () {
            $http.get("https://localhost/Api/Items")
                .then(function (response) {                        
                    $scope.items = response.Items;
                });
16

わかりました、私はこれを理解するために頭を壁に打ちつけてきました。 ADAL.js SPAアプリ(sans angular)を作成しようとすると、貴重なCORS対応のWeb APIにクロスドメインXHRリクエストが正常に送信されます。

このサンプルアプリ は、私のようなすべての初心者が使用しているもので、この問題があります。APIとSPAがすべて同じドメインから提供され、単一のADテナントアプリ登録のみが必要です。これは、物事を別々の部分に引き離すときになると物事を混乱させるだけです。

だから、箱から出して、サンプルはこれを持っています Startup.Auth.cs サンプルが行く限り問題なく動作します...

  public void ConfigureAuth(IAppBuilder app) {

        app.UseWindowsAzureActiveDirectoryBearerAuthentication(
            new WindowsAzureActiveDirectoryBearerAuthenticationOptions
            {
                Audience = ConfigurationManager.AppSettings["ida:Audience"],
                Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
            });
  }

しかし、上記のコードを変更し、Audience割り当てを削除して、オーディエンスの配列に移動する必要があります。そうです:ValidAudiences ..したがって、通信しているすべてのSPAクライアントについてWebAPI、SPA登録のClientIDをこの配列に配置する必要があります...

次のようになります...

public void ConfigureAuth(IAppBuilder app)
{
    app.UseWindowsAzureActiveDirectoryBearerAuthentication(
        new WindowsAzureActiveDirectoryBearerAuthenticationOptions
        {
            Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
            TokenValidationParameters = new TokenValidationParameters
            {
                ValidAudiences = new [] { 
                 ConfigurationManager.AppSettings["ida:Audience"],//my swagger SPA needs this 1st one
                 "b2d89382-f4d9-42b6-978b-fabbc8890276",//SPA ClientID 1
                 "e5f9a1d8-0b4b-419c-b7d4-fc5df096d721" //SPA ClientID 2
                 },
                RoleClaimType = "roles" //Req'd only if you're doing RBAC 
                                        //i.e. web api manifest has "appRoles"
            }
        });
}

[〜#〜]編集[〜#〜]

@JonathanRuppのフィードバックに基づいて、上に示したWeb APIソリューションを元に戻し、クライアントのJavaScriptを以下のように変更してすべてを機能させることができました。

    // Acquire Token for Backend
    authContext.acquireToken("https://mycorp.net/WebApi.MyCorp.RsrcID_01", function (error, token) {

        // Handle ADAL Error
        if (error || !token) {
            printErrorMessage('ADAL Error Occurred: ' + error);
            return;
        }

        // Get TodoList Data
        $.ajax({
            type: "GET",
            crossDomain: true,
            headers: {
                'Authorization': 'Bearer ' + token
            },
            url: "https://api.mycorp.net/odata/ToDoItems",
        }).done(function (data) {
            // For Each Todo Item Returned, do something
            var output = data.value.reduce(function (rows, todoItem, index, todos) {
                //omitted
            }, '');

            // Update the UI
            //omitted

        }).fail(function () {
            //do something with error
        }).always(function () {
            //final UI cleanup
        });
    });
2
bkwdesign

Web APIにクライアントアプリケーションを認識させる必要があります。クライアントからAPIに委任された権限を追加するだけでは不十分です。

APIクライアントを認識させるには、Azure管理ポータルに移動し、APIのマニフェストをダウンロードして、クライアントアプリケーションのClientIDを「knownClientApplications」のリストに追加します。

暗黙的なフローを許可するには、マニフェストでも「oauth2AllowImplicitFlow」をtrueに設定する必要があります。

マニフェストをAPIアプリケーションにアップロードします。

1
Pavel Pikat

ADAL.jsは、異なるドメインで実行されているAzure AD保護APIを呼び出すために、id_tokenとは別にaccess_tokenを取得します。最初は、ログイン時にid_tokenのみを受け取ります。このトークンには、同じドメインのリソースにアクセスするためのアクセス権があります。ただし、異なるドメインで実行されているAPIを呼び出すと、adalインターセプターは、API URLがadal.init()のエンドポイントとして構成されているかどうかを確認します。

その後、要求されたリソースに対してアクセストークンが呼び出されます。また、SPAがAPI APPにアクセスするためにAADで構成されている必要があります。

これを実現するための鍵は次のとおりです。1. adal.init()にエンドポイントを追加します

var endpoints = {

    // Map the location of a request to an API to a the identifier of the associated resource
    //"Enter the root location of your API app here, e.g. https://contosotogo.azurewebsites.net/":
    //    "Enter the App ID URI of your API app here, e.g. https://contoso.onmicrosoft.com/TestAPI",
    "https://api.powerbi.com": "https://analysis.windows.net/powerbi/api",
    "https://localhost:44300/": "https://testpowerbirm.onmicrosoft.com/PowerBICustomServiceAPIApp"
};

adalProvider.init(
    {
        instance: 'https://login.microsoftonline.com/',
        tenant: 'common',
        clientId: '2313d50b-7ce9-4c0e-a142-ce751a295175',
        extraQueryParameter: 'nux=1',
        endpoints: endpoints,
        requireADLogin: true,

        //cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.  
        // Also, token acquisition for the To Go API will fail in IE when running on localhost, due to IE security restrictions.
    },
    $httpProvider
    );
  1. Azure ADのSPAアプリケーションにAPIアプリケーションにアクセスする権限を付与します。 enter image description here

詳細については、このリンクを参照してください。 ADAL.jsの詳細

1
Rahul Mohan

私たちの設定がまったく同じかどうかはわかりませんが、比較できると思います。

Angular Azureを使用し、Azure API Management(APIM)を介して外部Web APIを使用するSPAがあります。私のコードはベストプラクティスではないかもしれませんが、今のところ私にとっては機能します:)

SPA Azure ADアプリには、外部API Azure ADアプリにアクセスするための委任されたアクセス許可があります。

SPAAdal TodoList SPAサンプル に基づいています)

app.js

adalProvider.init(
    {
        instance: 'https://login.microsoftonline.com/', 
        tenant: 'mysecrettenant.onmicrosoft.com',
        clientId: '********-****-****-****-**********',//ClientId of the Azure AD app for my SPA app            
        extraQueryParameter: 'nux=1',
        cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
    },
    $httpProvider
    );

todoListSvc.jsからのスニペット

getWhoAmIBackend: function () {
        return $http.get('/api/Employee/GetWhoAmIBackend');
    },

EmployeeControllerからのスニペット

public string GetWhoAmIBackend()
    {
        try
        {
            AuthenticationResult result = GetAuthenticated();

            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

            var request = new HttpRequestMessage()
            {
                RequestUri = new Uri(string.Format("{0}", "https://api.mydomain.com/secretapi/api/Employees/GetWhoAmI")),
                Method = HttpMethod.Get, //This is the URL to my APIM endpoint, but you should be able to use a direct link to your external API

            };
            request.Headers.Add("Ocp-Apim-Trace", "true"); //Not needed if you don't use APIM
            request.Headers.Add("Ocp-Apim-Subscription-Key", "******mysecret subscriptionkey****"); //Not needed if you don't use APIM

            var response = client.SendAsync(request).Result;
            if (response.IsSuccessStatusCode)
            {
                var res = response.Content.ReadAsStringAsync().Result;
                return res;
            }
            return "No dice :(";
        }
        catch (Exception e)
        {
            if (e.InnerException != null)
                throw e.InnerException;
            throw e;
        }
    }

        private static AuthenticationResult GetAuthenticated()
    {
        BootstrapContext bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext;
        var token = bootstrapContext.Token;

        Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authContext =
            new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.microsoftonline.com/mysecrettenant.onmicrosoft.com");

        //The Client here is the SPA in Azure AD. The first param is the ClientId and the second is a key created in the Azure Portal for the AD App
        ClientCredential credential = new ClientCredential("clientid****-****", "secretkey ********-****");

        //Get username from Claims
        string userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null ? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value : ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;

        //Creating UserAssertion used for the "On-Behalf-Of" flow
        UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);

        //Getting the token to talk to the external API
        var result = authContext.AcquireToken("https://mysecrettenant.onmicrosoft.com/backendAPI", credential, userAssertion);
        return result;
    }

これで、バックエンドの外部APIでは、Startup.Auth.csは次のようになります。

外部APIStartup.Auth.cs

        public void ConfigureAuth(IAppBuilder app)
    {
        app.UseWindowsAzureActiveDirectoryBearerAuthentication(
            new WindowsAzureActiveDirectoryBearerAuthenticationOptions
            {
                Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
                TokenValidationParameters = new TokenValidationParameters
                {
                    ValidAudience = ConfigurationManager.AppSettings["ida:Audience"],
                    SaveSigninToken = true
                },
                AuthenticationType = "OAuth2Bearer"
            });
    }

これで問題が解決するかどうか、またはさらにサポートが必要な場合はお知らせください。

0
Jonas