Dynamically adding policy claims for Blazor authorization - authentication

I am creating an authentication and authorization handler for internal authorization purposes. My intention is to make it easy for my colleagues to implement the solution into their own projects. We are using Azure AD for authentication, and for authorization we are using Azure Groups. In order to do that, I feel like I am stuck on figuring out how to add authorization policies in an efficient way.
Right now I'm adding it through the officially described way in the Program class of my Client project in a Blazor webassembly hosted configuration:
builder.Services.AddAuthorizationCore(options =>
options.AddPolicy("PolicyName", policy =>
{
policy.RequireClaim("ClaimType", "ClaimValue");
})
);
This works fine, but it's not intuitive, as any given project could require several different policies
I have also added a custom Authorization Policy Provider, as described in this documentation from Microsoft:
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-6.0
I figured this would be what I was looking for, based on their description for this documentation, especially the first couple of lines in the documentation. But I still can't seem to get it to work as intended, without specifically adding each policy manually.
If need be I can show my custom implementation of the Authorization Policy Provider, but it is pretty much exactly as seen in the Github for the documentation.

Policies are most commonly registered at application startup in the Startup classes ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(config =>
{
config.AddPolicy("IsDeveloper", policy => policy.RequireClaim("IsDeveloper","true"));
});
}
the policy IsDeveloper requires that a user have the claim IsDeveloper with a value of true.
Roles you can apply policies via the Authorize attribute.
[Route("api/[controller]")]
[ApiController]
public class SystemController
{
[Authorize(Policy = “IsDeveloper”)]
public IActionResult LoadDebugInfo()
{
// ...
}
}
Blazors directives and components also work with policies.
#page "/debug"
#attribute [Authorize(Policy = "IsDeveloper")]
< AuthorizeView Policy="IsDeveloper">
< p>You can only see this if you satisfy the IsDeveloper policy.< /p>
< /AuthorizeView>
Easier Management
With role-based auth, if we had a couple of roles which were allowed access to protected resources - let’s say admin and moderator. We would need to go to every area they were permitted access and add an Authorize attribute.
[Authorize(Roles = "admin,moderator")]
This doesn’t seem too bad initially, but what if a new requirement comes in and a third role, superuser, needs the same access? We now need to go round every area and update all of the roles. With policy-based auth we can avoid this.
We can define a policy in a single place and then apply it once to all the resources which require it. Then when extra roles need to be added, we can just update the policy from the central point without the need to update the individual resources.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(config =>
{
config.AddPolicy("IsAdmin", policy => policy.RequireRole("admin", "moderator", "superuser"));
});
}
[Authorize(Policy = "IsAdmin")]
Creating shared policies
We need to install the Microsoft.AspNetCore.Authorization package from NuGet in order to do this.
After that create a new class called Policies with the following code.
public static class Policies
{
public const string IsAdmin = "IsAdmin";
public const string IsUser = "IsUser";
public static AuthorizationPolicy IsAdminPolicy()
{
return new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
.RequireRole("Admin")
.Build();
}
public static AuthorizationPolicy IsUserPolicy()
{
return new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
.RequireRole("User")
.Build();
}
}
Here we’re using the AuthorizationPolicyBuilder to define each policy, both require the user to be authenticated then be in either the Admin role or User role, depending on the policy.
Configuring the server
Rregistering the policies in ConfigureServices in the Startup class. Add the following code under the existing call to AddAuthentication.
services.AddAuthorization(config =>
{
config.AddPolicy(Policies.IsAdmin, Policies.IsAdminPolicy());
config.AddPolicy(Policies.IsUser, Policies.IsUserPolicy());
});
registering each policy and using the constants we defined in the Policies class to declare their names, which saves using magic strings.
If we move over to the SampleDataController we can update the Authorize attribute to use the new IsAdmin policy instead of the old role.
[Authorize(Policy = Policies.IsAdmin)]
[Route("api/[controller]")]
public class SampleDataController : Controller
Again, we can use our name constant to avoid the magic strings.
Configuring the client
Our server is now using the new policies we defined, all that’s left to do is to swap over our Blazor client to use them as well.
As with the server we’ll start by registering the policies in ConfigureServices in the Startup class. We already have a call to AddAuthorizationCore so we just need to update it.
services.AddAuthorizationCore(config =>
{
config.AddPolicy(Policies.IsAdmin, Policies.IsAdminPolicy());
config.AddPolicy(Policies.IsUser, Policies.IsUserPolicy());
});
In Index.razor, update the AuthorizeView component to use policies - still avoiding the magic strings.
< AuthorizeView Policy="#Policies.IsUser">
< p>You can only see this if you satisfy the IsUser policy.< /p>
< /AuthorizeView>
< AuthorizeView Policy="#Policies.IsAdmin">
< p>You can only see this if you satisfy the IsAdmin policy.< /p>
< /AuthorizeView>
Finally, update FetchData.razors Authorize attribute.
#attribute [Authorize(Policy = Policies.IsAdmin)]
Refer here

Related

Asp.Net core type of actionfilter to use for the scenerio

The requirement is that a logged in user MUST accept privacy statement before accessing other areas of the application. I can write a Middleware or an actionfilter but not sure what's better suited.
Currently the flow will be something like below (Assuming it's a actionfilter).
Authenticate user and load claims from db including whether privacy statement is accepted and redirect to application dashboard.
Below things happen inside the actionfilter
Is user authenticated?
Has the user accepted the privacy statement if any available? (read claims for "PrivacyAccepted" = true)
If no privacy accepted claim available, redirect user to a page showing a message with buttons to accept/reject
If accepted, save it in database, update current user claims with a value like "PrivacyAccepted" = true (using IClaimsTransformation)?
If rejected, show a message and no matter what user does he'll always get the privacy statement page since action filter will redirect here until he accepts it.
From a design/best practice/performance standpoint what is the best thing to do here? Use a middleware or an ActionFilter?
Also point 5 using IClaimsTransformation should be used to update the current claims in logged in user if he accepts the privacy statement. But I haven't found any resources saying whether I can call IClaimsTransformation.TransformAsync() from my code. Everywhere it seems to be working as a middleware rather than I calling it manually.
Maybe you can define a Policy and go for Policy-Based Authorization to achieve your goal.
First when the user accepted the privacy terms add the user a new claim. (like "IsPrivacyAccepted", true)
await _userManager.AddClaimsAsync(user, new List<Claim>
{
new Claim("IsPrivacyAccepted", true),
//other claims
});
Then define a policy with the required claim.
services.AddAuthorization(x =>
{
x.AddPolicy("PrivacyAccepted", policy =>
{
policy.RequireClaim("IsPrivacyAccepted", true); //claim based authorization
});
});
Use the policy wherever you want to restrict users to access your actions.
[Authorize(Policy = "PrivacyAccepted")]
public ActionResult Index() { //... }
If you do like this, you don't need to create an action filter or middleware.
But as I understand you also want to redirect the user to the privacy policy page if he/she is not accepted yet (does not have IsPrivacyAccepted claim). If you want to do this you can write a basic middleware as below
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 401)
{
context.Request.Path = "/PrivacyPolicyPage";
await next();
}
});
If you don't want to define the [Authorize(Policy = "PrivacyAccepted")] for each of your controller, maybe you can create a base controller and inherit all of your controller from it.
[Authorize(Policy = "PrivacyAccepted")]
public class MyBaseController : Controller
{
}
and inherit all your controllers from this class instead of Controller class
public class MyController : MyBaseController
{
}

Restrict account registration to only Admin users with asp.net identity authentication

I am creating a Blazor server app that requires authenticated users in order to prevent external access, and I would like to limit the ability to register new accounts to be only available to Administrator users to prevent unwanted accounts from being created.
I'm using Identity user accounts, scaffolded out for Blazor. Solutions like this at least disable the registration, but from there I need to be able to enable it again for administrative users. I attempted to recreate the register page as a Blazor component, however, using the generated RegisterModel did not seem to work for me.
Upon a large amount of searching - the answer ended up being relatively simple. Muhammad Hammad Maroof's solution although technically correct, confused me and was mostly unhelpful for working with the register page specifically.
As I am using Role-Based Authentication scaffolded out from Blazor - in a seperate razor page I use this code to set up roles:
#code {
protected override async Task OnParametersSetAsync()
{
await SetUpAuth();
}
private async Task SetUpAuth()
{
const string Manager = "Manager";
string[] roles = { Manager };
foreach (var role in roles)
{
var roleExist = await roleManager.RoleExistsAsync(role);
if (!roleExist)
{
await roleManager.CreateAsync(new IdentityRole(role));
}
}
var user = await userManager.FindByEmailAsync(config.GetValue<string>("AdminUser"));
if (user != null)
{
await userManager.AddToRoleAsync(user, Manager);
}
}
}
Allowing the appropriate user to be marked as an administrator. This page has the [AllowAnonymous] tag on it in order to allow the administrative user as dictated by "AdminUser": "SomeEmail#test.com", in the appsettings.json page to be able to access the site on initial setup.
Preventing access to the Blazor site itself from anonymous users was as simple as adding this line to ConfigureServices in the startup class (Code taken from Microsoft Docs)
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
From this, allowing access to the register page was significantly easier than I had initially thought (likely due to my lack of .net experience). To do so, all you have to do is locate the Register.cshtml.cs page (I couldn't initially find the controller method Muhammad had mentioned) which I did by using visual studio to right click on the Register Model and then go to definition. This should take you to the Register.cshtml.cs page with the RegisterModel class. In order to restrict access to this page for only a specific role of users, all you have to do is change the [AllowAnonymous] tag above the class to look similar to this:
[Authorize(Roles ="Manager")]
public class RegisterModel : PageModel
It's important to note that the same technique used to secure the register page could be used to secure any of the of the other scaffolded Identity pages. For applications where you may have more than a few roles, the method provided by Muhammad of using policy based authorization may be the way to go, and this link he provided is a great tutorial for setting up and using that form of authentication.
//FORCE autentication for all RAZOR PAGES except [AllowAnonymous]
services.AddControllers(config => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
Only adding this code to my startup.cs solved my problem.
Here's how I am doing it in asp.net core mvc app
C# Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy(ADMIN_ACCESS, policy => policy.RequireRole($"{UserType.Admin}"));
});
}
[Authorize("AdminAccess")]
public class AdminController : Controller
{
//Some action methods here
}

Is there a way to specify which IAuthProvider to use for authentication on a particular Service class?

I have two services within the same project:
[Authenticate]
public class OnlyDoesBasicAuth : Service
{
}
[Authenticate]
public class OnlyDoesJwtAuth : Service
{
}
//AppHost
public override void Configure(Container container)
{
Plugins.Add(new AuthFeature(
() => new AuthUserSession(),
new IAuthProvider[]
{
new BasicAuthProvider(AppSettings),
new JwtAuthProvider(AppSettings)
}
)
{
HtmlRedirect = null
});
}
The way this is set up, I can get into both services using Basic Authentication or with a valid Bearer token. I'd prefer to have it so that only one service can do one means of authentication. Is there a way to specify with provider to use on authentication?
I'm at a loss on how to approach this. I was thinking that maybe there was a way via global request filter or something to that effect, but maybe that's not the way to go. I could split the project into two projects, which will definitely work, but it's not the way I want to approach it.
I'm just looking for a way for the OnlyDoesBasicAuth to only use the BasicAuthProvider on authentication and OnlyDoesJwtAuth to only use the JwtAuthProvider within the same project. Any and all suggestions will be greatly appreciated.
Thanks in advance.
Typically once you're authenticated using any of the Auth Providers you're considered as an Authenticated User everywhere in ServiceStack.
You can restrict access so that a Service needs to be Authenticated with by specifying the AuthProvider name in the [Authenticate] attribute, e.g:
[Authenticate(BasicAuthProvider.Name)]
public class OnlyDoesBasicAuth : Service
{
}
[Authenticate(JwtAuthProvider.Name)]
public class OnlyDoesJwtAuth : Service
{
}
Alternatively you can validate within your Service that they need to be authenticated with a specific Auth Provider, e.g:
if (SessionAs<AuthUserSession>().AuthProvider != JwtAuthProvider.Name)
throw HttpError.Forbidden("JWT Required");

Claims based authentication, with active directory, without ADFS

I have a client asking for an integrated authentication based solution utilizing a custom role/membership schema. My original plan was to use claims based authentication mechanism with integrated authentication. However, my initial research is not turning up a whole lot of useful information.
To the point, I have an ASP.NET (not core nor owin) WebAPI application, which has api actions used by angular SPA based (asp.net) web application. I am attempting to authorize the api calls using integrated authentication. My initial effort was focused around a custom AuthorizationAttribute and ClaimsAuthenticationManager implementation. However as I got deeper into that I started running into issues with the custom ClaimsAuthenticationManager, at this point I'm not sure that is the proper route to take.
So my question for you all is, can you at least give me some ideas of what it would take to make this happen? I don't need help with secific bits the code, just need to figure out the appropriate "stack" so to speak.
The only real requirement is WebAPI calls can be authorized, with a custom attribute passing a name of a claim to authorize on, but the claim is not in AD even though it is using windows authentication, the claims themselves would come from a database.
Thank you all in advance!
Look at https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api.
Your scenario isn't much different:
you're using AD for authentication
you're using your db for authorization
Simply put this can be addressed by configuring web-api to use windows authentication.
<system.web>
<authentication mode="Windows" />
</system.web>
And add your own IAuthorizationFilter to Web API pipeline, that will check current principal (should be set), and then override this principal with your own (i.e. query db - get claims, and override it with your custom claims principal by setting HttpContext.Current.User and Thread.CurrentPrincipal).
For how to add filter to WebAPI pipe line check out How to add global ASP.Net Web Api Filters?
public class CustomAuthenticationFilter : IAuthenticationFilter {
public bool AllowMultiple { get { return true; } }
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken) {
var windowsPrincipal = context.Principal as WindowsPrincipal;
if (windowsPrincipal != null) {
var name = windowsPrincipal.Identity.Name;
// TODO: fetch claims from db (i guess based on name)
var identity = new ClaimsIdentity(windowsPrincipal.Identity);
identity.AddClaim(new Claim("db-crazy-claim", "db-value"));
var claimsPrincipal = new ClaimsPrincipal(identity);
// here is the punchline - we're replacing original windows principal
// with our own claims principal
context.Principal = claimsPrincipal;
}
return Task.FromResult(0);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken) {
return Task.FromResult(0);
}
}
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.Filters.Add(new CustomAuthenticationFilter());
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute( ... );
}
}
Also there is no need for custom authorization attribute - use default one - its understood by everyone, and makes your code more readable.

ASP Core Add Custom Claim to Auth Token

I'm using openiddict and ASP Identity, and I am trying add a "GroupId" as a claim to the auth token that is returned when logging in (using the /connect/token endpoint - see example I followed below). The GroupId is a property in my AplicationUser class.
I have tried using an IClaimsTransformer but that seems clunky, I can't easily get to the UserManager from the ClaimsTransformationContext.
How would I go about either getting the UserManager through DI in my IClaimsTransformer or just adding the GroupId to the token that is generated at the connect/token endpoint?
I followed this example for setting up my site. This is what I would like to do:
var groupGuid = User.Claims.FirstOrDefault(c => c.Type == "GroupGuid");
There is a couple of ways to achieve it:
First, override CreateUserPrincipalAsync method in your custom SignInManager:
public override async Task<ClaimsPrincipal> CreateUserPrincipalAsync(ApplicationAdmin user)
{
var principal = await base.CreateUserPrincipalAsync(user);
// use this.UserManager if needed
var identity = (ClaimsIdentity)principal.Identity;
identity.AddClaim(new Claim("MyClaimType", "MyClaimValue"));
return principal;
}
The second way is to override CreateAsync method of your custom UserClaimsPrincipalFactory:
public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
var identity = (ClaimsIdentity)principal.Identity;
identity.AddClaim(new Claim("MyClaimType", "MyClaimValue"));
return principal;
}
which is, basically, the same, because base.CreateUserPrincipalAsync method in SignInManager calls this.UserClaimsPrincipalFactory() inside.
Don't forget to add your custom implementations into services:
either
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSignInManager<CustomSignInManager>();
}
or
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomClaimsPrincipalFactory>();
}
ASP Core Add Custom Claim to Auth Token
You can't change a token after it is signed by IdP, so you can't add claim to token.
I have tried using an IClaimsTransformer but that seems clunky, I
can't easily get to the UserManager from the
ClaimsTransformationContext.
I guess your problem is related this github issue. In summary(as far as i understand) if ClaimsTransformer class is registered as singletion and one of the its dependency is scoped or transient, it causes captive dependency. In this case you should use Service Locator pattern to avoid from captive dependency. Your code may be something like this(from #PinpointTownes' comment):
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
var userManager= context.Context.RequestServices.GetRequiredService<UserManager>();
//
}
-- My thoughts about your case --
You have basically two options to achieve your goal:
Add claim to token when token is generated by IdP:
You don't need this method most cases, but if you want to use it:
You should have control over IdP, because this option is possible on the IdP(as far as i understand your IdP and Resource Server is same, so you have control over IdP, but it might not be possible always) .
You should take care of inconsistency when using this option, because the claim is stored in the token and doesn't get each request. So the real value of claim might be different from claim in the token.(I don't prefer it for roles, permissions, groups etc. because these claims can be change anytime).
p.s: i don't know if it is possible to add claims to token with Openiddict.
Claims Transformation
Actually i used HttpContext.Items to store additional claims before i discovered this method and it worked well for me. But i think better way is to use Claims Transformation and it fits into your case.