Asp.Net WebApi 2: Add role claim to WindowsIdentity - c#

I want to add a role claim to an already authenticated windows user. My naive first approach was to add the role claim inside of a custom owin middleware which runs before WebApi. Like this:
public class IdentityMiddleware : OwinMiddleware
{
public IdentityMiddleware(OwinMiddleware next) : base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
var user = context.Request.User as WindowsPrincipal;
var identity = user.Identity as ClaimsIdentity;
identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
await Next.Invoke(context);
}
}
However when providing the Authorize attribute in the controller like this.
public class TestController : ApiController
{
[Authorize(Roles = "Admin")]
public string Get()
{
return User.Identity.Name;
}
}
..I will get a 401.
I noticed that the issuer of the new claim is "Local Authority" instead of "AD Authority" could this be the reason?

Have you tried this for your authorize attribute:
[Authorize(ClaimTypes.Role, "Admin")]

This works for me:
var сlaimsIdentity = user.Identity as ClaimsIdentity;
сlaimsIdentity?.AddClaim(new Claim(сlaimsIdentity.RoleClaimType, "Admin"));
if (user.IsInRole("Admin")) ... // always true

Related

Extend Authorize attribute .Net Web API

A lot of my methods in my WebAPI looks like this. I have an authorization-filter on policy, but then i also need to validate my token and that the user provided in the token exists in DB. I am wondering if there is a way of extending the Authorize attribute so that after policy is validated, the token gets validated too. And is it also possible to return the user into the method or context if existing?
[Authorize(Policy = "Admin")]
[Route("get")]
[HttpGet]
public async Task<IActionResult> Get()
{
var (success, user) = await UserHelpers.ValidateTokenAndGetUserAsync(HttpContext, _userManager, _configuration, _logger, ControllerContext, ModelState);
if (!success || user?.CustomerId == null) return Unauthorized(HttpResponseHelper.GetErrorResponse(StatusCodes.Status401Unauthorized, _localizer[ResourceConstants.Unauthorized.UserNotFoundError].Value));
//Code
await UserHelpers.CookieToResponse(user, _configuration, _webHostEnvironment, Response, Request).ConfigureAwait(false);
return Ok();
}
First, I should address that you shouldn't customize Authorize attribute.
Using (Policy, Claim, Requirement) can help you to customize your authorization based on your business.
Then my question is that your user was in the database while issuing the token. So how can it be removed now? (The user in the JWT-Token is being validated via the signature of that)
If you want to double-check if the user is enabled, or not archived yet you can write another filter like this:
public class ApiController : Controller
{
[Authorize(Policy = "Bla")]
[CheckUserIsEnabled]
public async Task<IActionResult> Get()
{
return Ok();
}
}
public class CheckUserIsEnabledAttribute : Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var userId = context.HttpContext.User.FindFirst(x => x.Type == System.Security.Claims.ClaimTypes.NameIdentifier);
if (userId is null)
{
context.Result = new UnauthorizedResult();
return;
}
bool exists = true;
//exists = db.CheckIfUserExistsAsync(userId);
if (!exists)
{
context.Result = new UnauthorizedResult();
return;
}
await next();
}
}
This solution works because the JWT claims will be added to current user claims on httpRequest instantiation (even if you don't put Authorize attribute on the corresponding action)

How to implement role based authentication in ASP.net MVC .core 3.1 without Entity Framework

I am currently working on a video streaming project which consists of 3 roles. "Admin", "Employee" and "Customers". The Administrator has the power to give an account a specific role. I cannot use Entity Framework for this. Does anyone have a good tutorial/documentation for me? Thanks!
You need to create a custom attribute like this
// Custom [Authorized] attribute for JWT and Enum based roles
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizedAttribute : Attribute, IAuthorizationFilter
{
private readonly Roles[] _roles;
public AuthorizedAttribute(params Roles[] roles)
{
_roles = roles;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
// We're checking here to see if the route has been decorated with an [AllowAnonymous] attribute. If it has, we skip authorization
// for the route. Doing this allows us to apply the [Authorize] attribute by default in the startup using:
//
// services.AddControllers().AddMvcOptions(x => x.Filters.Add(new AuthorizeAttribute()))
//
if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
var hasAllowAnonymousAttribute = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true)
.Any(a => a.GetType() == typeof(AllowAnonymousAttribute));
if (hasAllowAnonymousAttribute)
{
return;
}
}
// Check if valid user
var user = (AuthUser)context.HttpContext.Items["User"];
if (user == null)
{
// Not logged in
context.Result = new ObjectResult(Logger.Error("Unauthorized request", HttpStatusCode.Unauthorized))
{
StatusCode = (int)HttpStatusCode.Unauthorized
};
return;
}
// Check if valid role
Enum.TryParse(user.role, out Roles role);
if (_roles.Length > 0 && !_roles.Contains(role))
{
// User is logged in, but the role is not allowed to access this method
context.Result = new ObjectResult(Logger.Error($"Your current role `{user.role}` does not have access to this API", HttpStatusCode.Unauthorized))
{
StatusCode = (int)HttpStatusCode.Unauthorized
};
return;
}
}
}
And use in your action method of Controller like this:-
[HttpPost]
[Authorized(Roles.Employee, Roles.Admin, Roles.Customers)]
public async Task<IActionResult> Create()
{
//.............your code
}
here is the complete example, you can refer to it and modify your implementation accordingly. No need of entity framework
You could configure your application to log in with JWT (json web token) and set custom authentication. After user login successfully, you could generate a JWT token with Role claims, then the JWToken is used for HTTP requests, after that implement the role based authentication. More detail information, refer the following links:
Login And Role Based Custom Authentication In ASP.NET Core 3.1
ASP.NET Core 3.1 - Role Based Authorization Tutorial with Example API

How to return auth Token from .Net Core [Authorize] method

My main application delegates authentication to an AuthService application, asking it to authenticate against an Identity Provider through OpenId Connect and return a token.
[Authorize]
[HttpGet("Login")]
public IActionResult Login()
{
return Ok(
{
Token = ?
}, OpenIdConnectDefaults.AuthenticationScheme);
}
I have an API method with the [Authorize] attribute. The user is prompted to login if not already authenticated and then the method runs. My question is can I access the token from there so I can pass it in the response?
Is there a simple way to achieve this?
You can get that information injecting a IHttpContextAccessor service in the controller and defining the service in the startup.cs class
public class HomeController
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HomeController(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
//check the _httpContextAccessor.HttpContext object for information about the already logged user
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
The [Authorize] attribute you are putting on the method means that you are restricting access to the method to authorised users only, so putting that attribute on a Login method makes no sense, as you would have to be authenticated to call it.
What you are looking for is
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login()
{
}
[Authorize]
public ActionResult Logout()
{
}
}
As for how to generate a token, you need to have an identity provider to take a username and password, validate it and create a token that has roles and privs.
I would suggest having a look at Identity Server 4 to generate tokens.
For info on how to connect it to OpenID see Adding User Authentication with OpenID Connect
you must get a token to access the Authorize attribute method.
reference
this
Here's what I've come up with after looking through the suggestions here:
[AllowAnonymous]
[HttpGet("Login")]
public IActionResult Login()
{
return Challenge(new AuthenticationProperties
{
RedirectUri = $"{HttpContext.Request.PathBase.Value}/GetToken"
}, OpenIdConnectDefaults.AuthenticationScheme);
}
[Authorize(AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme)]
[HttpGet("GetToken")]
public IActionResult GetToken()
{
var token = _contextAccessor.HttpContext.GetTokenAsync(OpenIdConnectDefaults.AuthenticationScheme, "id_token").Result;
return Ok(new
{
Token = token
});
}
I combined two APIs. From the perspective of the client app it makes a call to a Login API and gets the token as the result. Behind the scenes the Login API redirects to a second API to get and return the token.
UPDATE:
Revisting this after a while in case anyone sees this. I don't remember exactly how, but I think there was a problem with above approach for me.
In the end I used the OnSignedIn CookieAuthenticationHandler event to intercept the token and returned it with the response in a Cookie. The Login API still returns a Challenge request.

Catching a failed authorization policy

In .Net Core 2, suppose there is an authorization policy that checks that specific claims exist for the user:
public class CompletedProfileRequirement : AuthorizationHandler<CompletedProfileRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CompletedProfileRequirement requirement)
{
// Check that all required claims exist
if (/* invalid user claims */) {
context.Fail();
}
context.Succeed(requirement);
return Task.FromResult(0);
}
}
And we stick the policy check on top of an MVC controller:
[Authorize(Policy = "CompletedProfile")]
public class HomeController : Controller
{
// Controller stuff
}
How can I write an IAuthorizationFilter so that we can catch a failure on this particular CompletedProfileRequirement policy so that we can redirect the user to a page where they may complete their profile?
According to the authorization documentation, it's possible to access the MVC context from your AuthorizationHandler:
Frameworks such as MVC or Jabbr are free to add any object to the Resource property on the AuthorizationHandlerContext to pass extra information.
For example, MVC passes an instance of AuthorizationFilterContext in the Resource property. This property provides access to HttpContext, RouteData, and everything else provided by MVC and Razor Pages.
Therefore, you can do something like this:
public class CompletedProfileRequirement : AuthorizationHandler<CompletedProfileRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CompletedProfileRequirement requirement)
{
// Check that all required claims exist
if (/* invalid user claims */) {
// retrieve MVC context
if (context.Resource is AuthorizationFilterContext mvcContext)
{
// we still need to mark the requirement as succeeded,
// otherwise responde code is 401 unauthorized
context.Succeed(requirement);
// HTTP 302 temporary redirect to your page
mvcContext.HttpContext.Response.Redirect("/Home/Wherever", false);
return Task.FromResult(0);
}
}
context.Succeed(requirement);
return Task.FromResult(0);
}
}
You should add your policy with requirments to authorization on application startup, like:
services.AddAuthorization(options =>
{
options.AddPolicy("CompletedProfile", builder =>
{
builder.AddRequirements(new CompletedProfileRequirement());
});
});
And register handlers, like:
services.AddScoped<IAuthorizationHandler, CompletedProfileRequirementHandler>();
UPD:
For redirecting you should inject and use in controller:
IAuthorizationService authorizationService
And invoke:
var result = _authorizationService.AuthorizeAsync(User, someObject, "CompletedProfile");
if (!result.Succeeded)
return RedirectToAction("CompleteProfileAction");

Refresh user cookie ticket in ASP.Net Core Identity

In a controller in an ASP.NET Core web application I want to refresh the user and claims in the cookie ticket stored on the client.
The client is authenticated and authorized, ASP.NET Core Identity stores this Information in the cookie ticket - now in some Controller actions I want to refresh the data in the cookie.
The SignInManager has a function to refresh RefreshSignInAsync, but it does not accept HttpContext.User as parameter.
[HttpPost("[action]")]
[Authorize]
public async Task<IActionResult> Validate()
{
// todo: update the Client Cookie
await _signInManager.RefreshSignInAsync(User); // wrong type
}
How do I refresh the cookie?
public static class HttpContextExtensions
{
public static async Task RefreshLoginAsync(this HttpContext context)
{
if (context.User == null)
return;
// The example uses base class, IdentityUser, yours may be called
// ApplicationUser if you have added any extra fields to the model
var userManager = context.RequestServices
.GetRequiredService<UserManager<IdentityUser>>();
var signInManager = context.RequestServices
.GetRequiredService<SignInManager<IdentityUser>>();
IdentityUser user = await userManager.GetUserAsync(context.User);
if(signInManager.IsSignedIn(context.User))
{
await signInManager.RefreshSignInAsync(user);
}
}
}
Then use it in your controller
[HttpPost("[action]")]
[Authorize]
public async Task<IActionResult> Validate()
{
await HttpContext.RefreshLoginAsync();
}
Or abstract it in an action filter
public class RefreshLoginAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
await context.HttpContext.RefreshLoginAsync();
await next();
}
}
Then use it like this in your controller
[HttpPost("[action]")]
[Authorize]
[RefreshLogin] // or simpler [Authorize, RefreshLogin]
public async Task<IActionResult> Validate()
{
// your normal controller code
}
This is also possible if the user is already logged out (their access token has expired but their refresh token is still valid).
Important note: the following only works if you have "Do you want to remember your user's devices?" set to "No" in the cognito config. If anyone knows how to get it to work with it on, please let me know.
We use the following flow (js client app connecting to .NET Core API):
User signs in using username/password (CognitoSignInManager<CognitoUser>.PasswordSignInAsync)
The client receives the token, userID, and refreshToken and stores them in localStorage.
When the original token expires (1 hour), the client gets a 401 error from the API.
The client calls another API endpoint with the userID and refreshToken which then in turn calls the code below on our user service.
If the refresh result is successful, we return the new token (AuthenticationResult.IdToken).
The client the repeats the call that originally errored in a 401 with the new token.
Here is the code we added to the User Service:
public async Task<UserLoginResult> SignInRefreshAsync(string uid, string refreshToken)
{
try
{
var result = await _cognitoIdentityProvider.InitiateAuthAsync(
new InitiateAuthRequest
{
AuthFlow = AuthFlowType.REFRESH_TOKEN_AUTH,
ClientId = _pool.ClientID,
AuthParameters = new Dictionary<string, string>
{
{ "REFRESH_TOKEN", refreshToken },
{ "SECRET_HASH", HmacSHA256(uid + _pool.ClientID, _options.UserPoolClientSecret) }
}
});
if (!result.HttpStatusCode.Successful() || string.IsNullOrEmpty(result.AuthenticationResult?.IdToken))
return new UserLoginResult(UserLoginStatus.Failed);
return new UserLoginResult(UserLoginStatus.Success, uid, null, null, result.AuthenticationResult.IdToken, null);
}
catch
{
return new UserLoginResult(UserLoginStatus.Failed);
}
}
private static string HmacSHA256(string data, string key)
{
using (var sha = new System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(key)))
{
var result = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(data));
return Convert.ToBase64String(result);
}
}
IAmazonCognitoIdentityProvider _cognitoIdentityProvider is resolved from DI.
AWSCognitoClientOptions _options = configuration.GetAWSCognitoClientOptions(); and IConfiguration configuration is also resolved from DI.
UserLoginResult is our class to hold the token and refresh token. Obviously, adjust accordingly.
Please note that setting SECRET_HASH may not be required based on your config is Cognito.

Categories