Custom Authorization attribute doesn't allow authorize in asp.net core 3

hdoitc

.Net core app authenticates with IdentityServer. I'm working without https.

I created custom authorization attribute, but it doesn't allow authorize. And I can't catch a problem.

Logs:

dbug: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[8]
      AuthenticationScheme: Cookies was successfully authenticated.
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler: Debug: AuthenticationScheme: Cookies was successfully authenticated.
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
      Authorization failed.
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService: Information: Authorization failed.
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[13]
      AuthenticationScheme: Cookies was forbidden.
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler: Information: AuthenticationScheme: Cookies was forbidden.
info: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[13]
      AuthenticationScheme: oidc was forbidden.
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler: Information: AuthenticationScheme: oidc was forbidden.

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAntiforgery(options =>
            {
                options.Cookie.HttpOnly = false;
                options.HeaderName = "XSRF-TOKEN";
            });
services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";

            })
.AddCookie("Cookies", options =>
                {
                    options.Cookie.HttpOnly = true;
                    options.Cookie.Name = "app";
                    options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
                    options.SlidingExpiration = true;           

                })
.AddOpenIdConnect("oidc", options =>
                {

                    options.Authority = $"{OpenId["ServerUrl"]}";
                    options.RequireHttpsMetadata = false;
                    options.ClientId = OpenId["ClientId"];
                    options.ClientSecret = OpenId["ClientSecret"];
                    options.ResponseType = "code";
                    options.UsePkce = true;
                    options.SignInScheme = "Cookies";

                    options.CallbackPath = "/signin-callback";
                    options.GetClaimsFromUserInfoEndpoint = true;
                    options.Scope.Clear();
                    options.Scope.Add("openid");
                    options.Scope.Add("offline_access");
                    options.Scope.Add("profile");
                    options.Scope.Add("role");
                    options.Scope.Add("myapp");
                    options.ClaimActions.MapJsonKey("website", "website");
                    options.ClaimActions.MapJsonKey(UserClaimTypes.Role, UserClaimTypes.Role);
                    options.ClaimActions.MapJsonKey(UserClaimTypes.UserId, UserClaimTypes.UserId);
                    options.SaveTokens = true;

                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        NameClaimType = "name",
                        RoleClaimType = UserClaimTypes.Role
                    };
                    options.Events = new OpenIdConnectEvents
                    {
                        OnTicketReceived = (e) =>
                       {
                           e.Properties.IsPersistent = true;
                           e.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60);

                           return Task.CompletedTask;
                       }

                    };

                });
services.AddSingleton<IAuthorizationPolicyProvider, AuthPolicyProvider>();
            services.AddSingleton<IAuthorizationHandler, ArticlePermissionHandler>();

            services.AddControllersWithViews(o =>
            {
                o.Filters.Add(typeof(ModelStateValidator));
                o.InputFormatters.Insert(0, GetJsonPatchInputFormatter());
            })
                .AddNewtonsoftJson();

}

public void Configure(
            IApplicationBuilder app,
            IWebHostEnvironment env,
            IDistributedCache cache,
            IHttpContextAccessor httpContextAccessor,
            ILogger<Startup> logger,
            IAntiforgery antiforgery)
        {
 app.UseStaticFiles();
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

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

ArticlePermissionRequirement.cs

using System;
using Microsoft.AspNetCore.Authorization;

namespace App.Application.Services.Authorization.Policies.Requirements
{
    public class ArticlePermissionRequirement : IAuthorizationRequirement
    {
        public string PermissionName { get; }

        public ArticlePermissionRequirement(string permissionName)
        {
            PermissionName = permissionName ?? throw new ArgumentNullException(nameof(permissionName));
        }
    }
}

AuthPolicyProvider.cs

using System.Threading.Tasks;
using App.Application.Services.Authorization.Operations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;

namespace App.Application.Services.Authorization.Policies.Providers
{
    public class AuthPolicyProvider : DefaultAuthorizationPolicyProvider
    {
        public AuthPolicyProvider(IOptions<AuthorizationOptions> options) : base(options)
        {
        }


        public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
        {
            var policy = await base.GetPolicyAsync(policyName);

            if (policy == null)
            {
                policy = new AuthorizationPolicyBuilder()
                .AddRequirements(RequirementOperations.Requirement(policyName))
                .Build();
            }

            return policy;
        }
    }
}

ArticlePermissionHandler.cs



namespace App.Application.Services.Authorization.Policies.Handlers
{
    public class ArticlePermissionHandler : AuthorizationHandler<ArticlePermissionRequirement, Owner>
    {
        protected override Task HandleRequirementAsync(
            AuthorizationHandlerContext context,
            ArticlePermissionRequirement requirement,
            Owner resource)
        {

           // for test purpose (always true) I putted this
            context.Succeed(requirement);

            var user = context.User;
           
            if (user == null)
            {
               
                return Task.CompletedTask;
            }


            var roleClaim = user.Claims.FirstOrDefault(
                c => c.Type == ClaimTypes.Role
                )?.Value;


            if (roleClaim == null)
            {
               
                return Task.CompletedTask;
            }

            // Administrator and Manager can do all things
            if (user.IsInRole(Roles.Administrator) || user.IsInRole(Roles.Manager))
            {
               
                context.Succeed(requirement);
            }
            // Moderator can read, create, update all articles
            else if (Equals(requirement.PermissionName, Permissions.ReadArticle)
                || Equals(requirement.PermissionName, Permissions.CreateArticle)
                || Equals(requirement.PermissionName, Permissions.UpdateArticle))
            {
                if (user.IsInRole(Roles.Moderator))
                {
                    context.Succeed(requirement);
                }
            }
            // Writer can create new article
            else if (Equals(requirement.PermissionName, Permissions.CreateArticle))
            {
                if (user.IsInRole(Roles.Writer))
                {
                    context.Succeed(requirement);
                }
            }
            // Onwer can read, update, delete self article
            else if (Equals(requirement.PermissionName, Permissions.ReadArticle)
                || Equals(requirement.PermissionName, Permissions.UpdateArticle)
                || Equals(requirement.PermissionName, Permissions.DeleteArticle))
            {
                ClaimsPrincipal claimUser = context.User;
                var userClaimExtractor = new UserClaimExtractor(claimUser);
                var userId = userClaimExtractor.GetId();

                if (resource.OwnerId == userId)
                {
                    context.Succeed(requirement);
                }
            }
          
            return Task.CompletedTask;
        }
    }
}

HasPermissionAttribute.cs

using Microsoft.AspNetCore.Authorization;

namespace App.Application.Services.Authorization.Policies.Attributes
{
    public class HasPermissionAttribute : AuthorizeAttribute
    {
        public HasPermissionAttribute(string permissionName)
        {
            PermissionName = permissionName;
        }

        public string PermissionName
        {
            get
            {
                return Policy;
            }
            set
            {
                Policy = value;
            }
        }

    }
}

Controller

 public class DraftController : ApiController
    {
        public DraftController(IMediator mediator,
            IHttpContextAccessor context) : base(mediator, context)
        {
        }

 [HasPermission(Permissions.CreateArticle)]
        [HttpPost("drafts")]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Drafts([FromBody] JsonPatchDocument<DraftDto> patchDoc)
        {
         ...
         }

But there is an interest thing - when I rewrite attribute with authorize:

        [Authorize]
        [HttpPost("drafts")]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Drafts(

Then app is authorized, and I can work with action Drafts. But with HasPermission attribute authorization always failed.

Where I can find a problem? Why the log writes: "Cookies was successfully authenticated" and then "Cookies was forbidden"?

hdoitc

Yes! I got! Problem was with resource Owner.

AuthorizationHandler<ArticlePermissionRequirement, Owner>

After remove this resource ArticlePermissionHandler started to work.

AuthorizationHandler<ArticlePermissionRequirement>

This info helped understand the problem.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Custom Authorize attribute - ASP .NET Core 2.2

Custom Authorization attribute asp.net core

Using the Authorize Attribute with Custom Cookie Authentication in ASP.NET Core

ASP.NET Core 3 API Ignores Authorize Attribute with Bearertoken

Authorization in ASP.NET Core. Always 401 Unauthorized for [Authorize] attribute

Authorize doesn't work in Signalr of ASP.NET Core 2.1

Intercept asp.net core Authorize action to perform custom action upon successful authorization

Custom Authorize Attribute on asp.net mvc

Custom Authorize Attribute not working asp.net

Custom authorization filter not working in ASP.NET Core 3

How do I create a custom Authorize attribute that does not depend on claims in ASP.NET Core?

Asp.Net Core Razor Pages authorization handlers for policies with Authorize attribute on PageModel still run after executing policies set to folders

.net core 3 middleware or authorization attribute ? and how to?

Asp Net Core 3.1 Authorization by Custom Roles

custom authorization in asp.net core mvc

How create custom authorization attribute for checking a role and url path in Asp.Net Core?

Return HTTP 403 using Authorize attribute in ASP.Net Core

What is the default behavior of violating the Authorize attribute in ASP.NET Core

Asp.net Core MVC Authorize Attribute not blocking

ASP.NET Core Authorize attribute not working with JWT

Asp.Net Core Identity - Authorize attribute with roles and caching?

Asp.Net Core WebApi: Authorize attribute Error 403

Generic Authorize Attribute multiple Roles ASP.NET Core

Custom middleware (or authorize) for specific route in ASP .NET Core 3.1 MVC

ASP.NET is it possible to add an attribute that doesn't allow special characters in a string?

ASP.NET CORE MVC: `asp-action` doesn't create the `href` attribute

How to create a custom attribute that will redirect to Login if it returns false, similar to the Authorize attribute - ASP.NET MVC

ASP.NET Core Custom Authorization & Memory Cache

Custom authorization in ASP.NET Core with enum roles