UserPasswordCredential .Net Standard - c#

I want to call graph api, https://graph.windows.net for that I am using a delegated permission as below for token
UserPasswordCredential credentials = new UserPasswordCredential("username", "password");
AuthenticationContext authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", "mytenant.onmicrosoft.com"));
var accessToken = authContext.AcquireTokenAsync("https://graph.windows.net", clientId, credentials).Result.AccessToken;
I am not a tenant admin and in AAD and I cannot add specific Applications as an owner for app authentication. This was working in full .net is there a workaround for this in .net standard? As I cannot use app authentication in this scenario.
I am trying to convert and webjob to an Azure function.

is there a workaround for this in .net standard?
Yes, we could use the rest API to get the token directly.
Post https://login.windows.net/<tenant-id>/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=password
&resource={resource}
&username={username}
&password={password}
&client_id={client-id}
The following is the demo code to get access token
var tenantId = "xxx.onmicrosoft.com";
var userName = "xxx#xxxx.onmicrosoft.com";
var password = "xxxxxx";
var clientId = "xxxxxxx";
using (HttpClient client = new HttpClient())
{
var tokenEndpoint = $"https://login.windows.net/{tenantId}/oauth2/token";
var accept = "application/json";
client.DefaultRequestHeaders.Add("Accept", accept);
string postBody = $"resource=https://graph.windows.net/&client_id={clientId}&grant_type=password&username={userName}&password={password}";
using (HttpResponseMessage response = await client.PostAsync(tokenEndpoint, new StringContent(postBody, Encoding.UTF8, "application/x-www-form-urlencoded")))
{
if (response.IsSuccessStatusCode)
{
var jsonresult = JObject.Parse(await response.Content.ReadAsStringAsync());
var token = (string)jsonresult["access_token"];
}
}
}
Note: Azure AD native application is requried.

Related

Using Microsoft Graph to obtain Access Token for Azure Web App Continuous Integration Deployment

I am attempting to wire up source control with continuous integration to an Azure Web App programmatically via C#. Historically I've used Azure PowerShell to do the same.
On the C# side, I am using the Microsoft.Azure.Management.Fluent libraries. The code to wire up source control is pretty straight forward:
await webApp.Update().DefineSourceControl().WithContinuouslyIntegratedGitHubRepository(GIT_URL).WithBranch(GIT_BRANCH).WithGitHubAccessToken(GIT_TOKEN).Attach().ApplyAsync();
This code runs for about 5 minutes and then returns the error:
{"Code":"BadRequest","Message":"Parameter x-ms-client-principal-name
is null or empty."}
I initially interpreted this to mean the Fluent libraries weren't passing a necessary value to the API, so I attempted to hit the API directly using Fluent libraries to abstract the authorization piece:
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal( CLIENT_ID, CLIENT_SECRET, TENANT_ID, AzureEnvironment.AzureGlobalCloud);
var client = RestClient.Configure().WithEnvironment(AzureEnvironment.AzureGlobalCloud).WithCredentials(credentials).Build();
CancellationToken cancellationToken = new CancellationToken();
var request = new HttpRequestMessage(HttpMethod.Get, $"https://management.azure.com/subscriptions/{SUBSCRIPTION_ID}/resourcegroups/{RESOURCE_GROUP}?api-version=2019-10-01");
request.Headers.Add("x-ms-client-principal-name", USERNAME);
client.Credentials.ProcessHttpRequestAsync(request, cancellationToken).GetAwaiter().GetResult();
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
Several variations of this resulted in the same error referencing x-ms-client-principal-name. This lead me to believe the problem was with the token being used and the associated permissions. To test this, I ran the PowerShell script mentioned above, watched it run through Fiddler, and grabbed the token it used to complete. Using that token with basically the same code, it worked fine:
var token = "<THE TOKEN I COPIED FROM FIDDLER>";
CancellationToken cancellationToken = new CancellationToken();
var request = new HttpRequestMessage(requestType, $"https://management.azure.com/subscriptions/{SUBSCRIPTION_ID}/resourceGroups/{RESOURCE_GROUP}/providers/Microsoft.Web/sites/{WEB_APP}/sourcecontrols/web?api-version=2015-08-01");
request.Headers.Add("Authorization", $"Bearer {token}");
if ((requestType == HttpMethod.Put || requestType == HttpMethod.Post) && !string.IsNullOrEmpty(postData))
{
request.Content = new StringContent(postData, Encoding.UTF8, "application/json");
}
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
So now, it seems to just be a matter of getting the access token myself. This is where things have become difficult. If I obtain the token as the service principal, I end up with the same x-ms-client-principal-name that I started with:
public static string GetAuthToken(string tenantId, string clientId, string clientSecret)
{
var request = new HttpRequestMessage(HttpMethod.Post, $"https://login.microsoftonline.com/{tenantId}/oauth2/token");
request.Content = new StringContent($"grant_type=client_credentials&client_id={clientId}&client_secret={clientSecret}&resource=https://management.azure.com", Encoding.UTF8, "application/x-www-form-urlencoded");
CancellationToken cancellationToken = new CancellationToken();
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var auth = JsonConvert.DeserializeObject<AuthResponse>(result);
return auth.AccessToken;
}
When I attempt to obtain token using my username and password, I get an error back telling me:
AADSTS90002: Tenant '' not found. This may happen if there are no
active subscriptions for the tenant. Check to make sure you have the
correct tenant ID. Check with your subscription administrator.
Here's the code I use for that:
public static string GetAuthToken(string tenantId, string username, string password, string clientId, string clientSecret)
{
var request = new HttpRequestMessage(HttpMethod.Post, $"https://login.microsoftonline.com/{tenantId}/oauth2/token");
request.Content = new StringContent($"grant_type=password&username={username}&password={password}&client_id={clientId}&client_secret={clientSecret}&resource=https://management.azure.com", Encoding.UTF8, "application/x-www-form-urlencoded");
CancellationToken cancellationToken = new CancellationToken();
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var auth = JsonConvert.DeserializeObject<AuthResponse>(result);
return auth.AccessToken;
}
I cannot have the C# code invoke the PS script as a work-around as it requires Azure PowerShell which is not guaranteed to be on the machine running the code (Azure Web App) and cannot be installed due to Admin restrictions.
I need to be able to obtain an access token that also has permissions for azure dev ops (formally VSTS) so I can bind/wire-up source control for continuous integration. Any guidance that can help me get past this is much appreciated.
I was finally able to get it working. First, I had to create a new Azure AD User and grant it the necessary permissions; this would not work with my regular Azure Login. With the new user and permissions, I am able to successfully obtain token like so:
public static async Task<string> GetAccessToken(string clientId, string userName, string password)
{
AuthenticationContext authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/<TENANT_ID>");
var resourceId = "https://management.azure.com";
var result = await authenticationContext.AcquireTokenAsync(resourceId, clientId, new UserPasswordCredential(userName, password));
return result.AccessToken;
}
This token then works for wiring up source control and continuous integration.

Unauthorized Error: Query or Save Contact/lead in Dynamics CRM Using C#

I am using the following web api to query data from Dynamics CRM
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
HttpClient client = new HttpClient(new HttpClientHandler() { Credentials = new NetworkCredential("xxxxx#mydomain.com", "!#Demo1$#2") });
client.BaseAddress = new Uri("https://xxxxx.crm.dynamics.com");
client.Timeout = new TimeSpan(0, 2, 0);
string contactAltUri = client.BaseAddress + "api/data/v9.0/accounts?$select=name&$top=3";
HttpResponseMessage createResponseAlt1 = await client.GetAsync(contactAltUri);
Now in object createResponseAlt1 I can see the following Unauthorized error. What is the right way to query and save data to Dynamics CRM? I have username, password and Ms crm subdomain url.
{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
x-ms-service-request-id: 4b323404-cc13-407c-bb6d-56b61823ab84
REQ_ID: 4b323404-cc13-407c-bb6d-56b61823ab84
AuthActivityId: 85696c96-6803-4664-9391-d28f45d1766a
NativeWebSession-Version: 2
Date: Thu, 07 Mar 2019 13:42:36 GMT
Set-Cookie: ApplicationGatewayAffinity=03da1c2a15fe28b54ffa99b7eab01d12cd9b55dfb1779b6cccce0809ec64f39a;Path=/;Domain=xxxxx.crm.dynamics.com
Server:
WWW-Authenticate: Bearer authorization_uri=https://login.microsoftonline.com/e20b0aa3-6ec3-4272-a76a-aaa32e0f10d6/oauth2/authorize, resource_id=https://xxxxx.crm.dynamics.com/
Content-Length: 0
}}
Step 1: Register an app in Azure Active directory (AAD) to get Application Id (aka Client Id). Read more
Step 2: Use the Client Id to get the authentication token using OAuth, as the CRM online needs to authenticate using AAD this is the approach we have to follow. Read more
Code sample:
static string serviceUri = "https://yourorg.crmx.dynamics.com/";
static string redirectUrl = "https://yourorg.api.crmx.dynamics.com/api/data/v9.0/";
public static string GetAuthToken()
{
// TODO Substitute your app registration values that can be obtained after you
// register the app in Active Directory on the Microsoft Azure portal.
string clientId = "3oi467rf-2336-4039-b82i-7c5b859c7be0"; // Client ID after app registration
string userName = "xyz#youOrg.onmicrosoft.com";
string password = "Password";
UserCredential cred = new UserCredential(userName, password);
// Authenticate the registered application with Azure Active Directory.
AuthenticationContext authContext = new AuthenticationContext("https://login.windows.net/common", false);
AuthenticationResult result = authContext.AcquireToken(serviceUri, clientId, cred);
return result.AccessToken;
}
public static void RetrieveAccounts(string authToken)
{
HttpClient httpClient = null;
httpClient = new HttpClient();
//Default Request Headers needed to be added in the HttpClient Object
httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Set the Authorization header with the Access Token received specifying the Credentials
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
httpClient.BaseAddress = new Uri(redirectUrl);
var response = httpClient.GetAsync("accounts?$select=name").Result;
if (response.IsSuccessStatusCode)
{
var accounts = response.Content.ReadAsStringAsync().Result;
}
}
Reference

Postman Generate Valid Token, C# Web Client Token from Newtonsoft JToken doesnt work

I'm trying to get the token from OAuth 2.0 Server and then pass to REST API with Authorization header.
It gets the token from the server and upon API call, it says the Token is Invalid.
Using token generated from Postman, and calling rest API from postman by that token works fine. Even if I try to paste the token generated by C# Client in Postman, Postman Rest API informed token is invalid.
I'm using .NET Core 2.1 and generating token like this.
var values = new Dictionary<string, string> {
{ "resource", baseUrl.Value.Replace("/commerce", "").Trim() },
{ "client_id", Startup.Configuration["clientId"] },
{ "grant_type", "client_credentials" },
{ "client_secret", Startup.Configuration["clientSecret"] }
};
var content = new FormUrlEncodedContent(values);
HttpClient client = new HttpClient();
var responseString = await client.PostAsync(tenantUrl.Value + "/oauth2/token", content);
string responseBody = await responseString.Content.ReadAsStringAsync();
JObject resJson = JObject.Parse(responseBody);
string token = resJson["access_token"].ToString();
Passing the Token to REST API by the following code.
When I insert token generated from POSTMAN, the REST API returns fine. There is some problem in C# Web Client Token Generation.
client = new HttpClient();
var tt = new StringContent(requestBody, Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var ounHeader = request.Headers.Where(m => m.Key == "OUN").FirstOrDefault();
client.DefaultRequestHeaders.Add(ounHeader.Key, ounHeader.Value.First());
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var rsActionResponse = await client.PostAsync(oDataRequest, tt);
responseBody = await rsActionResponse.Content.ReadAsStringAsync();
JObject rsActionResponseJSON = JObject.Parse(responseBody);
Token from Postman (Working) :
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayIsImtpZCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayJ9.eyJhdWQiOiJodHRwczovL3ZzaWZhc2hpb251cDgyNWM4Y2Y0OTQ3YzBkOTZmcmV0LmNsb3VkYXguZHluYW1pY3MuY29tLyIsImlzcyI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0Lzc1NjY4ZjM2LTY1ZDMtNGQ5MC1hOTkwLTRiYjhlYzE2NzI4Zi8iLCJpYXQiOjE1MjU0MjUyMjUsIm5iZiI6MTUyNTQyNTIyNSwiZXhwIjoxNTI1NDI5MTI1LCJhaW8iOiJZMmRnWUZBNEUvTXRraStSZWZISHZZeHE1NktlQVFBPSIsImFwcGlkIjoiNDZmMTRlMTgtNDA5MS00ODRiLTk0NzYtNjM3ODc4NDE5MWQxIiwiYXBwaWRhY3IiOiIxIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvNzU2NjhmMzYtNjVkMy00ZDkwLWE5OTAtNGJiOGVjMTY3MjhmLyIsIm9pZCI6ImJjODVmMGYzLTc5NjgtNDk4Ni1hMDMzLTViZTBjZDFiN2Y1NiIsInN1YiI6ImJjODVmMGYzLTc5NjgtNDk4Ni1hMDMzLTViZTBjZDFiN2Y1NiIsInRpZCI6Ijc1NjY4ZjM2LTY1ZDMtNGQ5MC1hOTkwLTRiYjhlYzE2NzI4ZiIsInV0aSI6InVnVkljaVNNaEV5YUhoaWpERFFHQUEiLCJ2ZXIiOiIxLjAifQ.fWJiUaOmPuRD21EcuGnUBWCNYl0TaUZ7OxIFQXHYmMvnivHiys5j9UjL3ZBRSZAVzrjrBS-v-0xyyzT_502NEkM0H77vnaxXFB2lrZz1GRcXr5oFSW4gfDTvPinByLr5LBglxZG6_PCP4oqChKQgigxR7xBjok1XXQOD2_h-gYZbmnDNexjzkxZAl4kqTCfSfoRJxUZxX1pgD5PRAAkx1eanc1jiJ4KQA6kvnSDL0PyGGmmQe36RrMKH5bclH3sMLO3wilgvLMp3ekKDj51P2emW9dhDx2BrblowpCcLGe3Q6PikuZrYOkx44WqJKAS6QovJwijxCEq9XKAzDEZbig
Token from C# Web Client (Not Working)
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayIsImtpZCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayJ9.eyJhdWQiOiJodHRwczovL3ZzaWZhc2hpb251cDgyNWM4Y2Y0OTQ3YzBkOTZmcmV0LmNsb3VkYXguZHluYW1pY3MuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvNzU2NjhmMzYtNjVkMy00ZDkwLWE5OTAtNGJiOGVjMTY3MjhmLyIsImlhdCI6MTUyNTQyNTA2MiwibmJmIjoxNTI1NDI1MDYyLCJleHAiOjE1MjU0Mjg5NjIsImFpbyI6IlkyZGdZUGhvZjdKaHI5YU9xZjBxVmJyTTk0NitBZ0E9IiwiYXBwaWQiOiI0NmYxNGUxOC00MDkxLTQ4NGItOTQ3Ni02Mzc4Nzg0MTkxZDEiLCJhcHBpZGFjciI6IjEiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83NTY2OGYzNi02NWQzLTRkOTAtYTk5MC00YmI4ZWMxNjcyOGYvIiwib2lkIjoiYmM4NWYwZjMtNzk2OC00OTg2LWEwMzMtNWJlMGNkMWI3ZjU2Iiwic3ViIjoiYmM4NWYwZjMtNzk2OC00OTg2LWEwMzMtNWJlMGNkMWI3ZjU2IiwidGlkIjoiNzU2NjhmMzYtNjVkMy00ZDkwLWE5OTAtNGJiOGVjMTY3MjhmIiwidXRpIjoidWh1T3dmaE41RS1uOTZWSXVWc0VBQSIsInZlciI6IjEuMCJ9.Bulbv3HZyufQevjMPI-OU5_0NCrtFXPU9PIxXHriWLg_Mj_uUtoFHVslUtNH4FTwMEq2lTheE87N5jDkTra3Z-aTOQhsj3sz-6wqA4HrDGpbPyHaGscFfHkitpUyzV4_HfLaVA4vWAHbwKEIqs1gaVp-81m3oMka0OmDh1Jjgg-Lvcr-TMOkdP1qsgSdcRmqVWwmjYTp7HSPS997poC54md_Bdx7hFRwEA7WNmCdSCLZ44izgFHb3ou47r3agXprERYDBo6Vi6ofSp4zAsvYdsxoFrM6LiOwWSKbqilYotgDKjUQpA7u41iwy6fjgV1wBerOJUBWCWN8w3Vs4cmhXA
Fixed:
Token was received from Url : www.xyz.com
URL was registered as www.xyz.com/
Need to call www.xyz.com/ to get the valid token and pass to other API methods.
There is difference between www.xyz.com/ and www.xyz.com in OAuth registered.

Trouble downloadig an thumbnail image in O365 Video using C#

I am running into an odd issue with trying to access a thumbnail image stored on O365 video via C#. I can access the REST API with no issue at all, I just add the Authentication: Bearer <token> to the header and I am off an running. The trouble is with a basic image URL that I get back from a specific video.
https://<mytenant>.sharepoint.com/portals/Channel1/pVid/myvideo.mp4.PNG?VideoPreview=1
When I access that URL from a browser it works 100% of the time. When I try to access it via the httpclient object, I am getting a 401 Unauthorized error.
The best I can figure is that the authorization header token is not being honored when accessing a basic URL. Which makes me thing that I need something else like a cookie? However I cannot seem to figure out which one. Looking for any advice :)
Pass credentials instead and yes you need an authentication cookie. Here is a sample:
private static async Task<string>getWebTitle(string webUrl)
{
//Creating Password
const string PWD = "softjam.1";
const string USER = "bubu#zsis376.onmicrosoft.com";
const string RESTURL = "{0}/_api/web?$select=Title";
//Creating Credentials
var passWord = new SecureString();
foreach (var c in PWD) passWord.AppendChar(c);
var credential = new SharePointOnlineCredentials(USER, passWord);
//Creating Handler to allows the client to use credentials and cookie
using (var handler = new HttpClientHandler() { Credentials = credential })
{
//Getting authentication cookies
Uri uri = new Uri(webUrl);
handler.CookieContainer.SetCookies(uri, credential.GetAuthenticationCookie(uri));
//Invoking REST API
using (var client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync(string.Format(RESTURL, webUrl)).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
string jsonData = await response.Content.ReadAsStringAsync();
return jsonData;
}
}
}

Azure Active Directory Graph API Authorization Failed to get Groups for user

First I used the following to implement role based access:
http://www.codeproject.com/Articles/749588/Role-Based-Access-Control-with-Azure-Active-Direct
It is working FINE with localhost url when I deployed I got following error:
The remote server returned an error: (401) Unauthorized.
[ActiveDirectoryAuthenticationException: AADSTS70002: Error validating credentials. AADSTS50012: Invalid client secret is provided.
Trace ID: b672d4b0-eccc-45b2-97e8-0f9c8c916aea
Correlation ID: dcb4bad8-e449-4919-832d-0791eef24570
To fix, I created a new application in Azure directory and give all permissions and updated web.config with the Client ID and Password of the app.
But still get the same error. So decided to do it manually:
used following code
string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value;
// Get a token for calling the Windows Azure Active Directory Graph
AuthenticationContext authContext = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, LoginUrl, tenantId));
ClientCredential credential = new ClientCredential(AppPrincipalId, AppKey);
AuthenticationResult assertionCredential = authContext.AcquireToken(GraphUrl, credential);
string authHeader = assertionCredential.CreateAuthorizationHeader();
and
To get all users
private static readonly string GraphAllUsersUrl = "https://graph.windows.net/{0}/users?api-version=2013-04-05";
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
GraphAllUsersUrl,
HttpUtility.UrlEncode(tenantId));
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
HttpResponseMessage response = await client.SendAsync(request);
string responseString = await response.Content.ReadAsStringAsync();
It is working fine.
Now to get all groups of a user:
private static readonly string UserGroupsUrl = "https://graph.windows.net/{0}/users/{1}/memberOf?api-version=2013-04-05";
string requestUrl2 = String.Format(
CultureInfo.InvariantCulture,
UserGroupsUrl,
HttpUtility.UrlEncode(tenantId),
HttpUtility.UrlEncode(ClaimsPrincipal.Current.Identity.Name)
);
HttpRequestMessage request2 = new HttpRequestMessage(HttpMethod.Get, requestUrl2);
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
HttpResponseMessage response2 = await client.SendAsync(request2);
string responseString2 = await response2.Content.ReadAsStringAsync();
it throws the same Authorization error.
On searching, I found to set something in manifest file, I did but no luck.

Categories