Why after adding a header to HttpClientRequestMessage CancellationToken.IsCancellationRequested changes to true - httpclient

I use custom HttpClientHandler to authorize test if it is not authorized.
Windows Store Unit Test App project type is used
using Microsoft.WindowsAzure.MobileServices;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.Security.Credentials;
using Windows.UI.Popups;
using XperiAndri.Efficiency.Common.Http.Handlers;
namespace XperiAndri.Efficiency.Common.Tests.Online
{
public class AuthenticationHandler : DelegatingHandler
{
private const string user = "";
private const string password = "";
private const string MobileServiceAuthenticationTokenHeader = "X-ZUMO-AUTH";
private readonly PasswordVault vault = new PasswordVault();
public IMobileServiceClient Client { get; set; }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
var headers = request.Headers;
PasswordCredential credential = FindCredential();
if (credential != null)
{
Client.CurrentUser = new MobileServiceUser(user) { MobileServiceAuthenticationToken = credential.Password };
headers.Remove(MobileServiceAuthenticationTokenHeader);
credential.RetrievePassword();
headers.Add(MobileServiceAuthenticationTokenHeader, credential.Password);
response = await base.SendAsync(request, cancellationToken);
}
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
JObject content = new JObject { { "user", user }, { "password", password } };
var result = await Client.InvokeApiAsync("UnitTestLogin", content, HttpMethod.Post, null);
string token = result["token"].Value<string>();
if (Client.CurrentUser != null)
Client.CurrentUser.MobileServiceAuthenticationToken = token;
else
Client.CurrentUser = new MobileServiceUser(user) { MobileServiceAuthenticationToken = token };
headers.Remove(MobileServiceAuthenticationTokenHeader);
headers.Add(MobileServiceAuthenticationTokenHeader, token); // After execution of this line cancellationToken.IsCancellationRequested changes to true
// try again!
response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode != HttpStatusCode.Unauthorized)
{
if (credential != null)
credential.Password = token;
else
{
credential = new PasswordCredential(Client.ApplicationUri.ToString(), user, token);
vault.Add(credential);
}
}
}
}
return response;
}
private PasswordCredential FindCredential()
{
try
{
return vault.FindAllByResource(Client.ApplicationUri.ToString()).FirstOrDefault();
}
catch
{
return null;
}
}
}
}
Look at the comment at the line
headers.Add(MobileServiceAuthenticationTokenHeader, token);
the second time it is executed.
Can somebody explain why does it happened? Why request becomes cancelled?

You're trying to add header to request after actually receiving response (since you already awaited SendAsync) which logically is not possible and causes cancellation of original request.

Related

Error 500 "Internal Server Error" between Flutter App and my BackEnd

I have an error and i tired to look up it and i still not find it.
Scenario: I made my Backend in C# with VS, the FrontEnd is with Flutter.
all my backend works perfectly with all my Flutter App, but there is one problem with only one Action.
When i try to "Save, Update or Delete " a record, it appears the next message in my app, when i put a revision point in my Flutter code in the action to save/delete or update, apperars the CodeError 500 and "Internal Server Error".
The DB is in Azure, and i allready erase and create it twice, and the error persist.
the error is only with this code, the other Ssave/update or delete action.
attach the Images of my code:
Code of The Backend
using System.ComponentModel.DataAnnotations;
namespace Vehicles.API.Models.Request
{
public class HistoryRequest
{
public int Id { get; set; }
[Required(ErrorMessage = "El campo {0} es obligatorio")]
public int VehicleId { get; set; }
[Required(ErrorMessage = "El campo {0} es obligatorio")]
public int Mileage { get; set; }
[Required(ErrorMessage = "El campo {0} es obligatorio")]
public string Remarks { get; set; }
}
}
Other Page, the API Contoller
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Vehicles.API.Data;
using Vehicles.API.Data.Entities;
using Vehicles.API.Models.Request;
namespace Vehicles.API.Controllers.API
{
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("api/[controller]")]
[ApiController]
public class HistoriesController : ControllerBase
{
private readonly DataContext _context;
private readonly IUserhelper _userhelper;
public HistoriesController(DataContext context, IUserhelper userhelper)
{
_context = context;
_userhelper = userhelper;
}
[HttpGet("{id}")]
public async Task<ActionResult<History>> GetHistory(int id)
{
History history = await _context.Histories
.Include(x => x.Details)
.ThenInclude(x => x.Procedure)
.FirstOrDefaultAsync(x => x.Id == id);
if (history == null)
{
return NotFound();
}
return history;
}
[HttpPost]
public async Task<ActionResult<History>> PostHistory(HistoryRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Vehicle vehicle = await _context.Vehicles.FindAsync(request.VehicleId);
if (vehicle == null)
{
return BadRequest("El Vehiculo no existe");
}
string email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;
User user = await _userhelper.GetUserAsync(email);
if (user == null)
{
return BadRequest("La usuario no existe");
}
History history = new()
{
Date = DateTime.UtcNow,
Details= new List<Detail>(),
Mileage=request.Mileage,
Remarks=request.Remarks,
User=user,
Vehicle=vehicle,
};
_context.Histories.Add(history);
await _context.SaveChangesAsync();
return Ok(history);
}
[HttpPut("{id}")]
public async Task<IActionResult> PutHistory(int id, HistoryRequest request)
{
if (id != request.Id)
{
return BadRequest();
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
History history = await _context.Histories.FindAsync(request.Id);
if (history == null)
{
return BadRequest("La historia no existe");
}
history.Mileage = request.Mileage;
history.Remarks= request.Remarks;
_context.Histories.Update(history);
await _context.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteHistory(int id)
{
History history = await _context.Histories
.Include(x => x.Details)
.FirstOrDefaultAsync(x => x.Id == id);
if (history == null)
{
return NotFound();
}
_context.Histories.Remove(history);
await _context.SaveChangesAsync();
return NoContent();
}
}
}
Code in VC Flutter:
Response response = await ApiHelper.delete(
'/api/Histories/', widget.history.id.toString(), widget.token);
setState(() {
_showLoader = false;
});
if (!response.isSuccess) {
await showAlertDialog(
context: context,
title: 'Error',
message: response.message,
actions: <AlertDialogAction>[
AlertDialogAction(key: null, label: 'Aceptar'),
]);
return;
}
Navigator.pop(context, 'yes');
}
And the Code of the API code to conect with the backend:
static Future<Response> delete(
String controller, String id, Token token) async {
if (!_validToken(token)) {
return Response(
isSuccess: false,
message:
'Sus Credenciales se han vencido, favor cierre sesion y vuelva a ingresar nuevamente al sistema.');
}
var url = Uri.parse('${Constans.apiUrl}$controller$id');
var response = await http.delete(
url,
headers: {
'content-type': 'application/json',
'accept': 'application/json',
'authorization': 'bearer ${token.token}',
},
);
if (response.statusCode >= 400) {
return Response(isSuccess: false, message: response.body);
}
return Response(isSuccess: true);
}

System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification calling Autorest generated code

I have a unit test, calling a service that makes use of Autorest generated code to call my Api.
I want my unit test to display the error that my Api is throwing, but there seems to be an error in the service's error handling.
I am using the following command to generate code to consume my api.
autorest --input-file=https://mywebsite.com.au:4433/myapi/api-docs/v1/swagger.json --output-folder=generated --csharp --namespace=MyConnector
The generated "client code" contains
/// <param name='request'>
/// </param>
/// <param name='customHeaders'>
/// Headers that will be added to request.
/// </param>
/// <param name='cancellationToken'>
/// The cancellation token.
/// </param>
/// <exception cref="HttpOperationException">
/// Thrown when the operation returned an invalid status code
/// </exception>
/// <exception cref="SerializationException">
/// Thrown when unable to deserialize the response
/// </exception>
/// <return>
/// A response object containing the response body and response headers.
/// </return>
public async Task<HttpOperationResponse<GetAvailableCarriersResponse>> GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(GetAvailableCarriersRequest request = default(GetAvailableCarriersRequest), Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
{
// Tracing
bool _shouldTrace = ServiceClientTracing.IsEnabled;
string _invocationId = null;
if (_shouldTrace)
{
_invocationId = ServiceClientTracing.NextInvocationId.ToString();
Dictionary<string, object> tracingParameters = new Dictionary<string, object>();
tracingParameters.Add("request", request);
tracingParameters.Add("cancellationToken", cancellationToken);
ServiceClientTracing.Enter(_invocationId, this, "GetAvailableCarriersByJobHeaderId", tracingParameters);
}
// Construct URL
var _baseUrl = BaseUri.AbsoluteUri;
var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "api/shipping-management/Get-Available-Carriers").ToString();
// Create HTTP transport objects
var _httpRequest = new HttpRequestMessage();
HttpResponseMessage _httpResponse = null;
_httpRequest.Method = new HttpMethod("POST");
_httpRequest.RequestUri = new System.Uri(_url);
// Set Headers
if (customHeaders != null)
{
foreach(var _header in customHeaders)
{
if (_httpRequest.Headers.Contains(_header.Key))
{
_httpRequest.Headers.Remove(_header.Key);
}
_httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value);
}
}
// Serialize Request
string _requestContent = null;
if(request != null)
{
_requestContent = SafeJsonConvert.SerializeObject(request, SerializationSettings);
_httpRequest.Content = new StringContent(_requestContent, System.Text.Encoding.UTF8);
_httpRequest.Content.Headers.ContentType =System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json-patch+json; charset=utf-8");
}
// Send Request
if (_shouldTrace)
{
ServiceClientTracing.SendRequest(_invocationId, _httpRequest);
}
cancellationToken.ThrowIfCancellationRequested();
_httpResponse = await HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false);
if (_shouldTrace)
{
ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse);
}
HttpStatusCode _statusCode = _httpResponse.StatusCode;
cancellationToken.ThrowIfCancellationRequested();
string _responseContent = null;
if ((int)_statusCode != 200)
{
var ex = new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", _statusCode));
if (_httpResponse.Content != null) {
_responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
}
else {
_responseContent = string.Empty;
}
ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent);
ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent);
if (_shouldTrace)
{
ServiceClientTracing.Error(_invocationId, ex);
}
_httpRequest.Dispose();
if (_httpResponse != null)
{
_httpResponse.Dispose();
}
throw ex;
}
// Create Result
var _result = new HttpOperationResponse<GetAvailableCarriersResponse>();
_result.Request = _httpRequest;
_result.Response = _httpResponse;
// Deserialize Response
if ((int)_statusCode == 200)
{
_responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
_result.Body = SafeJsonConvert.DeserializeObject<GetAvailableCarriersResponse>(_responseContent, DeserializationSettings);
}
catch (JsonException ex)
{
_httpRequest.Dispose();
if (_httpResponse != null)
{
_httpResponse.Dispose();
}
throw new SerializationException("Unable to deserialize the response.", _responseContent, ex);
}
}
if (_shouldTrace)
{
ServiceClientTracing.Exit(_invocationId, _result);
}
return _result;
}
I have a unit test to call the generated code using
var api = MakeApi();
var task=api.GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(req);
var carriers = task.Result.Body.Carriers;
where
private static MyApiService MakeApi()
{
var setting = new MyAPISettings(false);
var api = new MyApiService(setting);
return api;
}
and MyApiService contains (with altered namespaces)
public Task<HttpOperationResponse<GetAvailableCarriersResponse>> GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(
GetAvailableCarriersRequest request = default(GetAvailableCarriersRequest), Dictionary<string, List<string>> customHeaders = null,
CancellationToken cancellationToken = default(CancellationToken))
{
return ApiCaller.ExecuteAsync(
async headers => await API.GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(request, headers, cancellationToken),
async () => await GetTokenHeadersAsync(customHeaders));
}
where apicaller is
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyServices
{
public static class ApiCaller
{
private static Dictionary<string, List<string>> Headers { get; set; }
private static string GetHeadersMessage()
{
string ret = "";
if (Headers != null)
{
foreach (string key in Headers.Keys)
{
if (Headers[key] != null)
{
foreach (string value in Headers[key])
{
ret = $"{key}-{value}\n";
}
}
}
}
return ret;
}
public async static Task<T> ExecuteAsync<T>(Func<Dictionary<string, List<string>>, Task<T>> f,
Func<Task<Dictionary<string, List<string>>>> getHeaders)
{
T ret = default(T);
try
{
try
{
if (getHeaders != null && Headers == null)
{
Headers = await getHeaders();
}
ret = await f(Headers);
}
catch (Microsoft.Rest.HttpOperationException ex1)
{
if (ex1.Response?.StatusCode == System.Net.HttpStatusCode.Unauthorized && getHeaders != null)
{
Headers = await getHeaders();
ret = await f(Headers);
}
else
{
throw;
}
}
}
catch (Exception ex)
{
//Log.Error(ex, $"... API CALL ERROR ...\nHEADERS:{GetHeadersMessage()}");
throw new Exception($"Error calling the API. {ex.Message}", ex);
}
return ret;
}
}
}
My Api throws an InternalServerError
However when I run the unit test, I get an error in the client code.
The error occurs at
// Create Result
var _result = new HttpOperationResponse<GetAvailableCarriersResponse>();
And is
System.Exception: Error calling the API. Operation returned an invalid status code 'InternalServerError' ---> Microsoft.Rest.HttpOperationException: Operation returned an invalid status code 'InternalServerError'
at MyConnector.MyApi.<GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync>d__49.MoveNext()
How can I work around this?
I note that the code for HttpOperationResponse is
namespace Microsoft.Rest
{
/// <summary>
/// Represents the base return type of all ServiceClient REST operations.
/// </summary>
public class HttpOperationResponse<T> : HttpOperationResponse, IHttpOperationResponse<T>, IHttpOperationResponse
{
/// <summary>Gets or sets the response object.</summary>
public T Body { get; set; }
}
}
Here is the structure for GetAvailableCarriersResponse
using Newtonsoft.Json;
using System.Collections.Generic;
public partial class GetAvailableCarriersResponse
{
public GetAvailableCarriersResponse()
{
CustomInit();
}
public GetAvailableCarriersResponse(IList<DeliverBy> carriers = default(IList<DeliverBy>))
{
Carriers = carriers;
CustomInit();
}
partial void CustomInit();
[JsonProperty(PropertyName = "carriers")]
public IList<DeliverBy> Carriers { get; set; }
}
[Update]
In ApiCaller ExecuteAsync the following executes.
throw;
If I catch the error at this point, it's (edited) ToString() returns
"Microsoft.Rest.HttpOperationException: Operation returned an invalid status code 'InternalServerError' at MyAPI.
<GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync>d__49.MoveNext() in
MyConnector\\generated\\MyAPI.cs:line 4018
End of stack trace from previous location where exception was thrown at
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at
System.Runtime.CompilerServices.TaskAwaiter.
HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at MyApiService.<>c__DisplayClass39_0.<<GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync>b__0>d.MoveNext()
in MyApiService.cs:line 339
End of stack trace from previous location where exception was thrown
at System.Runtime.ExceptionServices.ExceptionDispatch
Info.Throw() at System.Runtime.CompilerServices.TaskAwaiter.
HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at MyServices.ApiCaller.<ExecuteAsync>d__5`1.MoveNext()
in ApiCaller.cs:line 50"
I edited some of the names in the above code to simplify and obfuscate.
[Update]
The problem seems to be to do with the getHeaders parameter to ApiCaller.ExecuteAsync
[Update]
If I examine ex1 thrown in ExecuteAsync, I can get my Api Error type using
ex1.Response.StatusCode
but how do I get the error description?
What I did to get error description is to cast it to one of the error types generated by Autorest.
if (myRawResponseObject is My422Response my422Response)
{
// Response has HTTP Status Code 422
Console.WriteLine($"Error Response Type: {my422Response.ToString()}");
}
If you OpenAPI document defines error properties for a 422 response, then you will find them on the My422Response object.

Npgsql Performance

I am trying to implement Npgsql in our DAL and running into issues under heavy load. the following sample application is a decent representation of just a simple query that under heavy load, throws a 'A command is already in progress' exception. I am assuming this is due to the lack of MARS support so I also tried creating a connection each time with a using statement around each command only to have the performance become unusable. I checked that the username is indexed so that shouldn't be an issue.
Not sure what I am doing wrong here but I need some advice on how to get this performing well.
OS: Docker Container: microsoft/dotnet:2.1.301-sdk
using Npgsql;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Threading.Tasks;
namespace npgsqlTest
{
class Program
{
static async Task Main(string[] args)
{
DAL dal = new DAL();
dal.Prepare();
var tasks = dal.Users.Select(async user =>
{
Console.WriteLine(await dal.RunTest(user));
});
await Task.WhenAll(tasks);
}
}
public class DAL
{
private static string _ConnectionString;
private NpgsqlConnection _Connection;
public List<string> Users { get; set; } = new List<string>();
public DAL()
{
_ConnectionString = $"Host=192.168.1.1;Username=admin;Port=5432;Password=password;Database=BigDB;";
_Connection = new NpgsqlConnection(_ConnectionString);
_Connection.Open();
}
public void Prepare()
{
string query = "SELECT username FROM usertable;";
using (var cmd = new NpgsqlCommand(query, _Connection))
{
var reader = cmd.ExecuteReader();
using (reader)
{
while (reader.Read())
{
Users.Add(reader[0].ToString());
}
}
}
}
public async Task<string> RunTest(string user)
{
var parameters = new Dictionary<string, Object> { { "username", user } };
var query = $"SELECT name FROM usertable WHERE username = (#username);";
var reader = await QueryAsync(query, parameters);
using (reader)
{
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var name = reader["name"];
if (!(hash is DBNull))
return (string)name;
}
}
}
return String.Empty;
}
public async Task<DbDataReader> QueryAsync(string query, Dictionary<string, Object> parameters)
{
using (var cmd = new NpgsqlCommand(query, _Connection))
{
foreach (var parameter in parameters)
{
cmd.Parameters.AddWithValue(parameter.Key, parameter.Value == null ? DBNull.Value : parameter.Value);
}
cmd.Prepare();
return await cmd.ExecuteReaderAsync();
}
}
}
}

Getting Data from HTTP Request Xamarin

I want to get and pass data from my HTTPWebRequest to my sync page. I am new to MVVM so I don't know if I am correct but so far I can check if there is a result. I tried to watch a lots of video tutorials and forums still I can't find what I need. I was hoping you guys have a answer for me. I have been struggling with this for a week now.
The flow of my project:
- The user will login when it is correct, The user will be redirected to my sync page where it will get all the users data from my server and insert the data into SQLite Database.
My questions are:
1. How can I get the data and specific data from my HTTPWebRequest?
2. Is my HTTPWebRequest service correct?
3. Is my MVVM correct?
4. Is my navigation to my next page is correct?
My code:
LoginPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TBSMobileApplication.Data;
using TBSMobileApplication.ViewModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TBSMobileApplication.View
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class LoginPage : ContentPage
{
public LoginPageViewModel loginModel;
public LoginPage ()
{
InitializeComponent ();
BindingContext = new LoginPageViewModel();
MessagingCenter.Subscribe<LoginPageViewModel,string>(this, "Login Alert", (sender,Username) =>
{
DisplayAlert("Login Error", "Please fill-up the form", "Ok");
});
MessagingCenter.Subscribe<LoginPageViewModel, string>(this, "Connected", (sender, Username) =>
{
DisplayAlert("Connection Info", "Connected", "Ok");
});
MessagingCenter.Subscribe<LoginPageViewModel, string>(this, "Not Connected", (sender, Username) =>
{
DisplayAlert("Connection Info", "Not Connected", "Ok");
});
MessagingCenter.Subscribe<LoginPageViewModel, string>(this, "Http", (sender, Username) =>
{
DisplayAlert("Login Error", "Username or Password is incorrect", "Ok");
});
entUsername.Completed += (object sender, EventArgs e) =>
{
entPassword.Focus();
};
}
protected async Task OnAppearingAsync()
{
var db = DependencyService.Get<ISQLiteDB>();
var conn = db.GetConnection();
if (conn != null)
{
await conn.CreateTableAsync<UserTable>();
await conn.CreateTableAsync<RetailerTable>();
await conn.CreateTableAsync<ContactsTable>();
}
base.OnAppearing();
}
}
}
LoginPageViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Text;
using System.Windows.Input;
using TBSMobileApplication.View;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace TBSMobileApplication.ViewModel
{
public class LoginPageViewModel : INotifyPropertyChanged
{
void OnProperyChanged(string PropertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
public string username;
public string password;
public string Username
{
get { return username; }
set
{
username = value;
OnProperyChanged(nameof(Username));
}
}
public string Password
{
get { return password; }
set
{
password = value;
OnProperyChanged(nameof(Password));
}
}
public ICommand LoginCommand { get; set; }
public LoginPageViewModel()
{
LoginCommand = new Command(OnLogin);
}
public void OnLogin()
{
if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(Password))
{
MessagingCenter.Send(this, "Login Alert", Username);
}
else
{
var current = Connectivity.NetworkAccess;
if (current == NetworkAccess.Internet)
{
var link = "http://192.168.1.25:7777/TBS/test.php?User=" + Username + "&Password=" + Password;
var request = HttpWebRequest.Create(string.Format(#link));
request.ContentType = "application/json";
request.Method = "GET";
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.OK)
{
Console.Out.WriteLine("Error fetching data. Server returned status code: {0}", response.StatusCode);
}
else
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
var content = reader.ReadToEnd();
if (content.Equals("[]") || string.IsNullOrWhiteSpace(content) || string.IsNullOrEmpty(content))
{
MessagingCenter.Send(this, "Http", Username);
}
else
{
App.Current.MainPage.Navigation.PushAsync(new DatabaseSyncPage(), true);
}
}
}
}
}
else
{
MessagingCenter.Send(this, "Not Connected", Username);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

Two factor auth with IdentityServer3 - remember browser

I'm implementing 2fa with IdentityServer3 + Asp.Net Identity (2.2.1). I'm stuck on the 2fa implementation. I've looked at the "AspNetIdentity_2fa" sample, which helped a lot.
I have everything wired up, except for the cookie that indicates the browser has been successfully authenticated. I can set the cookie during the code confirmation, but I cannot get to the cookie in the PostAuthenticateLocalAsync() call to see whether or not to take the 2fa path.
protected override Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
{
if (user.TwoFactorEnabled) // && !TwoFactorCookieSet...
{
return Task.FromResult(new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName));
}
return base.PostAuthenticateLocalAsync(user, message);
}
I believe I'm taking the correct approach in using the partial logins, but how would I detect that the current browser has already been approved?
More detail: the /auth/sendcode is the standard Asp.Net Identity pages/flow for 2fa, combined with the partial login logic from the sample.
Okay, I found that OwinEnvironmentService can be injected into IdentityServer services. I can get the cookies via OwinEnvironmentService. I'd be interested to hear any opinions on this solution (this isn't meant to be production-ready, it's just a concept):
internal class UserService : AspNetIdentityUserService<User, string>
{
private readonly OwinEnvironmentService _owinEnvironmentService;
public UserService(UserManager userMgr, OwinEnvironmentService owinEnvironmentService) : base(userMgr)
{
_owinEnvironmentService = owinEnvironmentService;
DisplayNameClaimType = IdentityServer3.Core.Constants.ClaimTypes.Name;
}
protected override Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
{
if (user.TwoFactorEnabled)
{
var twoFactorNeeded = false;
object httpContext;
if (_owinEnvironmentService.Environment.TryGetValue("System.Web.HttpContextBase", out httpContext))
{
var cookies = (httpContext as HttpContext)?.Request.Cookies;
if (cookies != null && !cookies.AllKeys.Contains(IdentityConstants.CookieNames.TwoFactorCompleted)) twoFactorNeeded = true;
}
if (twoFactorNeeded)
return Task.FromResult(new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName));
}
return base.PostAuthenticateLocalAsync(user, message);
}
}
UPDATED
Based on Brock's comment, I think I have a better solution.
// custom User Service
internal class UserService : AspNetIdentityUserService<User, string>
{
private readonly OwinEnvironmentService _owinEnvironmentService;
public UserService(UserManager userMgr, OwinEnvironmentService owinEnvironmentService) : base(userMgr)
{
_owinEnvironmentService = owinEnvironmentService;
DisplayNameClaimType = IdentityServer3.Core.Constants.ClaimTypes.Name;
}
protected override async Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
{
if (user.TwoFactorEnabled)
{
var owinContext = new OwinContext(_owinEnvironmentService.Environment);
var result = await owinContext.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
if(result == null) return new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName);
}
return await base.PostAuthenticateLocalAsync(user, message);
}
}
// (in MVC controller) generate the 2FA security code and send it
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
// ...some code removed for brevity...
var token = await UserManager.GenerateTwoFactorTokenAsync(userId, model.SelectedProvider);
var identityResult = await UserManager.NotifyTwoFactorTokenAsync(userId, model.SelectedProvider, token);
if (!identityResult.Succeeded) return View("Error");
return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, model.ReturnUrl, model.RememberMe });
}
// (in MVC controller) verify the code and sign in with 2FA
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model)
{
// ...some code removed for brevity...
var signInManager = new SignInManager<User, string>(UserManager, Request.GetOwinContext().Authentication);
if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, model.Provider, model.Code))
{
await UserManager.ResetAccessFailedCountAsync(user.Id);
await signInManager.SignInAsync(user, model.RememberMe, model.RememberBrowser);
var resumeUrl = await env.GetPartialLoginResumeUrlAsync();
return Redirect(resumeUrl);
}
else
{
await UserManager.AccessFailedAsync(user.Id);
ModelState.AddModelError("", "Invalid code.");
return View(model);
}
}
I implemented the same for remember browser requirement however following statement return always null when we logout and login again.so twofactory step is not skipped..
var result = await owinContext.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);