ASP.Net Core:Claims Transformation and Authorization Policy - asp.net-core-mvc

Please have a look at this url first https://weblogs.asp.net/imranbaloch/claims-transformation-and-authorization-policy-in-aspnet5-mvc6
public virtual Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (principal.Identity.IsAuthenticated)
{
// get this from cache or db
var country = "Pakistan";
(principal.Identity as ClaimsIdentity).AddClaim(new Claim("Nationality", country));
}
return Task.FromResult(principal);
}
when TransformAsync() will be called.......how to use it ?
[Authorize(Policy = "MustBePakistani")]
public IActionResult Message()
{
return Content("Hi Pakistani");
}
when Message action will be called then how asp.net mvc system will be able to understand what is user's nationality.....is it pakistani or indian ?
guide me how does it work. thanks

In addition to above two segments of the code in your question, you still need to add policy to authorization services in Startup.cs.
Example,
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthorization(options =>
{
options.AddPolicy("MustBePakistani", policy =>
policy.RequireClaim("Nationality", "Pakistan"));
});
services.AddSingleton<IClaimsTransformation, ClaimsTransformer>();
...
}
private class ClaimsTransformer : IClaimsTransformation
{
// Can consume services from DI as needed, including scoped DbContexts
public ClaimsTransformer(IHttpContextAccessor httpAccessor)
{
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (principal.Identity.IsAuthenticated)
{
// get this from cache or db
var country = "Pakistan";
(principal.Identity as ClaimsIdentity)
.AddClaim(new Claim("Nationality", country));
}
return Task.FromResult(principal);
}
}
}

Related

Extend ClaimsAbpSession

I need to extend the ClaimsAbpSession and create new properties to be sent in the requests between the angular app and the server.
Actually, I´ve stored these new properties using claims. But when the user refreshes the page, the values in the claims are lost.
MyAppSession.cs
// Define your own session and add your custom field to it.
// Then, you can inject MyAppSession and use its new property in your project.
public class MyAppSession : ClaimsAbpSession, ITransientDependency
{
public MyAppSession(
IPrincipalAccessor principalAccessor,
IMultiTenancyConfig multiTenancy,
ITenantResolver tenantResolver,
IAmbientScopeProvider<SessionOverride> sessionOverrideScopeProvider) :
base(principalAccessor, multiTenancy, tenantResolver, sessionOverrideScopeProvider)
{
}
public string UserEmail
{
get
{
var userEmailClaim = PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == "Application_UserEmail");
if (string.IsNullOrEmpty(userEmailClaim?.Value))
{
return null;
}
return userEmailClaim.Value;
}
}
}
UserClaimsPrincipalFactory.cs
// Override CreateAsync method to add your custom claim
public override async Task<ClaimsPrincipal> CreateAsync(User user)
{
var claim = await base.CreateAsync(user);
claim.Identities.First().AddClaim(new Claim("Application_UserEmail", user.EmailAddress));
return claim;
}

Adding custom authorize claim based on local database for Azure user .net core

I am trying to recognize database user with Azure AD email address, and then add custom claim to azure AD authenticated user, based on property from local database user. In startup.cs I got:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IClaimsTransformer, ClaimsTransformer>();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, KayttajatContext context)
{
...
app.UseClaimsTransformation(async (c) =>
{
IClaimsTransformer transformer = c.Context.RequestServices.GetRequiredService<IClaimsTransformer>();
return await transformer.TransformAsync(c);
});
...
}
Then ClaimsTransformer.cs looks like this:
namespace Authtest
{
public class ClaimsTransformer : IClaimsTransformer
{
private readonly KayttajatContext _context;
public ClaimsTransformer(KayttajatContext dbContext)
{
_context = dbContext;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext ctrans)
{
string sposti = ((ClaimsIdentity)ctrans.Principal.Identity).Name;
var user = await _context.Henkilöt.FirstOrDefaultAsync(t => t.Sposti == sposti);
if (user.Sposti == sposti)
{
((ClaimsIdentity)ctrans.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, "Administrator"));
((ClaimsIdentity)ctrans.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, "User"));
}
else
{
((ClaimsIdentity)ctrans.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, "User"));
}
return ctrans.Principal;
}
}
}
But this gives me "NullReferenceException: Object reference not set to an instance of an object." at if (user.Sposti == sposti)
If I instead give string value to either one of the variables, if statement works fine. I don't know what I'm doing wrong? Does it have something to do with async? Please help this is driving me nuts.
I was trying to call string sposti = ((ClaimsIdentity)ctrans.Principal.Identity).Name; before it was set. Works now, thanks :)

.Net Core DI to filter class from startup

How do I inject the database to my TestAttribute class as .net core does magically to my controller.
services.AddScoped<DbContextOptions>(p => p.GetRequiredService<DbContextOptions<Context>>());
services.TryAdd(new ServiceDescriptor(typeof(Context), typeof(Context), ServiceLifetime.Transient));
services.AddMvc(options =>
{
options.Filters.Add(new TestAttribute(/*need parameter*/));
}
public HouseController([FromServices] Context database)
{
this.Database = database;
}
public class TestAttribute : ActionFilterAttribute
{
public TestAttribute([FromServices] Context database)
{
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
}
}
To inject dependencies into Filters you need to use the ServiceFilter attribute (see docs)
Manually injecting it to an controller action
[ServiceFilter(typeof(TestAttribute))]
public Task<IActionResult> Index()
{
...
}
Also your registration is wrong in the startup for all actions! Filters.Add() accepts a Type, when you want to use attributes which require dependencies (because you can't instantiate them in code), see "Filter Scopes" in the docs.
services.AddMvc(options =>
{
options.Filters.Add(typeof(TestAttribute));
}

Kestrel and ASP.NET Core MVC use custom base path

How can you mount your app on a different base path?
For example, my controller's route is /api/keywords, but when running the web server I want the basepath to be /development, so my controller route would be /development/api/keywords. I would rather not have to modify my controllers. In old Web API versions you could mount an OWIN app in a different path so I'm looking to do something similar.
There's a new method called UsePathBase that can do this easily.
https://github.com/aspnet/HttpAbstractions/blob/bfa183747f6fb528087554c3d6ec58ef05f1c10a/src/Microsoft.AspNetCore.Http.Abstractions/Extensions/UsePathBaseExtensions.cs
You can view the original great article here
First create a class that inherits from IApplicationModelConvention interface
public class EnvironmentRouteConvention : IApplicationModelConvention
{
private readonly AttributeRouteModel _centralPrefix;
public EnvironmentRouteConvention(IRouteTemplateProvider routeTemplateProvider)
{
_centralPrefix = new AttributeRouteModel(routeTemplateProvider);
}
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
var matchedSelectors = controller.Selectors.Where(x => x.AttributeRouteModel != null).ToList();
if (matchedSelectors.Any())
{
foreach (var selectorModel in matchedSelectors)
{
//This will apply only to your API controllers. You may change that depending of your needs
if (selectorModel.AttributeRouteModel.Template.StartsWith("api"))
{
selectorModel.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_centralPrefix, selectorModel.AttributeRouteModel);
}
}
}
}
}
Then create a class just for the purpose of easier and cleaner use.
public static class MvcOptionsExtensions
{
public static void UseEnvironmentPrefix(this MvcOptions opts, IRouteTemplateProvider routeAttribute)
{
opts.Conventions.Insert(0, new EnvironmentRouteConvention(routeAttribute));
}
}
Now to use it, first very common, save your environment in a property of your Startup class
private IHostingEnvironment _env;
public Startup(IHostingEnvironment env)
{
_env = env;
}
And then all you need to do is to call your static extention class
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.UseEnvironmentPrefix(new RouteAttribute(_env.EnvironmentName));
});
}
But there is one last thing to care about. Whatever client you have that consume your API, you certainly don't want to change all URLs of the HTTP requests you send. So the trick is to create a middleware which will modify the Path of your request to include your environment name. (source)
public class EnvironmentUrlRewritingMiddleware
{
private readonly RequestDelegate _next;
public EnvironmentUrlRewritingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IHostingEnvironment env)
{
var path = context.Request.Path.ToUriComponent();
//Again this depends of your need, whether to activate this to your API controllers only or not
if (!path.StartsWith("/" + env.EnvironmentName) && path.StartsWith("/api"))
{
var newPath = context.Request.Path.ToString().Insert(0, "/" + env.EnvironmentName);
context.Request.Path = newPath;
}
await _next.Invoke(context);
}
}
and your ConfigureServices method in your Startup class becomes
public void ConfigureServices(IServiceCollection services)
{
app.UseMiddleware<EnvironmentUrlRewritingMiddleware>();
services.AddMvc(options =>
{
options.UseEnvironmentPrefix(new RouteAttribute(_env.EnvironmentName));
});
}
The only drawback is that it doesn't change your URL, so if you hit your API with your browser, you won't see the URL with your environment included. response.Redirect always sends a GET request even if the original request is a POST. I didn't find yet the ultimate solution to this to reflect the Path to the URL.
Take a look at this:
public class Program
{
public static void Main(string[] args)
{
var contentRoot = Directory.GetCurrentDirectory();
var config = new ConfigurationBuilder()
.SetBasePath(contentRoot)
.Build();
var hostBuilder = new WebHostBuilder()
//Server
.UseKestrel()
//Content root - in this example it will be our current directory
.UseContentRoot(contentRoot)
//Web root - by the default it's wwwroot but here is the place where you can change it
.UseWebRoot("wwwroot")
//Startup
.UseStartup<Startup>();
var host = hostBuilder.Build();
host.Run();
}
}
There are two extension methods - UseWebRoot() and UseContentRoot() - which can be used to configure web and content roots.

Custom route constraint causes intermittent 404 errors

I have an Asp.Net Core 1 RC1 application that uses a custom route constraint to control access to the application. The application (hosted on a server running IIS 7.5) is getting intermittent 404 errors which I suspect is caused by this routing constraint. Here you can see a screenshot that shows the intermittent 404 errors:
I suspect that this issue is related to the code that defines the route constraint not being thread-safe. The custom route constraint needs a DbContext because it needs to check in the database if the application is enabled for the brand specified in the route, and I suspect that this DbContext instance could be causing the issue. Here is how the routing is defined in the application:
// Add MVC to the request pipeline.
var appDbContext = app.ApplicationServices.GetRequiredService<AppDbContext>();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "branding",
template: "branding/{brand}/{controller}/{action}/{id?}",
defaults: new { controller="Home", action="Index" },
constraints: new { brand = new BrandingRouteConstraint(appDbContext) });
});
And here is the custom route constraint:
// Custom route constraint
public class BrandingRouteConstraint : IRouteConstraint
{
AppDbContext _appDbContext;
public BrandingRouteConstraint(AppDbContext appDbContext) : base() {
_appDbContext = appDbContext;
}
public bool Match(HttpContext httpContext, IRouter route, string routeKey, IDictionary<string, object> values, RouteDirection routeDirection)
{
if (values.Keys.Contains(routeKey))
{
var whiteLabel = _appDbContext.WhiteLabels.Where(w => w.Url == values[routeKey].ToString()).FirstOrDefault();
if (whiteLabel != null && whiteLabel.EnableApplication != null && (bool)whiteLabel.EnableApplication)
{
return true;
}
}
return false;
}
}
Can anyone confirm that this issue is caused by the code not being thread-safe and recommend a way to change the implementation so that it is thread-safe?
I can't comment on RouteContraint's, haven't used them much, but have you tried Resource Based Authorization instead? Looks like it might be more suited to what you're trying to achieve?
From here and here:
Request authentication service inside your controller
public class DocumentController : Controller
{
IAuthorizationService authorizationService;
public DocumentController(IAuthorizationService authorizationService)
{
this.authorizationService = authorizationService;
}
}
Apply authorization checks in your Action:
public async Task<IActionResult> Edit(Guid documentId)
{
Document document = documentRepository.Find(documentId);
if (document == null)
{
return new HttpNotFoundResult();
}
if (await authorizationService.AuthorizeAsync(User, document, Operations.Edit))
{
return View(document);
}
else
{
return new HttpUnauthorizedResult();
}
}
I've used the OperationAuthorizationRequirement class in the sample, so define this class in your project:
public static class Operations
{
public static OperationAuthorizationRequirement Create =
new OperationAuthorizationRequirement { Name = "Create" };
public static OperationAuthorizationRequirement Read =
new OperationAuthorizationRequirement { Name = "Read" };
public static OperationAuthorizationRequirement Update =
new OperationAuthorizationRequirement { Name = "Update" };
public static OperationAuthorizationRequirement Delete =
new OperationAuthorizationRequirement { Name = "Delete" };
}
Implement the authorization handler (using built in OperationAuthorizationRequirement requirement):
public class DocumentAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
protected override void Handle(AuthorizationContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
// Validate the requirement against the resource and identity.
// Sample just checks "Name"field, put your real logic here :)
if (resource.Name == "Doc1")
context.Succeed(requirement);
else
context.Fail();
}
}
And not forgetting ConfigureServices:
services.AddInstance<IAuthorizationHandler>(
new DocumentAuthorizationHandler());
It's a bit more work, but adds quite a lot of flexibility.

Resources