How do I build a custom role based authorization in ASP Core 2? - c#

I have a database that has a user table, access table and a join table assigning a user to multiple access. The site will verify a user by matching the Identity Username from AD with an username in the Users table to verify they can see the site (Intranet). The access table is used to specify which pages they are allow to visit.
In ASP Core 2 how can I use Authorization to perform the same check at Startup to verify they are in the Users table and then take it a step further and use Roles to allow the user access to specific web pages.
I've gone through the documentation but I can't figure out which way to go as the examples use a login that is not necessary in my case using AD.
I have a users table and don't use AD roles because we have a admin for exchange and I do not have access to that.
Thanks in advance

Authorize attribute is the what you are looking for. For example,
[Authorize(Roles = "Admin, User")]
If you are using OAuth for authentication, you will create a ClaimsIdentity while authenticating. Based on the claim, the Authorize attribute will work out of the box. For example,
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(oAuthIdentity);
}
You can refer to this post, where I have explained a similar scenario in a bit more detail.

Related

How can I get a User object returned when authenticating with ASP.NET Identity 2?

This authentication normally just involves calling the '/Token' endpoint, with user credentials, and receiving a ticket back containing an auth token for the user. I am calling this in my Web API from a WPF client application, and it would make life much easier for me, and the login process much quicker, if I could simple have one authentication request that returns the authenticated IdentityUser, or in a normal template based API project, an AspNetUser object.
I see the method TokenEndPoint in my API's ApplicationOAuthProvider does very little, so I don't think a change there could help much, but the GrantResourceOwnerCredentials seems to be the crux of that provider, yet its return is void.
The options for the provider include:
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin")
but I can find no evidence of that action being executed. I trace all successful requests to a log, and my Debug.WriteLine($"API TRACE: ExternalLogin called for {provider}.") doesn't appear in the output window, so I don't think I can use that action to server redirect to one that returns a User.
Is there anything I can do, except call the /Token endpoint from a login action that allows anonymous, and then redirect?
EDIT: The method that grants the token is in the ApplicationOAuthProvider class provided in my project template. It derives from OAuthAuthorizationServerProvider. It is:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<UserManager>();
var user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", $"The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
It is possible to do a Server.Transfer within the GrantResourceOwnerCredentials method of the ApplicationOAuthProvider : OAuthAuthorizationServerProvider class. This is provided out-the-box in the either the project template or a Nuget package.
The transfer would be to an added Login method in the AccountController, with username, password, and an additional ticket parameter, which will return an AspNetUser user class, to which you can add an extra property for the authentication ticket obtained in GrantResourceOwnerCredentials and normally returned by the /Token resource.
But this is too much meddling in code not written by me, so for this urgent prototype of the project, I just call /Token, and when I have the username, I call /User to get the now signed in AspNetUser.

how to get claims of another user using ASP.NET Core

I'm still learning identities with asp.net core. I'm doing a claims-based token authorization.
Most examples are about "Current" logged in user. In my case my RPC service is receiving a username & password of some user in the identity DB. I need to
verify that a user with such credentials exist
get all the claims of that user
so to verify if the user exists, I'm using this:
ApplicationUser applicationUser = await _userManager.FindByNameAsync(username);
bool exist = await _userManager.CheckPasswordAsync(applicationUser, password);
if (!exist)
{
// log and return
}
I don't know how to do the 2nd step properly. I guess I could do a simple linq to collect all user's claims, but I'm sure there is a better way using the identity methods.
You need to use the GetClaimsAsync() method. For example:
var claims = await _userManager.GetClaimsAsync(applicationUser);
See MSDN

IsInRole return false even if there is role in claims

I am working on claim base authentication and it is working fine.
Now I want to add role autorization.
I have role claim for user (eg. "Admin")
When the IsInRole() method is called, there is a check made to see if
the current user has that role. In claims-aware applications, the role
is expressed by a role claim type that should be available in the
token. The role claim type is expressed using the following URI:
http://schemas.microsoft.com/ws/2008/06/identity/claims/role
//Include all claims
//claims is List<Claim> with all claims
var id = new ClaimsIdentity(claims, "Cookies");
Request.GetOwinContext().Authentication.SignIn(id);
If i check if user is in role I will get false. Although I have Role claim with "Admin" value
User.IsInRole("Admin");
Also authorize attrubute on my api will not work
[Authorize (Roles = "Admin")]
I probably misih logic how to make roles visible to User. Probably is not enough to just have Roles in list of claims?
If your service is using Windows authentication, then the IPrincipal.Identity you receive will be of type WindowsPrincipal. It's a little misleading, but the ClaimType that WindowsPrincipal.IsInRole() looks for is not ClaimTypes.Role as you might reasonably expect, but ClaimTypes.GroupSid.
However, you should not assume the actual ClaimType that the current Identity uses for specifying roles because different types of identity use different values. Instead you should reference the ClaimsIdentity.RoleClaimType property.
We have implemented a IAuthenticationFilter along the following lines:
public Task AuthenticateAsync(HttpAuthenticationContext context, cancellationToken)
{
var principal = context.Principal;
if(principal.Identity is ClaimsIdentity && principal.Identity.IsAuthenticated)
{
var ci = (ClaimsIdentity)principal.Identity;
// get the user's additional roles from somewhere and add Claims
ci.AddClaim(new Claim(ci.RoleClaimType, "MyRole"));
}
}
This allows us to use the standard AuthorizeAttribute mechanism in our ASP.Net Controllers. e.g.
[Authorize(Roles="MyRole")]
public IHttpActionResult Get()
{
//authenticated and authorised code here
}
See ClaimsIdentity.RoleClaimType on MSDN for further clarification.
Please note: adding user-defined roles to the WindowsPrincipal can cause problems. It seems that the current implementation of .Net Framework 4.5 (as of April 2017) will sometimes throw an exception when checking roles, expecting the details of the role to be available from Active Directory. See this question for an alternative approach.
Probably, the ClaimType of the claim is just "role".
You should create the claim using Microsoft Schema:
manager.AddClaim(dn1.Id, claim: new Claim(ClaimTypes.Role.ToString(), "ADMINISTRATOR"));
Then User.IsInRole("Admin"); and [Authorize (Roles = "Admin")]will work properly.
This because Microsoft Identity uses the schema:
http://schemas.microsoft.com/ws/2008/06/identity/claims/role
When for role checking.
I suggest you to check ASPNETIdentity database to have a complete view of how che claim are inserted.
I'm pretty sure that the ClaimType of AspNetUserClaims is not like the Microsoft Schema.
Regards
TL;DR Case Sensitivity, Perhaps?
I found that the check used by default in...
[Authorize(Roles = "RoleA,RoleB")]
...was case sensitive.
I created roles in mixed case, and used AspNetCore's Identity manager, with an non-EF memory implementation for testing.
UserManager.IsInRole("RoleA") returned true, but when checked via the ClaimsPrincipal, HttpContext.User.IsInRole("RoleA") returned false. I dumped the claims out to text and could see that there were role claims for the correct MS schema...
ClaimType:[http://schemas.microsoft.com/ws/2008/06/identity/claims/role], ClaimValue:[ROLEA], Issuer:[TokenServer]
ClaimType:[http://schemas.microsoft.com/ws/2008/06/identity/claims/role], ClaimValue:[ROLEB], Issuer:[TokenServer]
...but the claim value (the role) was upper case.
To fix the problem, I just had to change the attribute to...
[Authorize(Roles = "ROLEA,ROLEB")]
... and it worked.
So, if you are having a problem getting roles authorization to work in AspNetCore, try to read the claims, and match the claims exactly. You can read the claims by accessing the HttpContext.User.Claims object...
foreach (var claim in HttpContext.User.Claims)
Console.WriteLine($"ClaimType:[{claim.Type}], ClaimValue:[{claim.Value}], Issuer:[{claim.Issuer}]");
It could of course be that I somehow donkey-coded the roles to upper case, or somewhere used the NormalisedRole, but you might have done the same thing...
Note that
HttpContext.User.Identity.RoleClaimType: "role"
may be different to
ClaimTypes.Role = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
So when generating the claims identity you may need to add claims using "role" as the key instead of the ClaimTypes constants. ClaimsIdentity.IsInRole(String) uses the claim key as defined by ClaimsIdentity.RoleClaimType.
My factory code looks like this ...
var identity = await base.GenerateClaimsAsync(user);
var roles = await UserManager.GetRolesAsync(user);
foreach (var role in roles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
identity.AddClaim(new Claim("role", role));
}
return identity;
The first add is really superfluous, but makes me feel like I am actually adding the right claim.
You did not mention which Authentication approach you are using, but if you are using JWT Authentication, then you need to add the roles to the ClaimsIdentity when generating the token, as detailed in this post: ASP.NET Core JWT mapping role claims to ClaimsIdentity

Asp.net Identity 2.0 custom login method

I'm developing ASP.NET 5 application using Identity 2.0. I have two types of users:
Normal - they authenticate using standard login method.
Temporary - they should login based on provided token.
I do not want to store temporary users, except from information required to authenticate user (some username and token). If the user provides username and valid password he should be logged in.
I'm not sure how to achieve this.
You could use Identity in both scenarios simultaneously as well. For first scenario use Identity just like you have done before without any change but for second scenario you a slight modify in login method.
public ActionResoult TempLogin(string username, string password)
{
// imaging you have own temp user manager, completely independent from identity
if(_tempUserManager.IsValid(username,password))
{
// user is valid, going to authenticate user for my App
var ident = new ClaimsIdentity(
new[]
{
// adding following 2 claim just for supporting default antiforgery provider
new Claim(ClaimTypes.NameIdentifier, username),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
// an optional claim you could omit this
new Claim(ClaimTypes.Name, username),
// you could even add some role
new Claim(ClaimTypes.Role, "TempUser"),
new Claim(ClaimTypes.Role, "AnotherRole"),
// and so on
},
DefaultAuthenticationTypes.ApplicationCookie);
// Identity is sign in user based on claim don't matter
// how you generated it Identity
HttpContext.GetOwinContext().Authentication.SignIn(
new AuthenticationProperties { IsPersistent = false }, ident);
// auth is succeed,
return RedirectToAction("MyAction");
}
ModelState.AddModelError("", "We could not authorize you :(");
return View();
}
Since we injected our logic to Identity, we don't need to do extra thing at all.
[Authorize]
public ActionResult MySecretAction()
{
// all authorized users could use this method don't matter how has been authenticated
// we have access current user principal by calling also
// HttpContext.User
}
[Authorize(Roles="TempUser")]
public ActionResult MySecretAction()
{
// just temp users have accesses to this method
}
You'll need to extend the ASP.NET Identity Libraries, using your custom logic and/or storage.
Here you can find an example in my Github account with some useful links that I used to read when I was trying to understand the ASP.NET Identity stuff: https://github.com/hernandgr/AspNetIdentityDemo
Hope it helps!

Mvc OAuth2 how to store data for the current Autentication in a OAuthAuthorizationServerProvider

im sorry for my bad English, im french.
I will try to explain my question the best i can.
i have a OAuthAuthorizationServerProvider wish work fine.
This is to allow other application to connect with my Asp.Net Identity 2.0 Authentication Server.
I wish to store data for the current authentication. If the user is connected twice, they will not necessary have the same stored data. I don't think Session is the right thing for this becose i dont use cookie. I use Bearer, an access_token and a refresh_token.
I can simply store the refresh_token in a table, then refer it on each request but i don't like to store sensible data like that, especially if the framework provide a way to do what i want.
I need to store the data relative to each external authentication, not to the user. Something like Claims but only for the current authentication session.
tanks to point me on the right path.
In your OAuthAuthorizationServerProvider, you will have overridden the GrantResourceOwnerCredentials method. This is where you will have validated the user, and it's the place where you can add additional claims for the user.
Here is an example that validates the user against ASPNet Identity, and adds an additional claim to the identity that is returned.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var mgr = context.OwinContext.GetUserManager<ApplicationUserManager>();
var user = await mgr.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
var usrIdentity = await mgr.CreateIdentityAsync(user, context.Options.AuthenticationType);
foreach (var c in usrIdentity.Claims)
{
identity.AddClaim(c);
}
//
// Add additional claims to your identity
//
identity.AddClaim(new Claim("your_custom_claim", "your_custom_claim_value"));
context.Validated(identity);
}
That said, in your comments you seem to be using Cookie and Token in the same sentence, and possibly confusing the two. Check out this blog post which should give you a good example.
Also check out the ASP.NET Identity Recommended Resources page too.

Categories