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)
Related
I'm trying to get dual authentication schemes working with Azure Ad and regular jwt tokens, the issue i'm having is, I can access both controller functions with just the JWT token, regardless of scheme. I tried setting up my configuration from this post but it didn't work (neither does my current configuration obviously):
Authenticating tokens from multiple sources (e.g Cognito and Azure)
[HttpGet]
[Authorize(AuthenticationSchemes = "TestBearer")]
[Route("Test1")]
public async Task Test()
{
}
[HttpGet]
[Authorize(AuthenticationSchemes = "AzureAd")]
[Route("Test2")]
public async Task Test2()
{
}
Startup config
services.AddAuthentication(options =>
{
options.DefaultScheme = "AzureAd";
})
.AddJwtBearer("TestBearer", x =>
{
x.RequireHttpsMetadata = false;
x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Guid.Parse(jwtConfig.SecretKey).ToByteArray())
};
})
.AddMicrosoftIdentityWebApi(x =>
{
x.Audience = config.Audience;
x.Authority = config.Authority;
x.RequireHttpsMetadata = false;
},
x =>
{
x.Instance = config.Instance;
x.ClientId = config.ClientId;
x.TenantId = config.TenantId;
x.Authority = config.Authority;
x.RequireHttpsMetadata = false;
}, "AzureAd");
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
"TestBearer", "AzureAd");
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
var scopes = new string[] { "https://graph.microsoft.com/.default" };
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(config.ClientId)
.WithTenantId(config.TenantId)
.WithClientSecret(config.ClientSecret)
.Build();
//Build the Microsoft Graph client.As the authentication provider, set an async lambda
// which uses the MSAL client to obtain an app-only access token to Microsoft Graph,
// and inserts this access token in the Authorization header of each API request.
var authenticationProvider = (new DelegateAuthenticationProvider(async (requestMessage) =>
{
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
var authResult = await confidentialClientApplication
.AcquireTokenForClient(scopes)
.ExecuteAsync();
//// Add the access token in the Authorization header of the API request.
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
}));
services.AddScoped(_ => new GraphServiceClient(authenticationProvider));
my jwt token generation
private string GenerateToken(TestEntity entity)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Guid.Parse(_jwtSettings.SecretKey).ToByteArray();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]{
new Claim(ClaimTypes.NameIdentifier, entity.Id.ToString()),
new Claim(ClaimTypes.MobilePhone, entity.Phone.ToString()),
new Claim(ClaimTypes.Email, entity.Email)
}),
Expires = DateTime.UtcNow.AddHours(_jwtSettings.ExpiryTimeInHours),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
Audience = "https://localhost:5001",
Issuer = _jwtSettings.Issuer,
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
So I ended up seperating the policies (leaving azure AD as my default policy) and creating an extra combined one in cases I want one of multiple policies to work. All seems to work correctly now.
services.AddAuthorization(options =>
{
options.AddPolicy("Test", policy =>
{
policy.AuthenticationSchemes.Add("TestBearer");
policy.RequireAuthenticatedUser();
});
options.AddPolicy("AzureOrTest", policy =>
{
policy.AuthenticationSchemes.Add("TestBearer");
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
});
options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
});
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.
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.
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();
}
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;
}