JwtBearer asp net core get signing key from api - asp.net-core

Is it possible to configure JwtBearer from asp.net-core that it can take signing key (in my case public key) required to verify is user is authorized?
I have somehing like this:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.BackchannelHttpHandler = new HttpClientHandler();
o.MetadataAddress = "http://auth-server.local.com/api";
o.Authority = "http://localhost:5000";
o.Audience = "http://localhost:5001";
o.RequireHttpsMetadata = false;
o.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = "here should be public key exposed by my auth api server"
ValidIssuer = "http://localhost:5000",
ValidAudience = "http://loclhost:5001"
};
})
;
but my client does not call my auth api in order to obtain public key.

The code you have there just tells your applications that you want to USE JWT Tokens for authentication and what parameters to validate incoming requests (with tokens) with.
You need to setup and endpoint now to issue those tokens.. or "public key" as you put it.
Your code (notice the "mysecret" in the issuer)
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.BackchannelHttpHandler = new HttpClientHandler();
o.MetadataAddress = "http://auth-server.local.com/api";
o.Authority = "http://localhost:5000";
o.Audience = "http://localhost:5001";
o.RequireHttpsMetadata = false;
o.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = "MySecret"
ValidIssuer = "http://localhost:5000",
ValidAudience = "http://loclhost:5001"
};
})
;
Now in a controller:
public class AccountController : Controller
{
[HttpGet]
public IActionResult getKey()
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MySecret"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "http://localhost:5000",
audience: "http://localhost:5001",
expires: DateTime.Now.AddYears(10),
signingCredentials: creds);
return Json(token);
}
}
Notice how in the services.. You set the private key to "MySecret" - this tells the application that any token used with a request.. must be signed with this value. Otherwise it rejects it.
In the controller.. We create a key with the same "MySecret" and issue it at host/account/getkey
Now - just add the [Authorize] tag to any function or controller you want to protect.
[Authorize]
[HttpPost]
public IActionResult Test()
{
}
EDIT: It appears you want some kind of permanent token. Just set the expires field in the new JWTtoken() line to expire in 1000 years or whatever and publicly broadcast that to whomever you want. Albiet - this is an insecure authorization model.

Related

Call WebApi with JWT Token from Blazor WebAssembly

I am getting an unexpected Unauthorized response from an Api when using JWT in Blazor WebAssembly. Note, I am not trying to secure anything on the WebAssembly client; just the API endpoint. I have deliberately left out expiry validation.
Server
appsettings.json
{
"JwtSecurity": {
"Key": "RANDOM_KEY_MUST_NOT_BE_SHARED",
"Issuer": "https://localhost",
"Audience": "https://localhost",
"ExpiryDays": 1
}
}
Program.cs
// Service registration
builder.Services
.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = builder.Configuration["JwtSecurity:Issuer"],
ValidateAudience = true,
ValidAudience = builder.Configuration["JwtSecurity:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSecurity:Key"])),
RequireExpirationTime = false,
ValidateLifetime = false
};
});
// Configure the HTTP request pipeline.
// SignalR Compression
app.UseResponseCompression();
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
// Logs the received token
app.UseJwtTokenHandler();
//explicitly only use blazor when the path doesn't start with api
app.MapWhen(ctx => !ctx.Request.Path.StartsWithSegments("/api"), blazor =>
{
blazor.UseBlazorFrameworkFiles();
blazor.UseStaticFiles();
blazor.UseRouting();
blazor.UseEndpoints(endpoints =>
{
endpoints.MapHub<Cosmos.App.Server.Hubs.TillSiteHub>("/tradingsessionhub");
endpoints.MapFallbackToFile("index.html");
});
});
//explicitly map api endpoints only when path starts with api
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/api"), api =>
{
api.UseStaticFiles();
api.UseRequestLogging();
api.UseRouting();
api.UseAuthentication();
api.UseAuthorization();
// HAVE ALSO TRIED
// api.UseAuthentication();
// api.UseRouting();
// api.UseAuthorization();
api.UseErrorHandling();
api.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
app.Run();
This link suggests UseRouting should come before
UseAuthentication and UseAuthorisation.
This link suggests UseRouting should come between them.
Have tried both to no avail.
Token Generation on Login
Helper Class
public class JwtHelper
{
public static JwtSecurityToken GetJwtToken(
string username,
string signingKey,
string issuer,
string audience,
TimeSpan expiration,
Claim[] additionalClaims = null)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub,username),
// this guarantees the token is unique
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
if (additionalClaims is object)
{
var claimList = new List<Claim>(claims);
claimList.AddRange(additionalClaims);
claims = claimList.ToArray();
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
return new JwtSecurityToken(
issuer: issuer,
audience: audience,
expires: DateTime.UtcNow.Add(expiration),
claims: claims,
signingCredentials: creds
);
}
}
Controller Method for Login
Guid userGid = await loginManager.LoginAsync(request.Email!, request.Password!);
if (userGid == default)
{
return base.NotFound();
}
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, userGid.ToString()));
claims.Add(new Claim(ClaimTypes.Name, userGid.ToString()));
string key = configuration["JwtSecurity:Key"];
string issuer = configuration["JwtSecurity:Issuer"];
string audience = configuration["JwtSecurity:Audience"];
string expiryDays = configuration["JwtSecurity:ExpiryDays"];
TimeSpan expiry = TimeSpan.FromDays(Convert.ToInt32(expiryDays));
var token = JwtHelper.GetJwtToken(
userGid.ToString(),
key,
issuer,
audience,
expiry,
claims.ToArray());
LoginResponse response = new(new JwtSecurityTokenHandler().WriteToken(token));
return base.Ok(response);
The token string response is stored in Local Storage.
Client
Adding header to Http Client
// GetTokenAsync retrieve the string from Local Storage
_httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"bearer",
(await _accessControlStateManager.GetTokenAsync())!.Write());
Server Logging of Received Token by middleware
public class JwtTokenHandlerMiddleware
{
readonly RequestDelegate _next;
readonly ILogger _logger;
public JwtTokenHandlerMiddleware(
RequestDelegate next,
ILoggerFactory loggerFactory)
{
_next = next;
_logger = loggerFactory.CreateLogger(typeof(JwtTokenHandlerMiddleware).FullName!);
}
public async Task Invoke(HttpContext context)
{
JwtSecurityToken? jwt = context.GetJwtTokenFromAuthorizationHeader();
if (jwt != null)
{
_logger.LogInformation("Request received with token: {uri}", context.Request.GetDisplayUrl());
_logger.LogInformation("Token: {token}", jwt.Write());
}
else
{
_logger.LogInformation("Request received from ANONYMOUS: {uri}", context.Request.GetDisplayUrl());
}
await _next(context);
}
}
public static JwtSecurityToken? GetJwtTokenFromAuthorizationHeader(this HttpContext httpContext)
{
string text = httpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (string.IsNullOrWhiteSpace(text))
{
return null;
}
return new JwtSecurityTokenHandler().ReadJwtToken(text);
}
I have confirmed from the logs that the JWT is being received
Using jwt.io I can confirm that the token logged in the request middleware can be read
jwt.io output
Given all of the above, it seems that:
I am generating JWT correcly.
JWT is being stored and retrieved correctly from Local Storage
JWT is being received in the Authorization header in the request
The controller is secured at Controller level using Authorize
attribute (no roles mentioned)
But still I get Unauthorized.
Any advice?
Attempts to Isolate
Disable validation parameters
Tried:
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = false,
RequireExpirationTime = false,
ValidateLifetime = false
};
And added simple test controller:
using Microsoft.AspNetCore.Authorization;
namespace Cosmos.App.Server.Controllers;
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class TestController : ControllerBase
{
[HttpGet]
[Route("test")]
public async Task<IActionResult> TestAsync()
{
await Task.Delay(1);
return Ok("Hello");
}
}
But same issue.
Found it!
The issue was that I was caching the returned string from login as a Token so I could quickly access claims in the client (whilst saving the string received from login in LocalStorage).
Then, when putting the token on the HttpClient, if I had a cached token, I was writing it out to string to populate the Authorization on the Http Request.
The problem is that the string received from the initial login, e.g.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5NGU2YWZhNy00NGYwLTRlNTUtODgxMy0xMTRmNGY1OWE2NzIiLCJqdGkiOiI2MWYwYTRiMi0wNjQwLTRiMjgtYmM2Mi0zMDZlYTVmYmJiM2UiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6Ijk0ZTZhZmE3LTQ0ZjAtNGU1NS04ODEzLTExNGY0ZjU5YTY3MiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiI5NGU2YWZhNy00NGYwLTRlNTUtODgxMy0xMTRmNGY1OWE2NzIiLCJleHAiOjE2NjM5NjEwMjMsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0IiwiYXVkIjoiaHR0cHM6Ly9sb2NhbGhvc3QifQ.5l9LRYIx3wXruW7BMa1DDbEoltVgP6Fbfkc2O03XAAY
was truncated when reading back into a token for caching. It appears to have truncated what I presume is the signing key. The following was missing:
5l9LRYIx3wXruW7BMa1DDbEoltVgP6Fbfkc2O03XAAY
This meant I had the full string stored in local storage but a cached token without the signing key.
When then used the cached token to write to string for the authorization header I got:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5NGU2YWZhNy00NGYwLTRlNTUtODgxMy0xMTRmNGY1OWE2NzIiLCJqdGkiOiI2MWYwYTRiMi0wNjQwLTRiMjgtYmM2Mi0zMDZlYTVmYmJiM2UiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6Ijk0ZTZhZmE3LTQ0ZjAtNGU1NS04ODEzLTExNGY0ZjU5YTY3MiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiI5NGU2YWZhNy00NGYwLTRlNTUtODgxMy0xMTRmNGY1OWE2NzIiLCJleHAiOjE2NjM5NjEwMjMsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0IiwiYXVkIjoiaHR0cHM6Ly9sb2NhbGhvc3QifQ.
without the suffix of the signing key.
This meant that the validation of that string failed authorization on the server, even though jwt.io would happily read it.
Using the full string that I'd stored in Local Storage instead of a 'written' string from the cached token solved the problem.

Create custom .well-known\openid-configuration in .net core

I'm new in JWT authentification, so maybe what i want to do is wrong.
I'm using asymmetric RSA key pair to sign and validate JWT.
In my startup.cs I've :
public void ConfigureServices(IServiceCollection services) {
services.AddControllers();
services.AddSingleton<RsaSecurityKey>(provider => {
RSA rsa = RSA.Create();
rsa.ImportRSAPublicKey(
source: Convert.FromBase64String(configuration["Jwt:PublicKey"]),
bytesRead: out int _
);
return new RsaSecurityKey(rsa);
});
services.AddAuthentication()
.AddJwtBearer("Asymmetric", options => {
SecurityKey rsa = services.BuildServiceProvider().GetRequiredService<RsaSecurityKey>();
options.TokenValidationParameters = new TokenValidationParameters {
IssuerSigningKey = rsa,
ValidAudience = "audience-test",
ValidIssuer = "test-issuer",
RequireSignedTokens = true,
RequireExpirationTime = true,
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
};
});
}
To generate my token I've in my controller :
[HttpPost]
[Route("Asymmetric")]
public IActionResult GenerateTokenAsymmetric()
{
using RSA rsa = RSA.Create();
rsa.ImportRSAPrivateKey(
source: Convert.FromBase64String(_configuration["Jwt:PrivateKey"]),
bytesRead: out int _);
var signingCredentials = new SigningCredentials(
key: new RsaSecurityKey(rsa),
algorithm: SecurityAlgorithms.RsaSha256
);
DateTime jwtDate = DateTime.Now;
var jwt = new JwtSecurityToken(
audience: "test-audience",
issuer: "test-issuer",
claims: new Claim[] { new Claim(ClaimTypes.NameIdentifier, "John") },
notBefore: jwtDate,
expires: jwtDate.AddMinutes(1),
signingCredentials: signingCredentials
);
string token = new JwtSecurityTokenHandler().WriteToken(jwt);
return Ok(new
{
jwt = token
});
}
As you see it, my public key is stored in my appsettings.
I would like to use options.MetadataAddress so that my client can download metadata about my api and retrieve public key to validate token.
My question is :
It is possible to create a custom .well-known/openid-configuration in .net core ? Or I must to use IdentityServer for example ?
Thanks for help
I don't think it is is that hard to create your own static "fake" .well-known/openid-configuration page. But at at the same time, having a dedicates token service like IdentityServer will give you advantages when your system grows. Don't forget you also need to create the JWKS endpoint as well. As the AddJwBearer handler makes requests to both.
Also you doing it all by your self also opens up potential security issues that is already fixed/solved in the existing solutions.

User.Identity.Name is empty with JWT when method is no decorated with Authorize in Asp.NET Core 3.0 API Controller

I have a Web Api project in .net core 3.1 and I have added JwT authentication.
The authentication and authorization work very well, but I need to get the UserId in every request. When the method is decorated with Authorize attribute, this works well.
[HttpGet]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IEnumerable<WeatherForecast> Get()
{
string user = User.Identity.Name; //Get a value
//Do something
}
However I have some method which authentication is not required, but if an authenticated user make a request, I would like to get the userId, but in this case, user.Identity.Name is always null.
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
string user = User.Identity.Name; //null
//Do somwthing
}
My configuration in statur file is:
private void ConfigureJwt(IServiceCollection services)
{
//Add Auth scheme
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
AuthSettings authSettings = Configuration.GetSection("AuthSettings").Get<AuthSettings>();
JwtIssuerOptions jwtIssuerOptions = Configuration.GetSection("JwtIssuerOptions").Get<JwtIssuerOptions>();
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 = jwtIssuerOptions.Issuer,
ValidAudience = jwtIssuerOptions.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.SecretKey))
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
//When method is no decorated with Authorize, it not working
var userId = int.Parse(context.Principal.Identity.Name);
return System.Threading.Tasks.Task.CompletedTask;
}
};
});
services.AddTransient<ITokenService, TokenService>(x =>
{
return new TokenService(Configuration);
});
}
TokenService class:
public class TokenService : ITokenService
{
IConfiguration configuration = null;
AuthSettings authSettings = null;
public TokenService(IConfiguration _configuration)
{
configuration = _configuration;
authSettings = configuration.GetSection("AuthSettings").Get<AuthSettings>();
}
public string GenerateAccessToken(IEnumerable<Claim> claims, ref JwtIssuerOptions jwtIssuerOptions)
{
//var authSettings = configuration.GetSection(nameof(AuthSettings));
//var authSettings = configuration.GetSection("EmailSettings").Get<AuthSettings>();
jwtIssuerOptions = configuration.GetSection("JwtIssuerOptions").Get<JwtIssuerOptions>();
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.SecretKey));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokeOptions = new JwtSecurityToken (
issuer: jwtIssuerOptions.Issuer,
audience: jwtIssuerOptions.Audience,
claims: claims,
expires: jwtIssuerOptions.Expiration,
//expires: DateTime.Now.AddMinutes(5),
signingCredentials: signinCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
return tokenString;
}
public string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
public ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
{
TokenValidationParameters tokenValidationParameters = GetValidationParameters();
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
var jwtSecurityToken = securityToken as JwtSecurityToken;
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
return principal;
}
private TokenValidationParameters GetValidationParameters()
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false, //you might want to validate the audience and issuer depending on your use case
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.SecretKey)),
ValidateLifetime = false //here we are saying that we don't care about the token's expiration date
};
return tokenValidationParameters;
}
}
AuthController
[HttpPost, Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel loginModel)
{
if (loginModel == null)
return BadRequest("Invalid client request");
var sessionInfo = await userBo.LoginUser(loginModel);
if (sessionInfo == null)
return Unauthorized();
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, sessionInfo.User.BusinessEntityId.ToString()),
new Claim(ClaimTypes.Role, sessionInfo.User.RoleCode)
};
JwtIssuerOptions tokeOptions = null;
var accessToken = tokenService.GenerateAccessToken(claims, ref tokeOptions);
var refreshToken = tokenService.GenerateRefreshToken();
await tokenBo.SaveToken(
new Token()
{
BusinessEntityId = sessionInfo.Person.BusinessEntityId,
RefreshToken = refreshToken,
RefreshTokenExpiryTime = tokeOptions.Expiration
}
);
sessionInfo.TokenInfo = new TokenInfo()
{
AccessToken = accessToken,
RefreshToken = refreshToken
};
return Ok(sessionInfo);
}
}
Thank you for your help!
As far as I know, if the controller doesn't need authorize, it will not add the user information into pipeline claims, so the user name is always null.
To solve this issue, I suggest you could try to add a custom middleware to check if the request contains the Authorization header. If it contains you could get the username and add it into http context item.
Then you could directly get the username in the api controller instead of getting it from User.Identity.Name.
More details, you could refer to below codes:
Add below middleware into startup.cs Configure method:
app.Use(async (context, next) =>
{
// you could get from token or get from session.
string token = context.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(token))
{
var tok = token.Replace("Bearer ", "");
var jwttoken = new JwtSecurityTokenHandler().ReadJwtToken(tok);
var jti = jwttoken.Claims.First(claim => claim.Type == ClaimTypes.Name).Value;
context.Items.Add("Username", jti);
}
await next();
});
Controller get the username:
object value;
ControllerContext.HttpContext.Items.TryGetValue("Username", out value);
var username = value.ToString();
Result:
After changing an application from using cookie-based authentication to using JWT I ran into this problem. You can work around it — sort of — by creating an authorization handler with no requirements thus allowing anonymous users access. The ASP.NET pipeline doesn't know which requirements will be required so it will provide the credentials of the user if they are present in the request. The end result is that anonymous users are allowed but if credentials are provided they will be available.
The trivial requirement:
class RequireNothing : IAuthorizationRequirement { }
And the handler:
class RequireNothingHandler : AuthorizationHandler<RequireNothing>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequireNothing requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
If the request contains credentials they will become available in the User object but the requirement also allow anonymous users access.
To use the requirement you can create a policy (and also add the handler to the DI container):
services
.AddAuthorization(options => options
.AddPolicy("AlsoAllowAnonymous", policy => policy
.AddRequirements(new RequireNothing())))
.AddSingleton<IAuthorizationHandler, RequireNothingHandler>();
To combine authenticated and anonymous access you decorate the action or controller with the attribute:
[Authorize(Policy = "AlsoAllowAnonymous")]
Unfortunately, this might not work so well. If you are using a long-lived JWT refresh tokens and short-lived access tokens that are refreshed when a 401 challenge is received there will be no challenge after the access token expires and the user will access the end-point anonymously possibly resulting in a degraded user experience even though the user has authenticated and has a refresh token to prove that.
This problem is not unique to using an authorization handler and you get more control by providing two different end-points: one for anonymous users and another one for authenticated users. You need some extra logic on the client side to select the correct API for things to work out right though.

Multiple JWT bearer authentication in .net core 2.1 - Claims issue

Project: .net core 2.1 APIs
In my project I have a requirement to include 2 JWT bearer authentication.
a) We create token JWT internally and use it for authentication
b) We get JWT token from external third party and need to get this authenticated as well.
I tried following code in start up:
services.AddAuthentication( )
.AddJwtBearer("InteralBearer", options =>
{
SymmetricSecurityKey key = TokenGenerator.GenerateKey();
options.Audience = "***************";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "***************",
ValidateAudience = true,
ValidAudience = "***************",
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateLifetime = true
};
})
.AddJwtBearer("ExternalBearer", options =>
{
options.Audience = "***************";
options.Authority = "***************";
});
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("ExternalBearer", "InteralBearer")
.Build();
options.AddPolicy("Applicant", new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("ExternalBearer", "InteralBearer")
.RequireClaim("role", "Applicant")
.Build());
});
In my controller I have:
[ApiController]
[Authorize(Policy = "Applicant")]
public class ApplicantController : ApplicantAbstract
{
}
I also have custom autorization filter:
public class SelfAuthorizationFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
ClaimsPrincipal principal = context.HttpContext.User;
........
}
}
When I above set up, issue is, context.HttpContext.User does not return any claims as part of "Identity" object in the request. I am expecting "Claims" object to have different claims which is already configured.
Every thing works fine if I have either "InternalBearer" or "ExternalBearer", but not both.
What am I doing wrong here?

ASP.Net Core - JWT Authentication with WebAPI and MVC Frontend not working

The Project consists of two Parts:
ASP.Net Core API
ASP.Net Core MVC Frontend
Basically, what I want to do is authentication via JWT. So the API issues JWT and the MVC Frontend uses Identity with the claims and roles declared in the JWT.
Startup.cs in the API:
private const string SecretKey = "my_Secret_Key";
private readonly SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
#region JWT Auth
// jwt wire up
// Get options from app settings
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
// Configure JwtIssuerOptions
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(configureOptions =>
{
configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
});
// api user claim policy
services.AddAuthorization(options =>
{
options.AddPolicy(Constants.Policies.ApiAccess, policy => policy.RequireClaim(Constants.JwtClaimIdentifiers.Rol, Constants.JwtClaims.ApiAccess));
});
#endregion
JWT Generation:
public async Task<string> GenerateEncodedToken(string userName)
{
User user = _userManager.GetUserByUserName(userName);
List<string> userRoles = _userManager.GetRoles(user.Guid);
var claimsToEncode = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userName),
new Claim("web", user.WebId),
new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
new Claim(Constants.JwtClaimIdentifiers.Rol,Constants.JwtClaims.ApiAccess),
};
// Create the JWT security token and encode it.
var jwt = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claimsToEncode,
notBefore: _jwtOptions.NotBefore,
expires: _jwtOptions.Expiration,
signingCredentials: _jwtOptions.SigningCredentials);
jwt.Payload.Add("roles", userRoles.ToArray());
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
return encodedJwt;
}
Authorization works like a charm with this in the API.
Now I want to do the following:
Implement the same in the Frontend, so that:
MVC Frontend receives Credentials, send them to the API, get Token, and Authorize with the Claims and Roles in the Token.
I tried several things, but none of them worked so far.
What do I have to insert in the Startup.cs in the Frontend so that Identity checks not against the secret key (which the Frontend is not allowed to have) but against a public key? Or do I have to implement a Endpoint in the API which validates the JWT remotely?
When you get the token in web client, you can store it in a session object and send that whenever you are requesting something from the webapi