I am developing an intranet asp.net core web api application. The requirements for authentications are:
REQ1 - when user which is trying to access the website is not in Active Directory's special group (let's name it "commonUsers") it is simply not authorized
REQ2 - when user which is trying to access the website is in Active Directory's group "commonUsers" is is authorized and a web resource is returned
REQ3 - when user which is trying to access the website is in Active Directory's group "superUser", it need to be prompted for his domain password once again (because it tries to access some very restricted resources)
Now, what I have so far:
My service is hosted using http.sys server in order to support windows authentication.
I am using claims transformer middlewere in order to check the user's Active Directory group, let's say something like this:
public class ClaimsTransformer : IClaimsTransformation {
private readonly IAuthorizationService _authorizationService;
public ClaimsTransformer(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
_authorizationService.Authorize(principal as IHmiClaimsPrincipal);
return Task.FromResult(principal);
}}
I have specified a special policies also in my service configuration, for instance something like that:
services.AddAuthorization(options =>
{
options.AddPolicy("TestPolicy", policy =>
policy.RequireClaim(ClaimTypes.Role, "TestUser"));
options.AddPolicy("TestPolicy2", policy =>
policy.RequireClaim(ClaimTypes.Role, "SuperUser"));
});
I am using [Authorize] attribute with specific policy in order to restrict access to specific resources based on policies
Now the question is, how should I satisfy REQ3?
I think I would try to use MVC Filters : https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#authorization-filters
Filters run after all Middleware, but before the Action. This will allow you to control the redirect to credentials page just for specific actions or controllers. Whilst normally this is not the recommended method for authorization, I think it fits your requirements for a hybrid secondary authentication.
public class SuperUserFilter : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.Request.Cookies.TryGetValue("SuperUserCookie", out string cookieVal))
{
if (!IsValidCookie(cookieVal))
context.Result = LoginPage(context);
}
else
{
context.Result = LoginPage(context);
}
}
private bool IsValidCookie(string cookieVal)
{
//validate cookie value somehow
// crytpographic hash, store value in session, whatever
return true;
}
private ActionResult LoginPage(AuthorizationFilterContext context)
{
return new RedirectToActionResult("SuperUser", "Login",
new {redirectUrl = context.HttpContext.Request.GetEncodedUrl()});
}
}
Then you create a Login Controller
public class LoginController : Controller
{
[HttpGet]
public IActionResult SuperUser(string redirectUrl)
{
// return a page to enter credentials
// Include redirectUrl as field
}
[HttpPost]
public IActionResult SuperUser(LoginData loginData)
{
// Validate User & Password
Response.Cookies.Append("SuperUserCookie", "SomeValue");
return Redirect(loginData.RedirectUrl);
}
}
Then you can decorate specific actions (or controllers) as required:
public class MyController : Controller
{
[HttpGet]
[SuperUserFilter]
public IActionResult MySensitiveAction()
{
// Do something sensitive
}
}
I'm guessing you are try to implement two step authentication for some of your resource.
To do that you must use multiple authentication scheme and Authorize policies,
but it's difficult because windows authentication is not controllable. we need to use some trick to know this is your second login.
authentication
The Default Authenticaiton Scheme : Windows, it's the basic scheme for authenticate a windows user.
Second Cookies base Authentication scheme : SuperUserTwoStep. we need this to goto our custom login logic.
Authorize
the Authorize policies for specified scheme.
a login page for login to SuperUserTwoStep scheme.
//startup
services.AddAuthentication(HttpSysDefaults.AuthenticationScheme)
.AddCookie("SuperUserTwoStep",op=>op.LoginPath = "/account/superuser2steplogin");
services.AddAuthorization(op =>
{
op.AddPolicy("SuperUser", b => b.AddAuthenticationSchemes("SuperUserTwoStep")
.RequireAuthenticatedUser()
.RequireClaim(ClaimTypes.Role, "SuperUser"));
});
// login
public static IDictionary<string, string> States { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
[Route("/account/superuser2steplogin")]
public async Task<IActionResult> LoginTwoStepConfirm(string returnUrl, [FromServices]IAuthorizationService authorizationService,
[FromServices]IAuthorizationPolicyProvider policyProvider)
{
var winresult = await HttpContext.AuthenticateAsync(IISDefaults.AuthenticationScheme);
if (winresult.Succeeded)
{
if (States.TryGetValue(winresult.Principal.Identity.Name, out _))
{
States.Remove(winresult.Principal.Identity.Name);
var principal = new System.Security.Claims.ClaimsPrincipal(new System.Security.Claims.ClaimsIdentity(winresult.Principal.Claims,"twostepcookie"));
await HttpContext.SignInAsync("SuperUserTwoStep", principal);
return Redirect(returnUrl);
}
else
{
States[winresult.Principal.Identity.Name] = "1";
return Challenge(IISDefaults.AuthenticationScheme);
}
}
else
{
return Challenge(IISDefaults.AuthenticationScheme);
}
}
[Authorize("SuperUser")]
public IActionResult YourSecurePage()
{
return Content("hello world");
}
the most difficult thing is to track that this is the second time to login, I try to use cookie , but it doen't work, so I crate a static IDitionary<string,string> to track ,maybe use distributed cache is better
I think in my opinion you should consider using: Policy-based authorization with Requirements, basically you have different authorization requirements that you want to treat them on and AND basis
REQ1 and REQ2 and REQ3
Here you have the link to the documentation: Requirements
But you need to understand that identity != permissions, the guys that introduce this concept of policies to Microsoft created a project named: PolicyServer and it is opensource: PolicyServer Git and they created a pattern there of how you should use your policies. Basically, you have external and internal users that are authenticated against your AD, all internal users should have permissions assigned to a role. And you only decorate your controller action with the permission rule you created for that policy
[Authorize("PerformSurgery")]
public async Task<IActionResult> PerformSurgery()
{
// omitted
}
To understand the code and how they evaluate a policy, I think you should see the video they have online on the website: Policy Server
Hope this helps
Related
I'm working on a web application built using .NET Framework 4.8. The application has one side for the public and one for admins.
The application is registered on Azure, and I'm trying to use Azure AD groups to authenticate and authorize users.
I setup the Middleware for Azure authentication in a a Startup.Auth.cs partial class. The code in the partial class is shown at below.
To access the admin side, the user has to type /admin in the URL which will go to the admin controller.
I use a custom Authorize attribute in the Admin Controller with a specific Role. It's used before the initialization of the AdminController class.
The custom Authorize class code is shown below.
Locally, the application seems to be working fine and it allows people in the group specified in the authorize attribute and redirects ones without access to the Home like we want.
When we publish the code to Production, the application always takes the user back to home when they try to go to the admin side.
The redirectURI is setup to be "https://example.com/admin" and it is added to the application in Azure.
Using Authorize attribute in the admin controller allows anyone that is part of the Azure AD for the tenant.
Please let me know if I'm missing anything or what your thoughts are
Startup.Auth.cs
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ClientId"];
private static string aadInstance = ConfigurationManager.AppSettings["AADInstance"];
private static string tenantId = ConfigurationManager.AppSettings["TenantId"];
private static string redirectUri = ConfigurationManager.AppSettings["RedirectUri"];
private static string authority = aadInstance + tenantId + "/v2.0";
public void ConfigurationAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UserOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions()
{
ClientId = clientId;
Authority = authority;
RedirectUri = redirectUri;
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = (context) => {
context.HandleResponse();
context.Response.Redirect("Home/index");
return Task.FromResult(0);
}
}
});
}
}
Custom authorize attribute AuthorizeAttribute.cs:
public class AuthorizeAD : AuthorizeAttribute
{
private bool noPermission = false;
protected override bool AuthorizationCore(HttpContextBase httpContext)
{
if(!httpContext.User.Identity.IsAuthenticated)
return false;
var roles = Roles.Trim().Split(',');
if(roles.ToList().Exists(role => httpContext.User.IsInRole(role)))
{
return true;
}
else
{
noPermission = true;
return false;
}
}
protected override void HandleUnAuthorizedRequest(AuthorizationContext filterContext)
{
if(noPermission)
filterContext.Result = new RedirectResult("Home/index");
else
base.HandleUnauthorizedRequest(filterContext);
}
}
Any help, feedback, or suggestions would be great. Thank you in advance!
• You can capture the return URL using the request information instead of using the ‘custom AuthorizeAttribute’ which will make your ‘returnURL’ or redirect URI available within ‘Request.QueryString[]’ dictionary. Also, you will need to add the below in your login view to make it actionable and the below it in your login form: -
#{
ViewBag.ReturnUrl = Request.QueryString["returnUrl"];
}
#using (Html.BeginForm("Login", "Account", new {returnUrl = ViewBag.ReturnUrl}, FormMethod.Post, new{#class="form-horizontal form-material", #onsubmit="return loading_event();", #id="loginForm"}))
Please find the below SO community thread for more reference and clarification on this: -
Custom Authorize attribute with redirect to original URL
Also, since you want to use the custom ‘AuthorizeAttribute’ class with a specific role for access in the ‘AdminController’ class, you can surely use them by leveraging the role-based authorization for the actions in the ‘AdminController’ class as follows: -
String constants in ‘Auth.cs’ : -
public static class RoleConstants
{
public const string Admin = "Admin";
public const string Moderator = "Moderator";
// more roles
}
‘AdminController’ class is as below after the above constant inclusion: -
[Authorize(Roles=RoleConstants.Admin+","+RoleConstants.Moderator)]
public class AdminController : Controller
{
// ...
}
Please find the below link for more information regarding the above: -
https://www.telerik.com/blogs/creating-custom-authorizeattribute-asp-net-core
I am trying to add some custom role based authorisation, but I am unable to
get the Startup configured to call my AuthorizationHandler.
I found some related information on GitHub: here.
Is this a bug or not ?
I am using ASP.NET Core 3.1 and my initializaion is as follows:
1: This retrieves the url/roles from the database using Dapper ORM:
private List<UrlRole> GetRolesRoutes()
{
var urlRole = DapperORM.ReturnList<UrlRole>("user_url_role_all");
return urlRole.Result.ToList();
}
2: In my Startup, I get the url/roles and store the result in a global variable:
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
this.environment = env;
UrlRoles = GetRolesRoutes();
}
3: My Configuration is: Note the UrlRoles which is passed along
public void ConfigureServices(IServiceCollection services)
{
// .. snip
services.AddAuthorization(o =>
o.AddPolicy(_RequireAuthenticatedUserPolicy,
builder => builder.RequireAuthenticatedUser()));
services.AddAuthorization(options =>
{
options.AddPolicy("Roles", policy =>
policy.Requirements.Add(new UrlRolesRequirement(UrlRoles)));
});
services.AddSingleton<AuthorizationHandler<UrlRolesRequirement>, PermissionHandler>();
}
5: My Handler: which is not being called
public class PermissionHandler : AuthorizationHandler<UrlRolesRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UrlRolesRequirement urlRolesRequirement)
{
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
}
return Task.CompletedTask;
}
}
6: My Requirement class:
public class UrlRolesRequirement : IAuthorizationRequirement
{
private List<UrlRole> UrlRoles { get; }
public UrlRolesRequirement(List<UrlRole> urlRoles)
{
UrlRoles = urlRoles;
}
}
When I debug the ASP.NET Core AuthorizationHandler, I never see that my custom Requirement as being a requirement, which I configured in the Startup. I expected to see the requirement, and if the requirement is present then the "callback" will happen I assume. But for some reason my configuration fails to add the requirement.
public virtual async Task HandleAsync(AuthorizationHandlerContext context)
{
if (context.Resource is TResource)
{
foreach (var req in context.Requirements.OfType<TRequirement>())
{
await HandleRequirementAsync(context, req, (TResource)context.Resource);
}
}
}
Anybody in 2022 having this problem with asp.net core on dotnet 6+ ?
The problem I had with my code was that, I registered my handlers without specifying their interface type of "IAuthorizationHandler".
For example:
Instead of doing this: builder.Services.AddScoped<MustHaveAdminManagementScopeHandler>();
Do this:
builder.Services.AddScoped<IAuthorizationHandler, MustHaveAdminManagementScopeHandler>();
Also, do not register multiple handlers as Singletons. I see the Microsoft docs recommending this, but for some reason, registering multiple IAuthorizationHandler as singletons cause an exception.
Without telling ASP.NET Core to do so, it will not use your configured policy to authorize anything. Authorization policies are so that you can predefine complex authorization conditions so that you can reuse this behavior when you need it. It however does not apply by default, and it couldn’t considering that you already configure two policies: Which of those should apply? All of them? Then why configure separate policies?
So instead, no policy is being used to authorize a user unless you explicitly tell the framework that. One common method is to use the [Authorize] attribute with the policy name. You can put that on controller actions but also on controllers themselves to make all its actions authorize with this policy:
[Authorize("Roles")] // ← this is the policy name
public class ExampleController : Controller
{
// …
}
If you have a policy that you want to use most of the times to authorize users, then you can configure this policy as the default:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
}
This for example will define a policy that requires an authenticated users as the default. So whenever you use the [Authorize] attribute without specificing an explicit policy, then it will use that default policy.
This all will still require you to mark your routes somehow that you require authorization. Besides using the [Authorize] attribute, you can also do this in a more central location: The app.UseEndpoints() call in your Startup class.
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}")
.RequireAuthorization("Roles");
This is the default route template for controllers but with a call to RequireAuthorization which will basically require the Roles authorization policy on all routes that match this route template.
You can use also use this place to configure different default authorization policies for your different routes: By splitting up your route template, you can have multiple calls to MapControllerRoute with different route templates that all specify their own authorization policy.
I was thinking that instead of decorating each and every controller or action, I rather wanted to have some pre-configuration map in a DB, and then in the pipeline verify the users role or roles which are allocated when the user authenticates. When the user then tries to access a url, the users role gets verified and access is granted or rejected.
You could move the logic how exactly a user is authorized into the authorization handler that verifies your requirement. You would still enable the policy that has this requirement for all the routes you want to test though.
However, I would generally advise against this: Authorization requirements are meant to be simple, and you usually, you want to be able to verify them without hitting a database or something other external resource. You want to use the user’s claims directly to make a quick decision whether or not the user is authorized to access something. After all, these checks run on every request, so you want to make this fast. One major benefit of claims based authorization is that you do not need to hit the database on every request, so you should keep that benefit by making sure everything you need to authorize a user is available in their claims.
Here is a tested solution, which enables runtime configuration changes.
Also relieving the burden of decorating each class or action.
In the Startup Add the Role Authorization Requirement, and also register the
RoleService which will be responsible for ensuring a particular role is authorised to access a particular URL.
Here is the Startup.cs where we configure the requirement and also the role service:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddRequirements(new UrlRolesRequirement())
.Build();
});
services.AddSingleton<IUserService, UserService>(); // authenticate
services.AddSingleton<IUserRoleService, UserRoleService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); // authorise
The role IUserRoleService: - the UserRoleService implementation
validates a users claimed role (a JWT claim) against a configuration map consisting of url/role entries that are allowed, by looking in either in cached map or retrieving the data from the database.
A typical url(path) to role map has the following format, retrieve from a database, then cached (if lookups fail, the data is retrieved from the database):
/path/to/resource ROLE
public interface IUserRoleService
{
public bool UserHasAccess(ClaimsPrincipal user, string path);
}
The Permission handler:
public class PermissionHandler : IAuthorizationHandler
{
private readonly IUserRoleService userRoleService;
private readonly IHttpContextAccessor contextAccessor;
public PermissionHandler(IUserRoleService userRoleService, IHttpContextAccessor contextAccessor)
{
this.userRoleService = userRoleService;
this.contextAccessor = contextAccessor;
}
public Task HandleAsync(AuthorizationHandlerContext context)
{
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
if (!(requirement is UrlRolesRequirement)) continue;
var httpContext = contextAccessor.HttpContext;
var path = httpContext.Request.Path;
if (userRoleService.UserHasAccess(context.User, path))
{
context.Succeed(requirement);
break;
}
}
return Task.CompletedTask;
}
}
The RolesRequirement - just a POCO
public class UrlRolesRequirement : IAuthorizationRequirement
{
}
Here is a partial implementation of the UserRoleService which validates the JWT role claimed.
private bool ValidateUser(ClaimsPrincipal user, string path)
{
foreach (var userClaim in user.Claims)
{
if (!userClaim.Type.Contains("claims/role")) continue;
var role = userClaim.Value;
var key = role + SEPARATOR + path;
if (urlRoles.ContainsKey(key))
{
var entry = urlRoles[key];
if (entry.Url.Equals(path) && entry.Role.Equals(role))
{
return true;
}
}
}
Console.WriteLine("Access denied: " + path);
return false;
}
I had an immediate 403 response, my custom authorization handler code was never reached. Turns out I forgot to inject them (Scoped is fine). That solved the issue for me.
I have created an IdentityServer4 application, if I login inside that application the user claims are all good. If I login from another client application (MVC) the UserInfo endpoint doesn't return the same claims.
The IdentityServer is configured with ASP.NET Identity, so the UserProfile is already configured to return all UserClaims, like the one I created.
I don't understand why it's not showed on consent view or it's not included in UserInfo endpoint result
Please check for the below points if they can solve your issue
1.) Your Identity resource and API resource should have the required UserClaims.
2.) Check if there is some custom logic to issue requested claims for userinfo endpoint in your profile service.
public class ProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
if (context.Caller == IdentityServerConstants.ProfileDataCallers.UserInfoEndpoint)
{
//custom logic to add requested claims
context.AddRequestedClaims(claims);
}
}
}
3.) Try to make the property 'GetClaimsFromUserInfoEndpoint=true' in your MVC client AddOpenIdConnect configuration.
have you configured your IdentityResources?
Something like:
services.AddIdentityServer()
.AddInMemoryIdentityResources(GetIdentityResources())
//where
public static List<IdentityResource> GetIdentityResources()
{
// Claims automatically included in OpenId scope
var openIdScope = new IdentityResources.OpenId();
openIdScope.UserClaims.Add(JwtClaimTypes.Locale);
// Available scopes
return new List<IdentityResource>
{
openIdScope,
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource(Constants.RolesScopeType, Constants.RolesScopeType,
new List<string> {JwtClaimTypes.Role, Constants.TenantIdClaimType})
{
//when false (default), the user can deselect the scope on consent screen
Required = true
}
};
}
I'm developing a custom middleware for authenticating clients that invokes an API.
I use an attribute to define if an Action requires authentication, but I can't figure out how to get a reference to the requested Controller and Action inside the Invoke method.
Below is my code so far
AuthenticateClient.cs:
public class AuthenticateClient
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly GenericUnitOfWork _worker;
public AuthenticateClient(RequestDelegate next, ApiDbContext db, IHttpContextAccessor httpContext, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptions<Utility.LCLog.Settings> settings)
{
_next = next;
_logger = loggerFactory.CreateLogger(settings.Value.ApplicationName);
_worker = new GenericUnitOfWork(new AppHelper(httpContext, db, env));
}
public async Task Invoke(HttpContext context)
{
if (!context.Request.Headers.Keys.Contains("ClientAuth"))
{
_logger.LogWarning("ClientAuth missing in request", new string[] { "Host: " + context.Request.Host, "IP: " + context.Request.HttpContext.Connection.RemoteIpAddress });
context.Response.StatusCode = 400;
await context.Response.WriteAsync("ClientAuth missing from request header values");
return;
}
else
{
string[] tmp = context.Request.Headers["ClientAuth"].ToString().Split("/");
if (tmp.Length != 2)
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("The format of the ClientAuth value is wrong");
return;
}
else
{
Client client;
string key, pass;
key = tmp[0];
pass = tmp[1];
client = await _worker.GetRepo<Client>().SingleOrDefault(clnt => clnt.Active && clnt.Key.Equals(key) && clnt.Password.Equals(pass));
if (client == null)
{
_logger.LogWarning("Client authentication failed", new string[] { "Key: " + key, "Password: " + pass, "Host: " + context.Request.Host, "IP: " + context.Request.HttpContext.Connection.RemoteIpAddress });
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Authentication failed");
return;
}
}
}
await _next.Invoke(context);
}
}
ClientAuthenticationAttribute.cs:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ClientAuthenticationAttribute : Attribute
{
private readonly bool _authRequired;
public ClientAuthenticationAttribute(bool authRequired = true)
{
_authRequired = authRequired;
}
public bool AuthRequired { get { return _authRequired; } }
}
I'd recommend you to split your logic for authentication and authorization and keep them in different places.
To recap from here:
Authentication is the process of verifying who you are.
Authorization is the process of verifying that, given that we know who you are, you have access to the specific resource.
What you're currently trying to do, is to both authenticate and authorize your user in the middleware component. Although you could probably get it to work by moving all such logic into filters which you register with the api framework (be it ASP.NET Core MVC, Web API 2 or something else), that would mean that none of your other middleware components have access to the user data (which, I'm guessing, is one of the reasons you chose to implement it in a middleware in the first place).
Given your new knowledge of the separation of authentication and authorization, a possible solution would be to do the following:
Middleware for authentication only
In your middleware, concern yourself only with authentication, and leave authorization up to components later in the pipeline. In practice, this means that your middleware should do the following:
Look for user tokens, cookies or whatever you use for the users to authenticate their request
If not present, treat the request as anonymous, and call the next pipeline component without attaching a user to the request context.
If a valid token is present, resolve the user data from it (e.g. parse the user's claims from a JWT, look up roles in a database, etc...) and store it on the request context. I've found it useful both to create an IPrincipal and set context.Request.User to it, as well as adding information to the context dictionary directly.
With the user registered in the request context, call the next pipeline component.
Authorization assuming an authenticated user
You can now re-write your authorization logic to assume that there's already an authenticated user registered on the request context.
In an ASP.NET Web API 2 application, you'd implement a custom filter attribute inheriting from AuthorizationFilterAttribute, to make sure it runs first of the filters. In my current application, for example, we have the following attribute to authorize that a user has a specific claim. Note that it doesn't do any work to figure out who the user is; if a user is not attached to the context, the response is simply Unauthorized. You could be more sophisticated here, and treat anonymous requests differently from authenticated requests for users who lack access, and, for example, redirect anonymous requests to the login form, while redirecting users lacking access to an error page stating as much.
[AttributeUsage(validOn: AttributeTargets.Method)]
public class AuthorizeClaimsFilterAttribute : AuthorizationFilterAttribute
{
public AuthorizeClaimsFilterAttribute(string claimType, string claimValue)
{
ClaimType = claimType;
ClaimValue = claimValue;
}
public string ClaimType { get; }
public string ClaimValue { get; }
public override void OnAuthorization(HttpActionContext actionContext)
{
if (!(actionContext.RequestContext.Principal is ClaimsPrincipal principal)
|| !principal.HasClaim(x => x.Type == ClaimType && x.Value == ClaimValue))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
To use it, we just decorate the action method with it:
[AuthorizeClaimsFilter("urn:ourapp:claims:admin", true)]
public IHttpActionResults OnlyAdminsCanAccess() { /* ... */ }
I believe I understand the basics of sessionless/stateless REST but I am having problems with implementation in Asp.Net Web Api 2 because I haven't used it before. I have set up ApiControllers that use a custom System.Web.Http.AuthorizeAttribute like this.
public class ApiAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization != null)
{
//Set identity??
return;
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
I have a database that contains users and I need to use them for getting privileges to get/post/put/delete things but dont want to use a session. I have never worked with the asp.net Identity so I am not familiar with its features and capabilities.
My idea for implementation is to use user credentials or api secret signing to authenticate and get privileges for a user for every request. The question is, by using a AuthorizeAttribute or something similar, how do i give the controller(s) during that one request the user information if their credentials were correct?
UPDATE:
Is using this.User (ApiController.User) session based or can it be used for that single request. If so, how does one set it
You can use HttpContext.Items to hold user data for current requests.
After setting identity based on Auth header , you can have
System.Web.HttpContext.Current.Items["userdata"]=userDataObject,
Another approach would be write your own action filter for authentication (but take utmost care)and pass data to controller.
public class MyAuthAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//do authorization here
base.OnActionExecuting(filterContext);
// Create object parameter.
filterContext.ActionParameters["userdata"] = new User("John", "Smith");
}
}
then in controller
[MyAuthAttribute]
ActionResult SomeAction(User userdata) //this will have user data
{
}
It looks like that using IPrincipal and setting HttpContext.Current.User will allow the ApiControllers to access that user through using
this.User
with web api not having access to the session
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization != null)
{
//Check user credentials/token here
//Get internal user
IPrincipal principal = new MyOwnCustomPrincipal(internalUser);
HttpContext.Current.User = principal;
return;
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}