ASP.NET MVC 5 get claims - c#

I use third-party auth nuget instagram package for login and set new claim:
app.UseInstagramAuthentication(new InstagramAuthenticationOptions
{
ClientId = "XXXXXXXXXXXXXXXXXXXXXXXXX",
ClientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXX",
Provider = new InstagramAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim("urn::instagram::accesstoken", context.AccessToken));
return Task.FromResult(0);
}
}
but when I try to get this claim
var ctx = HttpContext.GetOwinContext();
ClaimsPrincipal user = ctx.Authentication.User;
IEnumerable<Claim> claims = user.Claims;
This claim does not exist in the list. Why?

You need to retrieve and store those claims on external login, maybe something like:
private async Task StoreAuthTokenClaims(ApplicationUser user)
{
// Get the claims identity
ClaimsIdentity claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (claimsIdentity != null)
{
// Retrieve the existing claims
var currentClaims = await UserManager.GetClaimsAsync(user.Id);
// Get the list of access token related claims from the identity
var tokenClaims = claimsIdentity.Claims
.Where(c => c.Type.StartsWith("urn:tokens:"));
// Save the access token related claims
foreach (var tokenClaim in tokenClaims)
{
if (!currentClaims.Contains(tokenClaim))
{
await UserManager.AddClaimAsync(user.Id, tokenClaim);
}
}
}
}
And on the ExternalLoginConfirmation method:
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (result.Succeeded)
{
await StoreAuthTokenClaims(user);
// Sign in and redirect the user
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
After that, you can retrieve claims like:
var claimsIdentity = HttpContext.User.Identity as ClaimsIdentity;
if (claimsIdentity != null)
{
var claims = claimsIdentity.Claims;
}

Related

Blazor Server Add Claims after authentication

Hi I want user to select branch to enter after login and user may have different role for each branch. Then this info will be added to user claims. Below is how I implemented IClaimsTransformation TransformAsync method.
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
// Clone current identity
var clone = principal.Clone();
var newIdentity = (ClaimsIdentity)clone.Identity;
// Support AD and local accounts
var nameId = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier ||
c.Type == ClaimTypes.Name);
if (nameId == null)
{
return principal;
}
// Get userInfo from database
UserBranchRoleModel userInfo = await Task.Run(() => _userService.GetCurrentBranchRole(nameId.Value));
if (userInfo?.User == null)
{
return principal;
}
List<Claim> claims = new()
{
new Claim(newIdentity.NameClaimType, userInfo.User.UserID),
new Claim("BranchID", userInfo.Branch.BranchID.ToString()),
new Claim("BranchName", userInfo.Branch.BranchName),
new Claim(newIdentity.RoleClaimType, userInfo.Role.RoleID)
};
newIdentity.AddClaims(claims);
return clone;
}
The problem is claims don't get updated if I do not refresh the page. Is there any way refresh claims without refreshing page altogether ?
Here is How I display claims in Index page
#foreach (var c in #user.Claims)
{
<div>#c.Type: #c.Value</div>
}
And this is how I Update DB
async Task changeBranch(string UserID)
{
DialogOptions options = new DialogOptions() { CloseButton = false };
DialogParameters parameters = new DialogParameters();
parameters.Add("UserID", UserID);
var dialog = DialogService.Show<ChooseBranch>("Select A Branch", parameters, options);
var result = await dialog.Result;
if (!result.Cancelled)
{
UserBranchRoleModel BranchRole = (UserBranchRoleModel)result.Data;
_user.SetCurrentBranchRole(BranchRole);
StateHasChanged();
}
}
}

Asp.net core 2.1 Role based authorisation not working with JWT

I'm currently having an issues where a given client that has a valid claim identity specifying that the client is in the given role, not get authorised for actions and controllers that require that role.
I'm using an API to generate the token which the client will use to access both the API and the website, the tokens are generated as JWT.
I have looked on other posts that have this issue, but almost all of the are using identity which isn't required for this project, since we are using JWT.
I am using HttpContext.Signin to sign in the user, passing it the token received from the API which contains the user's roles and other claims.
I have included the code that's dealing with tokens, and the token received from the API -at the very end-. As clearly seen, the user has the role attribute, yet they are will not be authorised by the authentication middleware.
Creating the token
public async Task<string> CreateBearerTokenAsync(User user, string audience)
{
// Create identity for the client
var claims = await _clientManager.CreateUserClaimsIdentityAsync(user.Id);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = claims,
Expires = DateTime.UtcNow.AddHours(2),
IssuedAt = DateTime.UtcNow,
Issuer = "Bikefy Api",
Audience = audience,
SigningCredentials = _encryptionService.TokenSignKey
};
// Create the token
var token = new JwtSecurityTokenHandler().CreateJwtSecurityToken(tokenDescriptor);
// Write the token to string.
return new JwtSecurityTokenHandler().WriteToken(token);
}
Claims identity
public async Task<ClaimsIdentity> CreateUserClaimsIdentityAsync(Guid clientId)
{
if (clientId == null || Guid.Empty == clientId)
throw new ArgumentNullException($"{nameof(clientId)}");
var user = await GetClientByIdAsync(clientId);
var id = new ClaimsIdentity("ApiKey", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
var secKey = await GetSecurityStampAsync(clientId);
// Add default claims
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, clientId.ToString(), ClaimValueTypes.String));
if (user.FirstName != null)
id.AddClaim(new Claim(ClaimTypes.GivenName, user.FirstName, ClaimValueTypes.String));
if (user.LastName != null)
id.AddClaim(new Claim(ClaimTypes.Surname, user.LastName, ClaimValueTypes.String));
id.AddClaim(new Claim(IdentityProviderClaimType, "Bikey Identity", ClaimValueTypes.String));
id.AddClaim(new Claim(SecurityStampClaimType, secKey, ClaimValueTypes.String));
if (user.RoleName != null)
id.AddClaim(new Claim($"{nameof(User.RoleName)}", user.RoleName, ClaimValueTypes.String));
// Get roles
var roles = await GetRolesAsync(clientId);
foreach (var role in roles)
id.AddClaim(new Claim(ClaimTypes.Role, role));
// Add user claims
id.AddClaims(await GetClaimsAsync(clientId));
return id;
}
Cookie token configuration
public static void ConfigureAuthentication(this IServiceCollection services, IConfiguration configuration)
{
var secretKeys = new SecretKeys();
configuration.GetSection("SecretKeys").Bind(secretKeys);
var encryptService = new EncryptionService("00E7EB8C24190E2187", secretKeys);
services.AddSingleton(encryptService);
services.AddSingleton(secretKeys);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
x.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(c =>
{
c.Cookie.Name = "CityCyles.Auth";
c.Cookie.HttpOnly = true;
c.Cookie.Expiration = TimeSpan.FromDays(1);
c.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
c.LoginPath = $"/Account/Login";
c.LogoutPath = $"/Account/Logout";
c.AccessDeniedPath = $"/Account/AccessDenied";
});
}
User Login
try
{
result = await _apiProvider.SendPostRequest(_apiProvider.BuildUrl("Auth", "Authenticate"), apiModel);
// Convert the string into a token
var token = new JwtSecurityTokenHandler().ReadJwtToken(result);
// Create cookie options
var authOptions = new AuthenticationProperties()
{
AllowRefresh = model.Remember,
ExpiresUtc = DateTime.UtcNow.AddHours(2),
IssuedUtc = DateTime.UtcNow,
IsPersistent = true
};
// Get the user claims from the user
var claimsIdentity = new ClaimsIdentity(token.Claims, CookieAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role);
// Sign in the user
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authOptions);
// Redirect the user to the requested page or take them home
if (!string.IsNullOrEmpty(returnUrl))
return Redirect(returnUrl);
return RedirectToAction("Index", "Home");
}
catch (WebException ex)
{
_logger.LogWarning(LogEvents.HandlingLogin, ex, "Error authenticating user.");
//Display the error
ModelState.AddModelError("Custom-Error", ex.Message);
return View(model);
}
Controller Action
[HttpGet]
[Authorize(Roles = CityCyclesRoles.CityCyclesDocks)]
public async Task<IActionResult> OnBoard()
{
return View();
}
Token
{
"nameid": "3e637f01-85a9-4437-a113-50d2953d014e",
"given_name": "Stephanie",
"family_name": "Lee",
"http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider": "Bikey Identity",
"Bikeyfy.Identity.SecurityStamp": "1b6228cf-945a-4503-a64e-1dcb7b649c22",
"role": "CityCycles.Staff.Docks",
"nbf": 1552586386,
"exp": 1552593586,
"iat": 1552586386,
"iss": "CityCycles Api",
"aud": "CityCycles.Web"
}

Obtaining Access Tokens From External Login

I'm modify my authentication from using Identity server to use the built in .net-core authentication. I'm trying to obtain the Access Tokens From An external Login. But when I go to obtain the User from the User Manager it returns null. I'm using the OAuth Authorization Code flow to login. I have a callback method which signs in and returns a JWT (I've omited some irrelevant code for simplicity)
[HttpGet("ExternalLoginCallback")]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
var info = await _signInManager.GetExternalLoginInfoAsync();
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
var user = await (result.Succeeded ?
_userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey)
: this.CreateIdentityUser(info));
var serviceProvider = HttpContext.RequestServices;
var context = serviceProvider.GetRequiredService<PublicAstootContext>();
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
return await GenerateJwtResponse(_jwtFactory.GenerateClaimsIdentity(user.UserName, user.Id, userId),
_jwtFactory, _jwtOptions);
}
I create my User Identity like so:
private async Task<IdentityUser> CreateIdentityUser(ExternalLoginInfo info)
{
string email = await this.GetEmailFromLoginInfo(info);
//var email = info.Principal.FindFirstValue(ClaimTypes.Email);
var user = new IdentityUser { UserName = email, Email = email };
var signinResult = await _userManager.CreateAsync(user);
if (false == signinResult.Succeeded)
{
throw new Exception("Failed to Create User");
}
var addLoginResult = await _userManager.AddLoginAsync(user, info);
if (addLoginResult.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
}
return user;
}
When I go to obtain the Third Party Access Token
var user = httpContext.User;
var userManager = sp.GetRequiredService<UserManager<IdentityUser>>();
var userFromManager = userManager.GetUserAsync(user).Result;
var accessToken = userManager.GetAuthenticationTokenAsync(
userFromManager, "Coinbase", "access_token").Result;
EDIT
I cannot find my User in the the UserManager. How can I obtain my third party access token?
It looks like my HttpContext.User and my UserManager is out of sync.
var user = httpContext.User;
var userManager = sp.GetRequiredService<UserManager<IdentityUser>>();
if(false == user.TryGetClaimByType<string>(AuthenticationRule.EMAIL_CLAIM_TYPE, out var email))
{
throw new UnauthorizedAccessException();
}
How can I Sync the HttpContext.User and the Usermanger so I can simply call:
var userFromManager = userManager.GetUserAsync(user).Result;

How to refresh the JWT in net core?

I have a method to auth the user and create a token with expiration time, but if the token expired, the user cannot use the data. How to handle this?
This is my method:
[AllowAnonymous]
[HttpPost]
[Route("api/token")]
public IActionResult Post([FromBody]Personal personal)
{
string funcionID = "";
if (ModelState.IsValid)
{
var userId = GetUser(personal);
if (!userId.HasValue)
{
return Unauthorized();
}
else if (userId.Equals(2)) {
return StatusCode(404, "Vuelve a ingresar tu contraseƱa");
}
List<Claim> claims = new List<Claim>();
foreach (var funcion in Funcion) {
claims.Add(new Claim(ClaimTypes.Role, funcion.FuncionID.ToString()));
}
claims.Add(new Claim(JwtRegisteredClaimNames.Email, personal.CorreoE));
claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
var sesionExpira = new DatabaseConfig();
_configuration.GetSection("Database").Bind(sesionExpira);
var token = new JwtSecurityToken
(
issuer: _configuration["Issuer"],
audience: _configuration["Audience"],
claims: claims,
expires: DateTime.UtcNow.AddMinutes(sesionExpira.Sesion),
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SigningKey"])),
SecurityAlgorithms.HmacSha256)
);
var token_email = token.Claims.Where(w => w.Type == "email").Select(s => s.Value).FirstOrDefault();
var token_rol = claims.Where(x => x.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role").Select(s => s.Value).FirstOrDefault();
var nombre = _context.Personal.Where(x => x.CorreoE == personal.CorreoE).Select(x => x.Nombre).FirstOrDefault();
return Ok(new { email = personal.CorreoE, token = new JwtSecurityTokenHandler().WriteToken(token), nombre = nombre, funcion = Funcion});
}
return BadRequest();
}
First, in the GetUser(Personal personal) method, that returns int, i return a number that i use to create a new token. Everything works fine, but i need some information to refresh the token if the time has expired
You can create middleware that will update the token. If you move your token creation logic to a separate service then you can do it like:
public class JwtTokenSlidingExpirationMiddleware
{
private readonly RequestDelegate next;
private readonly ITokenCreationService tokenCreationService;
public JwtTokenSlidingExpirationMiddleware(RequestDelegate next, ITokenCreationService tokenCreationService)
{
this.next = next;
this.tokenCreationService= tokenCreationService;
}
public Task Invoke(HttpContext context)
{
// Preflight check 1: did the request come with a token?
var authorization = context.Request.Headers["Authorization"].FirstOrDefault();
if (authorization == null || !authorization.ToLower().StartsWith("bearer") || string.IsNullOrWhiteSpace(authorization.Substring(6)))
{
// No token on the request
return next(context);
}
// Preflight check 2: did that token pass authentication?
var claimsPrincipal = context.Request.HttpContext.User;
if (claimsPrincipal == null || !claimsPrincipal.Identity.IsAuthenticated)
{
// Not an authorized request
return next(context);
}
// Extract the claims and put them into a new JWT
context.Response.Headers.Add("Set-Authorization", tokenCreationService.CreateToken(claimsPrincipal.Claims));
// Call the next delegate/middleware in the pipeline
return next(context);
}
}
And register it in Startup.cs:
public void Configure(IApplicationBuilder app)
{
...
app.UseMiddleware<JwtTokenSlidingExpirationMiddleware>();
...
}
I did something similiar to an old application using the method RefreshTokenAsync from IdentityModel.
You could try something like this when the User gets an unauthorized:
var identityService = await DiscoveryClient.GetAsync("http://localhost:5000");
// request token
var tokenClient = new TokenClient(identityService.TokenEndpoint, "client", "secret");
var tokenResponse = await tokenClient.RequestRefreshTokenAsync(refreshToken);
return Ok(new { success = true, tokenResponse = tokenResponse });
Source: https://github.com/IdentityModel/IdentityModel.OidcClient.Samples/issues/4
EDIT: I have edited my original answer to provide a more clear and better answer according to the rules.

Update Claims after user login

Is it possible to update claims after user login? I have frontend and admin panel in my site, basically I'm using claims to achieve this. If a user is logged in as Susan in frontend, I did something like this in my code :
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
userIdentity.AddClaim(new Claim("Id", this.UserName));
... //other claims here
So when user finally login on admin panel (when still logged on in frontend), I just want to add more claims for example :
userIdentity.AddClaim(new Claim("BackEndId", this.UserName));
How to achieve this?
To be able to read the claim on a cookie you need to sign user our and sign them back in again during the same request:
idenityt.AddClaim(new Claim("myClaimType", "myClaimValue"));
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, idenityt);
Add function to ApplicationSignInManager class in Identity.Config.cs
public override Task SignInAsync(ApplicationUser user, bool isPersistent, bool rememberBrowser)
{
var claims = new List<Claim>()
{
new Claim("Id", this.UserName),
new Claim("BackEndId", this.UserName)
};
this.AuthenticationManager.User.AddIdentity(new ClaimsIdentity(claims));
return base.SignInAsync(user, isPersistent, rememberBrowser);
}
You can use this code to access claims.
private ClaimsPrincipal GetCurrentUser()
{
var context = HttpContext.GetOwinContext();
if (context == null)
return null;
if (context.Authentication == null || context.Authentication.User == null)
return null;
return context.Authentication.User;
}
private string GetUserId()
{
var user = GetCurrentUser();
if (user == null)
return null;
var claim = user.Claims.FirstOrDefault(o => o.Type == "Id");
if (claim == null)
return null;
return claim.Value;
}
private string GetBackEndId()
{
var user = GetCurrentUser();
if (user == null)
return null;
var claim = user.Claims.FirstOrDefault(o => o.Type == "BackEndId");
if (claim == null)
return null;
return claim.Value;
}

Categories