.Net Core microservice and inspecting Cognito JWT claims for use in authorization - asp.net-core

I am developing a .net service that I am passing a Cognito generated JWT in the client that has a group claim that I hope to use to restrict API access as the JWT is passed in as a Bearer token with each API call from the front-end. e.g.
"cognito:groups":["Guest"]
In my code now I have added:
services.AddAuthentication(DefaultScheme = JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
RoleClaimType = "cognito:groups"
};
});
I have setup up my roles and my permissions using the HeimGuard project. In testing as I change roles in the Bearer token none of my APIs that restrict access to Guest do not deny access when decorated with [Authorize]. So, nothing is restricted. Any thoughts or ideas you have in how to solve this problem helps immensly. Thank you!!

I tried custom AuthorizeAttribute as below:
To create the Token:
public class UserModel
{
public string Username { get; set; }
public string Role { get; set; }
public string EmailAddress { get; set; }
public string PassWord { get; set; }
public string Right { get; set; }
}
[HttpPost]
public IActionResult Authenticate(UserModel someuser)
{
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Role, someuser.Role),
new Claim(ClaimTypes.Name, someuser.Username),
new Claim("Right", someuser.Right)
};
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
signingCredentials: credentials,
expires: DateTime.Now.AddMinutes(2),
claims: claims
);
string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
return Content(jwtToken);
}
codes in appsettings.json:
"Jwt": {
"Key": "ThisismySecretKey",
"Issuer": "Test.com"
}
Custom requirement:
public class WriteRequirement : AuthorizationHandler<WriteRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, WriteRequirement requirement)
{
var right = context.User.FindFirst("Right").Value;
if(right=="Write")
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
}
in startup class:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = false,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
services.AddSingleton<IAuthorizationHandler, WriteRequirement>();
services.AddAuthorization(opyions => { opyions.AddPolicy("Write", policy => policy.Requirements.Add(new WriteRequirement())); });
...
}
The Action need Authrization:
[HttpGet]
[Authorize(Roles ="Admin")]
[Authorize(Policy="Write")]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2", "value3", "value4", "value5" };
}
The Result:

Related

JWT Authentication And Authorization In .NET 6.0 401Unauthorized Error

i am creating a token and it does not accept the token I created, the error code 401 returns
appsettings.json
{
"TokenOptions": {
"Audience": "https://localhost:7098",
"Issuer": "https://localhost:7098",
"AccessTokenExpiration": 500,
"SecurityKey": "mysecretkeymysecretkey"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
TokenOptions.cs
namespace Shared.Utilities.Security.Jwt;
public class TokenOptions
{
public string Audience { get; set; }
public string Issuer { get; set; }
public int AccessTokenExpiration { get; set; }
public string SecurityKey { get; set; }
}
JwtHelper.cs
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Shared.Entities.Concrete;
using Shared.Extensions;
using Shared.Utilities.Security.Encyption;
namespace Shared.Utilities.Security.Jwt;
public class JwtHelper : ITokenHelper
{
public IConfiguration Configuration { get; }
private TokenOptions _tokenOptions;
private DateTime _accessTokenExpiration;
public JwtHelper(IConfiguration configuration)
{
Configuration = configuration;
_tokenOptions = Configuration.GetSection(key: "TokenOptions").Get<TokenOptions>();
}
public AccessToken CreateToken(User user, List<OperationClaim> operationClaims)
{
_accessTokenExpiration = DateTime.Now.AddMinutes(_tokenOptions.AccessTokenExpiration);
var securityKey = SecurityKeyHelper.CreateSecurityKey(_tokenOptions.SecurityKey);
var signingCredentials = SigningCredentialsHelper.CreateSigningCredentials(securityKey);
var jwt = CreateJwtSecurityToken(_tokenOptions, user, signingCredentials, operationClaims);
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
var token = jwtSecurityTokenHandler.WriteToken(jwt);
return new AccessToken
{
Token = token,
Expiration = _accessTokenExpiration
};
}
public JwtSecurityToken CreateJwtSecurityToken(TokenOptions tokenOptions, User user, SigningCredentials signingCredentials, List<OperationClaim> operationClaims)
{
var Jwt = new JwtSecurityToken(
issuer: tokenOptions.Issuer,
audience: tokenOptions.Audience,
expires: _accessTokenExpiration,
notBefore: DateTime.Now,
claims: SetClains(user, operationClaims),
signingCredentials: signingCredentials
);
return Jwt;
}
private IEnumerable<Claim> SetClains(User user, List<OperationClaim> operationClaims)
{
var claims = new List<Claim>();
claims.AddNameIdentifier(user.Id.ToString());
claims.AddEmail(user.Email);
claims.AddName($"{user.FirstName} {user.LastName}");
claims.AddRole(operationClaims.Select(c => c.Name).ToArray());
return claims;
}
}
ITokenHelper.cs
using Shared.Entities.Concrete;
namespace Shared.Utilities.Security.Jwt;
public interface ITokenHelper
{
AccessToken CreateToken(User user, List<OperationClaim> operationClaims);
}
AccessToken.cs
namespace Shared.Utilities.Security.Jwt
{
public class AccessToken
{
public string Token { get; set; }
public DateTime Expiration { get; set; }
}
}
HashingHelper.cs
using System.Text;
namespace Shared.Utilities.Security.Hashing;
public class HashingHelper
{
public static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
{
using (var hmac = new System.Security.Cryptography.HMACSHA512())
{
passwordSalt = hmac.Key;
passwordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password));
}
}
public static bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
{
using (var hmac = new System.Security.Cryptography.HMACSHA512(passwordSalt))
{
var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password));
for (int i = 0; i < computedHash.Length; i++)
{
if (computedHash[i] != passwordHash[i])
{
return false;
}
}
}
return true;
}
}
SigningCredentialsHelper.cs
using Microsoft.IdentityModel.Tokens;
namespace Shared.Utilities.Security.Encyption;
public class SigningCredentialsHelper
{
public static SigningCredentials CreateSigningCredentials(SecurityKey securityKey)
{
return new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
}
}
SecurityKeyHelper.cs
using System.Text;
using Microsoft.IdentityModel.Tokens;
namespace Shared.Utilities.Security.Encyption;
public class SecurityKeyHelper
{
public static SecurityKey CreateSecurityKey(string securityKey)
{
return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
}
}
ClaimExtensions.cs
using System.Security.Claims;
using Microsoft.IdentityModel.JsonWebTokens;
namespace Shared.Extensions;
public static class ClaimExtensions
{
public static void AddEmail(this ICollection<Claim> claims, string email)
{
claims.Add(new Claim(type: JwtRegisteredClaimNames.Email, value: email));
}
public static void AddName(this ICollection<Claim> claims, string name)
{
claims.Add(new Claim(type: ClaimTypes.Name, value: name));
}
public static void AddNameIdentifier(this ICollection<Claim> claims, string nameIdentifier)
{
claims.Add(new Claim(type: ClaimTypes.NameIdentifier, value: nameIdentifier));
}
public static void AddRole(this ICollection<Claim> claims, string[] roles)
{
roles.ToList().ForEach(role => claims.Add(new Claim(type: ClaimTypes.Role, value: role)));
}
}
AutofacBusinessModule.cs
using Autofac;
using Data.Abstract;
using Data.Concrete.EntityFramework;
using Services.Abstract;
using Services.Concrete;
using Shared.Utilities.Security.Jwt;
namespace Services.DependencyResolvers.Autofac
{
public class AutofacBusinessModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ProductManager>().As<IProductService>();
builder.RegisterType<EfProductRepository>().As<IProductRepository>();
builder.RegisterType<CategoryManager>().As<ICategoryService>();
builder.RegisterType<EfCategoryRepository>().As<ICategoryRepository>();
builder.RegisterType<UserManager>().As<IUserService>();
builder.RegisterType<EfUserRepository>().As<IUserRepository>();
builder.RegisterType<AuthManager>().As<IAuthService>();
builder.RegisterType<JwtHelper>().As<ITokenHelper>();
}
}
}
AuthController.cs
using Entities.Dtos;
using Microsoft.AspNetCore.Mvc;
using Services.Abstract;
namespace WebAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private IAuthService _authService;
public AuthController(IAuthService authService)
{
_authService = authService;
}
[HttpPost("login")]
public async Task<IActionResult> Login(UserForLoginDto userForLoginDto)
{
var userToLogin = await _authService.Login(userForLoginDto);
if (!userToLogin.IsSuccess)
return BadRequest(userToLogin.Message);
var result = await _authService.CreateAccessToken(userToLogin.Data);
if (result.IsSuccess)
return Ok(result.Data);
return BadRequest(result.Message);
}
[HttpPost("register")]
public async Task<IActionResult> Register(UserForRegisterDto userForRegisterDto)
{
var userExists = await _authService.UserExists(userForRegisterDto.Email);
if (!userExists.IsSuccess)
return BadRequest(userExists.Message);
var registerResult = await _authService.Register(userForRegisterDto);
var result = await _authService.CreateAccessToken(registerResult.Data);
if (result.IsSuccess)
return Ok(result.Data);
return BadRequest(result.Message);
}
}
}
Program.cs
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Services.DependencyResolvers.Autofac;
using Shared.Utilities.Security.Encyption;
using Shared.Utilities.Security.Jwt;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()).ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterModule(new AutofacBusinessModule());
});
var tokenOptions = builder.Configuration.GetSection(key: "TokenOptions").Get<TokenOptions>();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = tokenOptions.Issuer,
ValidAudience = tokenOptions.Audience,
IssuerSigningKey = SecurityKeyHelper.CreateSecurityKey(tokenOptions.SecurityKey),
ValidateIssuerSigningKey = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
CreateTokenPostman.cs
enter image description here
401ErrorPostman.cs
enter image description here
Thanks. I found the error related to the latest version of JWT. The error was fixed when I downloaded the old version of JWT.
The version I used is 6.0.7:
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.7" />
I have compared your code with this repo. And I find something, it maybe useful to you.
You should change your code like below.
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = tokenOptions.Issuer,
ValidAudience = tokenOptions.Audience,
IssuerSigningKey = SecurityKeyHelper.CreateSecurityKey(tokenOptions.SecurityKey),
ValidateIssuerSigningKey = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
//ClockSkew = TimeSpan.Zero
};
Or
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = tokenOptions.Issuer,
ValidAudience = tokenOptions.Audience,
IssuerSigningKey = SecurityKeyHelper.CreateSecurityKey(tokenOptions.SecurityKey),
ValidateIssuerSigningKey = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
// add this line
RequireExpirationTime = true
};

how to solve 401 unauthorized error in postman when calling a .Net API

I have a .net core webapi working fine and tested with swagger, also the method has set to allow anonymous access so no authentication should be required. But when testing the POST method with Postman, I always get the 401 error.. Appreciate any help!
Since I am not clear about your specific code implementation, I wrote a demo here, which is an example of generating token from user login to access permission API. You can refer to it, maybe it will help you a little:
First,open the appsettings.json file and change the section named Jwt:
"Jwt": {
"Issuer": "testUser",
"Audience": "user",
"Key": "this is my custom Secret key for authnetication"
}
Enable the JWT authentication scheme and swagger authorization configuration when the configuration starts, the entire code is as follows:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using WebApplication129.Controllers.conf;
namespace WebApplication129
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//services.AddSingleton<IConfiguration>(Configuration);
services.AddAuthentication(opt => {
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
services.AddControllers(options =>
{
options.Conventions.Add(new GroupingByNamespaceConvention());
});
services.AddSwaggerGen(config =>
{
config.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description =
"JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat="JWT"
});
config.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
var titleBase = "Test API";
var description = "This is a Web API for Test operations";
var TermsOfService = new Uri("https://xxxxxx");
var License = new OpenApiLicense()
{
Name = "MIT"
};
var Contact = new OpenApiContact()
{
Name = "Test",
Email = "Test#hotmail.com",
Url = new Uri("https://xxxxxx")
};
config.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = titleBase + " v1",
Description = description,
TermsOfService = TermsOfService,
License = License,
Contact = Contact
});
config.SwaggerDoc("v2", new OpenApiInfo
{
Version = "v2",
Title = titleBase + " v2",
Description = description,
TermsOfService = TermsOfService,
License = License,
Contact = Contact
});
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
config.IncludeXmlComments(xmlPath);
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(config =>
{
config.SwaggerEndpoint("/swagger/v1/swagger.json", "Test v1");
//config.SwaggerEndpoint("/swagger/v2/swagger.json", "Test v2");
});
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Log in and generate the jwt part as follows. Since I did not use it with a database, I customized a user:
Model:
public class Usuario
{
public string NomeUsuario { get; set; }
public string Senha { get; set; }
}
Controller:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WebApplication129.Model;
namespace WebApplication129.Controllers.V1
{
[Route("api/[controller]")]
[ApiController]
public class SegurancaController : Controller
{
private IConfiguration _config;
public SegurancaController(IConfiguration Configuration)
{
_config = Configuration;
}
[HttpPost]
[Route("login")]
public IActionResult Login([FromBody] Usuario loginDetalhes)
{
bool resultado = ValidarUsuario(loginDetalhes);
if (resultado)
{
var tokenString = GerarTokenJWT();
return Ok(new { token = tokenString });
}
else
{
return Unauthorized();
}
}
private string GerarTokenJWT()
{
var issuer = _config["Jwt:Issuer"];
var audience = _config["Jwt:Audience"];
var expiry = DateTime.Now.AddMinutes(120);
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(issuer: issuer, audience: audience,
expires: expiry, signingCredentials: credentials);
var tokenHandler = new JwtSecurityTokenHandler();
var stringToken = tokenHandler.WriteToken(token);
return stringToken;
}
private bool ValidarUsuario(Usuario loginDetalhes)
{
if (loginDetalhes.NomeUsuario == "TestName" && loginDetalhes.Senha == "TestPwd")
{
return true;
}
else
{
return false;
}
}
}
}
Test and verify API:
[ApiController]
[Route("api/v1/[controller]")]
public class HomeController : ControllerBase
{
/// <summary>
/// Add the description information you need
/// </summary>
///
///
[Route("test")]
[HttpGet]
public string Test( int userID)
{
return "v1 test";
}
[Route("list_data")]
[HttpGet]
[Authorize]
public Object Data()
{
User user = new User();
user.id = 1;
user.userName = "Test";
user.email = "test#xxx.com";
user.address = "testAddress";
return user;
}
}
The above shows two APIs, one requires authorization and the other does not require authorization to access.
Resutl:
Still If anyone can't figure out the error after #Tupac answer, check that you have included proper
app.UseAuthentication();
app.UseAuthorization();
in Program.cs file

Role based Authentication: Bearer Token to Cookie using Microsoft .NetCore 3.1

I'm trying to transform.NetCore 3.1 Code from Bearer Token implementation to Cookie-based implementation Also trying to make Role-based authorization work with existing code. Can you please help me to change this code? The below code shows currently how Bearer Token is retrieved and the next part shows how role-based authorization is implemented in code.
Here is the current Bearer Token implementation.
var key = Encoding.ASCII.GetBytes(Configuration["AppSettings:Secret"]);
var signingKey = new SymmetricSecurityKey(key);
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,
ValidateLifetime = true,
IssuerSigningKey = signingKey,
ValidateIssuer = false,
ValidateAudience = false
};
});
Following annotation currently used for Role-based Authorization -
[Authorize(Roles = "1")]
[Route("api/[controller]")]
[ApiController]
public class JobLogsController : ControllerBase
{
private readonly EtpRepoContext _context;
private IJobLogsRepository _jobLogsRepository;
private IConfiguration _configuration;
public JobLogsController(EtpRepoContext context, IJobLogsRepository jobLogsRepository, IConfiguration configuration)
{
_context = context;
_jobLogsRepository = jobLogsRepository;
_configuration = configuration;
}
// GET: api/JobLogs
[HttpGet]
public async Task<ActionResult<IEnumerable<JobLog>>> GetJobLog()
{
return await _context.JobLog.ToListAsync();
}
// GET: api/JobLogs/5
[HttpGet("{id}")]
[ProducesResponseType(typeof(JobDetail), 200)]
[ProducesResponseType(typeof(string), 400)]
public IActionResult FindById([FromRoute] String id)
{
string contentStr = "";
try
{
if(id.Length >= 10)
{
contentStr = _jobLogsRepository.GetLogById(id);
}
else
{
contentStr = _jobLogsRepository.GetFileById(id);
}
var content = Newtonsoft.Json.JsonConvert.SerializeObject(new { content = contentStr });
return Ok(content);
}
catch (Exception ex)
{
return StatusCode(500, "Internal server error");
}
}
This is how the Microsoft identity model is used to claim the token.
public class ClaimsTransformer : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
ClaimsIdentity claimsIdentity = (ClaimsIdentity)principal.Identity;
// flatten realm_access because Microsoft identity model doesn't support nested claims
// by map it to Microsoft identity model, because automatic JWT bearer token mapping already processed here
if (claimsIdentity.IsAuthenticated && claimsIdentity.HasClaim((claim) => claim.Type == "identity"))
{
var realmAccessClaim = claimsIdentity.FindFirst((claim) => claim.Type == "identity");
dynamic realmAccessAsDict = JsonConvert.DeserializeObject<Object>(realmAccessClaim.Value);
string role = realmAccessAsDict.role.ToString();
claimsIdentity.AddClaim(new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", role));
//var role = realmAccessClaim.
//var realmAccessAsDict = JsonConvert.DeserializeObject<Object>(realmAccessClaim.Value);
/*if (realmAccessAsDict["role"] != null)
{
foreach (var role in realmAccessAsDict["role"])
{
claimsIdentity.AddClaim(new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", role));
}
}*/
}
return Task.FromResult(principal);
}
}
}

How can I determine why a bear token failed

Running a basic ASP.NET Core RESTful server, I'm having my endpoint secured using a JWT token that I provide on an endpoint (this is a proof of concept for the moment). When I test using Postman, I am able to authenticate properly, however, coming from a console app, getting 401, Unauthorized.
Here is what I have in a ServiceExtensions class:
public static IServiceCollection ConfigureJwtAuthentication(this IServiceCollection services,
IConfiguration config)
{
var section = config.GetSection("Jwt");
var jwtOptions = section.Get<JwtConfigOptions>();
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
//options.Authority = jwtOptions.AuthorityUrl;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtOptions.Issuer,
ValidAudience = jwtOptions.Audience,
IssuerSigningKey = jwtOptions.SymmetricSecurityKey
};
});
return services;
}
This is my JwtConfigOptions class:
public class JwtConfigOptions
{
public string Key { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public string AuthorityUrl { get; set; }
public string AudienceUrl { get; set; }
public SymmetricSecurityKey SymmetricSecurityKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Key));
}
And have the values in appsettings.json, so I'm using the same values for creating as vetting the token.
Is there any way to log why a given token is being rejected?
I was able to find an answer. I found this site which had link to a github solution that had the source code for the projects in the Microsoft.AspNetCore.Authentication.JwtBearer assembly. I attached to the JwtBearerHandler project and was able to step through the code. Turns out I encoded the bearer token incorrectly in the header. Actually had the correct code commented out the line before /redface

Is there any way to do token base auth in asp.net core application without redundant stuffs?

Anyone have a good example to do token based authorization in asp.net core without this crap like IdentityContext and other? I just want to set up settings for token generating in order to my system can generate and check token in right way and I want to manage authentication process by myself. Thanks
Having used a solution from this article go.microsoft.com/fwlink/?linkid=84547:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.Authority = Configuration["AuthOptions:Authority"];
o.RequireHttpsMetadata = false;
o.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["AuthOptions:Issuer"],
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthOptions:Key"])),
ValidateLifetime = true,
};
});
services.AddMvc();
ConfigureDependincies(services);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var configuration = new SlackConfiguration
{
WebhookUrl = new Uri("https://hooks.slack.com/services/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"),
MinLevel = LogLevel.Warning
};
loggerFactory.AddSlack(configuration, env);
// loggerFactory.AddDebug();
app.UseDefaultFiles();
app.UseDeveloperExceptionPage();
app.UseAuthentication();
//app.UseJwtBearerAuthentication(new JwtBearerOptions()
//{
// AutomaticAuthenticate = true,
// AutomaticChallenge = true,
// RequireHttpsMetadata = false,
// TokenValidationParameters = new TokenValidationParameters()
// {
// ValidIssuer = Configuration["AuthOptions:Issuer"],
// ValidateAudience = false,
// ValidateIssuerSigningKey = true,
// IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthOptions:Key"])),
// ValidateLifetime = true,
// }
//});
app.UseMvc();
}
var token = new JwtSecurityToken(
issuer: _root["AuthOptions:Issuer"],
notBefore: DateTime.UtcNow,
claims: identity.Claims,
expires: DateTime.UtcNow.Add(TimeSpan.FromMinutes(Convert.ToDouble(_root["AuthOptions:TokenLifeTime"]))),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_root["AuthOptions:Key"])), SecurityAlgorithms.HmacSha512)
);
return token;
It's working for me.
It can be done in following way
Have a token generator API endpoint (controller) i.e. http://localhost/auth/token
In this, you generate token by verifying the authenticate user (check user against store)
The generated token can be validated by providing authentication schema in ASP.NET Core pipeline. app.AddAuthentication()
Any subsequent calls to API, should have Authorization header with generated token.
This approach can be refined accordingly.
I've done this very thing. I got sick of all the third party things so I wrote my own.
You want to create tokens and provide them/ validate them through an api.
Here is an example of the api controller that creates the token initially.
[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly TokenCreatorOption _tco;
private readonly CryptoHash _ch;
public TokenController(IOptions<TokenCreatorOption> ioptTCO, IOptions<CryptoHash> ioptCH, IOptions<ConnectionStrings> ioptConn)
{
_tco = ioptTCO.Value;
_ch = ioptCH.Value;
}
[HttpPost("")]
public async Task<IActionResult> IssueToken([FromBody] CredentialUser model)
{
///if model is null, this is an incorrect format
if(model == null)
{
return BadRequest();
}
var user = GetUserFromDatabaseOrStore(model.userName, model.passWord);
if(user == null)
{
return NotFound();
}
TokenCreatorOption newTCO = _tco; ///get your initial instantiation of the TokenCreatorOption. This is set to default values based off appsettings or in configure services
newTCO.UserObject = user;
newTCO.Expiration = DateTime.UtcNow.AddMinutes(30).ToString("yyyy-MM-dd hh:mm:ss.ss tt");
///anything within the TokenCreatorOption will be hashed, anything in the token Provider is not going to be hashed (not secured), but acts as a good object to store just general things that are needed on client side.
TokenProvider _tpo = new TokenProvider();
_tpo.tco = TokenInteraction.CreateToken(newTCO, _ch.salt);
_tpo.listApp = xapp; ///put anything you wouldn't want to be hashed and claimed against outside of the object. so you always validate things inside the tco, but never exclude anything inside tco. This was a fatal flaw in tokens in the past.
///this is using messagepack to serialize, to make it smaller since this is going to be passed between every request/response. Consider zipping as well if large enough.
var serializer = MessagePackSerializer.Get<TokenProvider>();
byte[] obj = null;
using (var byteStream = new MemoryStream())
{
serializer.Pack(byteStream, _tpo);
obj = byteStream.ToArray();
}
return File(obj, "application/octet-stream");
}
TokenCreatorOption Class
public class TokenCreatorOption
{
public string Issuer { get; set; }
public UserFromThatDatabaseOrStore UserObject { get; set; }
public string Expiration { get; set; }
public string HashValue { get; set; }
}
Notice that all these objects in TokenCreatorOption are claims. Every single one is checked in the hash function.
Here is the Token Creator and the Token Validator, once a token is valid, you can reissue a new one.
TokenInteraction
public static class TokenInteraction
{
public static TokenCreatorOption CreateToken(TokenCreatorOption _tco, byte[] salt)
{
byte[] exp = Encoding.UTF8.GetBytes(_tco.Expiration);
byte[] issuer = Encoding.UTF8.GetBytes(_tco.Issuer);
byte[] user = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_tco.UserObject));
byte[] salty = salt;
IEnumerable<byte> rv = exp.Concat(issuer).Concat(user).Concat(salty);
HashAlgorithm alg = SHA512.Create();
_tco.HashValue = Convert.ToBase64String(alg.ComputeHash(rv.ToArray()));
return _tco;
}
public static bool ValidateToken(TokenCreatorOption _tco, byte[] salt)
{
byte[] exp = Encoding.UTF8.GetBytes(_tco.Expiration);
byte[] issuer = Encoding.UTF8.GetBytes(_tco.Issuer);
byte[] user = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_tco.UserObject));
byte[] salty = salt;
IEnumerable<byte> rv = exp.Concat(issuer).Concat(user).Concat(salty);
HashAlgorithm alg = SHA512.Create();
if (_tco.HashValue != Convert.ToBase64String(alg.ComputeHash(rv.ToArray())))
{
return false;
}
else
{
return true;
}
}
Notice in TokenInteraction The order of bytes added to rv needs to be in the same order when we validate the token.
Now we can have a validate controller.
[Route("api/[controller]")]
public class ValidateController : Controller
{
private readonly TokenCreatorOption _tco;
private readonly CryptoHash _ch;
public ValidateController(IOptions<TokenCreatorOption> ioptTCO, IOptions<CryptoHash> ioptCH)
{
_tco = ioptTCO.Value;
_ch = ioptCH.Value;
}
[HttpPost("")]
public async Task<IActionResult> ValidateToken([FromBody] TokenCreatorOption model)
{
if (model == null)
{
return BadRequest("Model Cannot be Null");
}
///Kick them right now if session is expired, so we don't have to do the full hashing.
if (DateTime.ParseExact(model.Expiration, "yyyy-MM-dd hh:mm:ss.ss tt", CultureInfo.InvariantCulture) < DateTime.UtcNow)
{
return BadRequest("Expired Datetime");
}
if(!TokenInteraction.ValidateToken(model, _ch.salt))
{
return Unauthorized();
}
model.Expiration = DateTime.UtcNow.AddMinutes(30).ToString("yyyy-MM-dd hh:mm:ss.ss tt");
TokenProvider _tpo = new TokenProvider();
_tpo.tco = TokenInteraction.CreateToken(model, _ch.salt);
var serializer = MessagePackSerializer.Get<TokenProvider>();
byte[] obj = null;
using (var byteStream = new MemoryStream())
{
serializer.Pack(byteStream, _tpo);
obj = byteStream.ToArray();
}
return File(obj, "application/octet-stream");
}
And of course when you are initially registering the services. Create your salt value either through a cert, or through a random crypto number generator.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddCors();
services.AddOptions();
services.AddSwaggerGen();
services.ConfigureSwaggerGen(options =>
{
options.SingleApiVersion(new Swashbuckle.Swagger.Model.Info
{
Version = "v1"
});
services.Configure<TokenCreatorOption>(myopt =>
{
myopt.Issuer = "Issuer"; //either from appsettings or manually
myopt.Expiration = null;
myopt.UserObject = null;
myopt.HashValue = "";
});
byte[] salty;
new RNGCryptoServiceProvider().GetBytes(salty = new byte[64]);
services.Configure<CryptoHash>(copt =>
{
copt.salt = (new Rfc2898DeriveBytes("Super!SecretKey!123456789!##$", salty, 1000)).GetBytes(64);
});
services.AddSingleton<IConfiguration>(Configuration);