Got a Xamarin.Forms app which requests data via an API implemented in ASP.NET Core. Before retrieving data the app has to login to the ASP.Net Core application which uses the identity service. The code works fine in iOS Simulator, Android 9 Emulator and on a real iPhone but not on a real Android 9 device! Network connection is working well, because I can access the web application via browser.
I'm using the System.Net.Http.HttpClient in shared project. The identity service requires sending an antiforgery key (By the way: Is there a better way for handling authorization only for the web api? Seems inconvenient handling the antiforgery key). Here's the code which handles the login process:
private async Task LoginAsync(string username, string password)
{
//Get login page with AntiForgeryToken
var pageWithToken = await _httpClient.GetStringAsync(BASE_URL);
//Extract AntiForgeryToken
string verificationToken = GetAntiForgeryToken(pageWithToken);
//Post parameter
var keyValues = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("Input.Email", username),
new KeyValuePair<string, string>("Input.Password", password),
new KeyValuePair<string, string>("__RequestVerificationToken", verificationToken),
new KeyValuePair<string, string>("Input.RememberMe", "false")
};
var request = new HttpRequestMessage(HttpMethod.Post, "Identity/Account/Login")
{
Content = new FormUrlEncodedContent(keyValues)
};
//login with Post parameter
var response = await _httpClient.SendAsync(request);
//read response - can see here that login failed
var content = await response.Content.ReadAsStringAsync();
}
This is what I see on the server side after sending the Post request with login data:
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 POST http://10.219.200.147:33224/Identity/Account/Login application/x-www-form-urlencoded 253
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executing endpoint 'Page: /Account/Login'
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Route matched with {page = "/Account/Login", area = "Identity", action = "", controller = ""}. Executing page /Account/Login
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Executing handler method C4S.WebApp.Areas.Identity.Pages.Account.LoginModel.OnPostAsync with arguments () - ModelState is Valid
Microsoft.EntityFrameworkCore.Infrastructure:Information: Entity Framework Core 2.2.6-servicing-10079 initialized 'ApplicationDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
Der Thread 0x28d4 hat mit Code 0 (0x0) geendet.
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (9ms) [Parameters=[@__normalizedUserName_0='?' (Size = 256)], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [u].[Id], [u].[AccessFailedCount], [u].[ConcurrencyStamp], [u].[Email], [u].[EmailConfirmed], [u].[LockoutEnabled], [u].[LockoutEnd], [u].[NormalizedEmail], [u].[NormalizedUserName], [u].[PasswordHash], [u].[PhoneNumber], [u].[PhoneNumberConfirmed], [u].[SecurityStamp], [u].[TwoFactorEnabled], [u].[UserName]
FROM [AspNetUsers] AS [u]
WHERE [u].[NormalizedUserName] = @__normalizedUserName_0
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (2ms) [Parameters=[@__user_Id_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
SELECT [uc].[Id], [uc].[ClaimType], [uc].[ClaimValue], [uc].[UserId]
FROM [AspNetUserClaims] AS [uc]
WHERE [uc].[UserId] = @__user_Id_0
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: Identity.Application signed in.
C4S.WebApp.Areas.Identity.Pages.Account.LoginModel:Information: User logged in.
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Executed handler method OnPostAsync, returned result Microsoft.AspNetCore.Mvc.LocalRedirectResult.
Microsoft.AspNetCore.Mvc.Infrastructure.LocalRedirectResultExecutor:Information: Executing LocalRedirectResult, redirecting to /.
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Executed page /Account/Login in 112.8493ms
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executed endpoint 'Page: /Account/Login'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 131.0183ms 302
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://10.219.200.147:33224/
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executing endpoint 'Page: /Kontakte/Index'
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Route matched with {page = "/Kontakte/Index", action = "", controller = "", area = ""}. Executing page /Kontakte/Index
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed.
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes ().
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: Identity.Application was challenged.
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Executed page /Kontakte/Index in 6.2539ms
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executed endpoint 'Page: /Kontakte/Index'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 14.9729ms 302
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://10.219.200.147:33224/Identity/Account/Login?ReturnUrl=%2F
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executing endpoint 'Page: /Account/Login'
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Route matched with {page = "/Account/Login", area = "Identity", action = "", controller = ""}. Executing page /Account/Login
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Executing handler method C4S.WebApp.Areas.Identity.Pages.Account.LoginModel.OnGetAsync with arguments (/) - ModelState is Valid
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: Identity.External signed out.
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Executed handler method OnGetAsync, returned result .
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Executing an implicit handler method - ModelState is Valid
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult.
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Executed page /Account/Login in 11.6849ms
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executed endpoint 'Page: /Account/Login'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 20.9535ms 200 text/html; charset=utf-8
I'm wondering why the log first says "User logged in." and later "Authorization failed.". Could there maybe a cookie problem? Just to say, I'm working in a test environment without encryption -> only http.
Thanks for your help!
Finally found a solution! I assumed there's something wrong with Identities cookies because login is successful but further requests seems to forget about that. Identity stores a cookie on clientside for authentication after successfull login and sends it to the server with every request. There's some extra code needed to access HttpClient's cookies while initilization:
HttpClientHandler _httpClientHandler = new HttpClientHandler();
HttpClient _httpClient = new HttpClient(_httpClientHandler) { BaseAddress = new Uri(BASE_URL), Timeout = new TimeSpan(0, 0, 30) };
Adding the HttpClienthandler to the HttpClient did the trick! I'm not sure why that's needed for Android but not for iOS. Maybe someone has a guess?