How to turn cURL call to an HttpRequest in C#/Xamarin [duplicate] - c#

I have an HttpClient that I am using for a REST API. However I am having trouble setting up the Authorization header. I need to set the header to the token I received from doing my OAuth request.
I saw some code for .NET that suggests the following,
httpClient.DefaultRequestHeaders.Authorization = new Credential(OAuth.token);
However the Credential class does that not exist in WinRT. Anyone have any ideas how to set the Authorization header?

So the way to do it is the following,
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "Your Oauth token");

request.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(
System.Text.ASCIIEncoding.ASCII.GetBytes(
$"{yourusername}:{yourpwd}")));

I look for a good way to deal with this issue and I am looking at the same question. Hopefully, this answer will be helping everyone who has the same problem likes me.
using (var client = new HttpClient())
{
var url = "https://www.theidentityhub.com/{tenant}/api/identity/v1";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
var response = await client.GetStringAsync(url);
// Parse JSON response.
....
}
reference from https://www.theidentityhub.com/hub/Documentation/CallTheIdentityHubApi

As it is a good practice to reuse the HttpClient instance, for performance and port exhaustion problems, and because none of the answers give this solution (and even leading you toward bad practices :( ), I put here a link towards the answer I made on a similar question :
https://stackoverflow.com/a/40707446/717372
Some sources on how to use HttpClient the right way:
https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
https://blogs.msdn.microsoft.com/alazarev/2017/12/29/disposable-finalizers-and-httpclient/

I suggest to you:
HttpClient.DefaultRequestHeaders.Add("Authorization", "Bearer <token>");
And then you can use it like that:
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
responseMessage = await response.Content.ReadAsAsync<ResponseMessage>();
}

In the case you want to send HttpClient request with Bearer Token, this code can be a good solution:
var requestMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
Content = new StringContent(".....", Encoding.UTF8, "application/json"),
RequestUri = new Uri(".....")
};
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "Your token");
var response = await _httpClient.SendAsync(requestMessage);

I was setting the bearer token
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
It was working in one endpoint, but not another. The issue was that I had lower case b on "bearer". After change now it works for both api's I'm hitting. Such an easy thing to miss if you aren't even considering it as one of the haystacks to look in for the needle.
Make sure to have "Bearer" - with capital.

Use Basic Authorization And Json Parameters.
using (HttpClient client = new HttpClient())
{
var request_json = "your json string";
var content = new StringContent(request_json, Encoding.UTF8, "application/json");
var authenticationBytes = Encoding.ASCII.GetBytes("YourUsername:YourPassword");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(authenticationBytes));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var result = await client.PostAsync("YourURL", content);
var result_string = await result.Content.ReadAsStringAsync();
}

For anyone finding this old thread now (2021), please look at this documentation about HttpClientFactory which is injectable and will also re-run on each request avoiding expired tokens which will make it useful for bearer tokens, generated clients, pooling etc.
TL;DR: Use HttpClientFactory and a DelegatingHandler which will act as middleware on all outgoing requests with your configured client.
This is how I add my bearer for Azure Identity (managed by Azure) but you can get the token however you want of course;
using Microsoft.Azure.Services.AppAuthentication;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public class BearerTokenHandler : DelegatingHandler
{
public BearerTokenHandler(AzureServiceTokenProvider tokenProvider, string resource)
{
TokenProvider = tokenProvider;
Resource = resource;
}
public AzureServiceTokenProvider TokenProvider { get; }
public string Resource { get; }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains("Authorization"))
{
// Fetch your token here
string token = await TokenProvider.GetAccessTokenAsync(Resource);
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
}
I configure my typed clients (generated with NSwag) like this in Startup;
var accessTokenProvider = new AzureServiceTokenProvider("<your-connection-string-for-access-token-provider>");
builder.Services.AddHttpClient<IOrdersClient, OrdersClient>().ConfigureHttpClient(async conf =>
{
conf.BaseAddress = new Uri("<your-api-base-url>");
}).AddHttpMessageHandler(() => new BearerTokenHandler(accessTokenProvider, "https://your-azure-tenant.onmicrosoft.com/api"));
Then you can inject your IOrdersClient wherever you like and all requests will have the bearer.

If you want to reuse the HttpClient, it is advised to not use the DefaultRequestHeaders as they are used to send with each request.
You could try this:
var requestMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
Content = new StringContent("...", Encoding.UTF8, "application/json"),
RequestUri = new Uri("...")
};
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes($"{user}:{password}")));
var response = await _httpClient.SendAsync(requestMessage);

To set basic authentication with C# HttpClient. The following code is working for me.
using (var client = new HttpClient())
{
var webUrl ="http://localhost/saleapi/api/";
var uri = "api/sales";
client.BaseAddress = new Uri(webUrl);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.ConnectionClose = true;
//Set Basic Auth
var user = "username";
var password = "password";
var base64String =Convert.ToBase64String( Encoding.ASCII.GetBytes($"{user}:{password}"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",base64String);
var result = await client.PostAsJsonAsync(uri, model);
return result;
}

This is how i have done it:
using (HttpClient httpClient = new HttpClient())
{
Dictionary<string, string> tokenDetails = null;
var messageDetails = new Message { Id = 4, Message1 = des };
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:3774/");
var login = new Dictionary<string, string>
{
{"grant_type", "password"},
{"username", "sa#role.com"},
{"password", "lopzwsx#23"},
};
var response = client.PostAsync("Token", new FormUrlEncodedContent(login)).Result;
if (response.IsSuccessStatusCode)
{
tokenDetails = JsonConvert.DeserializeObject<Dictionary<string, string>>(response.Content.ReadAsStringAsync().Result);
if (tokenDetails != null && tokenDetails.Any())
{
var tokenNo = tokenDetails.FirstOrDefault().Value;
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + tokenNo);
client.PostAsJsonAsync("api/menu", messageDetails)
.ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode());
}
}
}
This you-tube video help me out a lot. Please check it out.
https://www.youtube.com/watch?v=qCwnU06NV5Q

6 Years later but adding this in case it helps someone.
https://www.codeproject.com/Tips/996401/Authenticate-WebAPIs-with-Basic-and-Windows-Authen
var authenticationBytes = Encoding.ASCII.GetBytes("<username>:<password>");
using (HttpClient confClient = new HttpClient())
{
confClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(authenticationBytes));
confClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(Constants.MediaType));
HttpResponseMessage message = confClient.GetAsync("<service URI>").Result;
if (message.IsSuccessStatusCode)
{
var inter = message.Content.ReadAsStringAsync();
List<string> result = JsonConvert.DeserializeObject<List<string>>(inter.Result);
}
}

UTF8 Option
request.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(
System.Text.Encoding.UTF8.GetBytes(
$"{yourusername}:{yourpwd}")));

Using AuthenticationHeaderValue class of System.Net.Http assembly
public AuthenticationHeaderValue(
string scheme,
string parameter
)
we can set or update existing Authorization header for our httpclient like so:
httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", TokenResponse.AccessToken);

BaseWebApi.cs
public abstract class BaseWebApi
{
//Inject HttpClient from Ninject
private readonly HttpClient _httpClient;
public BaseWebApi(HttpClient httpclient)
{
_httpClient = httpClient;
}
public async Task<TOut> PostAsync<TOut>(string method, object param, Dictionary<string, string> headers, HttpMethod httpMethod)
{
//Set url
HttpResponseMessage response;
using (var request = new HttpRequestMessage(httpMethod, url))
{
AddBody(param, request);
AddHeaders(request, headers);
response = await _httpClient.SendAsync(request, cancellationToken);
}
if(response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync<TOut>();
}
//Exception handling
}
private void AddHeaders(HttpRequestMessage request, Dictionary<string, string> headers)
{
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (headers == null) return;
foreach (var header in headers)
{
request.Headers.Add(header.Key, header.Value);
}
}
private static void AddBody(object param, HttpRequestMessage request)
{
if (param != null)
{
var content = JsonConvert.SerializeObject(param);
request.Content = new StringContent(content);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
}
SubWebApi.cs
public sealed class SubWebApi : BaseWebApi
{
public SubWebApi(HttpClient httpClient) : base(httpClient) {}
public async Task<StuffResponse> GetStuffAsync(int cvr)
{
var method = "get/stuff";
var request = new StuffRequest
{
query = "GiveMeStuff"
}
return await PostAsync<StuffResponse>(method, request, GetHeaders(), HttpMethod.Post);
}
private Dictionary<string, string> GetHeaders()
{
var headers = new Dictionary<string, string>();
var basicAuth = GetBasicAuth();
headers.Add("Authorization", basicAuth);
return headers;
}
private string GetBasicAuth()
{
var byteArray = Encoding.ASCII.GetBytes($"{SystemSettings.Username}:{SystemSettings.Password}");
var authString = Convert.ToBase64String(byteArray);
return $"Basic {authString}";
}
}

In net .core you can use with Identity Server 4
var client = new HttpClient();
client.SetBasicAuthentication(userName, password);
or
var client = new HttpClient();
client.SetBearerToken(token);
see https://github.com/IdentityModel/IdentityModel/blob/main/src/Client/Extensions/AuthorizationHeaderExtensions.cs

this could works, if you are receiving a json or an xml from the service and i think this can give you an idea about how the headers and the T type works too, if you use the function MakeXmlRequest(put results in xmldocumnet) and MakeJsonRequest(put the json in the class you wish that have the same structure that the json response) in the next way
/*-------------------------example of use-------------*/
MakeXmlRequest<XmlDocument>("your_uri",result=>your_xmlDocument_variable = result,error=>your_exception_Var = error);
MakeJsonRequest<classwhateveryouwant>("your_uri",result=>your_classwhateveryouwant_variable=result,error=>your_exception_Var=error)
/*-------------------------------------------------------------------------------*/
public class RestService
{
public void MakeXmlRequest<T>(string uri, Action<XmlDocument> successAction, Action<Exception> errorAction)
{
XmlDocument XMLResponse = new XmlDocument();
string wufooAPIKey = ""; /*or username as well*/
string password = "";
StringBuilder url = new StringBuilder();
url.Append(uri);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url.ToString());
string authInfo = wufooAPIKey + ":" + password;
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
request.Timeout = 30000;
request.KeepAlive = false;
request.Headers["Authorization"] = "Basic " + authInfo;
string documento = "";
MakeRequest(request,response=> documento = response,
(error) =>
{
if (errorAction != null)
{
errorAction(error);
}
}
);
XMLResponse.LoadXml(documento);
successAction(XMLResponse);
}
public void MakeJsonRequest<T>(string uri, Action<T> successAction, Action<Exception> errorAction)
{
string wufooAPIKey = "";
string password = "";
StringBuilder url = new StringBuilder();
url.Append(uri);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url.ToString());
string authInfo = wufooAPIKey + ":" + password;
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
request.Timeout = 30000;
request.KeepAlive = false;
request.Headers["Authorization"] = "Basic " + authInfo;
// request.Accept = "application/json";
// request.Method = "GET";
MakeRequest(
request,
(response) =>
{
if (successAction != null)
{
T toReturn;
try
{
toReturn = Deserialize<T>(response);
}
catch (Exception ex)
{
errorAction(ex);
return;
}
successAction(toReturn);
}
},
(error) =>
{
if (errorAction != null)
{
errorAction(error);
}
}
);
}
private void MakeRequest(HttpWebRequest request, Action<string> successAction, Action<Exception> errorAction)
{
try{
using (var webResponse = (HttpWebResponse)request.GetResponse())
{
using (var reader = new StreamReader(webResponse.GetResponseStream()))
{
var objText = reader.ReadToEnd();
successAction(objText);
}
}
}catch(HttpException ex){
errorAction(ex);
}
}
private T Deserialize<T>(string responseBody)
{
try
{
var toReturns = JsonConvert.DeserializeObject<T>(responseBody);
return toReturns;
}
catch (Exception ex)
{
string errores;
errores = ex.Message;
}
var toReturn = JsonConvert.DeserializeObject<T>(responseBody);
return toReturn;
}
}
}

It may be easier to use an existing library.
For example, the extension methods below are added with Identity Server 4
https://www.nuget.org/packages/IdentityModel/
public static void SetBasicAuthentication(this HttpClient client, string userName, string password);
//
// Summary:
// Sets a basic authentication header.
//
// Parameters:
// request:
// The HTTP request message.
//
// userName:
// Name of the user.
//
// password:
// The password.
public static void SetBasicAuthentication(this HttpRequestMessage request, string userName, string password);
//
// Summary:
// Sets a basic authentication header for RFC6749 client authentication.
//
// Parameters:
// client:
// The client.
//
// userName:
// Name of the user.
//
// password:
// The password.
public static void SetBasicAuthenticationOAuth(this HttpClient client, string userName, string password);
//
// Summary:
// Sets a basic authentication header for RFC6749 client authentication.
//
// Parameters:
// request:
// The HTTP request message.
//
// userName:
// Name of the user.
//
// password:
// The password.
public static void SetBasicAuthenticationOAuth(this HttpRequestMessage request, string userName, string password);
//
// Summary:
// Sets an authorization header with a bearer token.
//
// Parameters:
// client:
// The client.
//
// token:
// The token.
public static void SetBearerToken(this HttpClient client, string token);
//
// Summary:
// Sets an authorization header with a bearer token.
//
// Parameters:
// request:
// The HTTP request message.
//
// token:
// The token.
public static void SetBearerToken(this HttpRequestMessage request, string token);
//
// Summary:
// Sets an authorization header with a given scheme and value.
//
// Parameters:
// client:
// The client.
//
// scheme:
// The scheme.
//
// token:
// The token.
public static void SetToken(this HttpClient client, string scheme, string token);
//
// Summary:
// Sets an authorization header with a given scheme and value.
//
// Parameters:
// request:
// The HTTP request message.
//
// scheme:
// The scheme.
//
// token:
// The token.
public static void SetToken(this HttpRequestMessage request, string scheme, string token);

Firstly, I wouldn't use HttpClient directly. It's too easy to make mistakes - particularly in the area of headers. The DefaultHeadersCollection is not immutable and not thread-safe because other parts of the app can change the headers on you. It's best to set the headers when you make the call. If you are working with an abstraction, and that is recommended because the classes in this area are a bit of a mess, you would want to have a headers collection and put those on your HttpRequestMessage before you send it. You need to make sure you put the content headers on the content, and not the message.
Code Reference
foreach (var headerName in request.Headers.Names)
{
//"Content-Type"
if (string.Compare(headerName, HeadersExtensions.ContentTypeHeaderName, StringComparison.OrdinalIgnoreCase) == 0)
{
//Note: not sure why this is necessary...
//The HttpClient class seems to differentiate between content headers and request message headers, but this distinction doesn't exist in the real world...
//TODO: Other Content headers
httpContent?.Headers.Add(HeadersExtensions.ContentTypeHeaderName, request.Headers[headerName]);
}
else
{
httpRequestMessage.Headers.Add(headerName, request.Headers[headerName]);
}
}
Here is a data structure that you could use to send the request which includes the headers.
Code Reference
public interface IRequest
{
CancellationToken CancellationToken { get; }
string? CustomHttpRequestMethod { get; }
IHeadersCollection Headers { get; }
HttpRequestMethod HttpRequestMethod { get; }
AbsoluteUrl Uri { get; }
}
public interface IRequest<TBody> : IRequest
{
TBody? BodyData { get; }
}
And, a headers collection:
Code Reference
public sealed class HeadersCollection : IHeadersCollection
{
#region Fields
private readonly IDictionary<string, IEnumerable<string>> dictionary;
#endregion
#region Public Constructors
public HeadersCollection(IDictionary<string, IEnumerable<string>> dictionary) => this.dictionary = dictionary;
public HeadersCollection(string key, string value) : this(ImmutableDictionary.CreateRange(
new List<KeyValuePair<string, IEnumerable<string>>>
{
new(key, ImmutableList.Create(value))
}
))
{
}
#endregion Public Constructors
#region Public Properties
public static HeadersCollection Empty { get; } = new HeadersCollection(ImmutableDictionary.Create<string, IEnumerable<string>>());
public IEnumerable<string> Names => dictionary.Keys;
IEnumerable<string> IHeadersCollection.this[string name] => dictionary[name];
#endregion Public Properties
#region Public Methods
public bool Contains(string name) => dictionary.ContainsKey(name);
public IEnumerator<KeyValuePair<string, IEnumerable<string>>> GetEnumerator() => dictionary.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => dictionary.GetEnumerator();
public override string ToString() => string.Join("\r\n", dictionary.Select(kvp => $"{kvp.Key}: {string.Join(", ", kvp.Value)}\r\n"));
#endregion
}
See all the working code and examples here.

You can too to use the follow exemple, that it use IHttpClientFactory:
readonly IHttpClientFactory _httpClientFactory;
public HTTPClientHelper(IHttpClientFactory httpClientFactory, string clientName = null)
{
this._httpClientFactory = httpClientFactory;
}
public Task<T> GetAsync(string url, string token) {
var client = _httpClientFactory.CreateClient(_clientName);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, token);
using (HttpResponseMessage response = await _client.GetAsync(url)){
......
}
}

I came across this old thread. The problem I had was that I know to use a static HttpClient, but my token needs refreshing every 59 minutes.
So I could have used HttpClientFactory, but because one of my projects was still in .NET 4.8, I created a class that inherited from HttpClient so I have similar code in all projects. A secret is needed to be able to get the token (I'm using identityserver4).
I then set that as a singleton in DI (I'm using Ninject here):
Bind<MyHttpClient>().ToMethod(c =>
{
var accessKey = ConfigurationManager.AppSettings["AccessKey"];
var client = new MyHttpClient(accessKey)
{
BaseAddress = new Uri(MyUrls.MyApiBaseUrl)
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
return client;
}).InSingletonScope();
Then the class itself - named after the API it is used to access:
public class MyHttpClient : BaseHttpClient
{
private static readonly HttpClient _authHttpClient = new HttpClient();
private string _secret;
public MyHttpClient(string secret)
{
_secret = secret;
}
/// <summary>
/// Add the token to each and every request, cached for 1 minute less than the token's lifetime
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var cacheSeconds = 3600 - 60; // Default of 59 minutes
var token = CacheHelper<string>.Get("MyToken", cacheSeconds * 60, () =>
{
var authorityUrl = MyUrls.AuthServerUrl;
// discover endpoints from metadata
DiscoveryDocumentResponse disco;
disco = _authHttpClient.GetDiscoveryDocumentAsync(authorityUrl).Result;
if (disco.IsError)
{
throw new Exception("Error getting discovery document: " + disco.Error);
}
// request token
var tokenResponse = _authHttpClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "myapp",
ClientSecret = _secret,
Scope = "myapi"
}).Result;
if (tokenResponse.IsError)
{
throw new Exception("Error getting token: " + tokenResponse.Error);
}
if (tokenResponse.ExpiresIn < cacheSeconds + 60)
{
throw new Exception($"Token expires in {tokenResponse.ExpiresIn}s, which is less than {cacheSeconds + 60}");
}
if (tokenResponse.ExpiresIn > cacheSeconds + 60)
{
Log.Warn().Message($"Token expiry in {tokenResponse.ExpiresIn}s, which is greater than {cacheSeconds}").Write();
}
return tokenResponse.AccessToken;
});
// THIS IS THE BIT - Assign this inside a SendAsync override and you are done!
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return base.SendAsync(request, cancellationToken);
}
}
Finally just for completeness, my CacheHelper class looks like this:
public static class CacheHelper<T>
{
private static readonly object _locker = new object();
public static T Get(string cacheName, int cacheTimeoutSeconds, Func<T> func)
{
var obj = MemoryCache.Default.Get(cacheName, null);
if (obj != null) return (T)obj;
lock (_locker)
{
obj = MemoryCache.Default.Get(cacheName, null);
if (obj == null)
{
obj = func();
var cip = new CacheItemPolicy
{
AbsoluteExpiration = new DateTimeOffset(DateTime.UtcNow.AddSeconds(cacheTimeoutSeconds))
};
MemoryCache.Default.Set(cacheName, obj, cip);
}
}
return (T)obj;
}
}

Oauth Process flow is complex and there is always a room for one error or another.
My suggestion will be to always use the boilerplate code and a set of libraries for OAuth authentication flow.It will make your life easier.
Here is the link for the set of libraries.OAuth Libraries for .Net

If you are using Visual Studio IISExpress debug mode and connecting to the HTTP port rather than the HTTPS port you may find that the auth headers are being dropped.
Switch to the SLL connection and they will appear again.
unsure why, possibly the setup redirects the http traffic and that causes the auth to be removed.

This may help Setting the header:
WebClient client = new WebClient();
string authInfo = this.credentials.UserName + ":" + this.credentials.Password;
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
client.Headers["Authorization"] = "Basic " + authInfo;

static async Task<AccessToken> GetToken()
{
string clientId = "XXX";
string clientSecret = "YYY";
string credentials = String.Format("{0}:{1}", clientId, clientSecret);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes(credentials)));
List<KeyValuePair<string, string>> requestData = new List<KeyValuePair<string, string>>();
requestData.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
FormUrlEncodedContent requestBody = new FormUrlEncodedContent(requestData);
var request = await client.PostAsync("https://accounts.spotify.com/api/token", requestBody);
var response = await request.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<AccessToken>(response);
}
}

Related

HttpMessaheHandler hit 2 times instead 1 when do another HttpClient call from it

I did research and didn't find anything related to that. Please, I hope someone can help me.
What do I have?
I have HttpMessaheHandler that is inherited from DelegatingHandler
What do I do?
I'm trying to handle OAuth2 logic inside that HttpMessaheHandler
So I do another request inside HttpMessaheHandler from freshly created HttpClient
What is the problem?
The problem is that HttpResponseMessage is hit 2 times! It happens on line 70
HttpResponseMessage identityResponse = await this._client.SendAsync(requestMessage);
When this line is executed then we are coming back into HttpResponseMessage and starting this logic from the beginning.
What I have already tried?
I have tried to use ASP.NET Core native functionality services.AddHttpClient(...) the same issue.
What do I expect?
I do expect that my freshly created HttpClient does not hit this HttpMessageHandler as it is not bound to this HttpClient
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Common.Core.Abstractions;
using Common.Data;
using Common.Data.Enums;
using Common.Services.Shared;
using Hrimsoft.StringCases;
using Integrations.ECommerce.Storm.Configuration;
using Integrations.ECommerce.Storm.Models;
using Integrations.ECommerce.Storm.Models.Requests;
using Integrations.ECommerce.Storm.Services;
using Microsoft.Extensions.DependencyInjection;
using UrlCombineLib;
namespace Integrations.ECommerce.Storm
{
internal class StormIdentityHttpMessageHandler : DelegatingHandler
{
private readonly RequestOptions _requestOptions;
private readonly IContextProvider _contextProvider;
private readonly IStormIdentityService _stormIdentityService;
private readonly StormSettings _settings;
private readonly IServiceProvider _serviceProvider;
private readonly string _authorizationHeader = "Authorization";
private readonly string _applicationId = "ApplicationId";
private HttpClient _client;
private IJsonSerializer _jsonSerializer;
public StormIdentityHttpMessageHandler(
IContextProvider contextProvider,
IStormIdentityService stormIdentityService,
StormSettings settings,
IServiceProvider serviceProvider)
{
this._contextProvider = contextProvider;
this._stormIdentityService = stormIdentityService;
this._settings = settings;
this._serviceProvider = serviceProvider;
this._requestOptions = new RequestOptions { ContentType = RequestContentType.FormUrlEncoded, PropertyCaseType = PropertyNameCaseType.SnakeCase };
this._client = new HttpClient { BaseAddress = new Uri(this._settings.IdentityServerUrl) };
this._jsonSerializer = serviceProvider.GetRequiredService<IJsonSerializer>();
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string culture = this._contextProvider.GetCulture();
var parameters = new StormOAuth2AuthenticationParameters
{
GrantType = this._settings.GrantType, ClientId = this._settings.ClientId, ClientSecret = this._settings.ClientSecret, Scope = this._settings.Scope
};
HttpRequestMessage requestMessage = this.Post(string.Empty, parameters, SetRequestOptions);
HttpResponseMessage identityResponse = await this._client.SendAsync(requestMessage);
StormAccessToken token = await RetrieveAccessToken(identityResponse);
// // Handle OAuth2. Requesting/Getting the access token.
// IStormIdentityService identityService = this._stormIdentityService;
// string accessToken = await identityService.RequestAccessToken();
//
// // Setting the AccessToken token to the outgoing request
// request.Headers.Add(this._applicationId, this._settings[culture].ApplicationId);
// request.Headers.Add(this._authorizationHeader, $"Bearer {accessToken}");
// HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
//
// if (response.StatusCode == HttpStatusCode.Unauthorized)
// {
// bool forceNewToken = true;
// accessToken = await identityService.RequestAccessToken(forceNewToken);
// request.Headers.Remove(this._authorizationHeader);
// request.Headers.Add(this._authorizationHeader, $"Bearer {accessToken}");
// response = await base.SendAsync(request, cancellationToken);
//
// if (response.StatusCode == HttpStatusCode.Unauthorized)
// {
// throw new ServiceException($"Can't authorize with current token. Token value:\n{accessToken}");
// }
// }
return response;
}
private async Task<StormAccessToken> RetrieveAccessToken(HttpResponseMessage httpResponse)
{
if (httpResponse.Content == null)
{
return default;
}
string content = await httpResponse.Content.ReadAsStringAsync();
if (string.IsNullOrEmpty(content))
{
return default;
}
return JsonSerializer.Deserialize<StormAccessToken>(content);
}
protected virtual async Task<Exception> OnNegativeResponse(HttpResponseMessage response)
{
HttpRequestMessage request = response.RequestMessage;
string details = null;
if (response.Content != null)
{
details = await response.Content.ReadAsStringAsync();
}
string message = $"REST request '{request.Method} - {request.RequestUri}' " +
$"error: {(int)response.StatusCode}, Message: {response.ReasonPhrase}{Environment.NewLine}{details}";
var exception = new Exception(message);
return exception;
}
protected HttpRequestMessage Post(string url = "", object data = null, Action<RequestOptions> setup = null)
=> BuildRequest(HttpMethod.Post, url, data, setup);
protected virtual string BuildResourceUrl(string baseUrl, string resource)
=> UrlCombine.Combine(baseUrl, resource);
private HttpRequestMessage BuildRequest(
HttpMethod method,
string resourceBase,
object body = null,
Action<RequestOptions> setup = null)
{
string fullUrl = BuildResourceUrl(this._client.BaseAddress.ToString(), resourceBase);
var request = new HttpRequestMessage(method, fullUrl);
if (body == null)
{
return request;
}
var options = new RequestOptions();
setup?.Invoke(options);
if (options.ContentType == RequestContentType.FormUrlEncoded)
{
Dictionary<string, string> content = GetFormContent(body, options);
request.Content = new FormUrlEncodedContent(content);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
}
else
{
throw new Exception($"Unsupported content type {options.ContentType}");
}
return request;
}
private Dictionary<string, string> GetFormContent(object data, RequestOptions options)
{
var pairs = data
.GetType()
.GetProperties()
.Select(s => new { Name = ToRequiredCase(s.Name, options.PropertyCaseType), Value = s.GetValue(data, null)?.ToString() })
.Where(s => s.Value != null)
.ToDictionary(k => k.Name, v => v.Value);
return pairs;
string ToRequiredCase(string value, PropertyNameCaseType type)
{
string processedValue = string.Empty;
if (type == PropertyNameCaseType.CamelCase)
{
processedValue = value?.ToCamelCase();
}
else if (type == PropertyNameCaseType.SnakeCase)
{
processedValue = value?.ToSnakeCase();
}
return processedValue;
}
}
private void SetRequestOptions(RequestOptions options)
{
options.ContentType = this._requestOptions.ContentType;
options.PropertyCaseType = this._requestOptions.PropertyCaseType;
}
}
}

Best practices for initializing a HttpClient [duplicate]

This question already has answers here:
What is the overhead of creating a new HttpClient per call in a WebAPI client?
(7 answers)
Closed 7 months ago.
The community reviewed whether to reopen this question 7 months ago and left it closed:
Original close reason(s) were not resolved
This is a made up scenario and I want to find the best way of implementing and learn which are the best practices.
I am writing a service that makes a get request to an api which requires an Authorization header, the problem is that I am initializing a new client for each service, I was thinking if it is the best to have a singleton client and use that anywhere in different services.
Here I have my BlogService, where I initialize the client and then add the Authorization header, username and password will be in configs but this is just an example.
namespace MyDemoProject.Services
{
public class BlogService : IBlogService
{
private readonly HttpClient client;
private string username = "myUsername";
private string password = "myPassword";
private static string url = "https://example.com/api/blogs";
public BlogService()
{
client = new HttpClient();
string encoded = System.Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
client.DefaultRequestHeaders.Add("Authorization", "Basic " + encoded);
}
public async Task<string> GetAllBlogs()
{
var request = await client.GetAsync(url);
var data = await request.Content.ReadAsStringAsync();
return data;
}
}
}
And here I have my CommentService, which is very similar, but with a different url. This is a simple example and it could be included in the same BlogService, but suppose that I want to make it granular for maintainability and scalability.
namespace MyDemoProject.Services
{
public class CommentService : ICommentService
{
private readonly HttpClient client;
private string username = "myUsername";
private string password = "myPassword";
private static string url = "https://example.com/api/comments";
public CommentService()
{
client = new HttpClient();
string encoded = System.Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
client.DefaultRequestHeaders.Add("Authorization", "Basic " + encoded);
}
public async Task<string> GetAllComments()
{
var request = await client.GetAsync(url);
var data = await request.Content.ReadAsStringAsync();
return data;
}
}
}
Is there any better way of implementing this, and are there any issues with this implementation?
The best practice in .net 6 is to use IHttpClientFactory to avoid the creation of new HttpClient each time you have to use it and to avoid code duplication: https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests.
Here a simple example based on your code.
You have to register and configure IHttpClientFactory like:
_ = services.AddHttpClient("MyHttpClientKey", (serviceProvider, httpClient) =>
{
private string username = "myUsername";
private string password = "myPassword";
string encoded = System.Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
_ = httpClient.BaseAddress = new Uri("https://example.com/api/comments");
_ = httpClient.Timeout = TimeSpan.FromSeconds(30);
httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + encoded);
});
Then you can use HttpClient injecting it in your classes:
public class CommentService : ICommentService
{
private readonly HttpClient client;
public CommentService(IHttpClientFactory httpClientFactory)
{
httpClient = httpClientFactory.CreateClient("MyHttpClientKey");
}
public async Task<string> GetAllComments()
{
var request = await client.GetAsync(url);
var data = await request.Content.ReadAsStringAsync();
return data;
}
}
Hope it helps!
You should not try to manually create instance of HttpClient better avoid it as it might cause unwanted issues due to DNS cache and not recycling of TCP connections.
Use HttpClientFactory instead, it will take care of creating and maintaining the HttpClient for you
Add this dependency from nuget
Microsoft.Extensions.DependencyInjection
If you are using dependency injection add this code in startup file
In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<BearerTokenHandler>();
services.AddHttpClient<ICommentService, CommentService >()
.ConfigureHttpClient(client =>
{
client.BaseAddress = new Uri("https://example.com/api");
}).AddHttpMessageHandler<BearerTokenHandler>();;
}
namespace MyDemoProject.Services
{
public class BearerTokenHandler : DelegatingHandler
{
private const string BasicAuthenticationScheme = "Basic";
private string username = "myUsername";
private string password = "myPassword";
public BearerTokenHandler()
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string encoded = System.Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
request.Headers.Authorization = new AuthenticationHeaderValue(BasicAuthenticationScheme, token);
return await base.SendAsync(request, cancellationToken);
}
}
public class CommentService : ICommentService
{
private readonly HttpClient client;
public CommentService(HttpClient httpClient)
{
client = httpclient
}
public async Task<string> GetAllComments()
{
string response = null;
using (var request = new HttpRequestMessage( HttpMethod.GET, "comments"))
{
response = await client.SendAsync(request);
}
var data = await response.Content.ReadAsStringAsync();
return data;
}
}
}

How to access Sharepoint files using app-level authentication / access token?

I am trying to access Sharepoint files without having to have the user login.
I can get an access token by either
Method 1:
var client = new RestClient("https://login.microsoftonline.com/app's-tenant-id-here/oauth2/token");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddHeader("Cookie", "fpc=AjMRWuGtzbFJgrxV0V1kMCkUHKO3AQAAAEqqRtgOAAAA");
request.AddParameter("resource", "https://graph.microsoft.com");
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("client_id", "client-id-here");
request.AddParameter("client_secret", ".client-secret-here");
IRestResponse response = client.Execute(request);
or Method 2 -
(This one gives the following error: The type initializer for 'AppForSharePointOnlineWebToolkit.TokenHelper' threw an exception.)
string siteUrl = "https://the-site-I-am-trying-to-access.sharepoint.com/sites/xxx/";
string realm = TokenHelper.GetRealmFromTargetUrl(new Uri(siteUrl));
string accessToken2 = TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal, new Uri(siteUrl).Authority, realm).AccessToken;
using (ClientContext cc = TokenHelper.GetClientContextWithAccessToken(siteUrl, accessToken2))
{
cc.Load(cc.Web, p => p.Title);
cc.ExecuteQuery();
Console.WriteLine(cc.Web.Title);
}
And even method 3
HttpWebRequest endpointRequest = (HttpWebRequest)WebRequest.Create("https://the-site-I-am-trying-to-access.sharepoint.com/sites/xxx/_api/web/getfilebyserverrelativeurl('~/Shared%20Documents/picture.png')");
endpointRequest.Method = "GET";
endpointRequest.Accept = "application/json;odata=verbose";
endpointRequest.Headers.Add("Authorization", "Bearer " + accessToken);
HttpWebResponse endpointResponse = (HttpWebResponse)endpointRequest.GetResponse();
None of which successfully access Sharepoint.
So my question is, am I doing something wrong or is there another way to achieve this?
Well, first of all I would suggest to use Graph API if possible. At least it's the preferred way to query data and it makes things a lot easier.
To access data inside the Microsoft 365 world via Graph API it's required to create a new app registration inside the azure portal > Azure Active Directory > App Registrations.
See this link for more information: MS Docs App Registration
After you've created a new app, configure the required scope and permissions to access SharePoint data (e.g. Sites.ReadWrite.All for full access).
After that simply use the generated and provided clientID, clientSecret, tenantID and scope to request a new access token.
I created a Class to carry out all the http requests for me:
public class GraphClient
{
private const string LOGIN_URL = "https://login.microsoftonline.com/{0}/oauth2/v2.0/token";
private const string BASE_URL = "https://graph.microsoft.com";
protected internal string HttpBaseAddress { get; }
protected internal readonly HttpClient HttpClient;
public GraphClient(string tenantId, string clientId, string clientSecret, string version = "1.0")
{
var msgHandler = new GraphAuthMessageHandler(string.Format(LOGIN_URL, tenantId),
$"{BASE_URL}/.default",
clientId,
clientSecret,
new HttpClientHandler());
HttpBaseAddress = $"{BASE_URL}/v{version}/";
HttpClient = new HttpClient(msgHandler)
{
BaseAddress = new Uri(HttpBaseAddress),
Timeout = new TimeSpan(0, 2, 0)
};
HttpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
HttpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
HttpClient.DefaultRequestHeaders.Add("Prefer", "odata.include-annotations=\"*\"");
HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
The provided MessageHangler requests the token and adds the provided access token to the header:
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
public class GraphAuthMessageHandler : DelegatingHandler
{
private readonly string _loginUrl;
private readonly string _clientId;
private readonly string _clientSecret;
private readonly string _scope;
public GraphAuthMessageHandler(string loginUrl, string scope, string clientId, string clientSecret, HttpMessageHandler innerHandler)
: base(innerHandler)
{
_loginUrl = loginUrl;
_clientId = clientId;
_clientSecret = clientSecret;
_scope = scope;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var result = await AcquireAccessToken();
request.Headers.Authorization = new AuthenticationHeaderValue(result.TokenType, result.AccessToken);
return await base.SendAsync(request, cancellationToken);
}
private async Task<AuthResponse> AcquireAccessToken()
{
var httpClient = new HttpClient();
var values = new List<KeyValuePair<string, string>>
{
new("client_id", _clientId),
new("client_secret", _clientSecret),
new("scope", _scope),
new("grant_type", "client_credentials")
};
var response = await httpClient.PostAsync(_loginUrl, new FormUrlEncodedContent(values));
return await response.Content.ReadFromJsonAsync<AuthResponse>();
}
}
Edit Here's the AuthResponse class, that simply maps the json response to an C# Object:
using System.Text.Json.Serialization;
public class AuthResponse
{
[JsonPropertyName("token_type")] public string TokenType { get; set; }
[JsonPropertyName("expires_in")] public int ExpiresIn { get; set; }
[JsonPropertyName("access_token")] public string AccessToken { get; set; }
}
And then simply use it:
var driveId = "your-drive-id-here";
var itemId = "your-item-id-here";
var client = new GraphClient(TENANT_ID, CLIENT_ID, CLIENT_SECRET);
var response = await client.HttpClient.GetAsync($"drives/{driveId}/items/{itemId}/content");
if (response.IsSuccessStatusCode)
{
var fileStream = await response.Content.ReadAsStreamAsync();
// do something with stream
}
// handle errors here
The Microsoft Docs are a good start to get it working, it helped me a lot about using Graph API and also the Graph Explorer to test queries and requests to the endpoint.
P.S. this is just a simple example and for sure there's room for improvements, but this will hopefully point you to the right direction ;)

Patching to TFS API with JSON gives 400 error (Bad Request)

I'm trying to do a patch http request to change one of the fields in TFS through the TFS REST API. I've tried several approaches, but I always end up with 400 error. Here is what I have right now:
public void SetFieldValue(string value, string path, int id)
{
var httpWebRequest = (HttpWebRequest)WebRequest.Create(PatchwebAPIUrl("wit/workitems", id.ToString()));
httpWebRequest.ContentType = "application/json-patch+json";
httpWebRequest.Method = "PATCH";
httpWebRequest.Headers["Authorization"] = "Basic" + Base64authorizationToken();
using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
string json = "[{\"op\":\"replace\"," +
$"\"path\":\"{path}\"," +
$"\"value\":\"{value}\"}}]";
streamWriter.Write(JsonConvert.SerializeObject(json));
streamWriter.Flush();
streamWriter.Close();
}
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
}
}
And the test method that calls this method:
[TestMethod()]
public void setFieldValue()
{
TFSWebAPIImplementation webAPI = new TFSWebAPIImplementation();
webAPI.SetFieldValue("654321", "/fields/Custom.Tracking", 61949);
}
The PatchwebAPIUrl("...") Method is fine, and returns a good URL, when I navigate to it I get the JSON data that I want to edit. I'm not 100% on the path variable but it's used the same as the example provided from Microsoft. The authorization works, just based on the fact that when I mess with it I get a 401 instead.
This is my sample code:
Class for work item:
public class WorkItemAtrr
{
[JsonProperty("id")]
public int id;
[JsonProperty("rev")]
public int rev;
[JsonProperty("fields")]
public Dictionary<string, string> fields;
[JsonProperty("_links")]
public Dictionary<string, Link> _links;
[JsonProperty("relations")]
public List<Relation> relations;
[JsonProperty("url")]
public string url;
}
public class Link
{
[JsonProperty("href")]
public string href;
}
public class Relation
{
[JsonProperty("rel")]
public string rel;
[JsonProperty("url")]
public string url;
[JsonProperty("attributes")]
public RelationAttribute attributes;
}
public class RelationAttribute
{
[JsonProperty("comment")]
public string comment = "";
[JsonProperty("isLocked")]
public bool isLocked;
}
Class for new and updated fields:
public class NewField
{
[JsonProperty("op")]
public string op = "add";
[JsonProperty("path")]
public string path;
[JsonProperty("value")]
public object value;
}
Class for exceptions:
public class RestApiExceptionContainer
{
[JsonProperty("id")]
public int id;
[JsonProperty("innerException")]
public string innerException;
[JsonProperty("message")]
public string message;
[JsonProperty("typeName")]
public string typeName;
[JsonProperty("typeKey")]
public string typeKey;
[JsonProperty("errorCode")]
public int errorCode;
[JsonProperty("evenId")]
public int eventId;
}
Method for update a work item:
private static WorkItemAtrr UpdateWorkItemRest()
{
Dictionary<string, string> _fields = new Dictionary<string, string>();
_fields.Add("REFERENCE_NAME", "VALUE");
var _updatedWi = UpdateWorkItem("ID", _fields).Result;
}
Method for preparing request:
public async Task<WorkItemAtrr> UpdatedWorkItem(int pId, Dictionary<String, String> pFields)
{
//PATCH https://{instance}/DefaultCollection/_apis/wit/workitems/{id}?api-version={version}
string _query_url = String.Format("https://YOUR_SERVER/DefaultCollection/_apis/wit/workitems/{id}?api-version=1.0", pId);
List<Object> flds = new List<Object>();
foreach (var _key in pFields.Keys)
flds.Add(new NewField { op = "add", path = "/fields/" + _key, value = pFields[_key] });
HttpResponseMessage _response = await DoRequest(_query_url, JsonConvert.SerializeObject(flds), ClientMethod.PATCH);
return JsonConvert.DeserializeObject<WorkItemAtrr>(await ProcessResponse(_response));
}
Universal method for request:
private async Task<HttpResponseMessage> DoRequest(string pRequest, string pBody, ClientMethod pClientMethod)
{
try
{
HttpClientHandler _httpclienthndlr = new HttpClientHandler();
//update for your auth
if (UseDefaultCredentials) _httpclienthndlr.Credentials = CredentialCache.DefaultCredentials;
else if (TFSDomain == "") _httpclienthndlr.Credentials = new NetworkCredential(TFSUserName, TFSPassword);
else _httpclienthndlr.Credentials = new NetworkCredential(TFSUserName, TFSPassword, TFSDomain);
using (HttpClient _httpClient = new HttpClient(_httpclienthndlr))
{
switch (pClientMethod)
{
case ClientMethod.GET:
return await _httpClient.GetAsync(pRequest);
case ClientMethod.POST:
return await _httpClient.PostAsync(pRequest, new StringContent(pBody, Encoding.UTF8, "application/json"));
case ClientMethod.PATCH:
var _request = new HttpRequestMessage(new HttpMethod("PATCH"), pRequest);
_request.Content = new StringContent(pBody, Encoding.UTF8, "application/json-patch+json");
return await _httpClient.SendAsync(_request);
default:
return null;
}
}
}
catch (Exception _ex)
{
throw new Exception("Http Request Error", _ex);
}
}
Universal method for response:
public async Task<string> ProcessResponse(HttpResponseMessage pResponse)
{
string _responseStr = "";
if (pResponse != null)
{
if (pResponse.IsSuccessStatusCode)
_responseStr = await pResponse.Content.ReadAsStringAsync();
else
{
_responseStr = await pResponse.Content.ReadAsStringAsync();
var _error = JsonConvert.DeserializeObject<RestApiExceptionContainer>(_responseStr);
throw new RestApiException(_error);
}
}
return _responseStr;
}
A 400 means that the request was malformed. In other words, the data stream sent by the client to the server didn't follow the rules.
In the case of a REST API with a JSON payload, 400's are typically, used to indicate that the JSON is invalid in some way according to the API specification for the service.
So, the issue is caused by the JSON body.
Just try it like below:
string json = "[{\"op\":\"replace\",\"path\":\"/fields/System.Title\",\"value\":\"Title\"}]";
You can also use below sample to update the fields with PATCH method via the REST API, it works for me:
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;
namespace UpdateWorkItemFiled0411
{
class Program
{
static void Main(string[] args)
{
string password = "xxxx";
string credentials = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "username", password )));
Object[] patchDocument = new Object[1];
patchDocument[0] = new { op = "replace", path = "/relations/attributes/comment", value = "Adding traceability to dependencies" };
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);
var patchValue = new StringContent(JsonConvert.SerializeObject(patchDocument), Encoding.UTF8, "application/json-patch+json");
var method = new HttpMethod("PATCH");
var request = new HttpRequestMessage(method, "http://server:8080/tfs/DefaultCollection/_apis/wit/workitems/21?api-version=1.0") { Content = patchValue };
var response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().Result;
}
}
}
}
}
Also you may use nugate package Microsoft.TeamFoundationServer.Client.
Connect to team project from here:
using Microsoft.TeamFoundation.Core.WebApi;
using Microsoft.VisualStudio.Services.Common;
...
//create uri and VssBasicCredential variables
Uri uri = new Uri(url);
VssBasicCredential credentials = new VssBasicCredential("", personalAccessToken);
using (ProjectHttpClient projectHttpClient = new ProjectHttpClient(uri, credentials))
{
IEnumerable<TeamProjectReference> projects = projectHttpClient.GetProjects().Result;
}
my code to add comments:
JsonPatchDocument PatchDocument = new JsonPatchDocument();
PatchDocument.Add(
new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/System.History",
Value = "Changes from script"
}
);
VssCredentials Cred = new VssCredentials(true);
WorkItemTrackingHttpClient WIClient = new WorkItemTrackingHttpClient(new Uri("http://YOUR_SERVER/tfs/DefaultCollection"), Cred);
WorkItem result = WIClient.UpdateWorkItemAsync(PatchDocument, id).Result;
Okay guys, so unfortunately none of your solutions worked, and I think it's because there was always an extra set of curly brackets on the outside that the TFS API didn't like. Here is my solution that solved my problem:
public void SetFieldValue(string value, string path, int id)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Base64authorizationToken());
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
using (JsonWriter writer = new JsonTextWriter(sw))
{
writer.Formatting = Formatting.Indented;
writer.WriteStartArray(); // [
writer.WriteStartObject(); // {
writer.WritePropertyName("op"); // "Product:"
writer.WriteValue("replace");
writer.WritePropertyName("path");
writer.WriteValue(path);
writer.WritePropertyName("value");
writer.WriteValue(value);
writer.WriteEndObject(); //}
writer.WriteEnd(); // ]
}
var method = new HttpMethod("PATCH");
var request = new HttpRequestMessage(method, PatchwebAPIUrl("wit/workitems", id.ToString())) { Content = new StringContent(sb.ToString().Trim(new char[] {'{','}'}), Encoding.UTF8, "application/json-patch+json") };
var response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().Result;
}
}
}

Setting Authorization Header of HttpClient

I have an HttpClient that I am using for a REST API. However I am having trouble setting up the Authorization header. I need to set the header to the token I received from doing my OAuth request.
I saw some code for .NET that suggests the following,
httpClient.DefaultRequestHeaders.Authorization = new Credential(OAuth.token);
However the Credential class does that not exist in WinRT. Anyone have any ideas how to set the Authorization header?
So the way to do it is the following,
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "Your Oauth token");
request.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(
System.Text.ASCIIEncoding.ASCII.GetBytes(
$"{yourusername}:{yourpwd}")));
I look for a good way to deal with this issue and I am looking at the same question. Hopefully, this answer will be helping everyone who has the same problem likes me.
using (var client = new HttpClient())
{
var url = "https://www.theidentityhub.com/{tenant}/api/identity/v1";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
var response = await client.GetStringAsync(url);
// Parse JSON response.
....
}
reference from https://www.theidentityhub.com/hub/Documentation/CallTheIdentityHubApi
As it is a good practice to reuse the HttpClient instance, for performance and port exhaustion problems, and because none of the answers give this solution (and even leading you toward bad practices :( ), I put here a link towards the answer I made on a similar question :
https://stackoverflow.com/a/40707446/717372
Some sources on how to use HttpClient the right way:
https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
https://blogs.msdn.microsoft.com/alazarev/2017/12/29/disposable-finalizers-and-httpclient/
I suggest to you:
HttpClient.DefaultRequestHeaders.Add("Authorization", "Bearer <token>");
And then you can use it like that:
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
responseMessage = await response.Content.ReadAsAsync<ResponseMessage>();
}
In the case you want to send HttpClient request with Bearer Token, this code can be a good solution:
var requestMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
Content = new StringContent(".....", Encoding.UTF8, "application/json"),
RequestUri = new Uri(".....")
};
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "Your token");
var response = await _httpClient.SendAsync(requestMessage);
I was setting the bearer token
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
It was working in one endpoint, but not another. The issue was that I had lower case b on "bearer". After change now it works for both api's I'm hitting. Such an easy thing to miss if you aren't even considering it as one of the haystacks to look in for the needle.
Make sure to have "Bearer" - with capital.
Use Basic Authorization And Json Parameters.
using (HttpClient client = new HttpClient())
{
var request_json = "your json string";
var content = new StringContent(request_json, Encoding.UTF8, "application/json");
var authenticationBytes = Encoding.ASCII.GetBytes("YourUsername:YourPassword");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(authenticationBytes));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var result = await client.PostAsync("YourURL", content);
var result_string = await result.Content.ReadAsStringAsync();
}
For anyone finding this old thread now (2021), please look at this documentation about HttpClientFactory which is injectable and will also re-run on each request avoiding expired tokens which will make it useful for bearer tokens, generated clients, pooling etc.
TL;DR: Use HttpClientFactory and a DelegatingHandler which will act as middleware on all outgoing requests with your configured client.
This is how I add my bearer for Azure Identity (managed by Azure) but you can get the token however you want of course;
using Microsoft.Azure.Services.AppAuthentication;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public class BearerTokenHandler : DelegatingHandler
{
public BearerTokenHandler(AzureServiceTokenProvider tokenProvider, string resource)
{
TokenProvider = tokenProvider;
Resource = resource;
}
public AzureServiceTokenProvider TokenProvider { get; }
public string Resource { get; }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains("Authorization"))
{
// Fetch your token here
string token = await TokenProvider.GetAccessTokenAsync(Resource);
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
}
I configure my typed clients (generated with NSwag) like this in Startup;
var accessTokenProvider = new AzureServiceTokenProvider("<your-connection-string-for-access-token-provider>");
builder.Services.AddHttpClient<IOrdersClient, OrdersClient>().ConfigureHttpClient(async conf =>
{
conf.BaseAddress = new Uri("<your-api-base-url>");
}).AddHttpMessageHandler(() => new BearerTokenHandler(accessTokenProvider, "https://your-azure-tenant.onmicrosoft.com/api"));
Then you can inject your IOrdersClient wherever you like and all requests will have the bearer.
If you want to reuse the HttpClient, it is advised to not use the DefaultRequestHeaders as they are used to send with each request.
You could try this:
var requestMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
Content = new StringContent("...", Encoding.UTF8, "application/json"),
RequestUri = new Uri("...")
};
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes($"{user}:{password}")));
var response = await _httpClient.SendAsync(requestMessage);
To set basic authentication with C# HttpClient. The following code is working for me.
using (var client = new HttpClient())
{
var webUrl ="http://localhost/saleapi/api/";
var uri = "api/sales";
client.BaseAddress = new Uri(webUrl);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.ConnectionClose = true;
//Set Basic Auth
var user = "username";
var password = "password";
var base64String =Convert.ToBase64String( Encoding.ASCII.GetBytes($"{user}:{password}"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",base64String);
var result = await client.PostAsJsonAsync(uri, model);
return result;
}
This is how i have done it:
using (HttpClient httpClient = new HttpClient())
{
Dictionary<string, string> tokenDetails = null;
var messageDetails = new Message { Id = 4, Message1 = des };
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:3774/");
var login = new Dictionary<string, string>
{
{"grant_type", "password"},
{"username", "sa#role.com"},
{"password", "lopzwsx#23"},
};
var response = client.PostAsync("Token", new FormUrlEncodedContent(login)).Result;
if (response.IsSuccessStatusCode)
{
tokenDetails = JsonConvert.DeserializeObject<Dictionary<string, string>>(response.Content.ReadAsStringAsync().Result);
if (tokenDetails != null && tokenDetails.Any())
{
var tokenNo = tokenDetails.FirstOrDefault().Value;
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + tokenNo);
client.PostAsJsonAsync("api/menu", messageDetails)
.ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode());
}
}
}
This you-tube video help me out a lot. Please check it out.
https://www.youtube.com/watch?v=qCwnU06NV5Q
6 Years later but adding this in case it helps someone.
https://www.codeproject.com/Tips/996401/Authenticate-WebAPIs-with-Basic-and-Windows-Authen
var authenticationBytes = Encoding.ASCII.GetBytes("<username>:<password>");
using (HttpClient confClient = new HttpClient())
{
confClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(authenticationBytes));
confClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(Constants.MediaType));
HttpResponseMessage message = confClient.GetAsync("<service URI>").Result;
if (message.IsSuccessStatusCode)
{
var inter = message.Content.ReadAsStringAsync();
List<string> result = JsonConvert.DeserializeObject<List<string>>(inter.Result);
}
}
UTF8 Option
request.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(
System.Text.Encoding.UTF8.GetBytes(
$"{yourusername}:{yourpwd}")));
Using AuthenticationHeaderValue class of System.Net.Http assembly
public AuthenticationHeaderValue(
string scheme,
string parameter
)
we can set or update existing Authorization header for our httpclient like so:
httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", TokenResponse.AccessToken);
BaseWebApi.cs
public abstract class BaseWebApi
{
//Inject HttpClient from Ninject
private readonly HttpClient _httpClient;
public BaseWebApi(HttpClient httpclient)
{
_httpClient = httpClient;
}
public async Task<TOut> PostAsync<TOut>(string method, object param, Dictionary<string, string> headers, HttpMethod httpMethod)
{
//Set url
HttpResponseMessage response;
using (var request = new HttpRequestMessage(httpMethod, url))
{
AddBody(param, request);
AddHeaders(request, headers);
response = await _httpClient.SendAsync(request, cancellationToken);
}
if(response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync<TOut>();
}
//Exception handling
}
private void AddHeaders(HttpRequestMessage request, Dictionary<string, string> headers)
{
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (headers == null) return;
foreach (var header in headers)
{
request.Headers.Add(header.Key, header.Value);
}
}
private static void AddBody(object param, HttpRequestMessage request)
{
if (param != null)
{
var content = JsonConvert.SerializeObject(param);
request.Content = new StringContent(content);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
}
SubWebApi.cs
public sealed class SubWebApi : BaseWebApi
{
public SubWebApi(HttpClient httpClient) : base(httpClient) {}
public async Task<StuffResponse> GetStuffAsync(int cvr)
{
var method = "get/stuff";
var request = new StuffRequest
{
query = "GiveMeStuff"
}
return await PostAsync<StuffResponse>(method, request, GetHeaders(), HttpMethod.Post);
}
private Dictionary<string, string> GetHeaders()
{
var headers = new Dictionary<string, string>();
var basicAuth = GetBasicAuth();
headers.Add("Authorization", basicAuth);
return headers;
}
private string GetBasicAuth()
{
var byteArray = Encoding.ASCII.GetBytes($"{SystemSettings.Username}:{SystemSettings.Password}");
var authString = Convert.ToBase64String(byteArray);
return $"Basic {authString}";
}
}
In net .core you can use with Identity Server 4
var client = new HttpClient();
client.SetBasicAuthentication(userName, password);
or
var client = new HttpClient();
client.SetBearerToken(token);
see https://github.com/IdentityModel/IdentityModel/blob/main/src/Client/Extensions/AuthorizationHeaderExtensions.cs
this could works, if you are receiving a json or an xml from the service and i think this can give you an idea about how the headers and the T type works too, if you use the function MakeXmlRequest(put results in xmldocumnet) and MakeJsonRequest(put the json in the class you wish that have the same structure that the json response) in the next way
/*-------------------------example of use-------------*/
MakeXmlRequest<XmlDocument>("your_uri",result=>your_xmlDocument_variable = result,error=>your_exception_Var = error);
MakeJsonRequest<classwhateveryouwant>("your_uri",result=>your_classwhateveryouwant_variable=result,error=>your_exception_Var=error)
/*-------------------------------------------------------------------------------*/
public class RestService
{
public void MakeXmlRequest<T>(string uri, Action<XmlDocument> successAction, Action<Exception> errorAction)
{
XmlDocument XMLResponse = new XmlDocument();
string wufooAPIKey = ""; /*or username as well*/
string password = "";
StringBuilder url = new StringBuilder();
url.Append(uri);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url.ToString());
string authInfo = wufooAPIKey + ":" + password;
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
request.Timeout = 30000;
request.KeepAlive = false;
request.Headers["Authorization"] = "Basic " + authInfo;
string documento = "";
MakeRequest(request,response=> documento = response,
(error) =>
{
if (errorAction != null)
{
errorAction(error);
}
}
);
XMLResponse.LoadXml(documento);
successAction(XMLResponse);
}
public void MakeJsonRequest<T>(string uri, Action<T> successAction, Action<Exception> errorAction)
{
string wufooAPIKey = "";
string password = "";
StringBuilder url = new StringBuilder();
url.Append(uri);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url.ToString());
string authInfo = wufooAPIKey + ":" + password;
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
request.Timeout = 30000;
request.KeepAlive = false;
request.Headers["Authorization"] = "Basic " + authInfo;
// request.Accept = "application/json";
// request.Method = "GET";
MakeRequest(
request,
(response) =>
{
if (successAction != null)
{
T toReturn;
try
{
toReturn = Deserialize<T>(response);
}
catch (Exception ex)
{
errorAction(ex);
return;
}
successAction(toReturn);
}
},
(error) =>
{
if (errorAction != null)
{
errorAction(error);
}
}
);
}
private void MakeRequest(HttpWebRequest request, Action<string> successAction, Action<Exception> errorAction)
{
try{
using (var webResponse = (HttpWebResponse)request.GetResponse())
{
using (var reader = new StreamReader(webResponse.GetResponseStream()))
{
var objText = reader.ReadToEnd();
successAction(objText);
}
}
}catch(HttpException ex){
errorAction(ex);
}
}
private T Deserialize<T>(string responseBody)
{
try
{
var toReturns = JsonConvert.DeserializeObject<T>(responseBody);
return toReturns;
}
catch (Exception ex)
{
string errores;
errores = ex.Message;
}
var toReturn = JsonConvert.DeserializeObject<T>(responseBody);
return toReturn;
}
}
}
It may be easier to use an existing library.
For example, the extension methods below are added with Identity Server 4
https://www.nuget.org/packages/IdentityModel/
public static void SetBasicAuthentication(this HttpClient client, string userName, string password);
//
// Summary:
// Sets a basic authentication header.
//
// Parameters:
// request:
// The HTTP request message.
//
// userName:
// Name of the user.
//
// password:
// The password.
public static void SetBasicAuthentication(this HttpRequestMessage request, string userName, string password);
//
// Summary:
// Sets a basic authentication header for RFC6749 client authentication.
//
// Parameters:
// client:
// The client.
//
// userName:
// Name of the user.
//
// password:
// The password.
public static void SetBasicAuthenticationOAuth(this HttpClient client, string userName, string password);
//
// Summary:
// Sets a basic authentication header for RFC6749 client authentication.
//
// Parameters:
// request:
// The HTTP request message.
//
// userName:
// Name of the user.
//
// password:
// The password.
public static void SetBasicAuthenticationOAuth(this HttpRequestMessage request, string userName, string password);
//
// Summary:
// Sets an authorization header with a bearer token.
//
// Parameters:
// client:
// The client.
//
// token:
// The token.
public static void SetBearerToken(this HttpClient client, string token);
//
// Summary:
// Sets an authorization header with a bearer token.
//
// Parameters:
// request:
// The HTTP request message.
//
// token:
// The token.
public static void SetBearerToken(this HttpRequestMessage request, string token);
//
// Summary:
// Sets an authorization header with a given scheme and value.
//
// Parameters:
// client:
// The client.
//
// scheme:
// The scheme.
//
// token:
// The token.
public static void SetToken(this HttpClient client, string scheme, string token);
//
// Summary:
// Sets an authorization header with a given scheme and value.
//
// Parameters:
// request:
// The HTTP request message.
//
// scheme:
// The scheme.
//
// token:
// The token.
public static void SetToken(this HttpRequestMessage request, string scheme, string token);
Firstly, I wouldn't use HttpClient directly. It's too easy to make mistakes - particularly in the area of headers. The DefaultHeadersCollection is not immutable and not thread-safe because other parts of the app can change the headers on you. It's best to set the headers when you make the call. If you are working with an abstraction, and that is recommended because the classes in this area are a bit of a mess, you would want to have a headers collection and put those on your HttpRequestMessage before you send it. You need to make sure you put the content headers on the content, and not the message.
Code Reference
foreach (var headerName in request.Headers.Names)
{
//"Content-Type"
if (string.Compare(headerName, HeadersExtensions.ContentTypeHeaderName, StringComparison.OrdinalIgnoreCase) == 0)
{
//Note: not sure why this is necessary...
//The HttpClient class seems to differentiate between content headers and request message headers, but this distinction doesn't exist in the real world...
//TODO: Other Content headers
httpContent?.Headers.Add(HeadersExtensions.ContentTypeHeaderName, request.Headers[headerName]);
}
else
{
httpRequestMessage.Headers.Add(headerName, request.Headers[headerName]);
}
}
Here is a data structure that you could use to send the request which includes the headers.
Code Reference
public interface IRequest
{
CancellationToken CancellationToken { get; }
string? CustomHttpRequestMethod { get; }
IHeadersCollection Headers { get; }
HttpRequestMethod HttpRequestMethod { get; }
AbsoluteUrl Uri { get; }
}
public interface IRequest<TBody> : IRequest
{
TBody? BodyData { get; }
}
And, a headers collection:
Code Reference
public sealed class HeadersCollection : IHeadersCollection
{
#region Fields
private readonly IDictionary<string, IEnumerable<string>> dictionary;
#endregion
#region Public Constructors
public HeadersCollection(IDictionary<string, IEnumerable<string>> dictionary) => this.dictionary = dictionary;
public HeadersCollection(string key, string value) : this(ImmutableDictionary.CreateRange(
new List<KeyValuePair<string, IEnumerable<string>>>
{
new(key, ImmutableList.Create(value))
}
))
{
}
#endregion Public Constructors
#region Public Properties
public static HeadersCollection Empty { get; } = new HeadersCollection(ImmutableDictionary.Create<string, IEnumerable<string>>());
public IEnumerable<string> Names => dictionary.Keys;
IEnumerable<string> IHeadersCollection.this[string name] => dictionary[name];
#endregion Public Properties
#region Public Methods
public bool Contains(string name) => dictionary.ContainsKey(name);
public IEnumerator<KeyValuePair<string, IEnumerable<string>>> GetEnumerator() => dictionary.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => dictionary.GetEnumerator();
public override string ToString() => string.Join("\r\n", dictionary.Select(kvp => $"{kvp.Key}: {string.Join(", ", kvp.Value)}\r\n"));
#endregion
}
See all the working code and examples here.
You can too to use the follow exemple, that it use IHttpClientFactory:
readonly IHttpClientFactory _httpClientFactory;
public HTTPClientHelper(IHttpClientFactory httpClientFactory, string clientName = null)
{
this._httpClientFactory = httpClientFactory;
}
public Task<T> GetAsync(string url, string token) {
var client = _httpClientFactory.CreateClient(_clientName);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, token);
using (HttpResponseMessage response = await _client.GetAsync(url)){
......
}
}
I came across this old thread. The problem I had was that I know to use a static HttpClient, but my token needs refreshing every 59 minutes.
So I could have used HttpClientFactory, but because one of my projects was still in .NET 4.8, I created a class that inherited from HttpClient so I have similar code in all projects. A secret is needed to be able to get the token (I'm using identityserver4).
I then set that as a singleton in DI (I'm using Ninject here):
Bind<MyHttpClient>().ToMethod(c =>
{
var accessKey = ConfigurationManager.AppSettings["AccessKey"];
var client = new MyHttpClient(accessKey)
{
BaseAddress = new Uri(MyUrls.MyApiBaseUrl)
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
return client;
}).InSingletonScope();
Then the class itself - named after the API it is used to access:
public class MyHttpClient : BaseHttpClient
{
private static readonly HttpClient _authHttpClient = new HttpClient();
private string _secret;
public MyHttpClient(string secret)
{
_secret = secret;
}
/// <summary>
/// Add the token to each and every request, cached for 1 minute less than the token's lifetime
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var cacheSeconds = 3600 - 60; // Default of 59 minutes
var token = CacheHelper<string>.Get("MyToken", cacheSeconds * 60, () =>
{
var authorityUrl = MyUrls.AuthServerUrl;
// discover endpoints from metadata
DiscoveryDocumentResponse disco;
disco = _authHttpClient.GetDiscoveryDocumentAsync(authorityUrl).Result;
if (disco.IsError)
{
throw new Exception("Error getting discovery document: " + disco.Error);
}
// request token
var tokenResponse = _authHttpClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "myapp",
ClientSecret = _secret,
Scope = "myapi"
}).Result;
if (tokenResponse.IsError)
{
throw new Exception("Error getting token: " + tokenResponse.Error);
}
if (tokenResponse.ExpiresIn < cacheSeconds + 60)
{
throw new Exception($"Token expires in {tokenResponse.ExpiresIn}s, which is less than {cacheSeconds + 60}");
}
if (tokenResponse.ExpiresIn > cacheSeconds + 60)
{
Log.Warn().Message($"Token expiry in {tokenResponse.ExpiresIn}s, which is greater than {cacheSeconds}").Write();
}
return tokenResponse.AccessToken;
});
// THIS IS THE BIT - Assign this inside a SendAsync override and you are done!
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return base.SendAsync(request, cancellationToken);
}
}
Finally just for completeness, my CacheHelper class looks like this:
public static class CacheHelper<T>
{
private static readonly object _locker = new object();
public static T Get(string cacheName, int cacheTimeoutSeconds, Func<T> func)
{
var obj = MemoryCache.Default.Get(cacheName, null);
if (obj != null) return (T)obj;
lock (_locker)
{
obj = MemoryCache.Default.Get(cacheName, null);
if (obj == null)
{
obj = func();
var cip = new CacheItemPolicy
{
AbsoluteExpiration = new DateTimeOffset(DateTime.UtcNow.AddSeconds(cacheTimeoutSeconds))
};
MemoryCache.Default.Set(cacheName, obj, cip);
}
}
return (T)obj;
}
}
Oauth Process flow is complex and there is always a room for one error or another.
My suggestion will be to always use the boilerplate code and a set of libraries for OAuth authentication flow.It will make your life easier.
Here is the link for the set of libraries.OAuth Libraries for .Net
If you are using Visual Studio IISExpress debug mode and connecting to the HTTP port rather than the HTTPS port you may find that the auth headers are being dropped.
Switch to the SLL connection and they will appear again.
unsure why, possibly the setup redirects the http traffic and that causes the auth to be removed.
This may help Setting the header:
WebClient client = new WebClient();
string authInfo = this.credentials.UserName + ":" + this.credentials.Password;
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
client.Headers["Authorization"] = "Basic " + authInfo;
static async Task<AccessToken> GetToken()
{
string clientId = "XXX";
string clientSecret = "YYY";
string credentials = String.Format("{0}:{1}", clientId, clientSecret);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes(credentials)));
List<KeyValuePair<string, string>> requestData = new List<KeyValuePair<string, string>>();
requestData.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
FormUrlEncodedContent requestBody = new FormUrlEncodedContent(requestData);
var request = await client.PostAsync("https://accounts.spotify.com/api/token", requestBody);
var response = await request.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<AccessToken>(response);
}
}

Categories