locked
Kept running into 401 unauthorize response using .NET Core v3.1 RRS feed

  • Question

  • User1428199325 posted

    I upgraded the source code to .NET Core v3.1 & I'm having trouble figuring how to debug the backend issue due to lot of dependency injections, abstractions & overriding classes/methods all over.  The employee who wrote this have overcomplicate things & he had left the company so we got stuck with the confusing source code mess here that take a lot of our time & energy, to make sense of the it.  :-/

    The error I'm having is a 401 unauthorize response.  I discovered the debugger doesnt respond in StartUp class when consuming the webservice, it only respond when you start up the Web App.  So, it took us a while & finally found a hitting debugger breakpoint on a MVC controller page to point us in the right direction.  There it is saying the Identity is not authenticated so that explain the unauthorize error.</div> <div> </div> <div>

    We're not familiar with this one authentication technology, `Odachi`.  We believe there are 2 seperate authentication architecture, which is ehe WebApp's webpages login authorization for the customer's web browser & Odachi deal with the WebApp's webservice login authorization for the 3rd party software making the webservice call.

    Source code below is the webservice MVC controller w/ Authorization filter.   Then further down will be the Startup w/ base Startup abstraction.

    [ Webservice call ]

    namespace ABC.Payments.AspNet.MVC
    {
        public class AuthorizeWithNoChallengeFilterAttribute : IAuthorizationFilter
        {
            public void OnAuthorization(AuthorizationFilterContext context)
            {
                if (context.HttpContext.User?.Identity.IsAuthenticated != true)
                    context.Result = new UnauthorizedResult();
                }
            }
        }

    namespace ABC.Payments.MerchantWeb.Api
    {
        [TypeFilter(typeof(AuthorizeWithNoChallengeFilterAttribute))]
        public class MerchantsV1Controller : Controller
        {
            [Route("api/v1/merchants/{merchantAccountId}/customers/payments/paymentmethods"), HttpPost]
            public async Task<ObjectResult> Payment([FromBody] ItemInfoViewModel itemInfo, CancellationToken cancellationToken)
            {
                var payments = whatever();
                return new HttpNotAcceptableObjectResult(payments);
            }
        }
    }

    [ Startup & Base Startup ]

    namespace ABC.Payments.MerchantWeb

    {

            // StackOverflow Post on how to find 401 Unathorize error (debug)

          // --> https://stackoverflow.com/questions/43574552/authorization-in-asp-net-core-always-401-unauthorized-for-authorize-attribute

    public class Startup : StartupBase<MerchantRequestContext, Merchant>

            {

                private const string _schemeCustomMerchantBasic = "CustomMerchantBasic";

                public Startup(IWebHostEnvironment webHostEnvironment)

                   : base(webHostEnvironment, PortalRoleType.Merchant)

                {

                }

                public void ConfigureServices(IServiceCollection services)

                {

                    base._configureServices(true, services);

                  services.AddTransient(sp => sp.GetService<MerchantRequestContext>()?.Merchant);

                    services.AddTransient(sp => sp.GetService<MerchantRequestContext>()?.Customer);

                    services.AddTransient(sp => sp.GetService<MerchantRequestContext>()?.TenantSettings);

                    services.AddLocalization(options =>

                        {

                          options.ResourcesPath = "Resources";

                        });

                  services.AddMvcCore()

                        .AddViewLocalization(LanguageViewLocationExpanderFormat.SubFolder, setup =>

                      {

                            setup.ResourcesPath = "Resources";

                      })

                      .AddDataAnnotationsLocalization()

                      .AddApiExplorer();

                    services.AddCors(options =>

                        {

                            options.AddPolicy("Internal", p => p.WithOrigins(base._configuration["Cors:InternalSource"]).WithMethods("POST").WithHeaders("accept", "request", "authorization", "content-type", "internal"));

                        });

                    services.AddAuthentication()

        // https://github.com/Kukkimonsuta/Odachi/blob/master/src/Odachi.AspNetCore.Authentication.Basic/Events/BasicSignInContext.cs  (Basic Sign Context)

        // https://github.com/Kukkimonsuta/Odachi/blob/master/samples/BasicAuthenticationSample/Startup.cs

                        .AddBasic(_schemeCustomMerchantBasic, options =>

                        {

        //    ////////Notice: AutomaticChallenge is depreciated, google search said to use DefaultChallengeScheme w/ given cookie-authentication-scheme but that still doesnt explain how to disable it

        //    ////////        https://stackoverflow.com/questions/45878166/asp-net-core-2-0-disable-automatic-challenge

        //    ////////        https://github.com/dotnet/aspnetcore/issues/2007

                          //## options.AutomaticChallenge = false;

                            options.Realm = "AutoPayment API v1";

                            options.Events = new BasicEvents()

                            {

                                OnSignIn = async context =>

                                {

                                  var claims = new List<Claim>();

                                  if (context.Username == "ndi3DanDba993nvbaqbn3d93" && context.Password == "aVd3Ed51dfDE5acCCni9l1IxPq9")

                                      claims.Add(new Claim(ClaimTypes.Role, "InternalAPIUser"));

                                  else

                                    {

                                        string merchantAccountId = context.Request.Path.Value.Split('/').Skip(4).FirstOrDefault();

                                        var merchantRepository = context.HttpContext.RequestServices.GetRequiredService<IMerchantRepository>();

                                    if (merchantAccountId == null || merchantAccountId.Length != 14 || merchantAccountId.Split('-').Length != 3)

                                            throw new Exception($"Invalid merchant account Id ({merchantAccountId ?? string.Empty}).");

                                        var merchant = await merchantRepository.GetMerchantAsync(merchantAccountId, context.HttpContext.RequestAborted);

                                      if (merchant == null || !merchant.IsActive || (merchant.GatePayApiKey != context.Username || merchant.GatePayApiSecret != context.Password))

                                      {

                                            context.Fail("Invalid merchant"); //## context.HandleResponse();

                                          return;

                                      }

                                    }

                                    var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));  //## options.AuthenticationScheme));

                                  context.Principal = principal;

                                    //## context.Ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), options.AuthenticationScheme);

                                    context.Success(); //## context.HandleResponse();

                                    //return Task.CompletedTask;

                                }

                          };

                        });

              }

                public void Configure(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, IServiceProvider serviceProvider)

              {

                    base._configure(true, applicationBuilder, loggerFactory, serviceProvider);

                  applicationBuilder.UseCors("Internal");

                  applicationBuilder.UseWhen(context => !context.Request.Path.StartsWithSegments(new PathString("/api/v1")), b => b.UseAuthentication());

              }

          }

        }

    namespace ABC.Payments

        {

            public class StartupBase<TRequestContext, TUserContext> 

                where TRequestContext : RequestContext<TUserContext>

            {

                public StartupBase(IWebHostEnvironment webHostEnvironment, PortalRoleType portalRoleType)

                {

                  _portalRoleType = portalRoleType;

                  _webHostEnvironment = webHostEnvironment;

                    var builder = new ConfigurationBuilder();

                  ConfigurationLoader.Load(builder, webHostEnvironment);

                  _configuration = builder.Build();

                    if (webHostEnvironment.EnvironmentName.Equals("Production", StringComparison.OrdinalIgnoreCase) == true && _configuration["ConfirmProduction"]?.Equals("Yes", StringComparison.OrdinalIgnoreCase) != true)

                        throw new Exception("Azure defaults to \"Production\" for the environment, so you need to create an AppSetting of \"ConfirmProduction\" to \"Yes\" to ensure that is the intent.");

              }

                private readonly IWebHostEnvironment _webHostEnvironment;

              public readonly IConfiguration _configuration;

                private readonly PortalRoleType _portalRoleType;

                public void _configureServices(bool isWebBrowserFrontendGui, IServiceCollection services)

                {

                    if (isWebBrowserFrontendGui)

                  {

                        services.AddDistributedRedisCache(options =>

                          {

                                options.Configuration = _configuration["Storage:Redis:Configuration"];

                          });

                      services.AddSingleton<RedisCache>();

                      services.AddSingleton<MemoryDistributedCache>();

                      services.AddSingleton<IDistributedCache>(

                              sp => new ResilientDistributedCache(sp.GetRequiredService<RedisCache>(), sp.GetRequiredService<MemoryDistributedCache>())

                          );

                        var azureBlobConnectionTring = _configuration["Storage:AzureBlob:ConnectionString"];

                        if (azureBlobConnectionTring != null)

                        {

                            var storageAccount = CloudStorageAccount.Parse(azureBlobConnectionTring);

                            var client = storageAccount.CreateCloudBlobClient();

                          var azureBlobContainer = client.GetContainerReference("dataprotection-key-container");

                          services.AddDataProtection().PersistKeysToAzureBlobStorage(azureBlobContainer, "keys.xml");

                      }

                      services.AddSession(options =>

                        {

                            //options.IdleTimeout = TimeSpan.FromMinutes(5);

                        });

                      services.AddDefaultIdentity<ApplicationUser>()

                          .AddRoles<IdentityRole<Guid>>()

                            .AddEntityFrameworkStores<ApplicationContext>()  // FYI - AddEntityFrameworkStores() deal with role that derives from IdentityRole, as per documentation. 

                          .AddDefaultTokenProviders();

                        services.ConfigureApplicationCookie(options => {

                          options.LoginPath = new PathString("/Home/Index");

                            options.SlidingExpiration = true;

                            options.ExpireTimeSpan = TimeSpan.FromMinutes(_configuration.GetValue<int?>("Authentication:SlidingExpirationTime").Value);

                            options.AccessDeniedPath = new PathString("/Home/AccessDenied");

                      });

                        services.Configure<IdentityOptions>(options => {

                          options.Password.RequireUppercase = false;

                          options.Password.RequireLowercase = false;

                            options.Password.RequireNonAlphanumeric = false;

                          options.Password.RequireDigit = false;

                          options.Password.RequiredLength = 7;

                        });

                      services.AddControllersWithViews();

                      services.AddRazorPages();

                      // AddMvc() vs AddMvcCore() explaination found at --> https://offering.solutions/blog/articles/2017/02/07/the-difference-between-addmvc-and-addmvccore/

                        //                                                --> https://stackoverflow.com/questions/42365275/how-to-implement-a-pure-asp-net-core-web-api-by-using-addmvccore/42365276#42365276

                       services.AddMvc().AddRazorRuntimeCompilation();

                     services.Configure<MvcRazorRuntimeCompilationOptions>();

                     services.Configure<AuthorizationOptions>(options =>

                     {

                          options.DefaultPolicy = AuthorizationPolicy.Combine(options.DefaultPolicy,

                              new AuthorizationPolicy(new IAuthorizationRequirement[] {

                              new RolesAuthorizationRequirement(new string[] { _portalRoleType.ToString(), PortalRoleType.Internal.ToString() }),

                                new ImpersonationNotExpiredAuthorizationRequirement(_portalRoleType, _configuration.GetValue<TimeSpan?>("Authentication:ImpersonationTimeLimit").Value)

                                }, new string[0]));

                      });

                    services.AddMvcCore(options =>

                        {

                            var requestContextAttribute = new LoadRequestContextAttribute(typeof(TRequestContext));

                          options.Filters.Add(requestContextAttribute); 

      options.ModelBinderProviders[options.ModelBinderProviders.IndexOf(

          options.ModelBinderProviders.OfType<ComplexTypeModelBinderProvider>().First()

      )] = new TryServicesModelBinderProvider(services.BuildServiceProvider());

                          options.ModelBinderProviders.Insert(0, new EnumModelBinderProvider(services.BuildServiceProvider()));

                      })

                      .AddDataAnnotationsLocalization()

                      .AddNewtonsoftJson(settings =>

                      {

                          settings.SerializerSettings.ContractResolver = new DefaultContractResolver();

                      });

                      services.Configure<ForwardedHeadersOptions>(options => options.RequireHeaderSymmetry = false);

                    }

                    //services.AddPayments<TRequestContext, TUserContext>(_configuration, string.Empty);

                }

                  public void _configure(bool isWebBrowserFrontendGui, IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, IServiceProvider serviceProvider)

              {

                   if (isWebBrowserFrontendGui)

                  {

                      serviceProvider.GetRequiredService<ITelemeter<StartupBase>>().TrackMetric("Startup Time", (DateTime.UtcNow - DateTime.UtcNow).TotalSeconds);

                      // Exception Page Handling.

                        if (!_webHostEnvironment.IsProduction())

                      {

                          applicationBuilder.UseDeveloperExceptionPage();

                          //applicationBuilder.UseDatabaseErrorPage();

                      }

                      else

                            applicationBuilder.UseExceptionHandler("/Home/ErrorPage.html");

                      applicationBuilder.UseStaticFiles(); // Note, we are not authenticating for static files if this is before them

                    //applicationBuilder.UseStatusCodePages();

                      // Session.

                      applicationBuilder.UseSession();

                      applicationBuilder.UseAuthentication();

                      // Routing.

                      applicationBuilder.UseRouting();

                      applicationBuilder.UseAuthorization();  // Exception error said to put this between UseRouting() & UseEnpoint().

                      applicationBuilder.UseEndpoints(endpoints =>

                        {

                          endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");

                            endpoints.MapRazorPages();

                      });

                        // Config Localization.

                      var options = serviceProvider.GetService<IOptions<RequestLocalizationOptions>>();

                      if (options != null)

                          applicationBuilder.UseRequestLocalization(options.Value);

                      applicationBuilder.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All });

                      // Ensure Https.

                      var portals = applicationBuilder.ApplicationServices.GetRequiredService<Portals>();

                      applicationBuilder.Use(next => async httpContext =>

                      {

                          if (httpContext.Request.Host.Value.Contains("localhost"))

                            {

                              await next(httpContext);

                          }

                          else

                          {

                              string host = portals.GetHostForRedirect(httpContext.Request.Host.Value);

                              if (!host.Equals((httpContext.Request.IsHttps ? "https://" : "http://") + httpContext.Request.Host, StringComparison.OrdinalIgnoreCase))

                          httpContext.Response.Redirect($"{host}{httpContext.Request.Path}{httpContext.Request.QueryString}");

                                else

                                  await next(httpContext);

                          }

                      });

                }

                  //applicationBuilder.UsePayments<TRequestContext, TUserContext>();

               }

          }

      }

    Thursday, June 4, 2020 5:22 PM

All replies

  • User1428199325 posted

    Can someone show me how do I fix the post formatting?   This forum kept screwing up the formatter.   Thanks.

    Thursday, June 4, 2020 5:26 PM
  • User2078676645 posted

    Hi,

    There is a code block button to the right of the top bar of the text box, you can paste the code into the code block and I will help you analyze the cause.

    Regards,

    Evern

    Friday, June 5, 2020 9:39 AM
  • User1428199325 posted

    I saw that one & tried it.  It made the muliple lines of code become 1 line of code & removed some spacing.  

    Also, every time I made a new post, it add wrapper to every line of text that mess it up too.

    So I think it is a forums.asp.net bug that is beyond my control.

    On the bright side, I clean up the post (removed the <div> tags) to make it more readable for now.   

    Friday, June 5, 2020 12:42 PM
  • User-474980206 posted

    It’s a github open source repo. 

       https://github.com/Kukkimonsuta/Odachi

    To debug, I’d fork it and add logging code for failure. Is the authentication missing, the user/password invalid or claims not loaded.

    Friday, June 5, 2020 2:46 PM