I'm authenticated users using Azure AD and I'm trying to add roles to the authenticated user using the middleware below. The problem is that I can't find anything that tells me how I can access the current User in the Startup class to be able to add the roles.
Everything talks about in the controller or in repositories further down.
Does anyone know how I can get access to the User in the Startup class?
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = ctx =>
{
// claimsIdentity we want to add our roles to...
ClaimsIdentity claimsIdentity = HttpContext.User.Identity as ClaimsIdentity;
// List of claims
var appRoles = new System.Collections.Generic.List<Claim>();
foreach (Claim claim in ClaimsPrincipal.Current.FindAll("groups"))
{
// use the OID and get a friendly name to use as the role (if it exists)
var groupStringValue = Configuration[$"AcceptedRoles:{claim.Value}"];
if (groupStringValue != null)
{
// build the list
appRoles.Add(new Claim(claimsIdentity.RoleClaimType, groupStringValue));
}
}
if (appRoles.Count > 0)
{
// if anything in the list, add these claims to the current identity
claimsIdentity.AddClaims(appRoles);
}
return Task.CompletedTask;
},
};
});
The current user (the ClaimsPrincipal) is available from TokenValidatedContext.HttpContext.User:
OnTokenValidated = ctx =>
{
var user = ctx.HttpContext.User;
...
}
However, because you are modifying the user that has already been authenticated by Open ID Connect, you should access the ClaimsPrincipal via TokenValidatedContext.Principal instead of TokenValidatedContext.HttpContext.User. Depending on your scenario, you can either add the additional claims directly on the default ClaimsIdentity (which contains the Open ID Connect claims), or create a separate ClaimsIdentity for your own purposes:
OnTokenValidated = ctx =>
{
// This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly
ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
claimsIdentity.AddClaim(new Claim(...));
// You can also add a new ClaimsIdentity to hold the claims that you'll add
ctx.Principal.AddIdentity(new ClaimsIdentity(...))
}
You can use this in your project:
httpContextAccessor.HttpContext.User.Identity.Name;
You can also reference below link
How to Get the Current User in ASP.NET Core
Related
I am working on a web application using ASP.Net Core 3.1 MVC, I have MVC controllers and APIControlers, and I am using the Identity to apply the authentication and authorization for the users, I registered the needed Identity services in the DI
services.AddScoped<UserManager<AccountInfo>>();
services.AddIdentity<AccountInfo, FeatureInfo>().AddDefaultTokenProviders();
services.AddScoped<IPasswordHasher<AccountInfo>, PasswordHasher>();
services.AddScoped<IUserStore<AccountInfo>, AccountStore>();
services.AddScoped<IRoleStore<FeatureInfo>, RoleStore>();
I am using my own custom TUser, TRole, IPasswordHasher, IUserStore, IRoleStore.
This is my login code:
if (!ModelState.IsValid)
{
return View(userModel);
}
var user = await userManager.FindByNameAsync(userModel.UserName);
if (user != null &&
await userManager.CheckPasswordAsync(user, userModel.Password))
{
var userRoles=await userManager.GetRolesAsync(user);
var identity = new ClaimsIdentity(IdentityConstants.ApplicationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
identity.AddClaim(new Claim("Id", user.AccountId.ToString()));
foreach(var role in userRoles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
}
await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme,
new ClaimsPrincipal(identity),new AuthenticationProperties{
ExpiresUtc=DateTime.UtcNow.AddHours(3),
IsPersistent=true
});
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Invalid UserName or Password");
return View();
}
In my logic, I have multiple companies each user has one or more companies assigned to him, and with each assigned company he has specific privileges with this company.
what I need to achieve is to give the user a dropdown to toggle between his assigned companies after login, and each time he toggles the company I need to update his privileges which loaded as claims in the user Identity, I did many trials to implement this approach with failure, I couldn't update the current logged in user claims without log out.
This should be achieved also in the mobile application through the APIControlers.
Is this approach is applicable? or do you think I should go to another approach?
I have a Identity Server client set up to be able to use the password and authorization code grants, I am able to use both, but when reviewing the tokens they do not contain the same claims, is this how its suppose to work or I am missing some configuration?
If this is how it works (different claims in each grant) when using the password grant should I use the Profile service to add the other claims?
You need to implement an IResourceOwnerPasswordValidator, and return the list of claims you need. The default implementation only sends the sub claim.
See the example implementation for ASP.NET Core Identity in IdentityServer repo.
Then modify it to send additional claims. Or use ProfileService to populate it:
public virtual async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
var user = await _userManager.FindByNameAsync(context.UserName);
if (user != null)
{
var result = await _signInManager.CheckPasswordSignInAsync(user, context.Password, true);
if (result.Succeeded)
{
var sub = await _userManager.GetUserIdAsync(user);
_logger.LogInformation("Credentials validated for username: {username}", context.UserName);
// return additional claims
var claims = await _userManager.GetClaimsAsync(user);
context.Result = new GrantValidationResult(sub, AuthenticationMethods.Password, claims);
return;
}
// ... see the link above for a full implementation
}
You can also create a new ClaimsPrincipal to populate the results. See the GrantValidationResult constructor overloads for other options.
I am using IdtentityServer4 where I have a scenario when a support personnel want to login on behalf of a user for support.
When this happens I want to know throughout my ASP.NET Core app that this support guy is acting on behalf of the user with a message on the screen that says:
"Mr Support on Behalf of Ms Username"
I am currently implementing IProfileUser for normal login. I am looking for a way to add an extra "attribute" to include the username of the support person in token without accessing the database.
I tried to pass items
await _signInManager.SignInAsync(applicationUser, new AuthenticationProperties
{
Items = {new KeyValuePair<string, string>("SupportName", "Mr Support")}
}).ConfigureAwait(false);
But I don't know how to get them from the ProfileDataRequestContext passed to GetProfileDataAsync in IProfileService implementation.
This is my IProfileService implementation:
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
ClaimsPrincipal claimsPrincipal = context.Subject;
if (claimsPrincipal == null)
{
throw new ArgumentNullException(nameof(context.Subject));
}
string id = claimsPrincipal.GetSubjectId();
ApplicationUser user = await _userManager.FindByIdAsync(id).ConfigureAwait(false);
if (user == null)
{
throw new ArgumentException("Invalid user identifier");
}
context.IssuedClaims.Add(new Claim(JwtClaimTypes.Email, user.Email));
IList<string> roles = await _userManager.GetRolesAsync(user).ConfigureAwait(false);
foreach (var role in roles)
{
context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, role));
}
context.IssuedClaims.Add(new Claim(JwtClaimTypes.Name, user.Name));
}
You could pass this data via a claim.
Modify your sign-in code to include the "SupportName" claim:
// Sign in the user and include the SupportName claim
await HttpContext.SignInAsync(userId, username, new Claim("SupportName", "Mr Support"));
and access this claim in GetProfileDataAsync:
public async Task GetProfileDataAsync(ProfileDataRequestContext context) {
// Find the "SupportName" claim added during sign-in
var supportNameClaim = context.Subject.FindFirst("SupportName");
if (supportNameClaim != null) {
Console.WriteLine($"Signed in on behalf of {supportNameClaim.Value}");
}
// Issue all the claims (if required)
context.IssuedClaims.AddRange(context.Subject.Claims);
}
I have an application that uses JwtBearerAuthentication. I am trying to add my application claims to the User(ClaimsPrincipal) at the beginning of each request. I managed to do that using ClaimsTransformationOptions:
app.UseClaimsTransformation(new ClaimsTransformationOptions
{
Transformer = new ClaimsTransformer<TUser, TRole>()
});
and in my TransformAsync:
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
var services = context.Context.RequestServices;
var userManager = services.GetRequiredService<UserManager<TUser>>();
var roleManager = services.GetRequiredService<RoleManager<TRole>>();
var userId = 1; // Get the UserId from my store, let say its 1 for now
if (userId != 0)
{
var user = await userManager.FindByIdAsync(userId);
var claimsPrincipal = await new UserClaimsPrincipalFactory<TUser, TRole>(userManager, roleManager, _optionsAccessor)
.CreateAsync(user);
context.Principal.AddIdentities(claimsPrincipal.Identities);
}
return context.Principal;
}
So far so good and the claims are being loaded from the database and added to the context.Principal. My problem is once I reach the controller, the identities are being overwritten !!
So I solved this problem by putting the app.UseClaimsTransformation after app.UseJwtBearerAuthentication which made sure that whenever JWT is going to amend the ClaimsPrincipal the ClaimsTransformation will be called afterwards to add my own claims.
I have searched but have not found any documentation outlining the best way to log each successful or failed attempt to get an access token and store the date/time and IP of the request. Where would I be able to do this within an application?
Ok. It's odd that there isn't any interest in answering this question.
After some trial/error and debug tracing, I found that the ApplicationOAuthProvider, located in the Providers folder in a typical ASP.NET Web API template, contains the following:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
//log the authentication attempt here
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);
}
I put a comment in the code to show where logging could be implemented. I hope that helps.