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がエンドポイントのトークンを取得できず(おそらくSPAで何か間違っているため)、必要なトークンを取得できないため、エンドポイントへのトラフィックを停止します。 SPAトークンには必要な権限が含まれていないため、APIに対して使用できません。 adal.jsがエンドポイントのトークンを取得しないのはなぜですか?どうすれば修正できますか?
追加情報:
セッションストレージ:
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;
});
わかりました、私はこれを理解するために頭を壁に打ちつけてきました。 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
});
});
Web APIにクライアントアプリケーションを認識させる必要があります。クライアントからAPIに委任された権限を追加するだけでは不十分です。
APIクライアントを認識させるには、Azure管理ポータルに移動し、APIのマニフェストをダウンロードして、クライアントアプリケーションのClientIDを「knownClientApplications」のリストに追加します。
暗黙的なフローを許可するには、マニフェストでも「oauth2AllowImplicitFlow」をtrueに設定する必要があります。
マニフェストをAPIアプリケーションにアップロードします。
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
);
詳細については、このリンクを参照してください。 ADAL.jsの詳細
私たちの設定がまったく同じかどうかはわかりませんが、比較できると思います。
Angular Azureを使用し、Azure API Management(APIM)を介して外部Web APIを使用するSPAがあります。私のコードはベストプラクティスではないかもしれませんが、今のところ私にとっては機能します:)
SPA Azure ADアプリには、外部API Azure ADアプリにアクセスするための委任されたアクセス許可があります。
SPA( Adal 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"
});
}
これで問題が解決するかどうか、またはさらにサポートが必要な場合はお知らせください。