I'm having a bit of an issue with HTTP calls in production. Here is the scenario:
We have a desktop app that communicates with the web server via HTTP requests. This works fine with the below blurb of code on both the server and client side in QA. Now we move from an internally hosted IIS to AWS and throw it live (same configuration, IIS7/Server 2016, port 443/80 allowed on AWS for all IPs), it now half works. Users are able to log in and authenticate remotely from the client application, but certain calls fail to even reach the method on the controller (returns a 500 error after hanging for a few seconds, whereas the calls that work go through instantly) in production. I'm having trouble understanding it as I would believe it would fail completely in QA (for either a network issue or something else) and not work in one place but not the next.
DB connectivity is verified (able to pull the user account/authenticate). All external DLLs are up to date (old files are burned each time a new build is pushed to avoid stagnation), verified connectivity on 443/80, other methods on the same controller are working, and everything works in QA.
[New Password Class]
[Serializable]
public class NewPassword
{
public int UserID { get; set; }
public string Password { get; set; }
public string Location { get; set; }
public string Username { get; set; }
public string Name { get; set; }
public string UserPassword { get; set; }
public int PasswordID { get; set; }
}
[Controller Code]
(Controller path is /API/)
[HttpPost] // Does not work with or without HttpPost attribute
[ValidateInput(false)] // disabled for testing, did not help
public JsonResult CSCreate(NewPassword Data)
{
}
[Client Code]
internal static Status CreatePassword(UserAccount Account, Password Credential)
{
try
{
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("https://" + Methods.Session.Connection.ServerName + "/API/CSCreate");
Request.ContentType = "application/json";
Request.Method = "POST";
using (StreamWriter SW = new StreamWriter(Request.GetRequestStream()))
{
NewPassword Data = new NewPassword() { UserID = Account.ID, Password = Credential.Text, Username = Credential.Username, Location = Credential.Location, UserPassword = Account.Password, Name = Credential.Name };
string JSON = new JavaScriptSerializer().Serialize(Data);
SW.Write(JSON);
}
HttpWebResponse Response = (HttpWebResponse)Request.GetResponse();
using (StreamReader SR = new StreamReader(Response.GetResponseStream()))
{
string Data = SR.ReadToEnd();
Status EndData = (Status)new JavaScriptSerializer().Deserialize(Data, typeof(Status));
if (EndData.ExitCode != 200) return new Status() { ExitCode = 400, ExitMessage = EndData.ExitMessage };
else return new Status() { ExitCode = 200, ExitMessage = EndData.ExitMessage };
}
}
catch (Exception Ex)
{
return new Status() { ExitCode = 500, ExitMessage = "Unable to update your account. An error occurred. Please try again later." };
}
}
Interestingly enough, the (500) response is mentions a GET method even though "POST" is specified in the request information. Attempting a PUT or GET method errors out (GET for obvious reasons) with a not allowed (which makes sense given the attribute). I'm completely stumped on this one.
Related
I have a scenario where I need to respond to a request that it's been received and send a response (request?) to another endpoint once internal api calls and logic has completed. The flow looks like this:
External request to my endpoint > endpoint responds to request with accepted > endpoint passes the request on internally > internal logic fetches and handles data from DB > internal logic uses data from DB to send a request back to a different endpoint from the same integration as the first call came from.
I have managed to get it to work using Queued Background Tasks to send the request to the correct internal handler with Mediatr. However in order for it to work I need to add the barer token from the request header to the request object and then use that barer token to validate against the internal API's. I'd like to avoid this since I might run into the issue of the token expiring or not being valid for the internal Api etc.
Request object example:
public class ExampleRequest : IRequest, IRequest<ExampleResponse>
{
public string? Token { get; set; } //Added for functioning version, want to get rid
//of it
public CommonData Data { get; set; }
public string RequestId { get; set; }
public string OperationId { get; set; }
public List<string> ObjectIdentifiers { get; set; }
}
public class CommonData
{
public string MessageId { get; set; }
public DateTime Timestamp { get; set; }
}
Response object example (response to the call):
public class ExampleResponseForCall
{
public CommonData Data { get; set; }
public string ResponseStatus { get; set; } //Will be accepted/acknowledged
}
Example response object (for final response)
public class ExampleResponse
{
public CommonData Data{ get; set; }
public string ResponseStatus { get; set; }
public string ErrorCode { get; set; }
public string ErrorDescription { get; set; }
public string RequestId { get; set; }
public string OperationId { get; set; }
}
My current working version looks something like this:
**Endpoint:**
public IActionResult Post(ExampleRequest request)
{
var authorization = Request.Headers[HeaderNames.Authorization];
if (AuthenticationHeaderValue.TryParse(authorization, out var headerValue))
{
var scheme = headerValue.Scheme;
var parameter = headerValue.Parameter;
}
var token = headerValue?.Parameter;
request.Token = token; //I added token as a nullable string on the request obj
_backgroundTaskQueue.StartTask(request);
return Ok(new ExampleResponseForCall
{
Data = request.Data,
ResponseStatus = HttpStatusCode.Accepted.ToString()
});
}
**Background Task queue:**
public void StartTask(IRequest request)
{
_logger.LogInformation("Task is starting.");
_request = request;
Task.Run(async () => await AddTaskAsync(), _cancellationToken);
}
private async ValueTask AddTaskAsync()
{
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
}
private async ValueTask BuildWorkItem(CancellationToken token)
{
var guid = Guid.NewGuid().ToString();
_logger.LogInformation("Task {Guid} is starting.", guid);
if (_request == null)
{
_logger.LogWarning("Request for task {Guid} is null.", guid);
return;
}
await _mediator.Send(_request, token);
_logger.LogInformation("Task {Guid} is complete.", guid);
}
I also have Handlers that can handle the request and Clients for sending requests internally and back to the caller. All of that works when awaiting the internal logic to be handled. However when I'm using the background task queue the internal client fails on the when getting the token here
protected async Task<HttpClient> CreateBaseClient()
{
var client = _clientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept",
$"application/json");
client.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", await GetToken());
return client;
}
public async Task<string> GetToken()
{
if (_httpContextAccessor.HttpContext == null)
throw new Exception("No HttpContext available when trying to
get Token.");
_httpContextAccessor.HttpContext.Items.TryGetValue(Constants.AuthenticationSchemeKey,
out var scheme);
if (scheme?.ToString() == Constants.Bearer)
return GetTokenFromRequest();
throw new MissingAccessTokenException("Unknown authentication type");
}
My workaround (that I want to get away from) looks like this:
protected async Task<HttpClient> CreateBaseClient(string version, string token)
{
var client = _clientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept",
$"application/json");
client.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", token); //token from requst.Token
return client;
}
I've tried to pass in the a lot of different things to the Background Task queue (and changing the parameter type to match ofc) but nothing works. I want to have the background task queue generic since I'll be implementing this for other end points as well.
That's a lot of text so TL:DR, I respond to a request but need to use the token from the request for other calls after responding.
We decided to go with the working solution provided in the question itself.
Due to how our infrastructure is set up we won't be able to get a refresh token (as suggested by #GHDevOps and #TheWallrus) since we won't be able to get the login/id and password/secret of the user in a safe and reasonable way.
However, the working solution in the question has some drawback which should be analyzed on a case-to-case basis. We know that the Api sending us the requests will fetch a new (relevant) token approximately 10 minutes before the current (relevant) token expires and use the new token for all coming requests. Since the logic we apply before passing on the request to our backend is very simple (just simple remapping) we should rarely run into issues with the token expiring before the request has been sent, and in the rare cases that is has, we will send that information in the request back to the external Api, giving them a chance to resend the original request. If the external Api isn't fetching a new token before the expiration of the current token that might cause the token to expire before reaching the internal Api more often which might be a good thing to look after if you're implementing a similar solution.
The code changes that I made for this to function are just minor refactoring (see below). Hope this help anyone else running into a similar issue!
//Endpoint
public IActionResult Post(ExampleRequest request)//Before
{
var authorization = Request.Headers[HeaderNames.Authorization];
if (AuthenticationHeaderValue.TryParse(authorization, out var headerValue))
{
var scheme = headerValue.Scheme;
var parameter = headerValue.Parameter;
}
var token = headerValue?.Parameter;
request.Token = token; //I added token as a nullable string on the request obj
_backgroundTaskQueue.StartTask(request);
return Ok(new ExampleResponseForCall
{
Data = request.Data,
ResponseStatus = HttpStatusCode.Accepted.ToString()
});
}
public IActionResult Post(ExampleRequest request)
{
request.Token = GetToken(Request);//Made into a separate function in the inherited class
_backgroundTaskQueue.StartTask(request);
return Ok(new ExampleResponseForCall
{
Data = request.Data,
ResponseStatus = HttpStatusCode.Accepted.ToString()
});
}
protected string GetToken(HttpRequest request)
{
var authorization = request.Headers[HeaderNames.Authorization];
_ = AuthenticationHeaderValue.TryParse(authorization, out var headerValue);
if (headerValue == null)
{
return "";
}
return string.Equals(headerValue.Scheme, "Bearer", StringComparison.InvariantCultureIgnoreCase) ?
headerValue.Parameter : "";
}
//Client
protected async Task<HttpClient> CreateBaseClient()//before
{
var client = _clientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept",
$"application/json");
client.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", await GetToken());
return client;
}
protected async Task<HttpClient> CreateBaseClient(string token = "")
{
var client = _clientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", $"application/json");
client.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", string.IsNullOrEmpty(token) ?
await GetToken() : token); //We will only send in a token if we are async
return client;
}
I have a WPF application where I need to retrieve configuration data from a remote API. The API has been tested and is working correctly returning what I believe to be the correct format. I have created a data type for the response etc etc but when I get to the line getting the response via the HttpClient it doesn't send any request to the API.
DataType :-
public class Mylist
{
public string ItemName { get; set; }
public string ItemText { get; set; }
public string ConfigName { get; set; }
public string ConfigValue { get; set; }
public string ConfigType { get; set; }
}
Code :-
string licKey = ConfigManager.GetSetting("Lic");
string Uri = ConfigManager.GetSetting("API");
string UserAPI = ConfigManager.GetSetting("Config");
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(Uri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync(UserAPI + "?Licence=" + licKey);
var data = await response.Content.ReadAsAsync<IEnumerable<Mylist>>();
int count = 0;
List<Mylist> nudges = new List<Mylist>((IEnumerable<Nudgelist>)data);
// Do something with the data
the code builds the correct URL (https://example.com/api/Uri?Licence=licencevalue) for the request and if input manually into the browser it gives a response as per the below :-
<Mylist>
<ConfigName>cName</ConfigName>
<ConfigType>cType</ConfigType>
<ConfigValue>cValue</ConfigValue>
<ItemName>iName</NudgeName>
<ItemText>iText</NudgeText>
</Mylist>
<Mylist>
...
</Mylist>
When I run the code above and step through it then we get to the line "var response = await client.GetAsync(UserAPI + "?Licence=" + licKey);" and it just skips the rest of the code and moves onto the next call, no error raised or failures anywhere to be found.
I have run logs on the API and we are not seeing the request coming in, if we use an identical model of code for calling another API controller to call Uri2 (https://example.com/api/Uri2?Licence=licencevalue) it works fine.
Try this
if (response.IsSuccessStatusCode)
{
var stringData = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<IEnumerable<MyList>>(stringData);
}
else
{
var statusCode= response.StatusCode);
}
I have a database that contains emails and password hashes.
I would like to secure http trigger's from Azure Functions to allow only authorized call thanks to the Authorization header with a BEARER token.
I think I will need
an http trigger that will generate the token from email/password
Authorize and authenticate the user based on the Authorization header
Can someone get me started on how to create a custom authentication provider or use an existing one and configure Azure Functions to work with it?
Microsoft identity platform supports the OAuth 2.0 Resource Owner Password Credentials (ROPC) grant, which allows an application to sign in the user by directly handling their password.
Get the email(username) and password from database, and send the following request to receive the access token.
POST {tenant}/oauth2/v2.0/token
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&scope=user.read%20openid%20profile%20offline_access
&username=MyUsername#myTenant.com
&password=SuperS3cret
&grant_type=password
You could have look following code snippet, I have tested on azure portal , Azure Function V2:
#r "Newtonsoft.Json"
using Newtonsoft.Json;
using System.Net;
using System.Net.Http.Headers;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
try
{
//Parse query parameter
log.LogInformation("C# HTTP trigger function processed a request.");
//Read Request Body
var content = await new StreamReader(req.Body).ReadToEndAsync();
//Extract Request Body and Parse To Class
UserAuthentication objUserInfo = JsonConvert.DeserializeObject<UserAuthentication>(content);
//Message Container
dynamic validationMessage;
//Validate required param
if (string.IsNullOrEmpty(objUserInfo.UserName.Trim()))
{
validationMessage = new OkObjectResult("User name is required!");
return (IActionResult)validationMessage;
}
if (string.IsNullOrEmpty(objUserInfo.Password.Trim()))
{
validationMessage = new OkObjectResult("Password is required!");
return (IActionResult)validationMessage;
}
// Authentication Token Request format
string tokenUrl = $"https://login.microsoftonline.com/common/oauth2/token";
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "password",
["client_id"] = "YourApplicationId",
["client_secret"] = "YourApplicationPassword",
["resource"] = "https://graph.microsoft.com",
["username"] = "" + objUserInfo.UserName + "",
["password"] = "" + objUserInfo.Password + ""
});
// Request For Token Endpoint
using (var _client = new HttpClient())
{
var tokenResponse = await _client.SendAsync(tokenRequest);
AccessTokenClass objAccessToken = JsonConvert.DeserializeObject<AccessTokenClass>(await tokenResponse.Content.ReadAsStringAsync());
// When Token Request Null
if (objAccessToken.access_token == null)
{
validationMessage = new OkObjectResult("Invalid Authentication! Please Check Your Credentials And Try Again!");
return (IActionResult)validationMessage;
}
else
{
return new OkObjectResult(objAccessToken.access_token);
}
}
}
catch (Exception ex)
{
validationMessage = new OkObjectResult("Sorry something went wrong! Please check your given information and try again! {0}" + ex.Message);
return (IActionResult)validationMessage;
}
}
Class I have Used:
UserAuthentication Class
public class UserAuthentication
{
public string UserName { get; set; }
public string Password { get; set; }
}
public class AzureFunctionCreateUserClass
{
public string access_token { get; set; }
public string expires_in { get; set; }
public string token_type { get; set; }
public string resource { get; set; }
}
Note: This an sample for azure portal which I have written on azure function . So try to run on there.
Hope this would help.
I’m new with HttpClient and Web API so I’m sure I’m missing something obvious to others. I’m trying to call a 3rd party Web API with a list of new Employees. The API is supposed to return JSON with information about the employees that were uploaded. This is going to be a console app that will be run once a night. There is no requirement for this to run Async.
I’m trying to follow along with these articles.
http://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client
and
http://typecastexception.com/post/2013/07/03/Building-Out-a-Clean-REST-ful-WebApi-Service-with-a-Minimal-WebApi-Project.aspx
What’s not working is the following code to try to get the API results into an array or list. When I run it in Debug it gives me an exception “One or more errors occurred.” which doesn’t give me a lot to go on.
if (response.IsSuccessStatusCode)
{
AddEmployeeResult employeeResults = response.Content.ReadAsAsync<AddEmployeeResult>().Result;
}
The vendor gave me this as an example of what should be returned.
[{"EmployeeNumber":"22449","SuccessfullyAdded":true,"Existing":false,"Comments":"App UserId: 6037"},{"EmployeeNumber":"379844","SuccessfullyAdded":true,"Existing":false,"Comments":" App UserId: 6038"},{"EmployeeNumber":"23718","SuccessfullyAdded":true,"Existing":false,"Comments":" App UserId ps: 6039"}]
I created a class in my console app to match what is supposed to be returned from the API.
class AddEmployeeResult
{
public string EmployeeNumber { get; set; }
public bool SuccessfullyAdded { get; set; }
public bool Existing { get; set; }
public string Comments { get; set; }
}
Here is the code I’m trying.
static void Main(string[] args)
{
UploadNewEmployee();
}
public static void UploadNewEmployee()
{
string serviceURI = "https://services.website.com/api/employees/upload";
var credentials = new NetworkCredential("username", "password");
var clientHandler = new HttpClientHandler();
clientHandler.Credentials = credentials;
clientHandler.PreAuthenticate = true;
//*** Get the Employee Data ***
List<Employee> lstEmployee = new List<Employee>();
try
{
lstEmployee = DataManager.GetEmployees();
}
catch (Exception ex)
{
logger.Error("Error while getting data. | {0} | Trace: {1}", ex.Message, ex.StackTrace);
}
//*** Process users if there is data. ***
if (lstEmployee != null && lstEmployee.Count > 0)
{
logger.Info("Employee count: " + lstEmployee.Count);
logger.Debug("Web API Uri: " + serviceURI);
try
{
using (var client = new HttpClient(clientHandler))
{
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.PostAsJsonAsync(serviceURI, lstEmployee).Result;
if (response.IsSuccessStatusCode)
{
AddEmployeeResult employeeResults = response.Content.ReadAsAsync<AddEmployeeResult>().Result;
}
}
}
catch (Exception ex)
{
logger.Error("Error posting to Web API. | {0} | Trace: {1}", ex.Message, ex.StackTrace);
}
}
else
{
logger.Info("No records found.");
}
}
I wish my WPF application to communicate with a webapi I'm in the process (once I get past this) of writing. I've never connected to two up before and I'm struggling to perform the most basic of functions, to register a user.
I'm using the standard MVC + WebApi implementation from VS 2013 with individual accounts.
I'm looking at other SO code spinets to understand how to connect the two, but I keep getting bad request back when I call API.
I've tried two different methods to format the JSON, without success.
Where am I going wrong?
public async void RegisterUser()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:2045/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var rbm = new RegisterBindingModel();
rbm.UserName = "someemail";
rbm.Password = "somepassword";
rbm.ConfirmPassword = "somepassword";
//MediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
//HttpContent content = new ObjectContent<RegisterBindingModel>(rbm, jsonFormatter);
//var resp = client.PostAsync("api/Account/Register", content).Result;
var response = await client.PostAsJsonAsync("api/Account/Register", rbm);
}
}
public class RegisterBindingModel
{
public string UserName { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
ok, the problem with trying to use other peoples code from SO to learn is that it sometimes has mistakes of its own.
Changed the model to
public string Email { get; set; }
now works great :)