I'm trying to return an appropriate error message when a user tries to call the service when their authorization token has expired or if they have an invalid token.
The problem I'm having is the first time it is called, the message is sent properly, but after the first time the SendAsync method is called 4 times and the message data returns null.
I'm confused as to why it loops 4 times, and I tried stepping through it, but I can't get any further in the code.
Here is the code:
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Headers != null)
{
// ....
if (request.Headers.GetValues(CustomTokenHeader).FirstOrDefault() == null)
{
//unauthorized response(401)
return FromResult(_unauthorizedResponse);
}
var authHeader = request.Headers.GetValues(CustomTokenHeader).FirstOrDefault();
if (String.IsNullOrWhiteSpace(authHeader))
{
//unauthorized response(401)
return FromResult(_unauthorizedResponse);
}
//authenticate token
return HandleTokenAuthentication(request, cancellationToken, authHeader);
}
}
static Task<T> FromResult<T>(T t)
{
var tcs = new TaskCompletionSource<T>();
tcs.SetResult(t);
return tcs.Task;
}
private Task<HttpResponseMessage> HandleTokenAuthentication(HttpRequestMessage request, CancellationToken cancellationToken, string authHeader)
{
//parse token
var token = ParseToken(authHeader);
if (String.IsNullOrWhiteSpace(token))
{
//unauthorized response(401)
return FromResult(_unauthorizedResponse);
}
//decrypt token
var tokenInfo = DecryptToken(token);
if (tokenInfo == null)
{
//unauthorized response(401)
return FromResult(_unauthorizedResponse);
}
//validate token
var claims = ValidateToken(tokenInfo, token);
if (claims == null)
{
//unauthorized response(401)
return FromResult(_unauthorizedTokenExpired);
}
var principal = CheckCustomAuthorization(claims);
if (principal == null)
{
//unauthorized response(401)
return FromResult(_unauthorizedResponse);
}
if (!principal.Identity.IsAuthenticated)
{
var loginFailureMessage = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new StringContent(((AgencyClaims)principal.Identity).LoginFailureReason)
};
return FromResult(loginFailureMessage);
}
//assign principal
Thread.CurrentPrincipal = principal;
return base.SendAsync(request, cancellationToken)
.ContinueWith(task => AuthorizedResponse(request, task.Result));
}
static HttpResponseMessage AuthorizedResponse(HttpRequestMessage request, HttpResponseMessage response)
{
if ((request.Method == HttpMethod.Get && response.StatusCode == HttpStatusCode.OK
&& !response.Headers.Contains(CustomTokenHeader))
|| (request.Method == HttpMethod.Post && response.StatusCode == HttpStatusCode.Created
&& !response.Headers.Contains(CustomTokenHeader)))
{
var token = ((AgencyClaims) Thread.CurrentPrincipal.Identity).Token;
response.Headers.Add(CustomTokenHeader, Convert.ToBase64String(Encoding.ASCII.GetBytes(token)));
}
return response;
}
readonly HttpResponseMessage _unauthorizedResponse =
new HttpResponseMessage(HttpStatusCode.Unauthorized) { Content = new StringContent("PROPER ERROR MESSAGE")};
Successful response:
<data contentType="text/plain; charset=utf-8" contentLength="21"><![CDATA[Authentication failed]]></data>
Here is the response after the first successful response:
<data contentType="null" contentLength="0"><![CDATA[]]></data>
Here is part of the request:
GET http://localhost:20559/api/Service?Name=Jack HTTP/1.1
Ok, I was able to resolve the issue. The _unauthorizedResponse class variable is somehow allowing the code to run once through successfully, but not a second time. This issue is not related to the readonly modifier, since it still doesn't work without it. I'm not sure how this works (and maybe someone on here can explain), but by moving them into the local scope within the method it is able to run correctly each time.
Related
In my ASP.NET Core-6 Web API Entity Framework Core, I have this code for user Registration:
public async Task<Response<string>> RegisterUserAsync(string userId)
{
var response = new Response<string>();
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
try
{
var userDetail = _dataAccess.GetSaffUserProfile(userId);
adminAccess = _config.GetSection("DDMAdminCredentials").GetValue<string>("adminaccess");
if (string.IsNullOrEmpty(userDetail.user_logon_name))
{
_logger.Error("Failed Authentication");
transaction.Dispose();
response.Successful = false;
response.StatusCode = (int)HttpStatusCode.BadRequest;
response.Message = "Staff Record Not Found!";
return response;
}
var user = new ApplicationUser()
{
UserName = userDetail.user_logon_name.ToLower().Trim(),
Email = string.IsNullOrWhiteSpace(userDetail.email) ? null : userDetail.email.Trim().ToLower(),
FirstName = string.IsNullOrWhiteSpace(userDetail.firstname) ? null : userDetail.firstname.Trim(),
LastName = string.IsNullOrWhiteSpace(userDetail.lastname) ? null : userDetail.lastname.Trim(),
MobileNumber = string.IsNullOrWhiteSpace(userDetail.mobile_phone) ? null : userDetail.mobile_phone.Trim()
CreatedAt = DateTime.Now
};
var result = await _userManager.CreateAsync(user, adminAccess);
if (result.Succeeded)
{
// Insert User Role
await _userManager.AddToRoleAsync(user, userRole);
userBranchNo = userDetail.branch_code.TrimStart('0');
var bankBranchId = _dbContext.BankBranches.Where(u => u.BranchNumber.ToString() == userBranchNo.ToString()).Select(m => m.Id).FirstOrDefault();
var bankUser = new BankUser()
{
UserId = user.Id,
BankBranchId = bankBranchId,
CreatedBy = "System"
};
await _unitOfWork.AdminUsers.InsertAsync(bankUser);
await _unitOfWork.Save();
response.StatusCode = (int)HttpStatusCode.Created;
response.Successful = true;
response.Data = user.Id;
response.Message = "Bank User Created Successfully!";
transaction.Complete();
await _signInManager.SignInAsync(user, isPersistent: false);// user, true, false
return response;
}
response.Message = GetErrors(result);
response.StatusCode = (int)HttpStatusCode.BadRequest;
response.Successful = false;
transaction.Dispose();
return response;
}
catch (Exception ex)
{
response.Message = "An error occured :" + ex.Message;
response.Successful = false;
response.StatusCode = (int)HttpStatusCode.BadRequest;
return response;
}
};
}
The application should immediately log in as soon as the user is created.
However, I got this error in the log:
An Error occured The current TransactionScope is already complete.
How do I get this resolved?
Thanks
You complete the transaction scope and then SignInManager tries to use it. You should complete the scope after signing user in:
await _signInManager.SignInAsync(user, isPersistent: false);
transaction.Complete();
What is also very important is the maintainability of your code - it's a single method, long and imperative. Split it to improve readability. You can have small top-level method which just gets required for registration data and passes it further:
public async Task<Response<string>> RegisterUserAsync(string userId)
{
try
{
var userProfile = GetStaffUserProfile(userId);
if (userProfile == null)
return BadRequestResponse("Staff Record Not Found!");
var user = _mapper.Map<ApplicationUser>(userProfile);
return await RegisterUserAsync(user, userProfile.GetBranchNo());
}
catch(Exception ex)
{
return BadRequestResponse(ex);
}
}
A few things were extracted here. Getting user profile:
private UserDetail? GetStaffUserProfile(string userId)
{
var userDetail = _dataAccess.GetSaffUserProfile(userId);
if (!userDetail.IsAuthenticated())
{
_logger.Error("Failed Authentication");
return null;
}
return userDetail;
}
Mapping exceptions, strings, and IdentityResults to responses (Note that you should not wrap all exceptions into a bad request response, because not all exceptions will be caused by bad requests from clients. There could be also internal server errors):
private Response<string> BadRequestResponse(IdentityResult result) =>
BadRequestResponse(GetErrors(result)); // create this on your own
private Response<string> BadRequestResponse(Exception ex) =>
new() {
Message = "An error occured :" + ex.Message,
Successful = false,
StatusCode = (int)HttpStatusCode.BadRequest
};
Declarative access to user details data:
public static class UserDetailExtensions
{
public static bool IsAuthenticated(UserDetail userDetail) =>
!String.IsNullOrEmpty(userDetail.user_logon_name);
public static string GetBranchNo(this UserDetail userDetail) =>
userDetail.branch_code.TrimStart('0');
}
Mapping user details to ApplicationUser is done by something like AutoMapper but you can extract method for manual mapping. And now you have a relatively small method for registering user:
private async Task<Response<string>> RegisterUserAsync(
ApplicationUser user, string userBranchNo)
{
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
var result = await _userManager.CreateAsync(user, adminAccessPassword);
if (!result.Succeeded)
return BadRequestResponse(result);
await _userManager.AddToRoleAsync(user, userRole);
await CreateBankUserAsync(user.Id, userBranchNo);
await _signInManager.SignInAsync(user, isPersistent: false);
transaction.Complete();
}
return new() {
StatusCode = (int)HttpStatusCode.Created,
Successful = true,
Data = user.Id,
Message = "Bank User Created Successfully!"
};
}
With a small helper method:
private async Task CreateBankUserAsync(string userId, string userBranchNo)
{
var bankBranchId = _dbContext.BankBranches
.Where(b => b.BranchNumber.ToString() == userBranchNo)
.Select(b => b.Id)
.FirstOrDefault();
var bankUser = new BankUser() {
UserId = userId,
BankBranchId = bankBranchId,
CreatedBy = "System"
};
await _unitOfWork.AdminUsers.InsertAsync(bankUser);
await _unitOfWork.Save();
}
Config reading of adminAccessPassword should be moved to the application startup logic.
I have the following code that is supposed to call the ExecuteAsync function from Polly. However, after return objectResponse_.Object; the call does not return to calling function and basically gets lost (events show thread has exited with code 0). I am sure I am doing something wrong with the await async but I can't tell what.
BTW, the Parallel Stacks shows
public async Task < T > SendRequestAsync < T > (HttpRequestMessage request, CancellationToken cancellationToken, bool someBool = true) {
var retryPolicy = Policy.Handle < Exception > ().Or < HttpRequestException > ().RetryAsync(3, onRetry: (exception, retryCount, context) =>{
_logger.Log($"API call failed. Doing retry attempt {retryCount}");
});
await retryPolicy.ExecuteAsync(async() =>{
using(var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false)) {
var headers = System.Linq.Enumerable.ToDictionary(response.Headers, h =>h.Key, h =>h.Value);
if (response.Content ? .Headers != null) {
foreach(var item in response.Content.Headers) {
headers[item.Key] = item.Value;
}
}
var status = (int) response.StatusCode;
switch (status) {
case 200:
case 201:
case 204:
{
if (someBool) {
var objectResponse_ = await ReadObjectResponseAsync < T > (response, headers, cancellationToken).ConfigureAwait(false);
if (objectResponse_.Object == null) {
throw new MyException("Response was null which was not expected.", status, objectResponse_.Text, headers, null);
}
return objectResponse_.Object;
}
else {
var objectResponse_ = await ReadObjectResponseAsync < T > (response, headers, cancellationToken);
if (objectResponse_.Object != null) return objectResponse_.Object;
else return
default (T);
}
}
case 404:
{
if (someBool) {
var objectResponse_ = await ReadObjectResponseAsync < T > (response, headers, cancellationToken).ConfigureAwait(false);
return objectResponse_.Object;
}
var responseText = (response.Content == null) ? string.Empty: await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new MyException("Not Found", status, responseText, headers, null);
}
case 500:
{
if (someBool) {
var objectResponse_ = await ReadObjectResponseAsync < T > (response, headers, cancellationToken).ConfigureAwait(false);
return objectResponse_.Object;
}
var objectResponse = await ReadObjectResponseAsync < MyResponse > (response, headers, cancellationToken).ConfigureAwait(false);
if (objectResponse.Object == null) {
throw new MyException("Response was null which was not expected.", status, objectResponse.Text, headers, null);
}
throw new MyException < MyResponse > ("Internal Server Error", status, objectResponse.Object.Message, headers, objectResponse.Object, null);
}
default:
{
var responseData = response.Content == null ? null: await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new MyException("The HTTP status code of the response was not expected (" + status + ").", status, responseData, headers, null);
}
}
}
});
return
default (T);
}
Problems
Whenever you call a return statement inside your async lambda which is provided to the ExecuteAsync you are returning from the async lambda not from the SendRequestAsync
Whenever you call a throw statement inside your async lambda which is provided to the ExecuteAsync you are throwing exception from your async lambda
Which will trigger a retry (because of your .Handle<Exception>() clause)
Don't know it is intentional or not
Solution for Problem #1
Change your policy to return T
var retryPolicy = Policy<T> //previously Policy
.Handle<Exception>()
.Or<HttpRequestException>()
.RetryAsync(3,
(exception, retryCount, context) => ...);
And return the result of ExecuteAsync
return await retryPolicy.ExecuteAsync(async () => {
Solution for Problem #2
I assume that whenever you throw a MyException inside your async lambda then you don't want to retry. If that's the case then all you need to do is to add a predicate to the Handle builder method
var retryPolicy = Policy<T>
.Handle<Exception>(ex => ex is not MyException)
.Or<HttpRequestException>()
.RetryAsync(3,
(exception, retryCount, context) => ...);
I'd want to determine whether JWT Token is valid in [AllowAnonymouse]] endpoint
edit
I have an endpoint which can be accessed by anyone (authorized and unauthorized people) and then: If that user has http authorization header OR he has token in cookies and his token valid then redirect him to X otherwise to Y
Pseudocode of my idea:
[Route("Passport/")]
public IActionResult Passport()
{
if (this.User.Identity.IsAuthenticated)
or pseudocode:
if (tokenIsValid(getJWTTokenFromHeader()));
{
return RedirectToAction("Resources");
}
else
{
return RedirectToAction("Login");
}
}
I thought about something like this:
[Route("Passport/")]
public IActionResult Passport()
{
var token = ExtractTokenFromHeader();
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue
("application/json"));
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var get = client.GetAsync($"http://localhost/verifyToken").Result;
var responseBody = await get.Content.ReadAsStringAsync().ConfigureAwait(false);
switch (get.StatusCode)
{
case HttpStatusCode.Unauthorized:
return RedirectToAction("Login");
case HttpStatusCode.OK:
return RedirectToAction("Resources");
default:
return RedirectToAction(...);
}
}
Where endpoint verifyToken has an [Authorize] attribute and just returns Unauthorized (defaultly) or OK (from code)
You may try below code
private static bool ValidateJWTToken(string token, out string username) {
username = null;
var simplePrinciple = JwtManager.GetPrincipal(token);
var identity = simplePrinciple.Identity as ClaimsIdentity;
if (identity == null) return false;
if (!identity.IsAuthenticated) return false;
}
I am using .NET Framework 4.6.1.
I have a controller in my web api, where I have static HttpClient to handle all the http requests. After I hosted my app on IIS, approximately once in a month, I get the following exception for all the incoming request to my app:
System.ArgumentNullException: Value cannot be null.
at System.Threading.Monitor.Enter(Object obj)
at System.Net.Http.Headers.HttpHeaders.ParseRawHeaderValues(String name, HeaderStoreItemInfo info, Boolean removeEmptyHeader)
at System.Net.Http.Headers.HttpHeaders.AddHeaders(HttpHeaders sourceHeaders)
at System.Net.Http.Headers.HttpRequestHeaders.AddHeaders(HttpHeaders sourceHeaders)
at System.Net.Http.HttpClient.PrepareRequestMessage(HttpRequestMessage request)
at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.PutAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken)
at Attributes.Controllers.AttributesBaseController.<UpdateAttributes>d__6.MoveNext() in D:\Git\PortalSystem\Attributes\Controllers\AttributesBaseController.cs:line 42
If I restart the app pool on IIS, everything starts to work fine again. Here is the code that I have:
public class AttributesBaseController : ApiController
{
[Inject]
public IPortalsRepository PortalsRepository { get; set; }
private static HttpClient Client = new HttpClient(new HttpClientHandler { Proxy = null, UseProxy = false })
{ Timeout = TimeSpan.FromSeconds(double.Parse(WebConfigurationManager.AppSettings["httpTimeout"])) };
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
protected async Task UpdateAttributes(int clientId, int? updateAttrId = null)
{
try
{
Client.DefaultRequestHeaders.Accept.Clear();
Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
#region Update Client Dossier !!! BELOW IS LINE 42 !!!!
using (var response = await Client.PutAsync(new Uri(WebConfigurationManager.AppSettings["dossier"] + "api/dossier?clientId=" + clientId), null))
{
if (!response.IsSuccessStatusCode)
{
logger.Error($"Dossier update failed");
}
}
#endregion
#region Gather Initial Info
var checkSystems = PortalsRepository.GetCheckSystems(clientId);
var currentAttributes = PortalsRepository.GetCurrentAttributes(clientId, checkSystems);
#endregion
List<Task> tasks = new List<Task>();
#region Initialize Tasks
foreach (var cs in checkSystems)
{
if (!string.IsNullOrEmpty(cs.KeyValue))
{
tasks.Add(Task.Run(async () =>
{
var passedAttributes = currentAttributes.Where(ca => ca.SystemId == cs.SystemId && ca.AttributeId == cs.AttributeId &&
(ca.SysClientId == cs.KeyValue || ca.OwnerSysClientId == cs.KeyValue)).ToList();
if (cs.AttributeId == 2 && (updateAttrId == null || updateAttrId == 2))
{
await UpdateOpenWayIndividualCardsInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 3 && (updateAttrId == null || updateAttrId == 3))
{
await UpdateEquationAccountsInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 8 && (updateAttrId == null || updateAttrId == 8))
{
await UpdateOpenWayCorporateInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 9 && (updateAttrId == null || updateAttrId == 9))
{
await UpdateEquationDealsInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 10 && (updateAttrId == null || updateAttrId == 10))
{
await UpdateOpenWayIndividualCardDepositsInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 16 && (updateAttrId == null || updateAttrId == 16))
{
await UpdateOpenWayBonusInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 17 && (/*updateAttrId == null ||*/ updateAttrId == 17))
{
await UpdateExternalCardsInfo(passedAttributes, cs, clientId);
}
if (cs.AttributeId == 18 && (updateAttrId == null || updateAttrId == 18))
{
await UpdateCRSInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 22 && (updateAttrId == null || updateAttrId == 22))
{
await UpdateCardInsuranceInfo(passedAttributes, cs, clientId);
}
}));
}
}
#endregion
// Run all tasks
await Task.WhenAny(Task.WhenAll(tasks.ToArray()), Task.Delay(TimeSpan.FromSeconds(double.Parse(WebConfigurationManager.AppSettings["taskWaitTime"]))));
}
catch (Exception ex)
{
logger.Error(ex);
}
}
}
Can anyone give me advice/help to figure out the problem? I just don't know whether the problem is in the way I am using HttpClient with tasks or something bad happens on IIS.
Looking at the implementation of DefaultRequestHeaders, we can see that it uses a simple Dictionary to store the headers:
private Dictionary<string, HttpHeaders.HeaderStoreItemInfo> headerStore;
DefaultRequestHeaders.Accept.Clear just removes the key from the dictionary, without any kind of synchronization:
public bool Remove(string name)
{
this.CheckHeaderName(name);
if (this.headerStore == null)
return false;
return this.headerStore.Remove(name);
}
Dictionary.Remove isn't thread-safe, unpredictable behavior can happen if you access the dictionary during this operation.
Now if we look at the ParseRawHeaderValues method in your stacktrace:
private bool ParseRawHeaderValues(string name, HttpHeaders.HeaderStoreItemInfo info, bool removeEmptyHeader)
{
lock (info)
{
// stuff
}
return true;
}
We can see that the error would be cause by info to be null. Now looking at the caller:
internal virtual void AddHeaders(HttpHeaders sourceHeaders)
{
if (sourceHeaders.headerStore == null)
return;
List<string> stringList = (List<string>) null;
foreach (KeyValuePair<string, HttpHeaders.HeaderStoreItemInfo> keyValuePair in sourceHeaders.headerStore)
{
if (this.headerStore == null || !this.headerStore.ContainsKey(keyValuePair.Key))
{
HttpHeaders.HeaderStoreItemInfo headerStoreItemInfo = keyValuePair.Value;
if (!sourceHeaders.ParseRawHeaderValues(keyValuePair.Key, headerStoreItemInfo, false))
{
if (stringList == null)
stringList = new List<string>();
stringList.Add(keyValuePair.Key);
}
else
this.AddHeaderInfo(keyValuePair.Key, headerStoreItemInfo);
}
}
if (stringList == null)
return;
foreach (string key in stringList)
sourceHeaders.headerStore.Remove(key);
}
Long story short, we iterate the dictionary in DefaultRequestHeaders (that's sourceHeaders.headerStore) and copy the headers into the request.
Summing it up, at the same time we have a thread iterating the contents of the dictionary, and another adding/removing elements. This can lead to the behavior you're seeing.
To fix this, you have two solutions:
Initialize DefaultRequestHeaders in a static constructor, then never change it:
static AttributesBaseController
{
Client = new HttpClient(new HttpClientHandler { Proxy = null, UseProxy = false })
{
Timeout = TimeSpan.FromSeconds(double.Parse(WebConfigurationManager.AppSettings["httpTimeout"]))
};
Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
Use SendAsync with your custom headers instead of PutAsync:
var message = new HttpRequestMessage(HttpMethod.Put, new Uri(WebConfigurationManager.AppSettings["dossier"] + "api/dossier?clientId=" + clientId));
message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
using (var response = await Client.SendAsync(message))
{
// ...
}
Just for fun, a small repro:
var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var storeField = typeof(HttpHeaders).GetField("headerStore", BindingFlags.Instance | BindingFlags.NonPublic);
FieldInfo valueField = null;
var store = (IEnumerable)storeField.GetValue(client.DefaultRequestHeaders);
foreach (var item in store)
{
valueField = item.GetType().GetField("value", BindingFlags.Instance | BindingFlags.NonPublic);
Console.WriteLine(valueField.GetValue(item));
}
for (int i = 0; i < 8; i++)
{
Task.Run(() =>
{
int iteration = 0;
while (true)
{
iteration++;
try
{
foreach (var item in store)
{
var value = valueField.GetValue(item);
if (value == null)
{
Console.WriteLine("Iteration {0}, value is null", iteration);
}
break;
}
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
}
catch (Exception) { }
}
});
}
Console.ReadLine();
Output:
System.Net.Http.Headers.HttpHeaders+HeaderStoreItemInfo
Iteration 137, value is null
Reproducing the issue may take a few tries because threads tend to get stuck in an infinite loop when accessing the dictionary concurrently (if it happens on your webserver, ASP.NET will abort the thread after the timeout elapses).
Im trying to follow this tutorial: http://bitoftech.net/2014/12/15/secure-asp-net-web-api-using-api-key-authentication-hmac-authentication/
On how to secure a webapi with HMAC. Somewhere in the tutorial code it it gives:
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
var req = context.Request;
if (req.Headers.Authorization != null && authenticationScheme.Equals(req.Headers.Authorization.Scheme, StringComparison.OrdinalIgnoreCase))
{
var rawAuthzHeader = req.Headers.Authorization.Parameter;
var autherizationHeaderArray = GetAutherizationHeaderValues(rawAuthzHeader);
if (autherizationHeaderArray != null)
{
var APPId = autherizationHeaderArray[0];
var incomingBase64Signature = autherizationHeaderArray[1];
var nonce = autherizationHeaderArray[2];
var requestTimeStamp = autherizationHeaderArray[3];
var isValid = isValidRequest(req, APPId, incomingBase64Signature, nonce, requestTimeStamp);
if (isValid.Result)
{
var currentPrincipal = new GenericPrincipal(new GenericIdentity(APPId), null);
context.Principal = currentPrincipal;
}
else
{
context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
}
}
else
{
context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
}
}
else
{
context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
}
return Task.FromResult(0);
}
I copied it of course, but mvc does not know about any unauthorized result with these parameters. So this line gives an error: context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
I can't figure out how to give back the right result. Does anyone know?
I used the wrong namespace. Should be using System.Web.Http.Results;