I have configured my web api to work with windows authentication. My goal is essentially to restrict certain actions in my controllers based on a users windows account. Some will be able to preform read actions while others will be able to preform actions that will write to the underlying database. I have found plenty of documentation on how to set up claims based authorization which is the route I think I need to go. What I have not found is how to set this up with windows auth. I think I am missing a middle step such as registering the windows auth as the identity provider?
startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddAuthorization(options =>
{
options.AddPolicy("readOnly", policy =>
policy.RequireClaim(`???????????????????????`));
options.AddPolicy("write", policy =>
policy.RequireClaim(`???????????????????????`));
});
}
Controller
[Authorize(Policy = "ReadOnly")]
public class MyController : Controller
{
public ActionResult SomeReadOnlyAction()
{
//Return data from database
}
[Authorize(Policy = "Write")]
public ActionResult AWriteAction()
{
//Create/Update/Delete data from database
}
}
I guess another way to ask this question is how do you configure or access claims/roles etc... with windows authentication.
That seems you want to use claims-based authorization via policies . After setting windows authentication in your application , you could add custom claim to ClaimsPrincipal ,check user's identity and confirm which permission current user has :
You can add a claims transformation service to your application:
class ClaimsTransformer : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var id = ((ClaimsIdentity)principal.Identity);
var ci = new ClaimsIdentity(id.Claims, id.AuthenticationType, id.NameClaimType, id.RoleClaimType);
if (ci.Name.Equals("name"))
{
ci.AddClaim(new Claim("permission", "readOnly"));
}
else
{
ci.AddClaim(new Claim("permission", "write"));
}
var cp = new ClaimsPrincipal(ci);
return Task.FromResult(cp);
}
}
Add to Startup.cs(.net Core 2.0) :
services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
Set your policy :
services.AddAuthorization(options =>
{
options.AddPolicy("Readonly", policy =>
policy.RequireClaim("permission", "readOnly"));
options.AddPolicy("Write", policy =>
policy.RequireClaim("permission", "write"));
});
Restrict access to a controller or action by requiring this policy:
[Authorize(Policy = "Write")]
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
If you have already add groups(write,readonly) in your AD and add the related users to group , you can also check the groups :
public static class Security
{
public static bool IsInGroup(this ClaimsPrincipal User, string GroupName)
{
var groups = new List<string>();
var wi = (WindowsIdentity)User.Identity;
if (wi.Groups != null)
{
foreach (var group in wi.Groups)
{
try
{
groups.Add(group.Translate(typeof(NTAccount)).ToString());
}
catch (Exception)
{
// ignored
}
}
return groups.Contains(GroupName);
}
return false;
}
}
And use like :
if (User.IsInGroup("GroupName"))
{
}
Related
I am creating a .NET 6 application that connects to an external identity provider for authentication using cookie authentication and OpenIdConnect. The external provider returns the user's JWT. Within that JWT, there are claims which denote which resources the user should have access to. These claims have a custom claim type of "privilege" and a unique value (something like "privilege": "create_blog").
I see that you can add a policy to check for a specific claim, like:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("CreateBlog", policy => policy.RequireClaim("privilege", "create_blog"));
});
However, in our application, I want to be able to specify the claim value via the attribute (because our system has hundreds of different privileges) like:
[PrivilegeAuthorize("create_blog")]
public IActionResult CreateBlog { ... }
I tried creating a custom authorization provider & authorization handler as described in the docs. That gets triggered & executed fine. However, when the provider fails (because the user is unauthenticated or the claim is missing), it doesn't trigger the right response. The user is redirected to /Account/Login (the default LoginPath for the CookieAuthenticationOptions). But I want it to OIDC identity provider if the user is not authenticated, and show my "Access Denied" page if the claim is missing.
I was finally able to develop a solution for this problem that conforms with Microsoft's suggested approaches for creating authentication policies and custom authorization providers. I created a custom authorization policy provider which uses Microsoft's provided AuthorizationOptions.AddPolicy(string, Action<AuthorizationPolicyBuilder>) method instead of instantiating a new AuthorizationPolicyBuilder for building the policy. I implemented that policy provider using a custom authorization attribute.
The content of the custom authorization provider is as follows:
public class PrivilegeAuthorizationProvider : IAuthorizationPolicyProvider
{
private readonly AuthorizationOptions _options;
private Task<AuthorizationPolicy>? _cachedDefaultPolicy;
private Task<AuthorizationPolicy?>? _cachedFallbackPolicy;
public PrivilegeAuthorizationProvider (IOptions<AuthorizationOptions> options)
{
this._options = options.Value;
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
{
if (this._cachedDefaultPolicy == null || this._cachedDefaultPolicy.Result != this._options.DefaultPolicy)
{
this._cachedDefaultPolicy = Task.FromResult(this._options.DefaultPolicy);
}
return this._cachedDefaultPolicy;
}
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
{
if (this._cachedFallbackPolicy == null || this._cachedFallbackPolicy.Result != this._options.FallbackPolicy)
{
this._cachedFallbackPolicy = Task.FromResult(this._options.FallbackPolicy);
}
return this._cachedFallbackPolicy;
}
public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
{
AuthorizationPolicy _policy = this._options.GetPolicy(policyName);
if (_policy != null)
{
return Task.FromResult(_policy);
}
if (_policy == null && policyName.StartsWith(PrivilegeAuthorizeAttribute.PolicyPrefix, StringComparison.OrdinalIgnoreCase))
{
string[] _splitPolicyName = policyName.Split(PrivilegeAuthorizeAttribute.PolicyNameSeparator);
if (_splitPolicyName.Length == 2)
{
string[] _values = _splitPolicyName[1].HasValue()
? _splitPolicyName[1].Split(PrivilegeAuthorizeAttribute.ClaimValuesSeparator)
: Array.Empty<string>();
if (_values.Length > 0)
{
this._options.AddPolicy(policyName, policy => policy.RequireClaim("privilege", _values));
_policy = this._options.GetPolicy(policyName);
}
}
}
return Task.FromResult(_policy);
}
}
In the application's program.cs (or startup.cs), the custom authorization provider is registered like:
_builder.Services.AddSingleton<IAuthorizationPolicyProvider, PrivilegeAuthorizationProvider>();
_builder.Services.AddAuthorization();
_builder.Services.AddAuthentication(opt =>
{
opt.DefaultScheme = "Cookies";
opt.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", opt => { ... });
The custom authorization attribute is as follows:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class PrivilegeAuthorizeAttribute : AuthorizeAttribute
{
internal const char ClaimValuesSeparator = ",";
internal const char PolicyNameSeparator = ":";
internal const string PolicyPrefix = "PrivilegeAuthorize";
public PrivilegeAuthorizeAttribute(string value)
{
this.Values = new[] { value };
}
public PrivilegeAuthorizeAttribute(params string[] values)
{
this.Values = values;
}
protected string?[] Values
{
get
{
if (this.Policy.HasValue())
{
string[] _splitPolicy = this.Policy!.Split(PolicyNameSeparator);
if (_splitPolicy.Length == 2 && _splitPolicy[1].HasValue()
{
return _splitPolicy[1].Split(ClaimValuesSeparator);
}
}
return Array.Empty<string>();
}
set
{
this.Policy = $"{PolicyPrefix}{PolicyNameSeparator}{string.Join(ClaimValuesSeparator, value)}";
}
}
}
Then the attribute can be applied to a controller, an action method, or a razor page like:
[PrivilegeAuthorize("MySpecialPrivilegeClaimValue")]
public class Index : PageModel
{
public void OnGet()
{ }
}
With this, the application checks the user's token for a claim of type "privilege" and a value which is specified in the attribute parameter. If the user isn't authenticated, they're redirected to the OIDC provider. If the user is authenticated, but the claim doesn't exist, the user is redirected to the application's internal "Access Denied" page. If the user is authenticated and has the proper claim, they can see the desired page.
I'm setting up a new instance of IdentityServer as an identity provider. While logging in, I want to set some extra, custom claims on my user object. Right now, I'm using the following code:
[HttpPost]
public async Task<IActionResult> ExecuteLogin(string returnUrl, string loginId)
{
TestUser user = Config.GetUsers().Find(x => x.SubjectId == loginId);
if (user != null)
{
var identityServerUser = new IdentityServerUser(user.SubjectId)
{
AdditionalClaims = user.Claims
};
await HttpContext.SignInAsync(identityServerUser);
return Redirect(returnUrl);
}
else
{
return Redirect("Login");
}
}
I expected the AdditionalClaims to show up on the User.Claims object on the receiving application, which I use as following:
[Authorize]
public class HomeController : Controller
{
public IActionResult Index()
{
var claims = User.Claims;
return View(claims);
}
}
However, in the view only the standard claims are visible. Not my additional claims.
In the setup of IdentityServer I specified a client with access to the scope these claims are in, and an IdentityResource with the claimtypes specified in the TestUser. On the receiving application, I specified I want to receive that scope.
What makes that my claims are not visible on the receiving application?
It is not said what type of authentication you are using, but I suppose you want to add the claims to the access_token from where they can be read by on the API.
AdditionalClaims on IdentityServerUser are only added to the cookie in your client.
What you have to do is to create a profile service (https://docs.identityserver.io/en/latest/reference/profileservice.html).
At the simplest it will be something like this:
public class ProfileService : IProfileService
{
private UserService _userService;
public ProfileService(UserService userService)
{
_userService = userService;
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = await _userService.GetUserByIdAsync(context.Subject.GetSubjectId());
context.IssuedClaims.AddRange(user.Claims);
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
var user = await _userService.GetUserByIdAsync(context.Subject.GetSubjectId());
context.IsActive = user.IsActive;
return Task.FromResult(0);
}
}
And register it in the Startup.cs:
services.AddIdentityServer()
.AddProfileService<ProfileService>();
These can then be read from the access_token on the API side (if that's what you wanted as it is not clear from the question):
var user = User.Identity as ClaimsIdentity;
var claims = user.Claims;
You need to explicitly map those extra claims in your client, using code like:
options.ClaimActions.MapUniqueJsonKey("website", "website");
options.ClaimActions.MapUniqueJsonKey("gender", "gender");
options.ClaimActions.MapUniqueJsonKey("birthdate", "birthdate");
There is also this option you can set:
options.GetClaimsFromUserInfoEndpoint = true;
I have a dotnet core 2.2 api with some controllers and action methods that needs to be authorized based on a user claim and the resource being accessed. Basically, each user can have 0 or many "roles" for each resource. This is all done using ASP.NET Identity Claims.
So, my understanding is that I need to make use of Resource-based authorization. But both examples there are mostly identical and require the explicit imperative if/else logic on each action method, which is what I'm trying to avoid.
I want to be able to do something like
[Authorize("Admin")] // or something similar
public async Task<IActionResult> GetSomething(int resourceId)
{
var resource = await SomeRepository.Get(resourceId);
return Json(resource);
}
And somewhere else define the authorization logic as a policy/filter/requirement/whatever and have access to both the current user claims and the resourceId parameter received by the endpoint. So there I can see if the user has a claim that denotes that he has the "Admin" role for that specific resourceId.
Edit: Based on feedback to make it dynamic
The key thing with RBAC and claims in .NET, is to create your ClaimsIdentity and then let the framework do it's job. Below is an example middleware that will look at the query parameter "user" and then generate the ClaimsPrincipal based on a dictionary.
To avoid the need to actually wire up to an identity provider, I created a Middleware that sets up the ClaimsPrincipal:
// **THIS CLASS IS ONLY TO DEMONSTRATE HOW THE ROLES NEED TO BE SETUP **
public class CreateFakeIdentityMiddleware
{
private readonly RequestDelegate _next;
public CreateFakeIdentityMiddleware(RequestDelegate next)
{
_next = next;
}
private readonly Dictionary<string, string[]> _tenantRoles = new Dictionary<string, string[]>
{
["tenant1"] = new string[] { "Admin", "Reader" },
["tenant2"] = new string[] { "Reader" },
};
public async Task InvokeAsync(HttpContext context)
{
// Assume this is the roles
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "John"),
new Claim(ClaimTypes.Email, "john#someemail.com")
};
foreach (KeyValuePair<string, string[]> tenantRole in _tenantRoles)
{
claims.AddRange(tenantRole.Value.Select(x => new Claim(ClaimTypes.Role, $"{tenantRole.Key}:{x}".ToLower())));
}
// Note: You need these for the AuthorizeAttribute.Roles
claims.AddRange(_tenantRoles.SelectMany(x => x.Value)
.Select(x => new Claim(ClaimTypes.Role, x.ToLower())));
context.User = new System.Security.Claims.ClaimsPrincipal(new ClaimsIdentity(claims,
"Bearer"));
await _next(context);
}
}
To wire this up, just use the UseMiddleware extension method for IApplicationBuilder in your startup class.
app.UseMiddleware<RBACExampleMiddleware>();
I create an AuthorizationHandler which will look for the query parameter "tenant" and either succeed or fail based on the roles.
public class SetTenantIdentityHandler : AuthorizationHandler<TenantRoleRequirement>
{
public const string TENANT_KEY_QUERY_NAME = "tenant";
private static readonly ConcurrentDictionary<string, string[]> _methodRoles = new ConcurrentDictionary<string, string[]>();
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TenantRoleRequirement requirement)
{
if (HasRoleInTenant(context))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
private bool HasRoleInTenant(AuthorizationHandlerContext context)
{
if (context.Resource is AuthorizationFilterContext authorizationFilterContext)
{
if (authorizationFilterContext.HttpContext
.Request
.Query
.TryGetValue(TENANT_KEY_QUERY_NAME, out StringValues tenant)
&& !string.IsNullOrWhiteSpace(tenant))
{
if (TryGetRoles(authorizationFilterContext, tenant.ToString().ToLower(), out string[] roles))
{
if (context.User.HasClaim(x => roles.Any(r => x.Value == r)))
{
return true;
}
}
}
}
return false;
}
private bool TryGetRoles(AuthorizationFilterContext authorizationFilterContext,
string tenantId,
out string[] roles)
{
string actionId = authorizationFilterContext.ActionDescriptor.Id;
roles = null;
if (!_methodRoles.TryGetValue(actionId, out roles))
{
roles = authorizationFilterContext.Filters
.Where(x => x.GetType() == typeof(AuthorizeFilter))
.Select(x => x as AuthorizeFilter)
.Where(x => x != null)
.Select(x => x.Policy)
.SelectMany(x => x.Requirements)
.Where(x => x.GetType() == typeof(RolesAuthorizationRequirement))
.Select(x => x as RolesAuthorizationRequirement)
.SelectMany(x => x.AllowedRoles)
.ToArray();
_methodRoles.TryAdd(actionId, roles);
}
roles = roles?.Select(x => $"{tenantId}:{x}".ToLower())
.ToArray();
return roles != null;
}
}
The TenantRoleRequirement is a very simple class:
public class TenantRoleRequirement : IAuthorizationRequirement { }
Then you wire everything up in the startup.cs file like this:
services.AddTransient<IAuthorizationHandler, SetTenantIdentityHandler>();
// Although this isn't used to generate the identity, it is needed
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = "https://localhost:5000/";
options.Authority = "https://localhost:5000/identity/";
});
services.AddAuthorization(authConfig =>
{
authConfig.AddPolicy(Policies.HasRoleInTenant, policyBuilder => {
policyBuilder.RequireAuthenticatedUser();
policyBuilder.AddRequirements(new TenantRoleRequirement());
});
});
The method looks like this:
// TOOD: Move roles to a constants/globals
[Authorize(Policy = Policies.HasRoleInTenant, Roles = "admin")]
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
Below are the test scenarios:
Positive: https://localhost:44337/api/values?tenant=tenant1
Negative: https://localhost:44337/api/values?tenant=tenant2
Negative: https://localhost:44337/api/values
The key thing with this approach is that I never actually return a 403. The code setups the identity and then lets the framework handle the result. This ensures authentication is separate from authorization.
You could create your own attribute which will check the user's role. I have done this in one of my applications:
public sealed class RoleValidator : Attribute, IAuthorizationFilter
{
private readonly IEnumerable<string> _roles;
public RoleValidator(params string[] roles) => _roles = roles;
public RoleValidator(string role) => _roles = new List<string> { role };
public void OnAuthorization(AuthorizationFilterContext filterContext)
{
if (filterContext.HttpContext.User.Claims == null || filterContext.HttpContext.User.Claims?.Count() <= 0)
{
filterContext.Result = new UnauthorizedResult();
return;
}
if (CheckUserRoles(filterContext.HttpContext.User.Claims))
return;
filterContext.Result = new ForbidResult();
}
private bool CheckUserRoles(IEnumerable<Claim> claims) =>
JsonConvert.DeserializeObject<List<RoleDto>>(claims.FirstOrDefault(x => x.Type.Equals(ClaimType.Roles.ToString()))?.Value)
.Any(x => _roles.Contains(x.Name));
}
It gets user role from claims and check is user have proper role to get this resouce.
You can use it like this:
[RoleValidator("Admin")]
or better approach with enum:
[RoleValidator(RoleType.Admin)]
or you can pass a multiple roles:
[RoleValidator(RoleType.User, RoleType.Admin)]
With this solution you must also use the standard Authorize attribute.
Edited based on comments
According to my understanding, you want to access current user (all information related to it), the role(s) you want to specify for a controller (or action) and parameters received by endpoint. Haven't tried for web api, but for asp.net core MVC, You can achieve this by using AuthorizationHandler in a policy-based authorization and combine with an injected service specifically created to determine the Roles-Resources access.
To do it, first setup the policy in Startup.ConfigureServices :
services.AddAuthorization(options =>
{
options.AddPolicy("UserResource", policy => policy.Requirements.Add( new UserResourceRequirement() ));
});
services.AddScoped<IAuthorizationHandler, UserResourceHandler>();
services.AddScoped<IRoleResourceService, RoleResourceService>();
next create the UserResourceHandler :
public class UserResourceHandler : AuthorizationHandler<UserResourceRequirement>
{
readonly IRoleResourceService _roleResourceService;
public UserResourceHandler (IRoleResourceService r)
{
_roleResourceService = r;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext authHandlerContext, UserResourceRequirement requirement)
{
if (context.Resource is AuthorizationFilterContext filterContext)
{
var area = (filterContext.RouteData.Values["area"] as string)?.ToLower();
var controller = (filterContext.RouteData.Values["controller"] as string)?.ToLower();
var action = (filterContext.RouteData.Values["action"] as string)?.ToLower();
var id = (filterContext.RouteData.Values["id"] as string)?.ToLower();
if (_roleResourceService.IsAuthorize(area, controller, action, id))
{
context.Succeed(requirement);
}
}
}
}
Accessing the parameters received by endpoint is achieved by casting context.Resource to AuthorizationFilterContext, so that we could access RouteData from it. As for UserResourceRequirement, we can leave it empty.
public class UserResourceRequirement : IAuthorizationRequirement { }
As for the IRoleResourceService, it's a plain service class so that we can inject anything to it. This service is the substitute of pairing a Role to an action in code so that we don't need to specify it in action's attribute. That way, we can have a freedom to choose the implementation, ex: from database, from config file, or hard-coded.
Accessing user in RoleResourceService is achieved by injecting IHttpContextAccessor. Please note that to make IHttpContextAccessor injectable, add services.AddHttpContextAccessor() in Startup.ConfigurationServices method body.
Here's an example getting the info from config file:
public class RoleResourceService : IRoleResourceService
{
readonly IConfiguration _config;
readonly IHttpContextAccessor _accessor;
readonly UserManager<AppUser> _userManager;
public class RoleResourceService(IConfiguration c, IHttpContextAccessor a, UserManager<AppUser> u)
{
_config = c;
_accessor = a;
_userManager = u;
}
public bool IsAuthorize(string area, string controller, string action, string id)
{
var roleConfig = _config.GetValue<string>($"RoleSetting:{area}:{controller}:{action}"); //assuming we have the setting in appsettings.json
var appUser = await _userManager.GetUserAsync(_accessor.HttpContext.User);
var userRoles = await _userManager.GetRolesAsync(appUser);
// all of needed data are available now, do the logic of authorization
return result;
}
}
Get the setting from database surely is a bit more complex, but it can be done since we can inject AppDbContext. For the hardcoded approach, exist plenty ways to do it.
After all is done, use the policy on an action:
[Authorize(Policy = "UserResource")] //dont need Role name because of the RoleResourceService
public ActionResult<IActionResult> GetSomething(int resourceId)
{
//existing code
}
In fact, we can use "UserResource" policy for any action that we want to apply.
You can use Roles if you are using identity. Simply call authorize and provide it with the Role name on a resource or a whole controller or even add more roles to the authorize like below:
[Authorize(Roles ="Clerk")]
I am authorizing a User Role with a name of Clerk on a certain resource. To add more roles simply add a comma after the clerk and add the other role name
I'm trying to setup my authorization policy with a jwt based token for my apis. I have two controllers, used by separate apis. I need to make sure a user can only access the ones that he/she is allowed to use. So I figured I'd go with policy based authorization
[Authorize(Policy = "API1")]
[Route("api1/endpoint")]
public class API1Controller : Controller
{
// my actions for api 1
}
[Authorize(Policy = "API2")]
[Route("api2/endpoint")]
public class API2Controller : Controller
{
// my actions for api 2
}
Adding policies on Startup
services.AddAuthorization(options => {
options.AddPolicy("API1User", policy => policy.Requirements.Add(new ApplicationTypeRequirement(ApplicationType.API1)));
options.AddPolicy("API2User", policy => policy.Requirements.Add(new ApplicationTypeRequirement(ApplicationType.API2)));
});
// Adding handlers after this
So my question is, where is the best place to call a stored procedure to check the database for the users application permission. Reading from the following, (https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1) , it details the use of the claims from the token.
Right now what I have with the JWT token that I save is the userid, first name, last name and email, that's it.
The best place to check authentication and authorization in ActionFilter, you can check auth policy in database side and also with JWT.
If you want to authorise your controller, you have to use a middle ware (ActionFilterAttribute), which will detect user's http request and validate them by decoding user's token. you can filter all http methods (GET,POST,PUT,DELETE...etc), and can implement your own authorisation logic for specific http method.
AuthorizationRequiredAttribute.cs
N.B: here all codes are not relevant to your problem. but hope you'll understand how actually i filter get/post request with condition.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class AuthorizationRequiredAttribute : ActionFilterAttribute
{
private readonly IAccessTokenServices _accessTokenServices;
private readonly IPermissionServices _permissionServices;
private readonly IAuditLogServices _auditLogServices;
private IConfiguration _config;
public AuthorizationRequiredAttribute(IAccessTokenServices accessTokenServices, IPermissionServices permissionServices,
IAuditLogServices auditLogServices,IConfiguration config)
{
_accessTokenServices = accessTokenServices;
_config = config;
_permissionServices = permissionServices;
_auditLogServices = auditLogServices;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
try
{
if (context.HttpContext.Request.Headers.ContainsKey(Constants.HttpHeaders.Token))
{
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadToken(context.HttpContext.Request.Headers[Constants.HttpHeaders.Token])
as JwtSecurityToken;
var expireDate = Convert.ToDateTime(token.Claims.First(claim => claim.Type == Constants.JwtClaims.ExpiresOn).Value);
if (context.HttpContext.Request.Method == WebRequestMethods.Http.Get)
{
if (expireDate < DateTime.Now)
{
context.Result = new UnauthorizedResult();
}
}
else
{
var accessToken = _accessTokenServices
.Details(x => x.Token == context.HttpContext.Request.Headers[Constants.HttpHeaders.Token]);
if (accessToken != null)
{
if (accessToken.ExpiresOn < DateTime.Now)
{
_accessTokenServices.Delete(accessToken);
context.Result = new UnauthorizedResult();
}
else
{
var userId = Convert.ToInt32(token.Claims.First(claim => claim.Type == Constants.JwtClaims.UserId).Value);
var userTypeId = Convert.ToInt32(token.Claims.First(claim => claim.Type == Constants.JwtClaims.UserTypeId).Value);
if (accessToken == null)
{
context.Result = new UnauthorizedResult();
}
else if (!_permissionServices.IsPermissionExist(context.HttpContext.Request.Path.ToString(), userTypeId))
{
context.Result = new StatusCodeResult((int)HttpStatusCode.NotAcceptable);
}
else
{
_auditLogServices.Save(context.HttpContext.Request.Path.ToString(), userId);
accessToken.ExpiresOn = DateTime.Now.AddMinutes(Convert.ToInt16(_config["Jwt:ExpiresOn"]));
_accessTokenServices.UpdateExpireTime(accessToken);
}
}
}
else
{
context.Result = new UnauthorizedResult();
}
}
}
else
{
context.Result = new NotFoundResult();
}
}
catch (Exception ex)
{
context.Result = new BadRequestResult();
}
base.OnActionExecuting(context);
}
}
}
HomeController.cs
Now you can use AuthorizationRequiredAttribute as api/controller filter service. i have modified your controller and see the Message method
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Chat.Controllers
{
[Route("api/home")]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpGet("message"), ServiceFilter(typeof(AuthorizationRequiredAttribute))]
public IActionResult Message()
{
return Ok("Hello World!");
}
}
}
I'm trying to do a really simple implementation of JWT bearer authentication with ASP.NET Core. I return a response from a controller a bit like this:
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(ClaimTypes.Name, applicationUser.UserName));
var jwt = new JwtSecurityToken(
_jwtOptions.Issuer,
_jwtOptions.Audience,
identity.Claims,
_jwtOptions.NotBefore,
_jwtOptions.Expiration,
_jwtOptions.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
return new JObject(
new JProperty("access_token", encodedJwt),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", (int)_jwtOptions.ValidFor.TotalSeconds),
new JProperty(".issued", DateTimeOffset.UtcNow.ToString())
);
I have Jwt middleware for incoming requests:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
This seems to work to protect resources with the authorize attribute, but the claims never show up.
[Authorize]
public async Task<IActionResult> Get()
{
var user = ClaimsPrincipal.Current.Claims; // Nothing here
You can't use ClaimsPricipal.Current in an ASP.NET Core application, as it's not set by the runtime. You can read https://github.com/aspnet/Security/issues/322 for more information.
Instead, consider using the User property, exposed by ControllerBase.
Access User.Claims instead of ClaimsPrinciple.Current.Claims.
From Introduction to Identity at docs.asp.net:
...inside the HomeController.Index action method, you can view the User.Claims details.
Here is the relevant source code from the MVC repository:
public ClaimsPrincipal User
{
get
{
return HttpContext?.User;
}
}
As part of ASP.NET Core 2.0, you can read the JWT Claims like Shaun described above. If you are only looking for the User Id (make sure you already add it as part of the claim using the "Sub" claim name) then you can use the following to two examples to read depending on your use case:
Read User ID Claim:
public class AccountController : Controller
{
[Authorize]
[HttpGet]
public async Task<IActionResult> MethodName()
{
var userId = _userManager.GetUserId(HttpContext.User);
//...
return Ok();
}
}
Read Other Claims:
public class AccountController : Controller
{
[Authorize]
[HttpGet]
public async Task<IActionResult> MethodName()
{
var rolesClaim = HttpContext.User.Claims.Where( c => c.Type == ClaimsIdentity.DefaultRoleClaimType).FirstOrDefault();
//...
return Ok();
}
}
With this solution, you can access the User.Identity and its claims in controllers when you're using JWT tokens:
Step 1: create a JwtTokenMiddleware:
public static class JwtTokenMiddleware
{
public static IApplicationBuilder UseJwtTokenMiddleware(
this IApplicationBuilder app,
string schema = "Bearer")
{
return app.Use((async (ctx, next) =>
{
IIdentity identity = ctx.User.Identity;
if (identity != null && !identity.IsAuthenticated)
{
AuthenticateResult authenticateResult = await ctx.AuthenticateAsync(schema);
if (authenticateResult.Succeeded && authenticateResult.Principal != null)
ctx.User = authenticateResult.Principal;
}
await next();
}));
}
}
Step 2: use it in Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseJwtTokenMiddleware();
}