Reputation: 51
I am struggling for several days now trying to access an azure stored web api (it's the default web api, with Values controller, etc.) from a Xamarin Forms Android app. I found a ton of documentation on how to achieve this, but I don't want to use the "traditional way", as in dealing with AuthenticationContext\Authentication Result. The request was to use a custom login form in the app, not the one provided by Microsoft, Google, etc.
I got the login working like this:
HttpClient client = new HttpClient();
var tokenEndpoint = "https://login.microsoftonline.com/testdir.onmicrosoft.com/oauth2/token";
var body = "resource=www.graph.microsoft.com&client_id=" + App.ClientId + "&grant_type=password&username=" + UsernameEntry.Text + "&password=" + PasswordEntry.Text + "";
var stringContent = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded");
var result = await client.PostAsync(tokenEndpoint, stringContent).ContinueWith<string>(response => response.Result.Content.ReadAsStringAsync().Result);
var jobject = JObject.Parse(result);
App.AccessToken = jobject["access_token"].Value<string>();
This works like a charm. I have now the access token and I can use it to retrieve users or whatever from the Azure AD.
I want something similar to use for retrieving data from the web api. I want the login to happen behind the scenes, not by providing a Microsoft login form and prompting the user to login.
I tried something like this:
tokenEndpoint = "https://login.microsoftonline.com/testdir.onmicrosoft.com/oauth2/authorize";
body = "client_id=xxxx&response_type=code&redirect_uri=https://testapi.azurewebsites.net/";
result = await client.PostAsync(tokenEndpoint, stringContent).ContinueWith<string>(response => response.Result.Content.ReadAsStringAsync().Result);
jobject = JObject.Parse(result);
But I always get an error stating that the user has to login. (I think that basically it tries to generate a Microsoft login form, and after a successful attempt it will direct with the authorization code required for accessing the api).
It would be awesome if I could retrieve the authorization code from a single call and then used it to query the web api.
Here are some useful questions I found, describing similar issues\scenarios, but I didn't manage to wrap all the information into a piece of working code:
Getting Authorization has been denied for this request in Fiddler with Azure AD
Use OAuth2 for authentication against Azure AD to call WebAPI
Unable to use bearer token to access AAD-secure Web API
Any help will be MUCH appreciated. Thank you!
Upvotes: 0
Views: 352
Reputation: 51
It took for ages but I finally got it working. I used almost the same approach I used for the login part but with a twist (to see the code sample for the working login mechanism, see the first part of the question). Using the following code snippet I was able to acquire a token and then use it to access my custom API:
var clientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var client_secret = "yyyy";
HttpClient client = new HttpClient();
string tokenEndpoint = "https://login.microsoftonline.com/testTenant.onmicrosoft.com/oauth2/token";
var body = "resource=" + clientId + "&client_id=" + clientId + "&client_secret=" + client_secret + "&grant_type=password&username=" + username + "&password=" + password + "";
var stringContent = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded");
var result = await client.PostAsync(tokenEndpoint, stringContent).ContinueWith<string>(response => response.Result.Content.ReadAsStringAsync().Result);
JObject jobject = JObject.Parse(result);
var accessToken = jobject["access_token"].Value<string>();
The tricky part was figuring out that the parameters "resource" and "client_id" must have the same value as in the App ID of the registered custom web api from Azure. Right before I made the changes that allowed the code to work, I got the following errors:
1) Application named https://customApi.azurewebsites.net/ was not found in the tenant named testTenant.onmicrosoft.com. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
The problem here was that I was using "https://customApi.azurewebsites.net/" as the resource.
2) Application 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' is requesting a token for itself. This scenario is supported only if resource is specified using the GUID based App Identifier.
After the first error, I tried to replace the resource to be "https://testTenant.onmicrosoft.com/CustomApi". Seeing the error message, I replaced the link with the APP ID of my api. That did the trick.
There may be cases when you will also get an error stating the client_secret is not valid, although you have a valid key generated. I had to regenerate a couple of times the key and then it worked.
I also posted a thread regarding this issue on MSDN. You can find useful resources there.
Please keep in mind that this is not the recommended approach and has serious security risks. But there may be rare cases when this applies:
"There could be times when the password grant mechanism can be "acceptable" - for instance in a non-public app installed only on approved corporate computers for internal users." That was exactly my scenario.
Shawn provided valuable documentation both on how to do it and what are the recommended practices.
Upvotes: 1
Reputation: 12434
You are looking for the Resource Owner Password Credentials Grant.
There is a basic documentation on this flow here.
You should know that in general, the Azure Active Directory team strongly discourages the use of this flow, as it requires your application to handle the username and password of the user, which bring about a number of security issues.
Please read here for more warnings on why you should NOT do this.
Vittorio Bertocci - Using ADAL .NET to Authenticate Users via Username/Password
Rich Randall - How to authenticate user with Azure Active Directory using OAuth 2.0?
OAuth 2.0 Specification - Security Considerations
In summary: I have shown you you how you can do it... but you should not do it.
Upvotes: 0