How to automatically retrieve roles from ASP.NET Core identity? - asp.net-core-identity

I'm moving my steps to OpenIDDict and I made my application based on Velusia example.
Everything works fine but I have a question: My access token doesn't include roles.
There's a way to automate the retrieving of .NET Core identity user roles and append them to the User property as Claim before accessing the action in my controller?
The purpose of all is being able to use (for example)
User.IsInRole("MyRole");
Thanks to everyone!

Reading this post gets me in the right direction: Is there a way to dynamically load claims in OpenIddict?
public class MyClaimTransformation : IClaimsTransformation
{
private readonly UserManager<UserInfo> _userManager;
public MyClaimTransformation(UserManager<UserInfo> userManager)
{
_userManager = userManager;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
ClaimsIdentity claimsIdentity = new ClaimsIdentity();
//claimsIdentity.RoleClaimType = OpenIddict.Abstractions.OpenIddictConstants.Claims.Role;
//claimsIdentity.NameClaimType = OpenIddict.Abstractions.OpenIddictConstants.Claims.Name;
var claimType = ClaimTypes.Role;
if (principal.Identity != null && principal.Identity.IsAuthenticated)
{
//Do I already have roles in the claim?
var roleClaimsAvailable = principal.Claims.Any(x => x.Type == claimType);
if (!roleClaimsAvailable)
{
//Roles not found, adding:
var userProfile = await _userManager.GetUserAsync(principal);
if (userProfile != null)
{
var roles = await _userManager.GetRolesAsync(userProfile);
foreach (var role in roles)
{
claimsIdentity.AddClaim(new Claim(claimType, role));
}
principal.AddIdentity(claimsIdentity);
}
}
}
return principal;
}
}
Than we need to register in the Startup.cs as a service:
//Adding roles on access token incoming
builder.Services.AddTransient<IClaimsTransformation, MyClaimTransformation>();

Related

How to allow specific roles to access API using identityserver3.accesstokenvalidation

I have an Identityserver4 that is providing access tokens to clients.
On my API, I want to be sure that client is allowed to access specific scope and that User belongs to a specific role before I give this user access to API.
To do that I am using Identityserver3.accesstokenvalidation package.
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "Authority",
RequiredScopes = new[] { "MyScope" },
});
This is blocking users that do not have access token from accessing my API, also it is checking if the provided scope is "MyScope".
My question is how do I also check that user has a specific role before allowing access to API.
You could put attribute [Authorize(Roles = "Admin")] for specific controller. If you need more advanced logic with claims you need to specify your own attribute e.g. AuthorizePermissionAttribute and use it with controller [AuthorizePermission("Preview")]:
public class AuthorizePermissionAttribute : AuthorizeAttribute
{
private readonly string grantedPermission;
public AuthorizePermissionAttribute(string permission)
{
this.grantedPermission = permission ?? throw new ArgumentNullException(nameof(permission));
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var claims = actionContext.ControllerContext.RequestContext.Principal as ClaimsPrincipal;
var permission = claims?.FindFirst(this.grantedPermission);
return permission != null && Convert.ToBoolean(permission.Value);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
var response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "insufficient_permissions");
actionContext.Response = response;
}
}
Also you need to put in Startup.cs:
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["IdentityProviderApi"],
PreserveAccessToken = true
});
Without JwtSecurityTokenHandler.InboundClaimTypeMap it will return always Unauthorized status code.

Adding role claims - should i use the IClaimsTransformer

We would like to add a lot of role claims to the current principal (we use the Authorize(Roles) attribute), and found the IClaimsTransformer that looks like a perfect fit.
We've registerd it like this
app.UseClaimsTransformation(new ClaimsTransformationOptions
{
Transformer = new GetRolesFromDatabaseClaimsTransformer(new RoleManager2(Configuration.GetConnectionString("ourcoolapp")))
});
And the transform is like this:
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
// A hacky way to not load on all requests. Better ideas?
if (!context.Context.Request.Path.Value.Contains("api/"))
{
return Task.FromResult(context.Principal);
}
var roleClaims = RoleManager.GetRolesForUser(context.Principal.Identity.Name).Select(roleName => new Claim("role", roleName));
var claims = new List<Claim> { };
var identity = context.Principal.Identity as ClaimsIdentity;
claims.AddRange(identity.Claims);
claims.AddRange(roleClaims);
var userIdentity = new ClaimsIdentity(claims, "local");
var userPrinicpal = new ClaimsPrincipal(userIdentity);
return Task.FromResult(userPrinicpal);
}
Question: Are there alternative, or smarter ways of adding the role claims?
Thanks
Larsi
Another option could be UserClaimsPrincipalFactory
It provides methods to create claims principal for a given user and you can customize it just like ClaimsTransformer.
By default It adds UserName and UserId to claim collection.
In order to customize it you can driver from UserClaimsPrincipalFactory and override CreateAsync
public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory<User, Role>
{
public AppClaimsPrincipalFactory(UserManager<User> userManager,
RoleManager<Role> roleManager,
IOptions<IdentityOptions> optionsAccessor,
ILogger<AppClaimsPrincipalFactory> logger)
: base(userManager, roleManager, optionsAccessor)
{
logger.LogInformation("AppClaimsPrincipalFactory ctor");
}
public override async Task<ClaimsPrincipal> CreateAsync(User user)
{
var principal = await base.CreateAsync(user);
((ClaimsIdentity)principal.Identity).AddClaims(new []
{
new Claim("Foo", "Bar"),
});
return principal;
}
}
And Register the Factory in DI:
services.AddScoped<IUserClaimsPrincipalFactory<User>, AppClaimsPrincipalFactory>();
It will change/override the claim whenever the user's claims requested.
For more info take a look at source at GitHub.

How to add default role to Asp.net identity user manager

I am new and exploring the Asp.net Identity, I wanted to add a default Role to my users. However, I failed to run this code during runtime and it shows me this error "Role admin does not exist."
var userManager = new UserManager<IdentityUser>(userStore);
var user = userManager.Find(UserName.Text, Password.Text);
userManager.AddToRole(user.Id,"admin");
I am aware that i didn't created the Role of "admin", Does there any Simplest way to implement this Role based authorization?
Here is how it should be done, first check if the role exists, and if it doesn't add the role.
public async Task<ActionResult> Register(RegisterViewModel model, ApplicationDbContext context)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
if (!roleManager.RoleExists("Administrator"))
{
await roleManager.CreateAsync(new IdentityRole("Administrator"));
}
await UserManager.AddToRoleAsync(user.Id, "Administrator");
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Notice how the ApplicationDbContext is passed in the method signature.

How to get user context during Web Api calls?

I have an web front end calling an ASP Web Api 2 backend. Authentication is managed with ASP Identity. For some of the controllers I'm creating I need to know the user making the call. I don't want to have to create some weird model to pass in including the user's identity (which I don't even store in the client).
All calls to the API are authorized using a bearer token, my thought is the controller should be able to determine the user context based on this but I do not know how to implement. I have searched but I don't know what I'm searching for exactly and haven't found anything relevant. I'm going for something like...
public async Task<IHttpActionResult> Post(ApplicationIdentity identity, WalkthroughModel data)
Update
I found the below which looked very promising... but the value is always null! My controller inherits from ApiController and has an Authorize header.
var userid = User.Identity.GetUserId();
Update 2
I have also tried all of the solutions in Get the current user, within an ApiController action, without passing the userID as a parameter but none work. No matter what I am getting an Identity that is valid and auth'd, but has a null UserID
Update 3
Here's where I'm at now.
[Authorize]
[Route("Email")]
public async Task<IHttpActionResult> Get()
{
var testa = User.Identity.GetType();
var testb = User.Identity.GetUserId();
var testc = User.Identity.AuthenticationType;
var testd = User.Identity.IsAuthenticated;
return Ok();
}
testa = Name: ClaimsIdentity,
testb = null,
testc = Bearer,
testd = true
The user is obviously authenticated but I am unable to retrieve their userID.
Update 4
I found an answer, but I'm really unhappy with it...
ClaimsIdentity identity = (ClaimsIdentity)User.Identity;
string username = identity.Claims.First().Value;
That gets me the username without any db calls but it seems very janky and a pain to support in the future. Would love if anyone had a better answer.
What if I need to change what claims are issued down the road? Plus any time I actually need the user's id I have to make a db call to convert username to ID
A common approach is to create a base class for your ApiControllers and take advantage of the ApplicationUserManager to retrieve the information you need. With this approach, you can keep the logic for accessing the user's information in one location and reuse it across your controllers.
public class BaseApiController : ApiController
{
private ApplicationUser _member;
public ApplicationUserManager UserManager
{
get { return HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>(); }
}
public string UserIdentityId
{
get
{
var user = UserManager.FindByName(User.Identity.Name);
return user.Id;
}
}
public ApplicationUser UserRecord
{
get
{
if (_member != null)
{
return _member ;
}
_member = UserManager.FindByEmail(Thread.CurrentPrincipal.Identity.Name);
return _member ;
}
set { _member = value; }
}
}
I use a custom user authentication (I dont use AspIdentity because my existing user table fields was far different from IdentityUser properties) and create ClaimsIdentity passing my table UserID and UserName to validate my bearer token on API calls.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
User user;
try
{
var scope = Autofac.Integration.Owin.OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext);
_service = scope.Resolve<IUserService>();
user = await _service.FindUserAsync(context.UserName);
if (user?.HashedPassword != Helpers.CustomPasswordHasher.GetHashedPassword(context.Password, user?.Salt))
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
catch (Exception ex)
{
context.SetError("invalid_grant", ex.Message);
return;
}
var properties = new Dictionary<string, string>()
{
{ ClaimTypes.NameIdentifier, user.UserID.ToString() },
{ ClaimTypes.Name, context.UserName }
};
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
properties.ToList().ForEach(c => identity.AddClaim(new Claim(c.Key, c.Value)));
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties(properties));
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(identity);
}
And how I use the ClaimsIdentity to retrieve my User table details on User ApiController Details call.
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("Details")]
public async Task<IHttpActionResult> Details()
{
var user = await _service.GetAsync(RequestContext.Principal.Identity.GetUserId<int>());
var basicDetails = Mapper.Map<User, BasicUserModel>(user);
return Ok(basicDetails);
}
Notice the
ClaimTypes.NameIdentifier = GetUserId() and ClaimTypes.Name = GetUserName()

How to create roles and add users to roles in ASP.NET MVC Web API

I have a .NET Web API project that users the individual accounts. I can register users fine using the standard template AccountController. However, I now want to set up roles and add users to roles depending on the type of user.
There are no roles automatically set up in the DB. How do I set up the roles and how do I add users to the roles?
The only information I can find on this is based on the old ASP.NET Membership, so it fails on the fact that the stored procedures are not set up for it.
Have scoured forums and tutorials on MSDN and can't seem to find an example for Web API.
You can add roles using the RoleManager...
using (var context = new ApplicationDbContext())
{
var roleStore = new RoleStore<IdentityRole>(context);
var roleManager = new RoleManager<IdentityRole>(roleStore);
await roleManager.CreateAsync(new IdentityRole { Name = "Administrator" });
var userStore = new UserStore<ApplicationUser>(context);
var userManager = new UserManager<ApplicationUser>(userStore);
var user = new ApplicationUser { UserName = "admin" };
await userManager.CreateAsync(user);
await userManager.AddToRoleAsync(user.Id, "Administrator");
}
You're right that documentation is a bit light right now. But I find that once you've worked with the RoleManager and the UserManager a bit, the API's are pretty discoverable (but perhaps not always intuitive and sometimes you have to run queries directly against the store or even the db context).
It took me awhile to figure out but I finally got it. Anthony please excuse me but going to repost a lot of your code so that dumb developers like me can understand.
In the latest WebAPI2 (Visual Studio 2013 Update 2) the registration method will look like so:
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
What you want to do is replace it with this:
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result;
using (var context = new ApplicationDbContext())
{
var roleStore = new RoleStore<IdentityRole>(context);
var roleManager = new RoleManager<IdentityRole>(roleStore);
await roleManager.CreateAsync(new IdentityRole() { Name = "Admin" });
var userStore = new UserStore<ApplicationUser>(context);
var userManager = new UserManager<ApplicationUser>(userStore);
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
result = await UserManager.CreateAsync(user, model.Password);
await userManager.AddToRoleAsync(user.Id, "Admin");
}
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
Now when you post it should correctly work, but you may run into a further problem. After I did this my response complained about the DB.
The model backing the <Database> context has changed since the database was created
To fix this error I had to go into the Package Manager Console and enable Migrations.
Enable-Migrations –EnableAutomaticMigrations
Then:
Add Migration
Finally:
Update-Database
A good post on enabling migrations here:
http://msdn.microsoft.com/en-us/data/jj554735.aspx

Resources