I am trying to upload a PDF file to the file field in the CRM entity. I followed the following document: https://learn.microsoft.com/en-us/powerapps/developer/data-platform/file-attributes#upload-file-data
Implemented the code of OAuth to generate an Access token using a Client ID and Client Secret. But I am getting a Bad Request as a response.
Entity name: msnfp_request
File field name: bna_file
var fileStream = System.IO.File.OpenRead(<file path>);
var url = new Uri("https://mydev.crm.dynamics.com/api/data/v9.1/msnfp_request(a528f300-7b53-ec11-8c62-0022482a2e7a)/bna_file);
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/<domain>");
ClientCredential credential = new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(<client id>,<client secret>);
AuthenticationResult result = authContext.AcquireToken("https://mydev.crm.dynamics.com/", credential);
using (var req = new HttpRequestMessage(new HttpMethod("PATCH"), url))
{
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
req.Content = new StreamContent(fileStream);
req.Content.Headers.Add("Content-Type", "application/octet-stream");
req.Content.Headers.Add("x-ms-file-name", "test.pdf");
HttpClient Client = new HttpClient();
using (var response = await Client.SendAsync(req))
{
response.EnsureSuccessStatusCode();
}
}
What am I doing wrong in the above code?
I will troubleshoot to see if you are able to find out issues step by step.
Make sure the authentication is working after passing the token, as you are not getting 401 it should be fine. But you can do a simple GET request to cross check the connectivity is working
The plural entity name could be a problem. You can find the status code text to see any inner exception like "The resource could not be found". Pls try in case entity plural name is msnfp_requests then it should be like https://mydev.crm.dynamics.com/api/data/v9.1/msnfp_requests(a528f300-7b53-ec11-8c62-0022482a2e7a)/bna_file (wild guess :))
I have two web APIs applications developed in .Net core. I need to import Json data from the second application to the first. However,I have a security issue. I need to secure the access to the external API. How should I securely manage the connection between these two APIs.
For example, I need to secure the access to the URL in the code bellow => securely access to the covid API without another authentication.
PS: I'm using JWT token authentication in both applications
Best regards.
using (var client = new HttpClient())
{
string url = string.Format("https://covid19.mathdro.id/api");
var response = client.GetAsync(url).Result;
string responseAsString = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<CovidResult>(responseAsString);
}
If both APIs are protected by the same accessToken, then you can read the authorization header from the first request and pass it to the second request.
Something like this to read the header:
var authHeader = context.Request.Headers.Get("Authorization");
You should end up with authHeader equal to "Bearer ey...(a bunch of base64)"
Then add the auth header to the client:
var request = new HttpRequestMessage() {
RequestUri = new Uri("http://https://covid19.mathdro.id/api"),
Method = HttpMethod.Get,
};
...
request.Headers.Authorization.Add(new AuthenticationHeaderValue(authHeader));
var task = client.SendAsync(request)
I have an async method that uses HttpClient:
private static HttpClient client = new HttpClient(); //As pointed by #maccettura
private async Task<string> GetResult(Uri url, string user, string pass)
{
var PassArray = new UTF8Encoding().GetBytes(user + ":" + pass);
client.BaseAddress = url;
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(_passArray));
string result;
using (HttpResponseMessage response = await client.GetAsync(url))
using (HttpContent content = response.Content)
{
result = await content.ReadAsStringAsync();
}
return result;
}
That I had to change to synchronous WebClient
private string GetResult(Uri url, string user, string pass)
{
using (var client = new WebClient())
{
client.UseDefaultCredentials = true;
client.Credentials = new NetworkCredential(user, pass);
using (var stream = client.OpenRead(url))
using (var streamReader = new StreamReader(stream, Encoding.UTF8, true))
{
return streamReader.ReadToEnd();
}
}
}
Am I compromising security by sending plain username and password? If so, is there a way to increase the security? (the url is a Https address)
In both cases you send credentials in "plain text". In both cases they are converted to base-64 before sending, but that does not make it any more secure. The only difference is that in second (WebClient) case web client will first make request without credentials. Then it will get 401 Unauthorized response and after that it will make second request with the exact same Authorization Basic <base64_here> header, so it's kind of less efficient than applying that header right away. But again both cases send exactly the same Authorization header. As already said - if you make request to https endpoint, your credentials should be safe against interception by third party, no need to implement your own encryption if you are already using encrypted channel.
Yes you are compromising by sending plain text username and password. There can be network sniffers that can pick up the packages you send accross http and read them. If user name and passwords are plain then uh-oh. Sniffers usually sniff in public places like libraries and coffee shops.
I am trying to make a httprequest to a web that is authenticated with ADFS of a private company.
I am able to login and get the token of my App. I am sure I am doing it correct due to I can get the contacts of my O365.After getting the token I try to make a request to the web. As I already have the token, I try to include it in the header of the request. The answer that I receive from the web is always the html with the login web not the result that I am requesting. As additional information I have added a "Connected Service" O365 API from VisualStudio.
This is my code:
public static async Task<string> GetAnswer(string wwweb)
{
var token = await GetAccessToken();
using (var client = new HttpClient())
{
var url = wwweb;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
//client.DefaultRequestHeaders.ProxyAuthorization= new AuthenticationHeaderValue("Bearer", token);
// client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetStringAsync(url);
return response;
}
}
I have tried with the 3 lines ( 2 commented and one not) without success, always giving back company's login web. Am I doing something wrong?
Thanks
PROBLEM:
I'm trying to make external authentication work with default Web API template project. I used the following instructions to add support for external authentication services (FB/Google/Microsoft): http://www.asp.net/web-api/overview/security/external-authentication-services
Just for the record, I was able to make external authentication work with default SPA template project.
Also, new local user creation works fine.
The problem appeared once I tried to use my client app (WPF-based) to register user using external provider (e.g., FB).
For the record, I used these two articles as the starting point: http://leastprivilege.com/2013/11/26/dissecting-the-web-api-individual-accounts-templatepart-3-external-accounts/ and thread #21065648 here at Stack Overflow.
They really helped me to understand the entire logic.
here's the short overview of the steps I've done:
two windows, main and the one for external authentication provider, with embedded WebBrowser
the flow:
2.1. user opens an app, main window appears
2.2. user clicks on a button to get the list of all supported external authentication providers,
and here's what happens in the code:
var client = new HttpClient();
client.BaseAddress = new Uri(baseAddress);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage loginResponse = await client.GetAsync("api/Account/ExternalLogins?returnUrl=%2F&generateState=true");
if (loginResponse.IsSuccessStatusCode)
{
var externalLoginProviders = await loginResponse.Content.ReadAsAsync<IEnumerable<AuthenticationProvider>>();
// cleaning resources
client.Dispose();
loginResponse.Dispose();
// obtained data is sent to UI
This leads to the list of External Authentication Providers, with name, url, and state. URL is relative, and is used as the redirect to the actual provider (e.g., FB, or Google).
2.3. As soon as the list with external authentication providers is populated, user can enter his/her email, and click on the "Login" button, which will lead to the following:
In second window, with embedded WebBrowser, the latter is Navigated to the provided URL obtained in the previous step. If user logs successfully into the selected provider (e.g., Facebook), and agrees to give my app (Facebook app) necessary permissions (profile), user is navigated back to our website, in the form:
(our base url) /#access_token=XXX&token_type=bearer&expires=YYY
This address is then parsed and address params (token, etc.) are saved in the structured form for later use.
Right after that I go to api/UserInfo to understand if user already logged on with this external authentication provider or not:
var client = new HttpClient();
client.BaseAddress = new Uri(baseAddress);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// setting Bearer Token obtained from Auth provider
client.SetBearerToken(result.AccessToken);
// calling /api/Account/UserInfo
var userInfoResponse = await client.GetAsync("api/Account/UserInfo");
var userInfoMessage = userInfoResponse.Content.ReadAsStringAsync().Result;
var userInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<UserInfo>(userInfoMessage);
// cleaning resources
client.Dispose();
userInfoResponse.Dispose();
if (userInfo.hasRegistered == true)
{
// going to login
}
Right after that, assuming that our user didn't create a user based on his/her login with the given external authentication provider (e.g., FB), yet, we do the following:
var data = new Dictionary<string, string>();
data.Add("Email", this.externalUserEmailTextBox.Text);
var registerExternalUrl = new Uri(string.Concat(baseAddress, #"api/Account/RegisterExternal"));
var client = new HttpClient();
client.BaseAddress = new Uri(baseAddress);
// setting Bearer Token obtained from External Authentication provider
client.SetBearerToken(result.AccessToken);
var response = client.PostAsync(registerExternalUrl.ToString(), new FormUrlEncodedContent(data)).Result;
At this point, user should be created (I have zero changes on the service side apart of uncommenting provider's app id and app secret lines in Startup.Auth.cs).
Unfortunately, this doesn't happen at all. Instead, I get "Internal Server Error" which means that this line,
var info = await Authentication.GetExternalLoginInfoAsync();
brings null, and so system fails to Authentication correctly with the provided Bearer Token.
I can't understand why. It seems that something is broken in the framework, or I'm doing something wrong...
SOLUTION:
Thanks to #berhir, here's the solution:
In MainWindow.xaml.cs, define cookieContainer
CookieContainer cookieContainer;
In MainWindow_Loaded event handler, instantiate it:
// we create new cookie container
cookieContainer = new CookieContainer();
As soon as user asks app to show all external login providers, instantiate HttpClient using suggested code by #berhir:
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
{
// send request
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage loginResponse = await client.GetAsync("api/Account/ExternalLogins?returnUrl=%2F&generateState=true");
As soon as list of external auth providers is obtained, the next step is to show second window, with embedded WebBrowser. There, you'll have to declare two WinAPI calls for cookies:
[DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool InternetSetCookie(string lpszUrlName, string lbszCookieName, string lpszCookieData);
[DllImport("wininet.dll", SetLastError = true)]
public static extern bool InternetGetCookieEx(string url, string cookieName, StringBuilder cookieData, ref int size, Int32 dwFlags, IntPtr lpReserved);
// and
private const Int32 InternetCookieHttponly = 0x2000;
/// <summary>
/// Gets the URI cookie container.
/// </summary>
/// <param name="uri">The URI.</param>
/// <returns></returns>
public static CookieContainer GetUriCookieContainer(Uri uri)
{
CookieContainer cookies = null;
// Determine the size of the cookie
int datasize = 8192 * 16;
StringBuilder cookieData = new StringBuilder(datasize);
if (!InternetGetCookieEx(uri.ToString(), null, cookieData, ref datasize, InternetCookieHttponly, IntPtr.Zero))
{
if (datasize < 0)
return null;
// Allocate stringbuilder large enough to hold the cookie
cookieData = new StringBuilder(datasize);
if (!InternetGetCookieEx(uri.ToString(), null, cookieData, ref datasize, InternetCookieHttponly, IntPtr.Zero))
return null;
}
if (cookieData.Length > 0)
{
cookies = new CookieContainer();
cookies.SetCookies(uri, cookieData.ToString().Replace(';', ','));
}
return cookies;
}
This window with embedded browser is instantiated, and constructor accepts several params, including start URL (to external auth provider), end URL (resulting with "#access_token=..." or "error...", callback, and, even more important, with the original cookieContainer. We use InternetSetCookie WinAPI method to pass that original cookieContainer to WebBrowser's session:
// set cookies
var cookies = cookieContainer.GetCookies(baseAddress).OfType<Cookie>().ToList();
foreach (var cookie in cookies)
{
InternetSetCookie(startUrl, cookie.Name, cookie.Value);
}
So, once user succeeds with signing into the selected external auth provider (e.g., Facebook), updated cookieContainer (which includes cookies set in the first HttpClient call, as well as right within WebBrowser right after signing in to external auth provider) obtained using InternetGetCookieEx WinAPI call, is sent back to MainWindow.xaml.cs via callback:
if (this.callback != null)
{
var cookies = GetUriCookieContainer(e.Uri);
this.callback(new AuthResult(e.Uri, this.providerName, cookies));
}
There, we issue two new requests, to api/Account/UserInfo, and then to api/Account/RegisterExternal:
this.cookieContainer = result.CookieContainer; // where result is AuthResult containing the cookies obtained from WebBrowser's session
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
{
// send request
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// setting Bearer Token obtained from Auth provider
client.SetBearerToken(result.AccessToken);
// calling /api/Account/UserInfo
var userInfoResponse = await client.GetAsync("api/Account/UserInfo");
var userInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<UserInfo>(userInfoMessage);
if (userInfo.hasRegistered == false)
{
var data = new Dictionary<string, string>();
data.Add("Email", this.externalUserEmailTextBox.Text);
var registerExternalUrl = new Uri(string.Concat(baseAddress, #"api/Account/RegisterExternal"));
var content = new FormUrlEncodedContent(data);
var response = client.PostAsync(registerExternalUrl.ToString(), content).Result;
// obtaining content
var responseContent = response.Content.ReadAsStringAsync().Result;
if (response != null && response.IsSuccessStatusCode)
{
MessageBox.Show("New user registered, with " + result.ProviderName + " account");
}
So, here we go. Cookies are used within the whole lifecycle, from first HttpClient request to the final moment when we register a new user using api/account/registerExternal.
It seems that you forgot to handle the cookies. The easiest way is to use the same HttpClient instance for all your requests or you can use the same CookieContainer.
var cookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
{
// send request
}