0
votes

.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"?

1

1 Answers

0
votes

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.