I want to use HttpClient to make requests using a certificate.
I generated a dev certificate using the following command
dotnet dev-certs https -ep dev_cert.pfx -p 1234
So I created an API to use that certificate. Here is the implementation.
public static class AuthenticationExtension
{
public static void ConfigureAuthetication(this IServiceCollection services)
{
services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
var cert = new X509Certificate2(#"D:\dev_cert.pfx", "1234");
options.AllowedCertificateTypes = CertificateTypes.All;
options.ChainTrustValidationMode = X509ChainTrustMode.CustomRootTrust;
options.CustomTrustStore = new X509Certificate2Collection { cert };
options.RevocationMode = X509RevocationMode.NoCheck;
options.ValidateCertificateUse = false;
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService = context.HttpContext.RequestServices.GetService<ClientCertificateValidationService>();
if (validationService != null && validationService.ValidateCertificate(context.ClientCertificate))
{
Console.WriteLine("Success");
context.Success();
}
else
{
Console.WriteLine("invalid cert");
context.Fail("invalid cert");
}
return Task.CompletedTask;
},
OnChallenge = context =>
{
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
Console.WriteLine("Failed");
return Task.CompletedTask;
}
};
});
services.AddAuthorization();
}
}
I added the Authorize attribute in the controller method
[HttpGet(Name = "GetWeatherForecast")]
[Authorize]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
I tested the request using Postman and it worked as expected.
Then, I created a console app to make the request using HttpClient. It failed always with the Forbidden Status code.
So I tested using the obsolete WebRequest. And It worked as expected.
Here is the implementation for that
using System.Net;
using System.Security.Cryptography.X509Certificates;
var handler = new HttpClientHandler();
X509Certificate2Collection certificates = new X509Certificate2Collection();
certificates.Import(#"D:\dev_cert.pfx", "1234", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
handler.ClientCertificates.AddRange(certificates);
using var client = new HttpClient(handler);
client.BaseAddress = new Uri("https://localhost:7148/");
try
{
var response = await client.GetAsync("weatherforecast/");
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseContent);
}
else
{
Console.WriteLine($"Failed {response.StatusCode}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed {ex}");
}
string host = #"https://localhost:7148/weatherforecast";
try
{
ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => true;
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(host);
req.AllowAutoRedirect = true;
req.ClientCertificates = certificates;
req.Method = "GET";
WebResponse resp = req.GetResponse();
Stream stream = resp.GetResponseStream();
using (StreamReader reader = new StreamReader(stream))
{
string line = reader.ReadLine();
while (line != null)
{
Console.WriteLine(line);
line = reader.ReadLine();
}
}
stream.Close();
}
catch (Exception e)
{
Console.WriteLine(e);
}
Console.ReadLine();
My question is... Since the WebRequest is obsolete what I can do to make the HttpClient work?
Thank you in Advance!
Related
I currently have webservice calls that create a proxy interface for a URL. I have a requirement to update the application to accept Oauth 2.0. Is it possible to use Oauth 2.0 with WCF Webservice calls?
This is my proxy interface initialization. I use it just like a regular class initialization.
var client = ServiceClient.CreateProxyInterface<MyWebServiceClass>(WebServiceUrl);
inside the proxy interface I do some authorization checks and create an instance of the requested object and return it back to the client
public static TInterface CreateProxyInterface<TInterface>(string ServiceUrl) where TInterface : class
{
var UseClientCertificate = true;
if (ServiceClient.IsUnsecuredHttpService(ServiceUrl, UseClientCertificate))
ServiceUrl = new UriBuilder(ServiceUrl)
{
Scheme = Uri.UriSchemeHttps,
Port = -1
}.Uri.ToString();
var key = TInterface.ToString() + ServiceUrl + UseClientCertificate.ToString();
ChannelFactory myChannelFactory = ServiceClient.FactoryCache[key];
proxy = ((ChannelFactory<TInterface>) mlifChannelFactory1.Factory).CreateChannel();
return proxyInterface;
}
the client can then call a method within that class
var address = client.GetAddress(personId);
On the server side, you can customize a class to inherit ServiceAuthorizationManager, and then override the CheckAccessCore method in ServiceAuthorizationManager to implement it.
Below is an example I found from previous answers:OAuth and WCF SOAP service. After my attempts, his example is effective, so I think it should help you.
public class OAuthAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
// Extract the action URI from the OperationContext. Match this against the claims
// in the AuthorizationContext.
string action = operationContext.RequestContext.RequestMessage.Headers.Action;
try
{
//get the message
var message = operationContext.RequestContext.RequestMessage;
//get the http headers
var httpHeaders = ((System.ServiceModel.Channels.HttpRequestMessageProperty)message.Properties.Values.ElementAt(message.Properties.Keys.ToList().IndexOf("httpRequest"))).Headers;
//get authorization header
var authHeader = httpHeaders.GetValues("Authorization");
if (authHeader != null)
{
var parts = authHeader[0].Split(' ');
if (parts[0] == "Bearer")
{
var tokenClaims = ValidateJwt(parts[1]);
foreach (System.Security.Claims.Claim c in tokenClaims.Where(c => c.Type == "http://www.contoso.com/claims/allowedoperation"))
{
var authorized = true;
//other claims authorization logic etc....
if(authorized)
{
return true;
}
}
}
}
return false;
}
catch (Exception)
{
throw;
}
}
private static IEnumerable<System.Security.Claims.Claim> ValidateJwt(string jwt)
{
var handler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters()
{
ValidAudience = "urn://your.audience",
IssuerSigningKey = new InMemorySymmetricSecurityKey(Convert.FromBase64String("base64encoded symmetric key")),
ValidIssuer = "urn://your.issuer",
CertificateValidator = X509CertificateValidator.None,
RequireExpirationTime = true
};
try
{
SecurityToken validatedToken;
var principal = handler.ValidateToken(jwt, validationParameters, out validatedToken);
return principal.Claims;
}
catch (Exception e)
{
return new List<System.Security.Claims.Claim>();
}
}
}
I tried to execute my application Rest api. Using a breakpoint, I found a problm in this line:
expenses.AmountTTC = Convert.ToDecimal(ttc.Text);
The error is "Specified cast is not valid".
AmountTTC has the type decimal? in the model , same for my service(i have two projects, one for the services and anthor for my mobile application).
private void Button_Clicked(object sender, EventArgs e)
{
ajoutD.Clicked += async delegate
{
try
{
LoginViews expenses = new LoginViews();
expenses.Name = nameLib.Text;
expenses.StartDate = dataDe.Date;
expenses.EndDate = dateAu.Date;
datenow.Date = DateTime.Now;
expenses.Description = description.Text;
expenses.CurrencyId = Convert.ToInt32(devises.Id);
expenses.AmountTTC = Convert.ToDecimal(ttc.Text);
remb.Text = expenses.AmountReimbursed.ToString();
expenses.Remboursable = Convert.ToBoolean(isremboursable);
expenses.Provider = marchand.Text;
HttpClient httpClient = new HttpClient();
HttpResponseMessage response;
var json = JsonConvert.SerializeObject(expenses);
var content = new StringContent(json, Encoding.UTF8, "application/json");
response = await httpClient.PostAsync(url, content);
AuthResponse responseData = JsonConvert.DeserializeObject<AuthResponse>(response?.Content?.ReadAsStringAsync()?.Result);
if (responseData.data.Success)
{
await DisplayAlert("heey", "connexion done", "ok");
}
else
{
await DisplayAlert("wake up !", responseData.data.ErrorMessage, "attention");
}
}catch(Exception eee)
{
string msg = eee.ToString();
}
};
}
Am trying to create a an azure function that is triggered in a Logic Apps,
The functions purpose is to web crawl certain web sites, take the desired information, compare that with a a SQL Server database in Azure, compare if we already have that information if not add it.
My issue is that when ever i run it I get the Server 500 error, I assume its accessing the database that cause. Help?
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log
)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string RequestBody = await new StreamReader(req.Body).ReadToEndAsync();
{
return await CrawlBlog(0, RequestBody);
}
}
private static async Task<IActionResult> CrawlBlog(int Picker, string req)
{
int BlogPicker = Picker;
string TheResult = req;
//Get the url we want to test
var Url = "";
if (BlogPicker == 0)
{
Url = "*********";
}
else if (BlogPicker == 1)
{
Url = "*********";
}
/*
else if (BlogPicker == 2)
{
Url = "https://azure.microsoft.com/en-in/blog/?utm_source=devglan";
}
*/
else
{
TheResult = "False we got a wrong pick";
return (ActionResult)new OkObjectResult
( new {TheResult });
}
var httpClient = new HttpClient();
var html = await httpClient.GetStringAsync(Url);
var htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(html);
//a list to add all availabel blogs we found
var Blog = new List<BlogStats>();
switch (BlogPicker)
{
case 0:
{
var divs =
htmlDocument.DocumentNode.Descendants("div")
.Where(node => node.GetAttributeValue("class", "").Equals("home_blog_sec_text")).ToList();
foreach (var divo in divs)
{
var Blogo = new BlogStats
{
Summary = divo.Descendants("p").FirstOrDefault().InnerText,
Link = divo.Descendants("a").FirstOrDefault().ChildAttributes("href").FirstOrDefault().Value,
Title = divo.Descendants("a").FirstOrDefault().InnerText
};
Blog.Add(Blogo);
}
break;
}
case 1:
{
var divs =
htmlDocument.DocumentNode.Descendants("div")
.Where(node => node.GetAttributeValue("class", "").Equals("post_header_title two_third last")).ToList();
foreach (var divo in divs)
{
//string TheSummary = "we goofed";
var ThePs = divo.Descendants("p").ToList();
var Blogo = new BlogStats
{
Summary = ThePs[1].GetDirectInnerText(),
Link = divo.Descendants("a").LastOrDefault().ChildAttributes("href").FirstOrDefault().Value,
Title = divo.Descendants("a").FirstOrDefault().InnerText
};
Blog.Add(Blogo);
}
break;
}
}
TheResult = await SqlCheck(Blog[0].Title, Blog[0].Summary, Blog[0].Link); //error 500
return (ActionResult)new OkObjectResult
(
new
{
TheResult
}
);
}
public static async Task<string> SqlCheck(string Tit, string Sumy, string Lin)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = "flygon.database.windows.net";
builder.UserID = "*****";
builder.Password = "********";
builder.InitialCatalog = "torkoal";
System.Data.DataSet ds = new System.Data.DataSet();
SqlConnection connection = new SqlConnection(builder.ConnectionString);
connection.Open();
SqlCommand CheckCommand = new SqlCommand("SELECT * FROM TableBoto WHERE Link = #id3 ", connection);
CheckCommand.Parameters.AddWithValue("#id3", Lin);
SqlDataAdapter dataAdapter = new SqlDataAdapter(CheckCommand);
dataAdapter.Fill(ds);
int i = ds.Tables[0].Rows.Count;
if (i > 0)
{
return $" We got a Duplicates in title : {Tit}";
}
try
{
{
string query = $"insert into TableBoto(Title,Summary,Link) values('{Tit}','{Sumy}','{Lin}');";
SqlCommand command = new SqlCommand(query, connection);
SqlDataReader reader = await command.ExecuteReaderAsync();
reader.Close();
}
}
catch (SqlException)
{
// Console.WriteLine(e.ToString());
}
connection.Close();
return $" Success Ign +{Tit} + Ign {Sumy}+ Ign {Lin} Ign Success SQL ";
}
}
500 HTTP status code is a generic code which means that the server was not able to process the request due to some issues, First step would be to add some exception handling to your function and see if the failure occurs and where it occurs.
On Side note, you should not use HTTP client in the way used in the code, you should not new it up every time your function executes, this client should be static in nature. Refer Manage connections in Azure Functions
I have implemented httpClientFactory but can't connect to the backend api using broken ssl.I tried a lot and i just got the task cancelled error -> HttpClient - A task was cancelled?
I have tried to set this
public static HttpClient getHttpClient()
{
if (_httpClient == null)
{
Uri baseUri = new Uri(Url.baseUrl);
if (baseUri.Scheme == "http")
{
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) =>
{
return true;
};
_httpClient = new HttpClient(handler);
}
else
{
_httpClient = new HttpClient();
}
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new
MediaTypeWithQualityHeaderValue("application/json"));
_httpClient.BaseAddress = baseUri;
}
return _httpClient;
}
I have two interfaces for components that each requires functionality from the other one. One that generates Oauth tokens, and another one that gets secrets from a secret provider (Azure Key Vault).
The problem is that the Token Provider needs to obtain a secret value (a password) to make its HTTP call, and the Secret Provider class needs to get a Token in order to call Azure. Chicken and Egg problem.
From the other questions I've read, one suggestion is to create a third class/interface on which the original 2 depend, but I'm not sure how that would work here.
Any help and suggestions would be appreciated. Code for all relevant classes/interfaces is shown below.
public interface ISecretProvider
{
string GetSecret(string secretName);
}
public interface ITokenProvider
{
string GetKeyVaultToken();
}
public class OktaTokenProvider : ITokenProvider
{
ISecretProvider _secretProvider;
public string GetKeyVaultToken()
{
var tokenUrl = ConfigurationManager.AppSettings["KeyVault.Token.Url"];
var clientId = ConfigurationManager.AppSettings["KeyVault.Token.ClientId"];
var clientSecret = _secretProvider.GetSecret("ClientSecret");
var scope = ConfigurationManager.AppSettings["KeyVault.Scope"];
var token = GetToken(tokenUrl, clientId, clientSecret, scope);
return token;
}
private string GetToken(string tokenUrl, string clientId, string clientSecret, string scope)
{
var clientCredentials = $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}"))}";
string responseFromServer = string.Empty;
bool success = false;
int retryCount = 0;
while (!success)
{
try
{
var tokenWebRequest = (HttpWebRequest)WebRequest.Create(tokenUrl);
tokenWebRequest.Method = "POST";
tokenWebRequest.Headers.Add($"Authorization:{clientCredentials}");
tokenWebRequest.Headers.Add("Cache-control:no-cache");
tokenWebRequest.ContentType = "application/x-www-form-urlencoded";
using (var streamWriter = new StreamWriter(tokenWebRequest.GetRequestStream()))
{
streamWriter.Write($"grant_type=client_credentials&scope={scope}");
streamWriter.Flush();
streamWriter.Close();
}
using (WebResponse response = tokenWebRequest.GetResponse())
{
using (var dataStream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
responseFromServer = reader.ReadToEnd();
reader.Close();
}
dataStream.Close();
}
response.Close();
response.Dispose();
}
success = true;
}
catch (Exception)
{
if (retryCount > 3)
{
throw;
}
else
{
retryCount++;
}
}
}
JToken token = JObject.Parse(responseFromServer);
var accessToken = $"Bearer {token.SelectToken("access_token").ToString()}";
return accessToken;
}
}
public class KeyVaultSecretProvider : ISecretProvider
{
ITokenProvider _tokenProvider;
public KeyVaultSecretProvider(ITokenProvider tokenProvider)
{
_tokenProvider = tokenProvider;
}
public string GetSecret(string secretName)
{
var KeyVaultUrl = ConfigurationManager.AppSettings[Constants.KEYVAULT_ENDPOINT];
var subscriptionKey = ConfigurationManager.AppSettings[Constants.KEYVAULT_SUBSCRIPTION_KEY];
string responseFromServer = "";
var requestedSecretUrl = $"{KeyVaultUrl}{secretName}";
var secretWebRequest = (HttpWebRequest)WebRequest.Create(requestedSecretUrl);
var accessToken = _tokenProvider.GetKeyVaultToken();
secretWebRequest.Method = "GET";
secretWebRequest.Headers.Add("authorization:" + accessToken);
secretWebRequest.Headers.Add("cache-control:no-cache");
secretWebRequest.Headers.Add("Ocp-Apim-Subscription-Key:" + subscriptionKey);
using (WebResponse response = secretWebRequest.GetResponse())
{
using (var dataStream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
responseFromServer = reader.ReadToEnd();
reader.Close();
}
dataStream.Close();
}
response.Close();
response.Dispose();
}
JToken secret = JObject.Parse(responseFromServer);
var secretValue = secret.SelectToken("Secret").ToString();
return secretValue;
}
}
Have a single class implement both interfaces. The two responsibilities are inter-dependent, so put them together in one class. There is nothing wrong with this.