Performing Authenticated Requests in Etsy API - c#

I'm creating a proof of concept that leverages the Etsy API via C# along with Angular.
I'm currently having issues performing authenticated requests to the Etsy API and receive the following response when requesting the authenticated user's profile:
"No logged in user; please specify a user id instead"
Below is the Controller action that I am calling.
You'll notice that if __SELF__ is passed in as the userId that the authentication code is called. Otherwise that code is skipped.
I am able to retrieve user profile's when I pass in a valid user name/id.
[HttpGet("{userId}")]
public async Task<IActionResult> UserProfile(string userId)
{
string requestUrl = String.Format("https://openapi.etsy.com/v2/users/{0}/profile?api_key={1}", userId, AccountController.ConsumerKey);
string token = Request.Headers["Access_Token"];
string tokenSecret = Request.Headers["Access_Verifier"];
if (userId.Equals("__SELF__"))
{
var client = new OAuthRequest
{
Method = "GET",
Type = OAuthRequestType.ProtectedResource,
SignatureMethod = OAuthSignatureMethod.HmacSha1,
ConsumerKey = AccountController.ConsumerKey,
ConsumerSecret = AccountController.ConsumerSecret,
Token = token,
TokenSecret = tokenSecret,
RequestUrl = requestUrl,
};
requestUrl += "&" + client.GetAuthorizationQuery();
}
using (HttpClient client = new HttpClient())
using (HttpResponseMessage res = await client.GetAsync(requestUrl))
using (HttpContent content = res.Content)
{
string data = await content.ReadAsStringAsync();
if (data != null)
{
try
{
UserModel userInfo = JsonConvert.DeserializeObject<UserModel>(data);
if (userInfo != null && userInfo.Count == 1 && userInfo.Results != null && userInfo.Results.Count == 1)
{
return Ok(userInfo.Results[0]);
}
}
catch (Exception)
{
return NotFound(data);
}
}
return NotFound();
}
}
I'm assuming that I am leveraging the OAuth Specific code incorrectly here. I have tried using the token and verifier from the client as well as the token and token secret, but none appear to work.

I was able to resolve this by removing the api key from the request uri when a request is made with OAuth credentials.
string requestUrl = String.Format("https://openapi.etsy.com/v2/users/{0}/profile", userId);
if (userId.Equals("__SELF__"))
{
var client = new OAuthRequest
{
Method = "GET",
Type = OAuthRequestType.ProtectedResource,
SignatureMethod = OAuthSignatureMethod.HmacSha1,
ConsumerKey = AccountController.ConsumerKey,
ConsumerSecret = AccountController.ConsumerSecret,
Token = token,
TokenSecret = tokenSecret,
RequestUrl = requestUrl,
};
requestUrl += "&" + client.GetAuthorizationQuery();
} else {
requestUrl += String.Format("?api_key={0}", AccountController.ConsumerKey);
}

Related

How to get access token for integration test with external api

For an integration test I have an authorized .NET Core 2.2 Controller that is calling another authorized controller (different project) or external api (like Microsoft Graph).
Both apis are authenticated against the Azure AD. In all the controller actions we need the authenticated user.
We can get in the first api by getting a token based on the username and password (grant_type=password). When the call continues to the second api, it breaks because of an interactive login prompt (We use ADAL).
Normally, the user authenticates with open id connect, we then have the authentication code and get the accesstoken + refresh token with the authentication code. With the refresh token we can get an access token for the second api.
We created a small sample project with default Values Controllers to explain our problem.
Get access token before calling the first api with native app registration:
public static async Task<string> AcquireTokenAsync(string username, string password)
{
var aadInstance = "https://login.windows.net/{0}";
var tenantId = "put id here";
var authority = string.Format(aadInstance, tenantId);
var clientId = "clientid here";
var resource = "put resource here";
var client = new HttpClient();
var tokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/token";
var body = $"resource={resource}&client_id={clientId}&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((response) =>
{
return response.Result.Content.ReadAsStringAsync().Result;
});
JObject jobject = JObject.Parse(result);
var token = jobject["access_token"].Value<string>();
return token;
}
First API:
[Authorize]
[HttpGet]
public async Task<IActionResult> Get()
{
string name = User.Identity.Name;
var result = await AcquireTokenSilentWithImpersonationAsync();
string BaseUrl = "https://localhost:44356/";
var client = new HttpClient
{
BaseAddress = new Uri(BaseUrl)
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
var url = "api/values";
HttpResponseMessage response = await client.GetAsync(url);
switch (response.StatusCode)
{
case HttpStatusCode.OK:
int x = 1;
break;
default:
throw new HttpRequestException($"Error - {response.StatusCode} in response with message '{response.RequestMessage}'");
}
return Ok();
}
private const string BackendResource = "Second api resource here";
/// <summary>
/// For more information: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-devhowto-adal-error-handling
/// </summary>
/// <returns></returns>
public async Task<AuthenticationResult> AcquireTokenSilentWithImpersonationAsync()
{
const string ClientId = "client id of first api here";
const string ClientSecret = "secret of first api here";
ClientCredential credential = new ClientCredential(ClientId, ClientSecret);
string userObjectId = _httpContextAccessor.HttpContext.User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
var authContext = GetAuthenticationContext(userObjectId);
AuthenticationResult authResult = null;
try
{
authResult = await authContext.AcquireTokenSilentAsync(BackendResource, credential, new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
}
catch (AdalSilentTokenAcquisitionException ex)
{
// Exception: AdalSilentTokenAcquisitionException
// Caused when there are no tokens in the cache or a required refresh failed.
// Action: Case 1, resolvable with an interactive request.
try
{
authResult = await authContext.AcquireTokenAsync(BackendResource, ClientId, new Uri("https://backurl.org"), new PlatformParameters(), new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
}
catch (Exception exs)
{
throw;
}
}
catch (AdalException e)
{
// Exception: AdalException
// Represents a library exception generated by ADAL .NET.
// e.ErrorCode contains the error code.
// Action: Case 2, not resolvable with an interactive request.
// Attempt retry after a timed interval or user action.
// Example Error: network_not_available, default case.
throw;
}
return authResult;
}
Second api:
[Authorize]
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
string name = User.Identity.Name;
return new string[] { "value1", "value2" };
}
You need to use the On-behalf-of flow in your Web API (not the interactive token acquisition, need)
If you want to use ADAL.NET, a sample is there: https://github.com/azure-samples/active-directory-dotnet-webapi-onbehalfof
but I would now recommend you use MSAL.NET. the sample is: active-directory-dotnet-native-aspnetcore-v2/2. Web API now calls Microsoft Graph, and the documentation: https://aka.ms/msal-net-on-behalf-of
Also note that for Web APIs, we don't use OIDC (this is to sign-in users), but rather a JWT bearer middleware

How to send request to Twitter api using access token in ASP.NET core?

I'm working on a project which is receiving the access-token from the Front-end client and using that access token I have to make request to Twitter API in order to get the user details including email address and profile picture url.
in the case of Facebook it is just a normal get request , in the case of Google and Microsoft I just have to add access token as Bearer token in Header but, I'm not able to find a way for Twitter.
This is the url where I have to make request.
https://api.twitter.com/1.1/account/verify_credentials.json
Here is the code for Facebook , Google and Microsoft.
private async Task<Profile> ProfileAsync(string token,string providerName)
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if((providerName=="Google") || (providerName=="Microsoft"))
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
var formatters = new List<MediaTypeFormatter>()
{
new JsonMediaTypeFormatter()
};
string url;
Profile profile = null;
if (providerName=="Facebook")
{
url = $"https://graph.facebook.com/me?fields=id,name,email&access_token={token}";
}
else if(providerName=="Google")
{
url = $"https://www.googleapis.com/userinfo/v2/me";
}
else if(providerName=="Microsoft")
{
url = $"https://graph.microsoft.com/v1.0/me/";
}
else
{
throw new Exception("Unsupported grant type.");
}
HttpResponseMessage response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
profile = await response.Content.ReadAsAsync<Profile>(formatters);
}
if(providerName=="Microsoft")
{
profile.email = profile.userPrincipalName;
profile.name = profile.displayName;
}
return profile;
}
}
In Twitter you should have the access token & access token secret. then you could call the verification API:
https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true
Its easier to use any Twitter library for this verification, for example:
https://github.com/CoreTweet/CoreTweet
or any .NET library for Twitter.
For example:
Tokens tokens = new Tokens()
{
AccessToken = "xxx",
AccessTokenSecret = "xxx",
ConsumerKey = "xxx",
ConsumerSecret = "xxx",
};
IDictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("include_email", "true");
var response = tokens.Account.VerifyCredentials(dict); // will throw exception if not authorized
Console.WriteLine(response.ScreenName + " " + response.Email + " " + response.Id);
Also you could try it from postman:

PowerBI and Azure AD Headless Login

I am trying to embed PowerBI dashboards into my customer MVC portal. My customers don't have AAD accounts, so they can't login to Live when they come to the website, they log into my MVC website with individual authority.
I have registered my App on PowerBI/AAD and have the ClientID and Secret. I make the call to AAD and get an Authorization Code which I then use to get an Athentication Token which the is returned successfully.
When ever I use the access token to get a dashboard it is continually rejected with a 403 Forbidden.
I have gone through all the samples from Microsoft, but they require a user login prompt. I have reviewed the ADAL2.0 code which refers to the AcquireToken Method, but this was deprecated in ADAL3 and replaced with AcquireTokenAsync which has different parameters and I am using this in my example below.
Here is the function to get the token:
protected AuthenticationResult GetAccessToken()
{
string pBiUser = Properties.Settings.Default.PowerBIUser;
string pBiPwd = Properties.Settings.Default.PowerBIPwd;
string pBiClientId = Properties.Settings.Default.PowerBIClientId;
string pBiSecret = Properties.Settings.Default.PowerBIClientSecret;
TokenCache TC = new TokenCache();
ClientCredential CC = new ClientCredential(pBiClientId,pBiSecret);
string AU = Properties.Settings.Default.PowerBIAuthority;
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authenticationContext
= new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(AU, TC);
AuthenticationResult result = authenticationContext.AcquireTokenAsync("https://analysis.windows.net/powerbi/api"
,CC).Result;
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the PowerBI token");
}
return result;
}
I then take the result token and call. The response receives the 403:
protected PBIDashboards GetDashboards(AuthenticationResult authResult)
{
PBIDashboards pbiDashboards = new PBIDashboards();
var baseAddress = new Uri("https://api.powerbi.com");
using (var httpClient = new System.Net.Http.HttpClient {BaseAddress = baseAddress})
{
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("authorization",
"Bearer " + authResult.AccessToken);
using (**var response** = httpClient.GetAsync("v1.0/myorg/dashboards").Result)
{
string responseData = response.Content.ReadAsStringAsync().Result;
//Deserialize JSON string
pbiDashboards = JsonConvert.DeserializeObject<PBIDashboards>(responseData);
if (pbiDashboards != null)
{
var gridViewDashboards = pbiDashboards.value.Select(dashboard => new
{
Id = dashboard.id,
DisplayName = dashboard.displayName,
EmbedUrl = dashboard.embedUrl
});
}
}
}
return pbiDashboards;
}
Based on the error message(403), the issue is relative to the permission.
And AFAIK the is no such permission we can use when we acquire the access token using the client credentials flow for the Power BI REST. You can refer the permission for the figure below:
To get the token for the Power BI REST without user interaction, we can use the Resource owner password credentials flow. And you can use the 3rd party library PowerBI.Api.Client which already implement this.
After a lot of research, you can make a direct AJAX call to get the token:
private async Task<string> GetAccessToken()
{
string pBiUser = Properties.Settings.Default.PowerBIUser;
string pBiPwd = Properties.Settings.Default.PowerBIPwd;
string pBiClientId = Properties.Settings.Default.PowerBIClientId;
string pBiSecret = Properties.Settings.Default.PowerBIClientSecret;
string pBITenant = Properties.Settings.Default.PowerBITenantId;
string tokenEndpointUri = "https://login.microsoftonline.com/"+pBITenant+"/oauth2/token";
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", pBiUser),
new KeyValuePair<string, string>("password", pBiPwd),
new KeyValuePair<string, string>("client_id", pBiClientId),
new KeyValuePair<string, string>("client_secret", pBiSecret),
new KeyValuePair<string, string>("resource", "https://analysis.windows.net/powerbi/api")
});
using (var client = new HttpClient())
{
HttpResponseMessage res = client.PostAsync(tokenEndpointUri, content).Result;
string json = await res.Content.ReadAsStringAsync();
AzureAdTokenResponse tokenRes = JsonConvert.DeserializeObject<AzureAdTokenResponse>(json);
return tokenRes.AccessToken;
}
}
Once you have the string AccessToken, you can then call the Dashboards request.
protected PBIDashboards GetDashboards(string token)
{
PBIDashboards pbiDashboards = new PBIDashboards();
var baseAddress = new Uri("https://api.powerbi.com");
using (var httpClient = new System.Net.Http.HttpClient {BaseAddress = baseAddress})
{
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("authorization",
"Bearer " + token);
using (var response = httpClient.GetAsync("v1.0/myorg/dashboards").Result)
{
string responseData = response.Content.ReadAsStringAsync().Result;
//Deserialize JSON string
pbiDashboards = JsonConvert.DeserializeObject<PBIDashboards>(responseData);
if (pbiDashboards != null)
{
var gridViewDashboards = pbiDashboards.value.Select(dashboard => new
{
Id = dashboard.id,
DisplayName = dashboard.displayName,
EmbedUrl = dashboard.embedUrl
});
}
}
}
return pbiDashboards;
}
This will provide you the list of dashboards and the dashboard Id to call the PowerBI API to build the embeded page in Javascript. I used hidden input fields to store the access token and embed URL to pass over to the Javascript call.
// check if the embed url was selected
var embedUrl = document.getElementById('embed').value;
if (embedUrl === "")
return;
// get the access token.
accessToken = document.getElementById('token').value;
// Embed configuration used to describe the what and how to embed.
// This object is used when calling powerbi.embed.
// You can find more information at https://github.com/Microsoft/PowerBI-JavaScript/wiki/Embed-Configuration-Details.
var config = {
type: 'dashboard',
accessToken: accessToken,
embedUrl: embedUrl
};
// Grab the reference to the div HTML element that will host the dashboard.
var dashboardContainer = document.getElementById('dashboard');
// Embed the dashboard and display it within the div container.
var dashboard = powerbi.embed(dashboardContainer, config);

How to prevent passing id and password in url for a get call in web api C#?

I have written a code to fetch details from active directory as a GET call based on user id and password. I am passing user id and password in the url like -http://localhost:1234/api/User/IsAuthorized/UserID=1234;password=qwerty
but this is an unsafe technique. Can anybody give me a solution to pass these values in the body and use it as a POST call instead of a get call
my code goes like-
[Route("IsAuthorized/UserID={userName};password={password}")]
[AllowAnonymous]
public IHttpActionResult GetIsAuthorized(string userName,string password)
{
HttpResponseMessage response = null;
string errorMessage = null;
bool hasError = false;
bool isValid;
UserDetails detail = null;
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "ABC"))
{
isValid = pc.ValidateCredentials(userName, password);
string token = null;
if (isValid)
{
detail = IsAuthenticated("abc", userName, password, out errorMessage, out hasError);
}
if (hasError)
{
detail = new UserDetails(isValid, userName, null, null, null, errorMessage, null);
}
else
{
if (detail != null)
{
token = CreateToken(userName);
detail = new UserDetails(isValid, userName, detail.AssociateName, detail.Mobile, detail.Email, null, token);
}
else
detail = new UserDetails(isValid, userName, null, null, null, "unknown username or bad password", null);
}
return Ok(detail);
}
}
Why are you even doing it like this? You must be calling you web api from a client. Why are you not using the HttpClient to pass your credentials to your api. Something like this:
public async Task<TResult> PostAsync<TResult, TInput>(string uriString, TInput payload = null) where TInput : class
{
var uri = new Uri(uriString);
using (var client = GetHttpClient())
{
var jsonContent = JsonConvert.SerializeObject(payload, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
HttpResponseMessage response = await client.PostAsync(uri, new StringContent(jsonContent, Encoding.UTF8, "application/json"));
if (response.StatusCode != HttpStatusCode.OK)
{
//Log.Error(response.ReasonPhrase);
return default(TResult);
}
var json = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TResult>(json);
}
}
private HttpClient GetHttpClient()
{
var client = new HttpClient();
var username = //get username
var password = // get password
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}")));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}

ADAL .Net Core nuget package does not support UserPasswordCredential

In ADAL.Net 3.x UserPasswordCredential is introduced on top of UserCredential from 2.x. But the same UserPasswordCredential is not exposed in the .Net Core under the same nuget package?
UserCredential class has only one property UserName
namespace Microsoft.IdentityModel.Clients.ActiveDirectory
{
//
// Summary:
// Credential used for integrated authentication on domain-joined machines.
public class UserCredential
{
//
// Summary:
// Constructor to create user credential. Using this constructor would imply integrated
// authentication with logged in user and it can only be used in domain joined scenarios.
public UserCredential();
//
// Summary:
// Constructor to create credential with client id and secret
//
// Parameters:
// userName:
// Identifier of the user application requests token on behalf.
public UserCredential(string userName);
//
// Summary:
// Gets identifier of the user.
public string UserName { get; }
}
}
Since UserPasswordCredential is not available in .NetCore and UserCredential takes only one parameter username, how to input the password of the user and implement below code in .Net Core?
authContext.AcquireTokenAsync(WebAPIResourceId, ClientId, userPasswordCredential);
I am using ADAL 3.13.4 version specifically in .Net Core 1.0 version
To use the resource owner password credentials grant flow to get the access token for Azure AD, we can call the http request diectly using the HttpClient. Here is an example for your reference :
HttpClient client = new HttpClient();
string tokenEndpoint = "https://login.microsoftonline.com/{tenantId}/oauth2/token";
var body = "resource={resourceUrl}&client_id={clientId}&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) =>
{
return response.Result.Content.ReadAsStringAsync().Result;
});
JObject jobject = JObject.Parse(result);
var token = jobject["access_token"].Value<string>();
You are correct, UserPasswordCredential is not available for .NET Core, and UserCredential no longer accepts username and password. This means ADAL v3 does not support the username/password flow on .NET Core.
Below is what i have been doing to get around this problem. I replicated the same behaviour in a static method for use in .NET Core, since the UserPasswordCredential class is missing. This is based on fiddler traces of what happens when the UserPasswordCredential class is used in the .NET version. Since the .NET DLL seems to be obfuscated, this is a best attempt at capturing what it does.
public const string Saml11Bearer = "urn:ietf:params:oauth:grant-type:saml1_1-bearer";
public const string Saml20Bearer = "urn:ietf:params:oauth:grant-type:saml2-bearer";
public const string JwtBearer = "urn:ietf:params:oauth:grant-type:jwt-bearer";
/// <summary>
/// Acquire an AAD authentication token silently for an AAD App (Native) with an AAD account
///
/// NOTE: This process was ported from the Microsoft.IdentityModel.Clients.ActiveDirectory's
/// AuthenticationContext.AcquireTokenAsync method, which can silently authenticate using the UserPasswordCredential class.
/// Since this class is missing from .NET Core, this method can be used to perform the same without any dependencies.
/// </summary>
/// <param name="user">AAD login</param>
/// <param name="pass">AAD pass</param>
/// <param name="tenantId">Tenant ID</param>
/// <param name="resourceUrl">Resource ID: the Azure app that will be accessed</param>
/// <param name="clientId">The Application ID of the calling app. This guid can be obtained from Azure Portal > app auth setup > Advanced Settings</param>
public static string GetAuthTokenForAADNativeApp(string user, SecureString pass, string tenantId, string resourceUrl, string clientId)
{
string tokenForUser = string.Empty;
string authority = "https://login.microsoftonline.com/" + tenantId; // The AD Authority used for login
string clientRequestID = Guid.NewGuid().ToString();
// Discover the preferred openid / oauth2 endpoint for the tenant (by authority)
string api = "https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=" + authority + "/oauth2/authorize";
string openIdPreferredNetwork = string.Empty;
var client = new HttpClient();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("client-request-id", clientRequestID);
client.DefaultRequestHeaders.Add("return-client-request-id", "true");
client.DefaultRequestHeaders.Add("Accept", "application/json");
var responseTask = client.GetAsync(api);
responseTask.Wait();
if (responseTask.Result.Content != null)
{
var responseString = responseTask.Result.Content.ReadAsStringAsync();
responseString.Wait();
try
{
dynamic json = JObject.Parse(responseString.Result);
openIdPreferredNetwork = json.metadata[0].preferred_network; // e.g. login.microsoftonline.com
}
catch { }
}
if (string.IsNullOrEmpty(openIdPreferredNetwork))
openIdPreferredNetwork = "login.microsoftonline.com";
// Get the federation metadata url & federation active auth url by user realm (by user domain)
responseTask = client.GetAsync("https://" + openIdPreferredNetwork + "/common/userrealm/" + user + "?api-version=1.0");
responseTask.Wait();
string federation_metadata_url = string.Empty;
string federation_active_auth_url = string.Empty;
if (responseTask.Result.Content != null)
{
var responseString = responseTask.Result.Content.ReadAsStringAsync();
responseString.Wait();
try
{
dynamic json = JObject.Parse(responseString.Result);
federation_metadata_url = json.federation_metadata_url; // e.g. https://sts.{domain}.com.au/adfs/services/trust/mex
federation_active_auth_url = json.federation_active_auth_url; // e.g. https://sts.{domain}.com.au/adfs/services/trust/2005/usernamemixed
}
catch { }
}
if(string.IsNullOrEmpty(federation_metadata_url) || string.IsNullOrEmpty(federation_active_auth_url))
return string.Empty;
// Get federation metadata
responseTask = client.GetAsync(federation_metadata_url);
responseTask.Wait();
string federationMetadataXml = null;
if (responseTask.Result.Content != null)
{
var responseString = responseTask.Result.Content.ReadAsStringAsync();
responseString.Wait();
try
{
federationMetadataXml = responseString.Result;
}
catch { }
}
if (string.IsNullOrEmpty(federationMetadataXml))
return string.Empty;
// Post credential to the federation active auth URL
string messageId = Guid.NewGuid().ToString("D").ToLower();
string postData = #"
<s:Envelope xmlns:s='http://www.w3.org/2003/05/soap-envelope' xmlns:a='http://www.w3.org/2005/08/addressing' xmlns:u='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'>
<s:Header>
<a:Action s:mustUnderstand='1'>http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
<a:MessageID>urn:uuid:" + messageId + #"</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand='1'>" + federation_active_auth_url + #"</a:To>
<o:Security s:mustUnderstand='1' xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'>
<u:Timestamp u:Id='_0'>
<u:Created>" + DateTime.Now.ToString("o") + #"</u:Created>
<u:Expires>" + DateTime.Now.AddMinutes(10).ToString("o") + #"</u:Expires>
</u:Timestamp>
<o:UsernameToken u:Id='uuid-" + Guid.NewGuid().ToString("D").ToLower() + #"'>
<o:Username>" + user + #"</o:Username>
<o:Password>" + FromSecureString(pass) + #"</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body>
<trust:RequestSecurityToken xmlns:trust='http://schemas.xmlsoap.org/ws/2005/02/trust'>
<wsp:AppliesTo xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy'>
<a:EndpointReference>
<a:Address>urn:federation:MicrosoftOnline</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<trust:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</trust:KeyType>
<trust:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</trust:RequestType>
</trust:RequestSecurityToken>
</s:Body>
</s:Envelope>";
var content = new StringContent(postData, Encoding.UTF8, "application/soap+xml");
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("SOAPAction", "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue");
client.DefaultRequestHeaders.Add("client-request-id", clientRequestID);
client.DefaultRequestHeaders.Add("return-client-request-id", "true");
client.DefaultRequestHeaders.Add("Accept", "application/json");
responseTask = client.PostAsync(federation_active_auth_url, content);
responseTask.Wait();
XmlDocument xml = new XmlDocument();
string assertion = string.Empty;
string grant_type = string.Empty;
if (responseTask.Result.Content != null)
{
HttpResponseMessage rseponse = responseTask.Result;
Task<string> responseContentTask = rseponse.Content.ReadAsStringAsync();
responseContentTask.Wait();
try { xml.LoadXml(responseContentTask.Result); }
catch { }
var nodeList = xml.GetElementsByTagName("saml:Assertion");
if (nodeList.Count > 0)
{
assertion = nodeList[0].OuterXml;
// The grant type depends on the assertion value returned previously <saml:Assertion MajorVersion="1" MinorVersion="1"...>
grant_type = Saml11Bearer;
string majorVersion = nodeList[0].Attributes["MajorVersion"] != null ? nodeList[0].Attributes["MajorVersion"].Value : string.Empty;
if (majorVersion == "1")
grant_type = Saml11Bearer;
if (majorVersion == "2")
grant_type = Saml20Bearer;
else
grant_type = Saml11Bearer; // Default to Saml11Bearer
}
}
// Post to obtain an oauth2 token to for the resource
// (*) Pass in the assertion XML node encoded to base64 in the post, as is done here https://blogs.msdn.microsoft.com/azuredev/2018/01/22/accessing-the-power-bi-apis-in-a-federated-azure-ad-setup/
UserAssertion ua = new UserAssertion(assertion, grant_type, Uri.EscapeDataString(user));
UTF8Encoding encoding = new UTF8Encoding();
Byte[] byteSource = encoding.GetBytes(ua.Assertion);
string base64ua = Uri.EscapeDataString(Convert.ToBase64String(byteSource));
postData = "resource={resourceUrl}&client_id={clientId}&grant_type={grantType}&assertion={assertion}&scope=openid"
.Replace("{resourceUrl}", Uri.EscapeDataString(resourceUrl))
.Replace("{clientId}", Uri.EscapeDataString(clientId))
.Replace("{grantType}", Uri.EscapeDataString(grant_type))
.Replace("{assertion}", base64ua);
content = new StringContent(postData, Encoding.UTF8, "application/x-www-form-urlencoded");
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("client-request-id", clientRequestID);
client.DefaultRequestHeaders.Add("return-client-request-id", "true");
client.DefaultRequestHeaders.Add("Accept", "application/json");
responseTask = client.PostAsync("https://" + openIdPreferredNetwork + "/common/oauth2/token", content);
responseTask.Wait();
if (responseTask.Result.Content != null)
{
var responseString = responseTask.Result.Content.ReadAsStringAsync();
responseString.Wait();
try
{
dynamic json = JObject.Parse(responseString.Result);
tokenForUser = json.access_token;
}
catch { }
}
if (string.IsNullOrEmpty(federationMetadataXml))
return string.Empty;
return tokenForUser;
}
private static string FromSecureString(SecureString value)
{
string stringBSTR;
IntPtr bSTR = Marshal.SecureStringToBSTR(value);
if (bSTR == IntPtr.Zero)
{
return string.Empty;
}
try
{
stringBSTR = Marshal.PtrToStringBSTR(bSTR);
}
finally
{
Marshal.FreeBSTR(bSTR);
}
return stringBSTR;
}
Fast forward to 2020, with ADAL 3.19.8, you should be able to use the ClientCredential class for AAD authentication. It is working for me when integrating with a D365 CRM web API. I documented my experience in the following blog post. Hope you will find it useful.

Categories

Resources