Search code examples
c#asp.net-coreaspnetboilerplateasp.net-core-identity

System.IO.IOException on HttpClient.GetAsync when user is not logged in


I'm creating a WPF client for a web service based on ABP, using HttpClient.

When I make a request to some controller method annotated with AbpAuthorizeAttribute from a user who's not logged in, or a user without permissions, I got a System.IO.IOException.

In the browser, I got this JSON:

{
    "result": null,
    "targetUrl": null,
    "success": false,
    "error": {
        "code": 0,
        "message": "Current user did not login to the application!",
        "details": null,
        "validationErrors": null
    },
    "unAuthorizedRequest": true,
    "__abp": true
}

When the user is logged in, then everything is OK. I want to get the same JSON in the WPF client. What did I do wrong?

public virtual async Task<TResult> GetAsync<TResult>(string url, int? timeout = null, params NameValue[] urlParameters)
        where TResult : class
{
    var cookieContainer = new CookieContainer();

    using (var handler = new HttpClientHandler { CookieContainer = cookieContainer })
    {
        using (var client = new HttpClient(handler))
        {
            client.Timeout = timeout.HasValue ? TimeSpan.FromMilliseconds(timeout.Value) : Timeout;
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            foreach (var header in RequestHeaders)
            {
                client.DefaultRequestHeaders.Add(header.Name, header.Value);
            }

            foreach (var cookie in Cookies)
            {
                if (!string.IsNullOrEmpty(BaseUrl))
                {
                    cookieContainer.Add(new Uri(BaseUrl), cookie);
                }
                else
                {
                    cookieContainer.Add(cookie);
                }
            }

            var uri = new Uri(BaseUrl).AddPathSegment(url);
            var urlBuilder = new UriBuilder(uri);

            if (urlParameters != null && urlParameters.Any())
            {
                BuildUrlParametersString(urlBuilder, urlParameters);
            }

            using (var response = await client.GetAsync(urlBuilder.Uri)) // <--- Exception here
            {
                SetResponseHeaders(response);
                GenerateExceptionOnBadStatus(response, urlBuilder.ToString());

                var ajaxResponse = JsonString2Object<AjaxResponse<TResult>>(await response.Content.ReadAsStringAsync());

                if (!ajaxResponse.Success)
                {
                    Generateexception(ajaxResponse);
                }

                return ajaxResponse.Result;
            }
        }
    }
}

Server log:

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://localhost:62114/api/User
info: Microsoft.AspNetCore.Mvc.Internal.ObjectResultExecutor[1]
      Executing ObjectResult, writing value Microsoft.AspNetCore.Mvc.ControllerContext.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action PAG.Identity.Web.Api.UsersContoller.GetAll (PAG.Identity.Web.Mvc) in 870.4395ms
fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HL8JLDUTGR59", Request id "0HL8JLDUTGR59:00000001": An unhandled exception was thrown by the application.
Abp.Authorization.AbpAuthorizationException: Текущий пользователь не вошёл в приложение!
   at Abp.Authorization.AuthorizationHelper.<AuthorizeAsync>d__19.MoveNext() in D:\Github\aspnetboilerplate\src\Abp\Authorization\AuthorizationHelper.cs:line 43
--- 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 Abp.Authorization.AuthorizationHelper.<CheckPermissions>d__22.MoveNext() in D:\Github\aspnetboilerplate\src\Abp\Authorization\AuthorizationHelper.cs:line 98
--- 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 Abp.Authorization.AuthorizationHelper.<AuthorizeAsync>d__20.MoveNext() in D:\Github\aspnetboilerplate\src\Abp\Authorization\AuthorizationHelper.cs:line 57
--- 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 Nito.AsyncEx.Synchronous.TaskExtensions.WaitAndUnwrapException(Task task)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
--- 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 Nito.AsyncEx.Synchronous.TaskExtensions.WaitAndUnwrapException(Task task)
   at Nito.AsyncEx.AsyncContext.Run(Func`1 action)
   at Abp.Authorization.AuthorizationInterceptor.Intercept(IInvocation invocation) in D:\Github\aspnetboilerplate\src\Abp\Authorization\AuthorizationInterceptor.cs:line 20
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at Microsoft.AspNetCore.Mvc.Controller.Dispose()
   at Castle.MicroKernel.ComponentActivator.AbstractComponentActivator.ApplyConcerns(IEnumerable`1 steps, Object instance)
   at Castle.MicroKernel.ComponentActivator.AbstractComponentActivator.Destroy(Object instance)
   at Castle.MicroKernel.Lifestyle.AbstractLifestyleManager.Release(Object instance)
   at Castle.MicroKernel.Burden.Release()
   at Castle.MicroKernel.Releasers.LifecycledComponentsReleasePolicy.Release(Object instance)
   at Castle.Windsor.MsDependencyInjection.MsLifetimeScope.DisposeInternal() in D:\Github\castle-windsor-ms-adapter\src\Castle.Windsor.MsDependencyInjection\MsLifeTimeScope.cs:line 108
   at Microsoft.AspNetCore.Hosting.Internal.RequestServicesFeature.Dispose()
   at Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.<Invoke>d__3.MoveNext()
--- 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 Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Frame`1.<ProcessRequestsAsync>d__2.MoveNext()
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 1055.3429ms 401 application/json; charset=utf-8

Update

I think the problem is because I used AbpAuthorize instead of AbpMvcAuthorize attribute for my controller. Now the problem is: I got 302 and then 404 on an unauthorized request. I think it is because of Identity setting authentication with AutomaticChallenge, but I don't know how to disable it.


Solution

  • ASP.NET Core 1.x

    ABP v2.x / module-zero-core-template v2.x

    Modify IdentityRegistrar in .Core project:

    // Before
    services.AddAbpIdentity<Tenant, User, Role>()
    
    // After
    services.AddAbpIdentity<Tenant, User, Role>(options =>
    {
        options.Cookies.ApplicationCookie.AutomaticChallenge = false;
    })
    

    Reference: https://github.com/aspnet/Security/issues/804

    ASP.NET Core 2.0

    ABP v3.x / module-zero-core-template v3.0.0 – v3.4.0

    Modify AuthConfigurer in .Web.Host project:

    // Before
    services.AddAuthentication()
    
    // After
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = "JwtBearer";
        options.DefaultChallengeScheme = "JwtBearer";
    })
    

    Reference: 92b6270 in module-zero-core-template v3.5.0