I have swagger set up but it shows all the controllers for everyone. I'd like to only show controllers based on API key permissions, so they'll have to enter their API key in the Explore section of swagger to see anything. Is this do-able?
If we talk about Swashbuckle package then we need to use implement IDocumentFilter.
Some initial information you can review from this post.
Basic scenario:
Define custom Attribute
Setup this attribute to Controllers / Actions
Implement filtration logic in your DocumentFilter class
Code sample available here:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class LicenseValidatorAttribute : Attribute
{
public FeatureType Feature { get; set; }
}
public class FeatureDocumentFilter : IDocumentFilter
{
private readonly IFeatureService _featureService;
public FeatureDocumentFilter(IFeatureService featureService)
{
_featureService = featureService;
}
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
foreach (var api in context.ApiDescriptions)
{
var attribute = api.CustomAttributes().OfType<FeatureValidatorAttribute>().FirstOrDefault();
if (attribute != null)
{
var success = _featureService.ValidateFeature(attribute.Feature);
if (!success.Valid)
{
var route = "/" + api.RelativePath;
swaggerDoc.Paths.Remove(route);
}
}
}
}
}
Related
I have a logic to apply in case the request received is a BadRequest, to do this I have created a filter:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
// Apply logic
}
}
}
In Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => { options.Filters.Add<ValidateModelAttribute>(); });
}
Controller:
[Route("api/[controller]")]
[ApiController]
public class VerifyController : ControllerBase
{
[Route("test")]
[HttpPost]
[ValidateModel]
public ActionResult<Guid> validationTest(PersonalInfo personalInfo)
{
return null;
}
}
Model:
public class PersonalInfo
{
public string FirstName { get; set; }
[RegularExpression("\\d{4}-?\\d{2}-?\\d{2}", ErrorMessage = "Date must be properly formatted according to ISO 8601")]
public string BirthDate { get; set; }
}
The thing is when I put a break point on the line:
if (!context.ModelState.IsValid)
execution reaches this line only if the request I send is valid. Why it is not passing the filter if I send a bad request?
The [ApiController] attribute that you've applied to your controller adds Automatic HTTP 400 Responses to the MVC pipeline, which means that your custom filter and action aren't executed if ModelState is invalid.
I see a few options for affecting how this works:
Remove the [ApiController] attribute
Although you can just remove the [ApiController] attribute, this would also cause the loss of some of the other features it provides, such as Binding source parameter inference.
Disable only the Automatic HTTP 400 Responses
Here's an example from the docs that shows how to disable just this feature:
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// ...
options.SuppressModelStateInvalidFilter = true;
// ...
}
This code goes inside of your Startup's ConfigureServices method.
Customise the automatic response that gets generated
If you just want to provide a custom response to the caller, you can customise what gets returned. I've already described how this works in another answer, here.
An example of intersection for logging is describe in Log automatic 400 responses
Add configuration in Startup.ConfigureServices.
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// To preserve the default behavior, capture the original delegate to call later.
var builtInFactory = options.InvalidModelStateResponseFactory;
options.InvalidModelStateResponseFactory = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Startup>>();
// Perform logging here.
//E.g. logger.LogError($”{context.ModelState}”);
logger.LogWarning(context.ModelState.ModelStateErrorsToString());
// Invoke the default behavior, which produces a ValidationProblemDetails response.
// To produce a custom response, return a different implementation of IActionResult instead.
return builtInFactory(context);
};
});
public static String ModelStateErrorsToString(this ModelStateDictionary modelState)
{
IEnumerable<ModelError> allErrors = modelState.Values.SelectMany(v => v.Errors);
StringBuilder sb = new StringBuilder();
foreach (ModelError error in allErrors)
{
sb.AppendLine($"error {error.ErrorMessage} {error.Exception}");
}
return sb.ToString();
}
As the attribute filter in the life cycle of the .Net Core you can’t handle it. The filter layer with ModelState will run after the model binding.
You can handle it with .Net Core middleware as the following https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.1&tabs=aspnetcore2x
If you want to SuppressModelStateInvalidFilter on individual action, consider to use custom attribute suggested on https://learn.microsoft.com/en-us/answers/questions/297568/how-to-suppress-suppressmodelstateinvalidfilter-at.html. (And similar answer https://github.com/aspnet/Mvc/issues/8575)
public class SuppressModelStateInvalidFilterAttribute : Attribute, IActionModelConvention
{
private const string FilterTypeName = "ModelStateInvalidFilterFactory";
public void Apply(ActionModel action)
{
for (var i = 0; i < action.Filters.Count; i++)
{
//if (action.Filters[i] is ModelStateInvalidFilter)
if (action.Filters[i].GetType().Name == FilterTypeName)
{
action.Filters.RemoveAt(i);
break;
}
}
}
}
Example of use
[ApiController]
public class PersonController
{
[SuppressModelStateInvalidFilter]
public ActionResult<Person> Get() => new Person();
}
I need to integrate a new Security API into several existing applications in my organization. Some applications use ASP.NET MVC and use the .NET AuthorizeAttribute class to decorate classes with security.
For example:
[Authorize(Roles="MY_CORP\Group1,MY_CORP\Group2")]
public class MyClass
{
//
}
The code above is based on a Windows authentication configuration. I need to update this implementation to use the new Security API. The new Security API will retrieve a user like this:
var user = new SecurityApi().GetUser(userId);
var groups = user.Groups;
So ideally the updated decorator would look something like this, where GroupX and GroupY exist as user.Groups returned from the Security API:
[Authorize(Roles="GroupX, GroupY")]
public class MyClass
{
//
}
Any idea how I would go about implementing this?
I use something along the lines of this:
public class RequireAuthAttribute : TypeFilterAttribute
{
public RequireAuthAttribute(params Roles[] rolesRequirement)
: base(typeof(RequireAuthFilter))
{
Arguments = new object[] { rolesRequirement };
}
public enum Roles: ushort
{
CompanyOnly,
AuthenticatedCustomer,
AuthorizedCustomer,
AuthorizedOwnerManager
}
}
With:
public class RequireAuthFilter : IAsyncActionFilter
{
private readonly Roles[] _rolesToAllow;
public RequireAuthFilter(Roles[] rolesRequirement = default(Roles[]))
{
_rolesToAllow = rolesRequirement;
}
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next )
{
// Verify is Authenticated
if (context.HttpContext.User.Identity.IsAuthenticated != true)
{
context.HttpContext.SetResponse(401, "User is not Authenticated");
return;
}
var isCompanyAdmin = context.HttpContext.IsCompanyAdmin();
// ^ HttpContext Extension method that looks at our JWT Token
// and determines if has required Cliams/Roles.
if (isCompanyAdmin == true)
{
await next();
return;
} else {
context.HttpContext.SetResponse(401, "Restricted to Company");
return;
}
// Other custom logic for each role.
// You will want to decide if comma represents AND or an OR
// when specifying roles.
}
}
And use like this:
[RequireAuth(Roles.CompanyOnly, Roles.AuthorizedOwnerManager)]
public class MyClass
{
//
}
Is this an acceptable implementation of a custom bearer token authorization mechanism?
Authorization Attribute
public class AuthorizeAttribute : TypeFilterAttribute
{
public AuthorizeAttribute(): base(typeof(AuthorizeActionFilter)){}
}
public class AuthorizeActionFilter : IAsyncActionFilter
{
private readonly IValidateBearerToken _authToken;
public AuthorizeActionFilter(IValidateBearerToken authToken)
{
_authToken = authToken;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
const string AUTHKEY = "authorization";
var headers = context.HttpContext.Request.Headers;
if (headers.ContainsKey(AUTHKEY))
{
bool isAuthorized = _authToken.Validate(headers[AUTHKEY]);
if (!isAuthorized)
context.Result = new UnauthorizedResult();
else
await next();
}
else
context.Result = new UnauthorizedResult();
}
}
Validation Service. APISettings class is used in appSettings, but validation can be extended to use a database ... obviously :)
public class APISettings
{
public string Key { get; set; }
}
public class ValidateBearerToken : IValidateBearerToken
{
private readonly APISettings _bearer;
public ValidateBearerToken(IOptions<APISettings> bearer)
{
_bearer = bearer.Value;
}
public bool Validate(string bearer)
{
return (bearer.Equals($"Bearer {_bearer.Key}"));
}
}
Implementation
[Produces("application/json")]
[Route("api/my")]
[Authorize]
public class MyController : Controller
appSettings
"APISettings": {
"Key": "372F78BC6B66F3CEAF705FE57A91F369A5BE956692A4DA7DE16CAD71113CF046"
}
Request Header
Authorization: Bearer 372F78BC6B66F3CEAF705FE57A91F369A5BE956692A4DA7DE16CAD71113CF046
That would work, but it's kind of reinventing the wheel.
I good approach these days is to use JWTs, you can find more info about it here: http://www.jwt.io/
Some advantages are that it integrates quite nicely with asp.net core and you can also add some information to the token (username, role, etc). That way, you don't even need to access the database for validation (if you want to).
Also, storing keys in appsettings file could lead to accidentally adding them to your source-code manager (security). You could use user secrets for local development (or disable the key when environment = dev) and environment variables for production.
Here is one good example of how to use jwt with asp.net: https://jonhilton.net/2017/10/11/secure-your-asp.net-core-2.0-api-part-1-issuing-a-jwt/
As I am working on Asp.Net core Authorization part, I needed a new property in AuthorizeAttribute which I want to utilize as a extra permission value. So, I have extended the AuthorizeAttribute in my own custom Authorize attribute. See below:
public class RoleAuthorizeAttribute : Microsoft.AspNetCore.Authorization.AuthorizeAttribute
{
public string Permission { get; private set; }
public RoleAuthorizeAttribute(string policy, string permission) : base(policy)
{
this.Permission = permission;
}
}
Then, I've created an AuthorizationHandler to check for the requirement as below:
public class RolePermissionAccessRequirement : AuthorizationHandler<RolePermissionDb>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolePermissionDb requirement)
{
// check here..
context.Succeed(requirement);
return Task.FromResult(0);
}
}
All respective service collection mapping I have already done, just omitted here.
Now, I want my attribute to use like this on controller action level:
[RoleAuthorize("DefaultPolicy", "CustomPermission")]
public IActionResult List()
{
}
Would anybody suggest me how would I access the permission property value given on the top of Action method in the handler RolePermissionAccessRequirement ??
I want to perform some sort of access rule based on custom permission value given in the Authorize attribute on top of Action method.
Thanks in advance!
To parametrize a custom Authorize attribute, create an authorization filter implementing IAsyncAuthorizationFilter. Then wrap the filter in a TypeFilterAttribute-derived attribute. This attribute can accept parameters and pass it to the authorization filter's constructor.
Usage example:
[AuthorizePermission(Permission.Foo, Permission.Bar)]
public IActionResult Index()
{
return View();
}
Implementation:
public class AuthorizePermissionAttribute : TypeFilterAttribute
{
public AuthorizePermissionAttribute(params Permission[] permissions)
: base(typeof(PermissionFilter))
{
Arguments = new[] { new PermissionRequirement(permissions) };
Order = Int32.MinValue;
}
}
public class PermissionFilter : Attribute, IAsyncAuthorizationFilter
{
private readonly IAuthorizationService _authService;
private readonly PermissionRequirement _requirement;
public PermissionFilter(
IAuthorizationService authService,
PermissionRequirement requirement)
{
//you can inject dependencies via DI
_authService = authService;
//the requirement contains permissions you set in attribute above
//for example: Permission.Foo, Permission.Bar
_requirement = requirement;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
bool ok = await _authService.AuthorizeAsync(
context.HttpContext.User, null, _requirement);
if (!ok) context.Result = new ChallengeResult();
}
}
In addition, register a PermissionHandler in DI to handle PermissionRequirement with permission list:
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
Look at this this GitHub project for a complete example.
I'm looking for a way to program a custom authorization filter in ASP.NET 5 as the current implementation relies in Policies/Requirements which in turn rely solely in the use of Claims, thus on the umpteenth and ever-changing Identity System of which I'm really tired of (I've tried all it's flavors).
I have a large set of permissions (over 200) which I don't want to code as Claims as I have my own repository for them and a lot faster way to be check against it than comparing hundreds of strings (that is what claims are in the end).
I need to pass a parameter in each attribute that should be checked against my custom repository of permissions:
[Authorize(Requires = enumPermission.DeleteCustomer)]
I know this is not the most frequent scenario, but I think it isn't an edge case. I've tried implementing it in the way described by #leastprivilege on his magnificent post "The State of Security in ASP.NET 5 and MVC 6: Authorization", but I've hit the same walls as the author, who has even opened an issue on the ASP.NET 5 github repo, which has been closed in a not too much clarifying manner: link
Any idea of how to achieve this? Maybe using other kind of filter? In that case, how?
Following is an example of how you can achieve this scenario:
Let's assume you have a service called IPermissionStore which validates if a given user has the required permissions specified on the attribute.
public class MyCustomAuthorizationFilterAttribute : Attribute, IFilterFactory, IOrderedFilter
{
private readonly Permision[] _permissions;
public MyCustomAuthorizationFilterAttribute(params Permision[] permissions)
{
_permissions = permissions;
}
public int Order { get; set; }
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var store = serviceProvider.GetRequiredService<IPermissionStore>();
return new MyCustomAuthorizationFilter(store, _permissions)
{
Order = Order
};
}
}
public class MyCustomAuthorizationFilter : IAuthorizationFilter, IOrderedFilter
{
private readonly IPermissionStore _store;
private readonly Permision[] _permissions;
public int Order { get; set; }
public MyCustomAuthorizationFilter(IPermissionStore store, params Permision[] permissions)
{
_store = store;
_permissions = permissions;
}
public void OnAuthorization(AuthorizationContext context)
{
// Check if the action has an AllowAnonymous filter
if (!HasAllowAnonymous(context))
{
var user = context.HttpContext.User;
var userIsAnonymous =
user == null ||
user.Identity == null ||
!user.Identity.IsAuthenticated;
if (userIsAnonymous)
{
Fail(context);
}
else
{
// check the store for permissions for the current user
}
}
}
private bool HasAllowAnonymous(AuthorizationContext context)
{
return context.Filters.Any(item => item is Microsoft.AspNet.Authorization.IAllowAnonymous);
}
private void Fail(AuthorizationContext context)
{
context.Result = new HttpUnauthorizedResult();
}
}
// Your action
[HttpGet]
[MyCustomAuthorizationFilter(Permision.CreateCustomer)]
public IEnumerable<string> Get()
{
//blah
}