I am using Botframework adaptive dialog template (c#). I already obtained a token from a HttpRequest and saved it as a conversation state property conversation.token, now I am trying to use this token to make another API call with HttpRequest. But from the official document of HttpRequest Class, it seems there is no options to add the authentication token. I tried to add the token in the Headers, but did not work, it showed 401 Unauthorized error. How should the authorization be handled in HttpRequest in adaptive dialog?
new HttpRequest()
{
Url = "http://example.com/json",
ResultProperty = "conversation.httpResponse",
Method = HttpRequest.HttpMethod.GET,
ResponseType = HttpRequest.ResponseTypes.Json,
Headers = new Dictionary<string, AdaptiveExpressions.Properties.StringExpression>()
{
{"Authorization", "Bearer ${conversation.token.content.token}"},
},
},
new SendActivity("${conversation.httpResponse}"),
Instead of using HttpRequest, I made the API call inside CodeAction with custom code.
First make a POST request to get the token, then make a GET request to call the main API. In the GET request, the authorization can be added in this way: client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);.
new CodeAction(async (dc, options) =>
{
var my_jsondata = new
{
Username = "username",
Password = "password"
};
var json = JsonConvert.SerializeObject(my_jsondata);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var Tokenurl = "https://example.com/token?HTTP/1.1";
using var Tokenclient = new HttpClient();
var Tokenresponse = await Tokenclient.PostAsync(Tokenurl, data);
string Toeknresult = Tokenresponse.Content.ReadAsStringAsync().Result;
var Tokenjo = JObject.Parse(Tokenresult);
using var client = new HttpClient();
var url = "https://example.com/mainapi?HTTP/1.1";
var accessToken = Tokenjo["token"];
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
var response = await client.GetAsync(url);
string result = response.Content.ReadAsStringAsync().Result;
dc.State.SetValue("conversation.httpresponse", response);
dc.State.SetValue("conversation.result", result);
return await dc.EndDialogAsync();
}),
Related
I'm trying to implement a rest client in c# .net core that needs to first do Basic Authentication, then leverage a Bearer token in subsequent requests.
When I try to do Basic Authentication in combination with client.PostAsync with a FormUrlEncodedContent object, I'm getting an exception:
System.InvalidOperationException occurred in System.Net.Http.dll: 'Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.'
//setup reusable http client
HttpClient client = new HttpClient();
Uri baseUri = new Uri(url);
client.BaseAddress = baseUri;
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.ConnectionClose = true;
//Post body content
var values = new List<KeyValuePair<string,string>>();
values.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
var content = new FormUrlEncodedContent(values);
//Basic Authentication
var authenticationString = $"{clientId}:{clientSecret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(authenticationString));
content.Headers.Add("Authorization", $"Basic {base64EncodedAuthenticationString}");
//make the request
var task = client.PostAsync("/oauth2/token",content);
var response = task.Result;
response.EnsureSuccessStatusCode();
string responseBody = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(responseBody);
Exception has occurred: CLR/System.InvalidOperationException
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Net.Http.dll: 'Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.'
at System.Net.Http.Headers.HttpHeaders.GetHeaderDescriptor(String name)
at System.Net.Http.Headers.HttpHeaders.Add(String name, String value)
It looks like you can't use PostAsync and have access to mess with the Headers for authentication. I had to use an HttpRequestMessage and SendAsync.
//setup reusable http client
HttpClient client = new HttpClient();
Uri baseUri = new Uri(url);
client.BaseAddress = baseUri;
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.ConnectionClose = true;
//Post body content
var values = new List<KeyValuePair<string, string>>();
values.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
var content = new FormUrlEncodedContent(values);
var authenticationString = $"{clientId}:{clientSecret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(authenticationString));
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/oauth2/token");
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", base64EncodedAuthenticationString);
requestMessage.Content = content;
//make the request
var task = client.SendAsync(requestMessage);
var response = task.Result;
response.EnsureSuccessStatusCode();
string responseBody = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(responseBody);
It's not a good practice to create HttpClients explicitly from your calling code.
Please use HttpClientFactory that simplifies a lot of things.
However, if you want to use basic authentication, just create an HttpRequestMessage and add the following header:
var request = new HttpRequestMessage(HttpMethod.Post, getPath)
{
Content = new FormUrlEncodedContent(values)
};
request.Headers.Authorization = new BasicAuthenticationHeaderValue("username", "password");
// other settings
If you decide to use a recommended IHttpClientFactory it's even simpler:
serviceCollection.AddHttpClient(c =>
{
c.BaseAddress = new Uri("your base url");
c.SetBasicAuthentication("username", "password");
})
Don't encode the whole authentication string - encode the "Username:Password" expression and append the result to the "Basic " prefix.
var authenticationString = $"{clientId}:{clientSecret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.UTF8.GetBytes(authenticationString));
content.Headers.Add("Authorization", "Basic " + base64EncodedAuthenticationString);
Also, consider using just ASCII encoding - the UTF8 may not be understood by the server unless you add a charset declaration to the header.
Wikipedia seems to cover this quite well.
The specific problem is this line (below)
content.Headers.Add("Authorization", $"Basic {base64EncodedAuthenticationString}");
This fails because HttpContent.Headers (System.Net.Http.Headers.HttpContentHeaders) is only for headers that are content-specific, such as Content-Type, Content-Length, and so on.
You've stated that you can't use DefaultRequestHeaders because you only need it for a single request - but you also can't use it with PostAsync - only SendAsync provided you construct the HttpRequestMessage yourself, as per your own answer and #NeilMoss' answer - but you could use an extension-method in future.
But for the benefit of other readers, another alternative is to add a new extension method based on the existing PostAsync, which is actually really simple (only 3 lines!):
public Task<HttpResponseMessage> PostAsync( this HttpClient httpClient, Uri requestUri, HttpContent content, String basicUserName, String basicPassword, String? challengeCharSet = null, CancellationToken cancellationToken = default )
{
if( basicUserName.IndexOf(':') > -1 ) throw new ArgumentException( message: "RFC 7617 states that usernames cannot contain colons.", paramName: nameof(basicUserName) );
HttpRequestMessage httpRequestMessage = new HttpRequestMessage( HttpMethod.Post, requestUri );
httpRequestMessage.Content = content;
//
Encoding encoding = Encoding.ASCII;
if( challengeCharSet != null )
{
try
{
encoding = Encoding.GetEncoding( challengeCharSet );
}
catch
{
encoding = Encoding.ASCII;
}
}
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(
scheme : "Basic",
parameter: Convert.ToBase64String( encoding.GetBytes( userName + ":" + password ) )
);
return SendAsync( httpRequestMessage, cancellationToken );
}
Usage:
HttpClient httpClient = ...
using( HttpResponseMessage response = await httpClient.PostAsync( uri, content, basicUserName: "AzureDiamond", basicPassword: "hunter2" ).ConfigureAwait(false) )
{
// ...
}
Just something to add that I struggled with, which I only experienced with Basic authentication endpoints. If you add Json as StringContent then it adds a charset=utf-8, this often return a BadRequest 400.
Here is the code I got to fix this: reference:
https://dzone.com/articles/httpclient-how-to-remove-charset-from-content-type
using (var client = new HttpClient())
using (var content = new StringContent(ParseJSON(data), Encoding.Default, "application/json"))
{
//Remove UTF-8 Charset causing BadRequest 400
content.Headers.ContentType.CharSet = "";
var clientId = "client";
var clientSecret = "secret";
var authenticationString = $"{clientId}:{clientSecret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.UTF8.GetBytes(authenticationString));
client.DefaultRequestHeaders.TryAddWithoutValidation(authHeader, authorization);
var response = await client.PostAsync(url, content);
return response;
}
I have resolve this by using below code, that serve my purpose also. Added Code for both Get/Post, this will help you. Moreover I have added one more Header key. So to pass extra data to header. Hope that will resolve your issue.
class Program {
private static readonly string Username = "test";
private static readonly string Password = "test#123";
static void Main(string[] args) {
var response = Login();
}
public static async Task Login()
{
var anotherKey ="test";
HttpClient httpClient = new HttpClient
{
BaseAddress = new Uri("https://google.com/")
};
httpClient.DefaultRequestHeaders.Add($"Authorization", $"Basic {Base64Encode($"{Username}:{Password}")}");
httpClient.DefaultRequestHeaders.Add($"anotherKey", $"{anotherKey}");
HttpResponseMessage httpResponseMessage = await httpClient.GetAsync("user/123").ConfigureAwait(false);
// For Get Method
var response= await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
// For Post Method
User user = new User (1,"ABC");
HttpResponseMessage httpResponseMessage = await httpClient.PostAsJsonAsync("/post", user).ConfigureAwait(false);
UserDetail userDetail = await httpResponseMessage.Content.ReadAsAsync<UserDetail>().ConfigureAwait(false);
}
}
Using .NET 6, I use the HttpClient.DefaultRequestHeaders.Authorization property to set the Authorization header.
// This example will send a signing request to the RightSignature API
var api = "https://api.rightsignature.com/public/v2/sending_requests";
// requestJson is the serialized JSON request body
var contentData = new StringContent(requestJson, Encoding.UTF8, "application/json");
// Instantiate client (for testing), use Microsoft's guidelines in production
var client = new HttpClient();
// Use basic auth, the token has already been converted to base64
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", tokenB64);
try
{
var response = await client.PostAsync(api, contentData);
}
...
Good luck!
Well Im new in Xamarin and I'm developing and App, the authentication is JWT based.
Im using a HttpClient and setting the AuthenticationHeaders but It always returns Unauthorized when I try it on Postman it Works but I can't make it work in my app.
Here is how im trying to do it:
var client = new HttpClient(new HttpClientHandler());
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("JWT", accessToken);
client.BaseAddress = new Uri(urlBase);
var url = string.Format("{0}{1}", servicePrefix, controller);
var response = await client.GetAsync(url);
Try something like this
using (var client = new HttpClient())
{
var uri = new Uri(string.Format($"{<yourURLString>}", string.Empty));
var jsonTransport = "";
var jsonPayload = new StringContent(jsonTransport, Encoding.UTF8, "application/json");
//client.DefaultRequestHeaders.Add("Content-type", "application/json");
client.DefaultRequestHeaders.Add("Authorization", "JWT " + accessToken);
var response = await client.PostAsync(uri, jsonPayload);
string responseContent = await response.Content.ReadAsStringAsync();
}
then deserialize the responseContent to your object using JsonConvert.DeserializeObject
Note: Below are code samples, edit to your own objects
SubscriptionResponse profileResponse = JsonConvert.DeserializeObject<SubscriptionResponse>(responseContent);
then if your method returns something, use the return statement. Something like this
return profileResponse.Data.Subscriptions;
If you're using a get, this could be a guide
var uri = new Uri(string.Format($"{<yourURLHere>}", string.Empty));
client.DefaultRequestHeaders.Add("Authorization", "JWT " + accessToken);
var httpResponse = await client.GetAsync(uri);
var responseContent = await httpResponse.Content.ReadAsStringAsync();
then deserialize your string response
Note: this is a sample - edit to your model (You may use PostMan to get the response format in JSON and model it in C#)
var UserDetailResponse = JsonConvert.DeserializeObject<UserDetail>(responseContent);
return UserDetailResponse;
I am trying to write a local console application which will swap an Azure Web App slot using the Azure REST API. Using the following code I get a 401 (Unauthorized) response:
public async Task Swap(string subscription, string resourceGroup, string site, string slot)
{
var client = new HttpClient();
var url =
$"https://management.azure.com/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{site}/applySlotConfig?api-version=2016-08-01";
var data = new {preserveVnet = true, targetSlot = slot};
var message = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Post,
Content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")
};
var response = await client.SendAsync(message);
Console.WriteLine(response.StatusCode);
}
I know I need to put in some kind of credentials but what I have found seems to apply to apps using Azure AD for authentication. This will be a publicly accessible web app with anonymous authentication.
Generally speaking you need to attach a Authorization header to the request with the Auth token. There are numerous ways of getting it, see this link or this.
This is how I managed to do it (using the provided links):
private async Task<string> GetAccessToken(string tenantName, string clientId, string clientSecret)
{
var authString = "https://login.microsoftonline.com/" + tenantName;
var resourceUrl = "https://management.azure.com/";
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;
}
And then in my previous method:
public async Task Swap(string subscription, string resourceGroup, string site, string slot)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await GetAccessToken("XXX", "XXX", "XXX"));
var url =
$"https://management.azure.com/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{site}/applySlotConfig?api-version=2016-08-01";
var data = new {preserveVnet = true, targetSlot = slot};
var message = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Post,
Content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")
};
var response = await client.SendAsync(message);
Console.WriteLine(response.StatusCode);
}
I've checking many forums but I can't make it work. I'm trying to authenticate with headers to an url that will return a JSON string if authentication were successful. In Postman I simply used Get method with username and password in header to get the JSON data. What changes do I need to make my following C# code achieve same thing? I think I even failed to add username and password into headers.
public async Task<string> LogMeIn(string username, string password)
{
var client = new HttpClient {
BaseAddress = new Uri("http://x.com")
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("grant_type","password"),
new KeyValuePair<string,string>("Username ", username),
new KeyValuePair<string,string>("Password", password)
});
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("/login", content); //should it be GetAsync?
var jsonResp = await response.Content.ReadAsStringAsync();
var jsonResult = JsonConvert.DeserializeObject<JsonResult>(jsonResp); //JsonResult = class for json
return jsonResult.token;
}
}
I have the following code:
...
AuthenticationHeaderValue authHeaders = new AuthenticationHeaderValue("OAuth2", Contract.AccessToken);
string result = await PostRequest.AuthenticatedGetData(fullUrl, null, authHeaders);
return result;
...
public static async Task<string> AuthenticatedGetData(string url, FormUrlEncodedContent data, AuthenticationHeaderValue authValue)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authValue.Parameter);
HttpResponseMessage response = await client.PostAsync(new Uri(url), data);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
return responseBody;
}
The response = await part just continues an ongoing loop and nothing happens. Any ideas what I am doing wrong?
The question really is, how do I send the following header:
Authorization: OAuth2 ACCESS_TOKEN
to an external web api
I struggled with this. I kept getting an error saying "invalid format" because I have a custom implementation and the Authorization header is validated against certain standards. Adding the header this way however worked:
var http = new HttpClient();
http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "key=XXX");
This line
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(authValue.Parameter);
Will produce this header value
Authorization: ACCESS_TOKEN
Where ACCESS_TOKEN is the value of authValue.Parameter. You want to assign the value you passed instead to get the required header
client.DefaultRequestHeaders.Authorization = authValue;
Will produce
Authorization: OAuth2 ACCESS_TOKEN
Had a similar issue when getting AuthenticationHeaderValue to work with my requests.
I was also using JWT JsonWebToken from GitHub.
I was able to get a token from the API, but was struggling to use it in other GETs and POSTs.
var jwt = JsonWebToken.Encode(token, APISECRET, JwtHashAlgorithm.HS256);
var tk = GetTokenFromApi(); // basically returns an encrypted string.
Manually using WebRequest:
Which worked fine.
request.ContentType = "application/json";
request.Method = "POST";
request.Headers.Set("Authorization", string.Format("Bearer {0}", tk));
When we switched to an HttpClient, and used the AuthenticationHeaderValue, could not figure out how to set it up correctly.After looking at the request string, i saw it added the "Authorization" for me. Played around with parameters, and this finally this worked.
var authenticationHeaderValue = new AuthenticationHeaderValue("Bearer", tk);
Maybe intresting for other people. Since I searched on this for a long time. But you have to save your cookies also and give it with your next request. First this is how i got my authentication code and hold my cookies in a static variable (in the first time i call this method I give an empty value to token).
public static CookieContainer CookieContainer;
public static async Task<string> Post<TRequest>( TRequest requestBody, string path, string token = "")
{
var baseUrl = new Uri($"urlFromApi");
CookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = CookieContainer })
using(var client = new HttpClient(handler){BaseAddress = baseUrl})
{
client.DefaultRequestHeaders.ConnectionClose = false;
if (!string.IsNullOrWhiteSpace(token))
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", $"{token}");
}
ServicePointManager.FindServicePoint(client.BaseAddress).ConnectionLeaseTimeout = 60 * 1000; //1 minute using (var content = new ByteArrayContent(GetByteData(requestBody)))
using (var content = new ByteArrayContent(GetByteData(requestBody)))
{
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync(String.Empty, content);
return await GetResponseContent(response);
}
}
}
After this if I do any request to the api I include the cookies (token is what you get from the first response as a result)
public static async Task Get(string path, string token = "")
{
var baseUrl = $"https://innoviris-ai.collibra.com/rest/2.0{path}";
using (var handler = new HttpClientHandler() { CookieContainer = CookieContainer })
using (var client = new HttpClient(handler) {BaseAddress = new Uri(baseUrl)})
{
client.DefaultRequestHeaders.ConnectionClose = false;
if (!string.IsNullOrWhiteSpace(token))
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", $"{token}");
}
ServicePointManager.FindServicePoint(client.BaseAddress).ConnectionLeaseTimeout = 60 * 1000; //1 minute
var response = await client.GetAsync(String.Empty);
return await GetResponseContent(response);
}
}
In your code you are doing this:
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", $"{token}");
I think the following should work the same manner without using string interpolation:
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
This is because the string interpolation is just generating a string with the token in it!