Reputation: 93
I've hit a problem that looks a bit like this one, but I'm hitting the issue in a subtly different context, and the original question doesn't have a satisfactory answer other than 'try using a different authentication flow', which I don't think will work for me.
I have a web API hosted in Azure, secured with active directory bearer tokens. I would like to be able to run some automated integration tests of this, as part of a VSTS build/release flow.
To do this, my first step has been to build a console application that uses an AD native application so I can use the non-interactive authentication flow, and at the end of last week, the code appeared to be working. The relevant code is:
private async Task<string> GetToken(string username, string password)
{
string resourceId = <application ID of AD web API app>;
string clientId = <application ID of AD native app>;
string authority = "https://login.microsoftonline.com/<mytennant>.onmicrosoft.com";
AuthenticationContext authContext = new AuthenticationContext(authority);
UserCredential credential = new UserCredential(username, password);
var result = await authContext.AcquireTokenAsync(resourceId, clientId, credential);
return result.AccessToken;
}
When I left work on Friday, this code was working. When I came back today, it instead provides the following stack trace:
parsing_wstrust_response_failed: Parsing WS-Trust response failed
at Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.WsTrust.WsTrustResponse.CreateFromResponseDocument(XDocument responseDocument, WsTrustVersion version)
at Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.WsTrust.WsTrustRequest.<SendRequestAsync>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.Flows.AcquireTokenNonInteractiveHandler.<PreTokenRequestAsync>d__5.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.Flows.AcquireTokenHandlerBase.<RunAsync>d__57.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.<AcquireTokenCommonAsync>d__37.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContextIntegratedAuthExtensions.<AcquireTokenAsync>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at GetToken.Program.<GetToken>d__5.MoveNext()
When running locally, I can fail-over to the interactive login that you need to use for the first time anyway to grant the rights to log in non-interactively going forward. To do so, I replaced the call to AcquireToken
in the above code with this:
AuthenticationResult result = null;
try
{
// the same as the old call to AcquiteTokneAsync
result = await authContext.AcquireTokenAsync(resourceId, clientId, credential);
}
catch (AdalException aex)
{
// this failover uses an interactive flow, so can't be used to run
// automated deployment integration tests
string replyUri = <one of the reply URIs in the native AD app>;
result = await authContext.AcquireTokenAsync(resourceId, clientId, new Uri(replyUri));
}
So, I can get the tokens interactively, and the tokens obtained work (just as the ones that I got last week using the non-interactive flow worked). But this error is blocking me from being able to run automated tests against the deployed web application as part of my continuous delivery pipeline. Is there a way of avoiding this parsing error?
Upvotes: 0
Views: 634
Reputation: 93
The first line in the answer by axfd provided the key input.
I had left some details out of the question, which I incorrectly thought were irrelevant. In our dev environment we had a separate Azure AD component, and all the accounts we use in qa, pre-production and production are invited as guest accounts.
The Resource Owner Password Credentials Grant
flow will not work for guest accounts. This is alluded to in some of the doucmentation, as not allowing daisychaning because of the relative lower security in the flow. I had, foolishly, been using my account, which is a guest account in the dev AAD, but is a true account in the production AAD.
Creating and using a new account in the dev AAD and using that with the tool instead solved my problem.
Upvotes: 3
Reputation: 321
Base on your error message, the user is not federated with WS-Trust.
In fact, Resource Owner Password Credentials Grant flow is not recommend. This should only be used when there is a high degree of trust between the resource owner and the client (e.g., the client is part of the device operating system or a highly privileged application), and when other authorization grant types are not available (such as an authorization code).
I suggest you use client credentials to get access token. My code is as below.
string authority = "https://login.microsoftonline.com/b29343ba-***/oauth2/token"; //token endpoint
string resourceUri = "";
string clientId = "your application id";
string clientkey = "your app key";
try
{
AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential clientCredential = new ClientCredential(clientId, clientkey);
AuthenticationResult authenticationResult = authContext.AcquireTokenAsync(resourceUri, clientCredential).Result;
Console.WriteLine("--------------------------------");
Console.WriteLine(authenticationResult.AccessToken);
Console.Read();
}
catch (Exception ex)
{
}
Besides, you also can refer to the blog.
Upvotes: 1