How to Authenticate a xamarin.Forms app using AAD - c#

I am a newbie to Mobile app and took a new project recently. I'm developing a Xamarin.Forms App and trying to get an access token from Azure Active Directory in order able to get access to some Web API services on an other server.
using this code below I was able to get the response on console app but not from Xamarin.forms.
Q1. How can I implement this functionality in my PCL?
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Xamarin.Auth;
using Newtonsoft.Json;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.Serialization.Json;
internal class UtilityClass
{
public static async Task<string> GetTokenAsync()
{
var authenticationContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(aadInstance, null);
ClientCredential clientCredential = new ClientCredential(clientId, clientSecret);
try
{
if (Token == null)
{
AuthenticationResult result = await authenticationContext.AcquireTokenAsync(audienceApi, clientCredential);
if (result == null)
throw new InvalidOperationException("Failed to obtain the JWT token");
Token = result.AccessToken;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return Token;
}
public async static Task<TReturn> CallApiAsync<TReturn>(string URI)
{
TReturn result = default(TReturn);
HttpClient client = new HttpClient();
client.MaxResponseContentBufferSize = 256000;
client.BaseAddress = new Uri(audienceApi);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await GetTokenAsync());
var uri = new Uri(string.Format(URI, string.Empty));
var response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<TReturn>(content);
}
return result;
}
}
with in my PCL's app Method, try to access it this way
public App ()
{
var CustomerContactList = UtilityClasses.CallApiAsync<List<CustomerContact>>();
}
Q2. I want to run my web API service on a local server and debug the call from Xamarin.Forms App, so how can I configure my Visual Studio 2015 and Android Emulator to get access to https://localhost:PortNumber/api/getCustomerContacts?
Thank You in Advance, for your support

You need to configure your local IIS Express to handle requests it receives on your external IP, follow steps here, debug steps if you get stuck in comments
Cheers

Q1. How can I implement this functionality in my PCL?
There is something that is suitable for you if you take a look at this link
https://forums.xamarin.com/discussion/19021/using-xamarin-auth-with-xamarin-forms
Substitute the auth with the below redirectUrl and authorizeUrl should work, although i haven't tested this.
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code
var auth = new OAuth2Authenticator (
clientId: "clientId",
scope: "",
authorizeUrl: new Uri ("https://login.microsoftonline.com/{yourtenant}/oauth2/authorize?"),
redirectUrl: new Uri ("urn:ietf:wg:oauth:2.0:oob"));
Q2. I want to run my web API service on a local server and debug the call from Xamarin.Forms App, so how can I configure my Visual Studio 2015 and Android Emulator to get access to https://localhost:PortNumber/api/getCustomerContacts?
In your IIS , expose your local web site in a specific port, then in your android emulator you can use
https://10.0.2.2:portNumber/api/getCustomerContacts

Related

I get the following error "AADSTS90002 Tenant authorize not found."

I'm working on an application to obtain data through
OAuth of Dynamics 365
to do this use the following example
https://www.youtube.com/watch?v=Td7Bk3IXJ9s
public static async Task Auth()
{
string URL = "https://grupolg.api.crm.dynamics.com/api/data/v9.1/";
AuthenticationParameters API = AuthenticationParameters.CreateFromResourceUrlAsync(new Uri(URL)).Result;
//APP KEY, SECRET KEY
ClientCredential Creds = new ClientCredential("hidden for security", "hidden for security");
AuthenticationContext authContext = new AuthenticationContext(API.Authority);
string token = authContext.AcquireTokenAsync(API.Resource, Creds).Result.AccessToken;
using (HttpClient httpClient = new HttpClient())
{
httpClient.Timeout = new TimeSpan(0,2,0);
httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer",token);
HttpResponseMessage res = await httpClient.GetAsync(URL+"/contacts$top=1");
}
}
but I get the following error:
The error is caused by the ADAL version you're using and how the authority URL is generated.
If you downgrade the Microsoft.IdentityModel.Clients.ActiveDirectory library version to 3.9.18 you'll be able to connect without problems. There's an open bug in GitHub where you can track its progress or comment to add more information to get a definitive fix for it.

How to list Virtual Machines Classic in Azure

I would like to programmatically list and control virtual machines classic (old one) in Azure. For managed it is not problem, there are libraries and the rest API is working, but once I am calling the old API for listing classic, I got 403 (Forbidden).
Is the code fine? Do I need to manage credentials for old API on another place?
My code is here:
static void Main(string[] args)
{
string apiNew = "https://management.azure.com/subscriptions/xxxxxxxxxxxxxxxxxxxxxxxx/providers/Microsoft.Compute/virtualMachines?api-version=2018-06-01";
string apiOld = "https://management.core.windows.net/xxxxxxxxxxxxxxxxxxxxxxxx/services/vmimages"
AzureRestClient client = new AzureRestClient(credentials.TenantId, credentials.ClientId, credentials.ClientSecret);
//OK - I can list the managed VMs.
string resultNew = client.GetRequestAsync(apiNew).Result;
// 403 forbidden
string resultOld = client.GetRequestAsync(apiOld).Result;
}
public class AzureRestClient : IDisposable
{
private readonly HttpClient _client;
public AzureRestClient(string tenantName, string clientId, string clientSecret)
{
_client = CreateClient(tenantName, clientId, clientSecret).Result;
}
private async Task<string> GetAccessToken(string tenantName, string clientId, string clientSecret)
{
string authString = "https://login.microsoftonline.com/" + tenantName;
string resourceUrl = "https://management.core.windows.net/";
var authenticationContext = new AuthenticationContext(authString, false);
var clientCred = new ClientCredential(clientId, clientSecret);
var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientCred);
var token = authenticationResult.AccessToken;
return token;
}
async Task<HttpClient> CreateClient(string tenantName, string clientId, string clientSecret)
{
string token = await GetAccessToken(tenantName, clientId, clientSecret);
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
return client;
}
public async Task<string> GetRequestAsync(string url)
{
return await _client.GetStringAsync(url);
}
}
UPDATE 1:
Response details:
HTTP/1.1 403 Forbidden
Content-Length: 288
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 22 Oct 2018 11:03:40 GMT
HTTP/1.1 403 Forbidden
Content-Length: 288
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 22 Oct 2018 11:03:40 GMT
<Error xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Code>ForbiddenError</Code>
<Message>The server failed to authenticate the request.
Verify that the certificate is valid and is associated with this subscription.</Message>
</Error>
Update 2:
I found that same API is used by powershell command Get-AzureVMImage and it is working from powershell. Powershell ask me first to login to Azure with interactive login windows by email and password and the the request use Bearer header to authenticate like mine code.
If I sniff the access token (Bearer header) from communication created by Powershell, I can communicate with that API with success.
Update 3: SOLVED, answer bellow.
1. Reason for 403 when you're calling List VM Images API
It's because your Azure AD registered application is not using the "Windows Azure Service Management API" delegated permissions correctly. I say this because I see your code is acquiring the token directly using application identity (ClientCredential) and not as a user.
Please see the screenshots below. Window Azure Service Management API clearly does not provide any application permissions, only thing that can be used is a delegated permission. If you want to understand more about the difference between the two kinds of permissions, read Permissions in Azure AD. To put it very briefly, when using delegated permissions, the app is delegated permission to act as the signed-in user when making calls to an API. So there has to be a signed-in user.
I was able to reproduce the 403 error using your code and then able to make it work and return a list of classic VM's with some changes. I'll explain the required changes next.
Go to your Azure AD > App registrations > your app > Settings > Required permissions :
2. Changes required to make it work
Change will be to acquire token as a signed in user and not directly using application's clientId and secret. Since your application is a console app, it would make sense to do something like this, which will prompt the user to enter credentials:
var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto));
Also, since your application is a console application, it would be better to register it as a "Native" application instead of a web application like you have it right now. I say this because console applications or desktop client based applications which can run on user systems are not secure to handle application secrets, so you should not register them as "Web app / API" and not use any secrets in them as it's a security risk.
So overall, 2 changes and you should be good to go. As I said earlier, I have tried these and can see the code working fine and getting a list of classic VMs.
a. Register your application in Azure AD as a native app (i.e. Application Type should be native and not Web app / API), then in required permissions add the "Window Azure Service Management API" and check the delegated permissions as per earlier screenshots in point 1
b. Change the way to acquire token, so that delegated permissions can be used as per the signed in user. Of course, signed in user should have permissions to the VM's you're trying to list or if you have multiple users, the list will reflect those VM's which currently signed in user has access to.
Here is the entire working code after I modified it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net.Http;
using System.Net.Http.Headers;
namespace ListVMsConsoleApp
{
class Program
{
static void Main(string[] args)
{
string tenantId = "xxxxxx";
string clientId = "xxxxxx";
string redirectUri = "https://ListClassicVMsApp";
string apiNew = "https://management.azure.com/subscriptions/xxxxxxxx/providers/Microsoft.Compute/virtualMachines?api-version=2018-06-01";
string apiOld = "https://management.core.windows.net/xxxxxxxx/services/vmimages";
AzureRestClient client = new AzureRestClient(tenantId, clientId, redirectUri);
//OK - I can list the managed VMs.
//string resultNew = client.GetRequestAsync(apiNew).Result;
// 403 forbidden - should work now
string resultOld = client.GetRequestAsync(apiOld).Result;
}
}
public class AzureRestClient
{
private readonly HttpClient _client;
public AzureRestClient(string tenantName, string clientId, string redirectUri)
{
_client = CreateClient(tenantName, clientId, redirectUri).Result;
}
private async Task<string> GetAccessToken(string tenantName, string clientId, string redirectUri)
{
string authString = "https://login.microsoftonline.com/" + tenantName;
string resourceUrl = "https://management.core.windows.net/";
var authenticationContext = new AuthenticationContext(authString, false);
var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto));
return authenticationResult.AccessToken;
}
async Task<HttpClient> CreateClient(string tenantName, string clientId, string redirectUri)
{
string token = await GetAccessToken(tenantName, clientId, redirectUri);
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Add("x-ms-version", "2014-02-01");
return client;
}
public async Task<string> GetRequestAsync(string url)
{
return await _client.GetStringAsync(url);
}
}
}
According to the linked documentation you appear to be missing a required request header when requesting the classic REST API
x-ms-version - Required. Specifies the version of the operation to use for this request. This header should be set to 2014-02-01 or higher.
Reference List VM Images: Request Headers
To allow for the inclusion of the header, create an overload for GET requests in the AzureRestClient
public async Task<string> GetRequestAsync(string url, Dictionary<string, string> headers) {
var request = new HttpRequestMessage(HttpMethod.Get, url);
if (headers != null)
foreach (var header in headers) {
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
var response = await _client.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
and include the required header when calling apiOld
var headers = new Dictionary<string, string>();
headers["x-ms-version"] = "2014-02-01";
string resultOld = client.GetRequestAsync(apiOld, headers).GetAwaiter().GetResult();
Finnaly I got it to work:
First Open Powershell:
Get-AzurePublishSettingsFile
and save that file.
then type in Powershell
Import-AzurePublishSettingsFile [mypublishsettingsfile]
Open certificate store and find imported certificate. And use that certificate
at the same time with credentials within the HttpClient.
Based on my test, you need to get the access token interactively.
I've perfectly reproduced your issue.
Unfortunately, I didn't get a working source code with the Old API reaching your needs.
Although I've found a Microsoft.ClassicCompute provider, instead of the usual used Microsoft.Compute one, but still failing to have a working test.
I'm pretty sure you should no more "manually" use the old obsolete API, and should use modern Microsoft packages allowing management of Classic and "Normal" elements, like Virtual Machines, or Storage accounts.
The key package is Microsoft.Azure.Management.Compute.Fluent
You can find the documentation here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.management.compute.fluent?view=azure-dotnet
Let me know if you still need help.

Web service call from visual studio xamarin in ios

We are developing an iOS shopping cart application in c# and Visual Studio 2017 for Xamarin. We are using rest web services, Here I could not call web services. when I call web service I am getting null response with an error[ConnectFailure (Connection refused)]. My question is How to get a value from localhost URL like [http://localhost:56207/api/Users/Raju/Password#123]. When I enter this URL in the browser I am getting true or false depending upon user and password validation.I request you to help me to resolve this issue.I paste the code in below:
public class RestInterfaceImp : IRestLogin
{
HttpClient client;
private const string WebServiceUrl = "http://localhost:56207/api/Users/Raju/Password#123";
public async Task<List<User>> RefreshDataAsync()
{
try
{
var httpClient = new HttpClient();
var resp = await httpClient.GetAsync(WebServiceUrl);
if (resp.IsSuccessStatusCode)
{
var respStr = await resp.Content.ReadAsStringAsync();
var listaAtletas = JsonConvert.DeserializeObject<List<User>>(respStr);
}
}
catch (HttpRequestException e)
{
Debug.WriteLine(e.InnerException.Message);
}
return null;
}
}
Whenever we need to expose a local api to a simulator like you are doing, we use ngrok:
https://github.com/inconshreveable/ngrok
For some reason their website is down right now so it's possible they are no longer a thing but here is the url:
https://ngrok.com/

Getting 401 (Unauthorized) error in Active Directory user image in Multi tenant Application

I have implemented Multi tenant application using Azure Active Directory in Angular 4.After user logged into my application i'm able get user info.But user photo is not getting from the Active directory for that i have implemented Graph API like below snippet.
public Task<UserDto> getPhoto(TenantDto tenantDto)
{
var client = new HttpClient();
client.BaseAddress = new Uri(String.Format("https://graph.windows.net/{0}/users/{1}/thumbnailPhoto?api-version=1.6", tenantDto.tenantKey, tenantDto.email));
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("image/jpeg"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tenantDto.token);
HttpResponseMessage response = client.GetAsync("").Result;
if (response.IsSuccessStatusCode)
{
return null;
//Status status = response.Content.ReadAsAsync<Status>().Result;
//if (status.Code == 200)
// InBoundResponse = JsonConvert.DeserializeObject<InBoundCallResponse>(status.Data.ToString());
//return InBoundResponse;
}
else
{
return null;
}
}
Here tenantDto.token is nothing but a logged in user "token" While calling this Graph API i'm getting 401 (Unauthorized) error. I have tried all but no use.
I have changed Graph API setting s in Active Directory APP also like below attachment
Also i have tried like below code it's working only for single tenant
[Route("AdUserImage"), HttpGet]
public async Task<HttpResponseMessage> userImage()
{
var authContext = new AuthenticationContext("https://login.windows.net/sampletest.onmicrosoft.com/oauth2/token");
var credential = new ClientCredential(clientID, clientSecret);
ActiveDirectoryClient directoryClient = new ActiveDirectoryClient(serviceRoot, async () =>
{
var result = await authContext.AcquireTokenAsync("https://graph.windows.net/", credential);
return result.AccessToken;
});
var user = await directoryClient.Users.Where(x => x.UserPrincipalName == "balu#sampletest.onmicrosoft.com").ExecuteSingleAsync();
DataServiceStreamResponse photo = await user.ThumbnailPhoto.DownloadAsync();
using (MemoryStream s = new MemoryStream())
{
photo.Stream.CopyTo(s);
var encodedImage = Convert.ToBase64String(s.ToArray());
}
//string token = await HttpAppAuthenticationAsync();
Status status = new Status("OK");
status = new Status("Found", null, "User exists.");
return Request.CreateResponse(HttpStatusCode.OK, status, _jsonMediaTypeFormatter);
}
but i need to implement for Multi tenant app.
Any Answer Appreciated.
Thanks in Advance........!
Delegate-user token:
1 .Acquire the token via the implict flow:
https://login.microsoftonline.com/{tenant}/oauth2/authorize?response_type=token&client_id={clientId}&redirect_uri={redirect_uri}&resource=https%3A%2F%2Fgraph.windows.net&nonce={nonce}
2 .Call the Azure AD Graph
GET: https://graph.windows.net/{tenant}/me/thumbnailPhoto?api-version=1.6
Content-Type: image/jpeg
Application token:
1 .Acquire the token via the client credentials flow
POST:https://login.microsoftonline.com/{tenant}/oauth2/token
grant_type=client_credentials&client_id={client_id}&client_secret={client_secret}&resource=https%3A%2F%2Fgraph.windows.net
2 .Call the Azure AD Graph
GET:https://graph.windows.net/{tenant}/users/{upn}/thumbnailPhoto?api-version=1.6
Content-Type: image/jpeg
If you only to get the thumbnail photo of sign-in user for the multiple tenant, you should login-in with Azure AD first and acquire the access token for the delegate user and used that token to call Azure AD Graph REST. Difference between these two kinds of token, you can refer the links below:
Get access on behalf of a user
Get access without a user
I'm using Delegate-user token as per your explnation using below url
https://login.microsoftonline.com/{tenant}/oauth2/authorize?response_type=token&client_id={clientId}&redirect_uri={redirect_uri}&resource=https%3A%2F%2Fgraph.windows.net&nonce={nonce}
But still not able receiving but i'm able getting 200 status but token is not return.i have implemented like below
var client = new HttpClient();
client.BaseAddress = new Uri("https://login.microsoftonline.com/{TenantID}/oauth2/authorize?response_type=token&client_id={ClientID}&redirect_uri={ApplicationUrl}&resource=https%3A%2F%2Fgraph.windows.net&nonce=a9d7730c-79f3-4092-803a-07f346de2cdf");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
HttpResponseMessage response = client.GetAsync("").Result;
if (response.IsSuccessStatusCode)
{
}
else
{
//return null;
}
It's not return the token.it is returning html content in success block

Mobile Service Client throws exception when response is not found

I am trying to port an application from an azure mobile service to an azure web app. (the mobile service was working). I have added microsoft account authentication to the web-app, and the web app api has a MobileAppController attribute. I have a Universal windows app front end that calls the api. The app first checks if a player is in the database, if not I get a not found response. If I call the method using the following code with the MobileServiceClient I get an exception.
private async Task<HttpResponseMessage> GetAZMAsyncP(string apiext, IDictionary<string,string> param )
{
string myuri = String.Format("{0}{1}", urlbase, apiext);
//client is the MobileServiceClient that is correctly logged in
//I do not get response which is 404 not found, I get an exception "The request could not be completed, Not Found"
var response = await client.InvokeApiAsync(myuri, System.Net.Http.HttpMethod.Get, param);
return response;
}
If I call the api from an httpclient and add my own headers, which the mobile client is supposed to do for me, then I get the response as requested. Here is the code:
private async static Task<HttpResponseMessage> GetAZAsync(string apiext)
{
string completeUrl = String.Format("{0}{1}", urlbase, apiext);
// Call out to AZ
using (var http = new HttpClient())
{
// http.BaseAddress = new Uri(completeUrl);
HttpRequestMessage rq = new HttpRequestMessage()
{
RequestUri = new Uri(completeUrl),
Method = HttpMethod.Get
};
addauthheader(rq);
var response = await http.SendAsync(rq);
return response;
}
}
private static void addauthheader(HttpRequestMessage rq)
{
MobileServiceUser user = App.client.CurrentUser;
rq.Headers.Add("X-ZUMO-FEATURES", "AT,QS");
rq.Headers.Add("X-ZUMO-INSTALLATION-ID",
"ff90f37e-0c03-4c52-a343-af711752e383");
rq.Headers.Add("X-ZUMO-AUTH", user.MobileServiceAuthenticationToken);
rq.Headers.Add("Accept", "application/json");
rq.Headers.Add("User-Agent", "ZUMO/2.1");
rq.Headers.Add("User-Agent",
"(lang = Managed; os = Windows Store; os_version = --; arch = X86; version = 2.1.40707.0)");
rq.Headers.Add("X-ZUMO-VERSION",
"ZUMO/2.1(lang = Managed; os = Windows Store; os_version = --; arch = X86; version = 2.1.40707.0)");
rq.Headers.Add("ZUMO-API-VERSION", "2.0.0");
}
You can try this out as it is live (and buggy).
https://gamenote2.azurewebsites.net/api/Players?displayname=Paul Goldschmidt&teamid=arizona-diamondbacks
Should give you a 404,
https://gamenote2.azurewebsites.net/api/Players?displayname=Chase Utley&teamid=los-angeles-dodgers
should give you a chase utley object. (YOu will be asked to log into a Microsoft Account).
So my questions: 1. Can I fix the mobileclient call to get a response instead of an execption
2. Is there any good reason for me to be spending so much time on this.
If you examine the exception, you will note that the status code is in there - it's just in a property that is not serialized. Just surround your InvokeApiAsync() call with a try/catch and test for the StatusCode. It should be a lot easier than writing your own HTTP Client code for the same purpose.
Specifically, MobileServiceInvalidOperationException contains the HttpResponse of the failed request, so you can check exception.Response.StatusCode value.

Categories

Resources