This part 5 of 10 part series which outlines my
implementation of Multi-Tenant Claim Based Identity. For more details please
see my index post.
I am using MVC Authorization Filter to authorize user access
to the controller action. If the user is not allowed then I am returning 403
error response and the action is not invoked. Here are the sequence of checks
to authorize the authenticated user. If any of these checks then I am returning
403 response.
- Validate that the controller and action exists
- Certain actions can be marked for Anonymous access, in that case let the user access the page
- Check whether the user has access to the company he is requesting
- Check if the user is an admin so that he will have unrestricted access to the all the claims in that module
- Check if user has any denial claims. If the current claim is denied then return 403
- Finally check if the user has the current claim
Here are the code snippets for each of the above
check/validations.
Checking that the controller and action exists in our
database scheme:
var page = PageService.Pages.Where(c => string.Compare(c.Controller,
controller, true) == 0 && string.Compare(c.ActionMethod, action, true) ==
0).FirstOrDefault();
if (page == null)
{
context.Result = new StatusCodeResult(403);
return;
}
Verify whether anonymous action is allowed for this page. If
yes, then bypass authorization
//
checking for annonymous claim
if
(page.PageClaims.Any(p => p.ClaimType == SecuritySettings.AnonymouseClaimType
&& p.ClaimValue == SecuritySettings.AnonymousClaim))
{
return;
}
Get all the claims for the current user
var userClaims =
context.HttpContext.User.Claims;
Check whether the user has permissions for the company
(tenant) he is trying to access:
//
checking the companyid passed in headers
string companies =
userClaims.Where(c => c.Type == NTClaimTypes.Companies).Select(c =>
c.Value).FirstOrDefault();
string companyId =
context.HttpContext.Request.Headers[SecurityConstants.HeaderCompanyId];
companyId = companyId ?? userClaims.Where(c => c.Type == NTClaimTypes.CompanyId).Select(c
=> c.Value).FirstOrDefault();
if (companies == null || companyId == null || !companies.Split(',').Contains(companyId))
{
context.Result = new StatusCodeResult(403);
return;
}
Checking whether user is an admin user using his roles
//
getting current roles and then get all the child roles
string[] roles =
userClaims.Where(c => c.Type == ClaimTypes.Role).Select(c =>
c.Value).ToArray();
roles = PageService.AdminRoles.Where(r => roles.Contains(r.Key)).Select(r =>
r.Item).ToArray();
//
checking whether user is an admin
if (!roles.Any(r =>
page.PageClaims.Any(p => r == p.ClaimType + SecuritySettings.AdminSuffix)))
{
//
additional checks
}
Checking for denial claims
//
checking for deny claim
if (userClaims.Any(c
=> page.PageClaims.Any(p => c.Type == p.ClaimType + SecuritySettings.DenySuffix &&
c.Value == p.ClaimValue)))
{
context.Result = new StatusCodeResult(403); // new
HttpUnauthorizedResult();
}
Finally checking whether user has claims for the current
page:
//
checking for current claim
if (!userClaims.Any(c
=> page.PageClaims.Any(p => c.Type == p.ClaimType && c.Value ==
p.ClaimValue)))
{
context.Result = new StatusCodeResult(403);
}
With the above checks we can ensure that user is authorized
to access the current page.
Here is the full code for this filter
//-------------------------------------------------------------------------------------------------
// <copyright
file="NTAuthorizeFilter.cs" company="Nootus">
//
Copyright (c) Nootus. All rights reserved.
// </copyright>
// <description>
//
MVC filter to authorize user for a page using claims
// </description>
//-------------------------------------------------------------------------------------------------
namespace MegaMine.Services.Security.Filters
{
using System;
using System.Linq;
using
System.Security.Claims;
using
MegaMine.Core.Context;
using MegaMine.Services.Security.Common;
using
MegaMine.Services.Security.Identity;
using
MegaMine.Services.Security.Middleware;
using
Microsoft.AspNetCore.Mvc;
using
Microsoft.AspNetCore.Mvc.Filters;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple
= true, Inherited = true)]
public class NTAuthorizeFilter : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
// getting the current module and claim
string action = context.RouteData.Values["action"].ToString().ToLower();
string controller = context.RouteData.Values["controller"].ToString().ToLower()
+ "controller";
var page = PageService.Pages.Where(c => string.Compare(c.Controller,
controller, true) == 0 && string.Compare(c.ActionMethod, action, true) ==
0).FirstOrDefault();
if (page == null)
{
context.Result = new StatusCodeResult(403);
return;
}
// checking for annonymous claim
if (page.PageClaims.Any(p => p.ClaimType == SecuritySettings.AnonymouseClaimType
&& p.ClaimValue == SecuritySettings.AnonymousClaim))
{
return;
}
var userClaims = context.HttpContext.User.Claims;
// checking the companyid passed in headers
string companies = userClaims.Where(c => c.Type
== NTClaimTypes.Companies).Select(c
=> c.Value).FirstOrDefault();
string companyId =
context.HttpContext.Request.Headers[SecurityConstants.HeaderCompanyId];
companyId = companyId ??
userClaims.Where(c => c.Type == NTClaimTypes.CompanyId).Select(c =>
c.Value).FirstOrDefault();
if (companies == null || companyId == null || !companies.Split(',').Contains(companyId))
{
context.Result = new StatusCodeResult(403);
return;
}
// checking for annonymous claim for each
module
if (page.PageClaims.Any(p => p.ClaimValue ==
SecuritySettings.AnonymousClaim))
{
return;
}
// getting current roles and then get all the
child roles
string[] roles = userClaims.Where(c => c.Type ==
ClaimTypes.Role).Select(c =>
c.Value).ToArray();
roles = PageService.AdminRoles.Where(r
=> roles.Contains(r.Key)).Select(r => r.Item).ToArray();
// checking whether user is an admin
if (!roles.Any(r => page.PageClaims.Any(p
=> r == p.ClaimType + SecuritySettings.AdminSuffix)))
{
// checking for deny claim
if (userClaims.Any(c =>
page.PageClaims.Any(p => c.Type == p.ClaimType + SecuritySettings.DenySuffix &&
c.Value == p.ClaimValue)))
{
context.Result = new StatusCodeResult(403); // new HttpUnauthorizedResult();
}
// checking for current claim
else if (!userClaims.Any(c =>
page.PageClaims.Any(p => c.Type == p.ClaimType && c.Value ==
p.ClaimValue)))
{
context.Result = new StatusCodeResult(403);
}
}
}
}
}
This is really a good filter for authorization! I think I need to write in my website
ReplyDeleteMy husband is a programmer and I guess he will help me to get the things right here and get the result I need.
ReplyDeleteThe free roblox hack is used for the free resources and sources hack for the free roblox free robux game to play free then use my link for the free roblox robux game to play free.
ReplyDeleteI need some time to understand this post. I don't know the coding, unfortunately.
ReplyDeleteGreat to have some peopel who can explain different things that you don't understand. Be happy here Online Tik Tok Viewer
ReplyDelete