Why when requesting a token using the grant password and authorization code, I do not get the same claims? - asp.net-core

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.

Related

Access Current User in ASP.NET Core Startup.cs

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

Cannot Use Controller.User After Sign In

Currently I'm creating an ASP.NET Core app that uses Identity.
I successfully built Sign In feature, with my code was made briefly:
public async Task<JsonResult> SignIn(string email, string password)
{
var result = new ApiResult();
var user = await userManager.FindByEmailAsync(email);
var signInResult = signInManager.CheckPasswordSignInAsync(user, password, lockOnFailure:true);
result.Success = signInResult.Succeeded;
return Json(result);
}
However, I couldn't use this.User in Controllers, also I can't get currently logged user.
m_currentUser = await userManager.GetUserAsync(User); // This cannot be accomplished
As I know, SignInManager sets automatically authentication data, What's wrong with this code?
I referred bunch of examples but nothing found any different.
SignInManager.CheckPasswordSignInAsync checks whether the given password is valid for the specified user. But it doesn't perform the sign-in process, which ends up creating a user's ClaimsPrincipal and persisting it via a cookie. You should use SignInManager.PasswordSignInAsync instead(this method also check the password use CheckPasswordSignInAsync) :
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
Or manually complete sign in after CheckPasswordSignInAsync :
await _signInManager.SignInAsync(user, isPersistent:true);

Passing Data to IProfileService in IdentityServer4

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);
}

Custom claims with Jwt Bearer Authentication

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.

ASP.NET Web API Login History Implementation

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.