JWT Authentication in ASP.NET Project - c#

I am Trying to extract the Id from JWT Token.
Controller Code:
public async Task<IActionResult> GetUser(string id)
{
var currentUserId = (User.FindFirst(ClaimTypes.NameIdentifier).Value); //Line number: 27
bool isCurrentUser = String.Equals(currentUserId, id);
var user = await _repo.GetUser(id, isCurrentUser);
var userToReturn = _mapper.Map<UserForDetailed>(user);
return Ok(userToReturn);
}
But this shows a run time error.
If I remove .Value (commented line) (User.FindFirst(ClaimTypes.NameIdentifier) it returns null but no error.
Both error messages are identical
Startup.cs:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration
.GetSection("AppSettings:Token").Value)),
ValidateIssuer = true,
ValidateAudience = true
};
});
Method that generate JWT token:
private async Task<string> GenerateJwtToken(User user)
{
var claims = new List<Claim> {
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Email, user.Email)
};
var roles = await _userManager.GetRolesAsync(user);
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.GetSection("AppSettings:Token").Value));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(7),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
I like to mention, another project where I used the same method, it works.
public async Task<IActionResult> GetUser(int id)
{
var isCurrentUser = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value) == id;
var user = await _repo.GetUser(id, isCurrentUser);
var userToReturn = _mapper.Map<UserForDetailed>(user);
return Ok(userToReturn);
}
Difference is, one have id: string another id: int
Please let me know, If you need any additional Info
Edit:
Example Token: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIwMDMzMmVlMy1lZmVlLTRiOTctYTQ5ZS04ODRhYmJhOGE0NzciLCJ1bmlxdWVfbmFtZSI6InJhanUiLCJlbWFpbCI6InJhanVAZ21haWwuY29tIiwicm9sZSI6WyJkZXYiLCJtYW0iXSwibmJmIjoxNTk0NDk1NTA2LCJleHAiOjE1OTUxMDAzMDYsImlhdCI6MTU5NDQ5NTUwNn0.wvfOst-3lMk0d1-LafzuXKzeC_yN2ZQL3GSsZ5114IukOfwipNnTaFm-RlTbu52KesuRl4NyWiHoEt5IR0n7EQ
Payload of Decoded Token:

Edited:
Your token does not seem to have a Sub claim which will be mapped to ClaimTypes.NameIdentifier (aka http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier)
Also, I hope you are saving the token in the AppSettings just for testing purposes, because Tokens are meant to be expired.

Related

issues faced while extend Role based authorization

i faced issues when i tried to extend role based authorization by using Microsoft identity.
when call login action method it will generate token if not added role into claim List.
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name,user.UserName),
new Claim(ClaimTypes.NameIdentifier,user.Id.ToString())
};
var roles = await _userManager.GetRolesAsync(user); // If remove this await call then it work fine.
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
After add new role into Claims while generate Token and i did get any response from server for few min after some time it will shown error like below.
Error is : Exception has been thrown by the target of an invocation.
In Startup.cs class under ConfigureServices Method.
services.AddDbContext<DataContext>(x => x.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
IdentityBuilder builder = services.AddIdentityCore<User>(opt =>
{
opt.Password.RequireDigit = false;
opt.Password.RequiredLength = 4;
opt.Password.RequireNonAlphanumeric = false;
opt.Password.RequireUppercase = false;
});
builder = new IdentityBuilder(builder.UserType, typeof(Role), builder.Services);
builder.AddEntityFrameworkStores<DataContext>();
builder.AddRoleValidator<RoleValidator<Role>>();
builder.AddRoleManager<RoleManager<Role>>();
builder.AddSignInManager<SignInManager<User>>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(auth =>
{
auth.TokenValidationParameters = new TokenValidationParameters()
{
// ValidateIssuer = true,
// ValidIssuer = Configuration["AuthSettings:Issuer"],
// ValidateAudience = true,
// ValidAudience = Configuration["AuthSettings:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["AuthSettings:Key"])),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddControllers(opt =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
opt.Filters.Add(new AuthorizeFilter(policy));
})
.AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
In AuthController
[HttpPost("login")]
public async Task<IActionResult> Login(UserForLoginDto loginDto)
{
var user = await _userManager.FindByNameAsync(loginDto.Username);
var result = await _signInManager.CheckPasswordSignInAsync(user, loginDto.Password, false);
if (result.Succeeded)
{
var appUsers = await _userManager.Users.Include(e => e.Photos)
.FirstOrDefaultAsync(next => next.NormalizedUserName == loginDto.Username.ToUpper());
var userToReturn = _mapper.Map<UserForListDto>(appUsers);
return Ok(new
{
token = GeneratejwtToken(appUsers),
user = userToReturn
});
}
else
{
return Unauthorized();
}
}
private async Task<string> GeneratejwtToken(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name,user.UserName),
new Claim(ClaimTypes.NameIdentifier,user.Id.ToString())
};
var roles = await _userManager.GetRolesAsync(user);
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["AuthSettings:Key"]));
var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(1),
SigningCredentials = signingCredentials
};
var TokenHandler = new JwtSecurityTokenHandler();
var token = TokenHandler.CreateToken(tokenDescriptor);
return TokenHandler.WriteToken(token);
}
I fixed that issue,
while calling GeneratejwtToken method i forgot use await keyword because that method is async method.
token = await GeneratejwtToken(appUsers)

ASP.NET Core 3.1 Web API Role based authorization not working

I couldn't understand why I always get 401 unauthorized where the user I logged in has a role of SuperAdmin. I tried looking at other solutions and projects and they seem identical to the code I have still does not work. I use Postman to test the API and in Authorization tab bearer token I pasted the token of the user I logged in and make a request on this API.
//API
[Route("create")]
[Authorize(Roles = "SuperAdmin")]
public async Task<IActionResult> RegisterUserAsync([FromBody] Request request)
{
return something;
}
//StartUp.cs
private void ConfigureAuth(IServiceCollection services)
{
services.AddIdentity<UserEntity, RoleEntity>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddRoles<RoleEntity>();
}
var key = Encoding.ASCII.GetBytes(jwtSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
//JWT
public string GenerateToken(UserEntity userEntity, IList<string> roles)
{
var token = string.Empty;
var tokenHandler = new JwtSecurityTokenHandler();
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.jwtOptions.GetJwtOptions().Secret));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, userEntity.UserName),
new Claim(ClaimTypes.GivenName, userEntity.FirstName),
new Claim(ClaimTypes.Surname, userEntity.LastName),
new Claim(ClaimTypes.NameIdentifier, userEntity.Id.ToString()),
new Claim(ClaimTypes.Role, roles.FirstOrDefault()) //SuperAdmin
};
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddMinutes(this.jwtOptions.GetJwtOptions().ExpiresInMinutes),
SigningCredentials = credentials
};
token = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
return token;
}
You need to add app.UseAuthentication() before app.UseAuthorization() , the authentication middleware will handle the JWT bearer authentication , validate and decode token , at last write to user's principle.

Get JWT claims directly from the token, ASP Net Core 2.1

I working on an ASP Net Core 2.1 Web API. I've implemented successfully JWT within my project. Everything with the Authorization works fine.
Normally, when I need user claims, I know I can get them like this (E.g. Email claim):
var claimsIdentity = User.Identity as ClaimsIdentity;
var emailClaim = claimsIdentity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Email);
The thing is, I am not in a controller that inherits from ControllerBase class, so I don't have any User object or [Authorize] attributes.
What I have though is the token itself.
e.g.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImFkbWluIiwiZW1haWwiOiJhZG1pbiIsIm5iZiI6MTU2ODYzNjYxMywiZXhwIjoxNTY4NjQ3NDEzLCJpYXQiOjE1Njg2MzY2MTN9.ED9x_AOvkLQqutb09yh3Huyv0ygHp_i3Eli8WG2S9N4
I want to get the claims directly from the token, because:
I have access to the token.
I am not located in a Controller class and the request is not going through any [Authorize] attributes, so IHttpContextAccessor can't be used as well.
How can I achieve this in ASP Net Core 2.1? In case someone wants to see how I add the user claims:
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddHours(3),
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, email),
new Claim(ClaimTypes.Email, email)
}),
SigningCredentials = new SigningCredentials(key: new SymmetricSecurityKey(key), algorithm: SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
I'm located in a class that derives from IDocumentFilter (Swagger class)
Here is a simple workaround:
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddHours(3),
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "user#hotmail.com"),
new Claim(ClaimTypes.Email, "user#hotmail.com")
}),
SigningCredentials = new SigningCredentials(key: new SymmetricSecurityKey(key), algorithm: SecurityAlgorithms.HmacSha256Signature)
};
var Securitytoken = new JwtSecurityTokenHandler().CreateToken(tokenDescriptor);
var tokenstring = new JwtSecurityTokenHandler().WriteToken(Securitytoken);
var token = new JwtSecurityTokenHandler().ReadJwtToken(tokenstring);
var claim = token.Claims.First(c => c.Type == "email").Value;
return claim;
For example in my current project I get claims by validation. Its refresh token, so I cant use [Authorize] attribute.
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
public ClaimsPrincipal ValidateRefreshToken(string refreshToken)
{
try
{
var validationParams = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(tokenSecurityKey),
ValidateLifetime = true
};
return new JwtSecurityTokenHandler().ValidateToken
(
refreshToken,
validationParams,
out SecurityToken token
);
}
catch (Exception e)
{
Log.Error(e.Message);
return null;
}
}
and then
var claims = ValidateRefreshToken(refreshToken);
...
var userIdString = claims.Claims.FirstOrDefault(x => x.Type == "userId")?.Value;
Here is an easy way to extract the claims:
public IEnumerable<Claim> ExtractClaims(string jwtToken)
{
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
JwtSecurityToken securityToken = (JwtSecurityToken)tokenHandler.ReadToken(jwtToken);
IEnumerable<Claim> claims = securityToken.Claims;
return claims;
}

I can t find why my bearer token is not valid, its returns 401

I have created role-based authentication. Token is generated succesfully.
In my vue app, after login i m adding token to localStorage and then, im sending it in header by use of axios in format:
Authorization: "Bearer"
I m getting unauthorize everytime i m trying to do GET on items.
In postman is this same.
I m using IIS to host my app localy, and as frontend i m using Vue.js
I have already tried:
- authorization and Authorization in header
- changing http to https and vice versa.
Startup ConfigureServices:
#region JWT
var key = Encoding.ASCII.GetBytes(Configuration.GetValue<string>("SecretKey"));
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
#endregion
Startup Configure:
app.UseAuthentication();
My method to generate token based on roles:
public User Authenticate(string login, string password)
{
var user = Context.Users.SingleOrDefault(p => p.Login == login && p.Password == password);
if (user == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString()),
new Claim(ClaimTypes.Role, user.Role)
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);
user.Password = null;
return user;
}
And this is my Endpoint (in case if something is wrong here)
[Route("api/[controller]")]
[EnableCors("MyPolicy")]
[Authorize]
[ApiController]
public class ItemsController : ControllerBase
{
public ItemsController()
{
}
[HttpGet]
[Authorize(Roles = "Admin")]
public ActionResult<IEnumerable<Item>> Get()
{
var items = new ItemsService();
return items
.GetItems();
}

User.IsInRole always returns false with Token Authentication

I have ASP.NET Core 2 configured to use JWT Tokens to authenticate. The configuration looks like this:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
};
});
Then I created a method where a user signs in which looks like this:
[AllowAnonymous]
[HttpPost]
[Route("token")]
public async Task<IActionResult> Token([FromBody] LoginViewModel model)
{
if (!ModelState.IsValid) return BadRequest("Could not create token");
var user = await _userManager.FindByNameAsync(model.UserName);
if (user == null) return BadRequest("Could not create token");
var result = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);
if (!result.Succeeded) return BadRequest("Could not create token");
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Tokens:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var roles = await _userManager.GetRolesAsync(user);
var token = new JwtSecurityToken(_configuration["Tokens:Issuer"],
_configuration["Tokens:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return Ok(new {
access_token = new JwtSecurityTokenHandler().WriteToken(token),
roles });
}
}
I save the token and use it in my request. I have a very simple api endpoint:
[HttpGet("users")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult GetUsers()
{
var isInRole = HttpContext.User.IsInRole(Roles.Administrator);
return Ok(_service.GetAllUsers());
}
Here I get in the method, but isInRole is always false. Even though my var roles = await _userManager.GetRolesAsync(user); returns a list of roles including Administrator. Why does this not work then?
You need to add the Role claim into your claims array like so
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
//role claim
new Claim(ClaimTypes.Role, "Administrator")
};
This is what Asp.Net will look at in the [Authorize(Roles="Administrator")] attribute and HttpContext.User.IsInRole("Administrator");
Similarly ClaimTypes.Name is used to produce User.Identity.Name in your controller
I suggest you read the excellent article by Rui Figueiredo Secure a Web Api in ASP.NET Core on the subject
public async Task<string> GenerateEncodedToken(string userName, string email, List<string> roles, ClaimsIdentity identity)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, userName),
new Claim(JwtRegisteredClaimNames.UniqueName, userName),
new Claim(JwtRegisteredClaimNames.Email, email),
new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64)
};
var id = identity.FindFirst(Constants.Strings.JwtClaimIdentifiers.Id);
claims.Add(id);
roles.ForEach(role =>
{
claims.Add(new Claim(ClaimTypes.Role, role));
});
var jwt = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
notBefore: _jwtOptions.NotBefore,
expires: _jwtOptions.Expiration,
signingCredentials: _jwtOptions.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
return encodedJwt;
}

Categories

Resources