Adal.js没有为外部api端点资源获取令牌

时间:2015-09-02 11:47:43

标签: adal.js

我正在尝试使用Angular SPA(单页应用程序)网站获取adal.js,该网站从外部Web API站点(不同的域)获取数据。使用adal.js可以很容易地对SPA进行身份验证,但是当需要承载令牌时,让它与API通信是根本不起作用的。除了无数的博客之外,我还使用了https://github.com/AzureAD/azure-activedirectory-library-for-js作为模板。

问题在于,当我在启动adal.js时设置端点时,adal.js似乎将所有传出端点流量重定向到microsofts登录服务。

观察:

  • Adal.js会话存储包含两个adal.access.token.key条目。一个用于SPA Azure AD应用程序的客户端ID,另一个用于外部API。只有SPA令牌具有值。
  • 如果我没有将$ httpProvider注入adal.js,那么调用会转到外部API,然后我得到一个401。
  • 如果我手动将SPA令牌添加到http标头(授权:bearer'令牌值'),我会收到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;
                });

4 个答案:

答案 0 :(得分:2)

好吧,我一直在靠墙砸我的脑袋来解决这个问题。试图使我的ADAL.js SPA应用程序(无角度)成功地将跨域XHR请求转移到我宝贵的启用CORS的Web API上。

This sample app,就像我这样的新手一样,有这个问题:它的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
        });
    });

答案 1 :(得分:1)

您需要让Web API了解您的客户端应用程序。仅从客户端向API添加委派权限是不够的。

要识别API客户端,请转到Azure管理门户,下载API清单并将客户端应用程序的ClientID添加到“knownClientApplications”列表中。

要允许隐式流,您还需要在清单中将“oauth2AllowImplicitFlow”设置为true。

将清单上传回API应用程序。

答案 2 :(得分:1)

ADAL.js确实将access_token与id_token区分开来,用于调用在不同域上运行的Azure AD保护API。 最初,在登录期间,它只需要id_token。此令牌具有访问同一域的资源的访问权限。 但是,在调用在不同域中运行的API时,adal拦截器会检查API URL是否在adal.init()中作为端点配置。

只有这样才能为所请求的资源调用访问令牌。还需要在AAD中配置SPA以访问API APP。

实现这一目标的关键是:  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
  2. 您可以参阅此链接了解详情:ADAL.js deep dive

答案 3 :(得分:0)

我不确定我们的设置是否完全相同,但我认为它具有可比性。

我有一个Angular SPA,它通过Azure API Management(APIM)使用和外部Web API。我的代码可能不是最佳实践,但到目前为止它对我有用:)

SPA Azure AD应用程序具有访问外部API Azure AD应用程序的委派权限。

SPA (基于Adal TodoList SPA sample

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如下所示:

外部API Startup.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"
            });
    }

如果这有帮助,或者我可以提供进一步帮助,请告诉我。