I wrote a small middleware code (asp.net core v2.2 + c#) that run AFTER a call to server is executed and run some logic if the user is authenticated. Since it's WebAPI - the authentication is done by using Bearer token.
This how the middleware looks like:
public class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
await _next(httpContext).ConfigureAwait(false); // calling next middleware
if (httpContext.User.Identity.IsAuthenticated) // <==================== Allways false
{
// Do my logics
}
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
The problem is that the expression httpContext.User.Identity.IsAuthenticated allways return false, even if the request successfully authenticated with the service.
My Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ...
app.UseAuthentication();
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
// Formatting numbers, dates, etc.
SupportedCultures = new[] { new CultureInfo("en-US") },
// UI strings that we have localized.
SupportedUICultures = supportedCultures,
});
app.UseMvc();
app.UseMyMiddleware(ConfigurationManager.ApplicationName);
}
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthentication().AddJwtBearer(options =>
{
// ...
});
}
I also checked that the httpContext.Request object contain the Authorization header and it does.
Why the httpContext.User object seems like the request is unauthorized?
Here is a simple demo like below:
1.Generate the token:
[Route("api/[controller]")]
[ApiController]
public class LoginController : Controller
{
private IConfiguration _config;
public LoginController(IConfiguration config)
{
_config = config;
}
[AllowAnonymous]
[HttpPost]
public IActionResult Login([FromBody]UserModel login)
{
IActionResult response = Unauthorized();
var user = AuthenticateUser(login);
if (user != null)
{
var tokenString = GenerateJSONWebToken(user);
response = Ok(new { token = tokenString });
}
return response;
}
private string GenerateJSONWebToken(UserModel userInfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new List<Claim>{
new Claim(JwtRegisteredClaimNames.Sub, userInfo.Username),
new Claim(JwtRegisteredClaimNames.Email, userInfo.EmailAddress),
new Claim("DateOfJoing", userInfo.DateOfJoing.ToString("yyyy-MM-dd")),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
private UserModel AuthenticateUser(UserModel login)
{
UserModel user = null;
//Validate the User Credentials
//Demo Purpose, I have Passed HardCoded User Information
if (login.Username == "Jignesh")
{
user = new UserModel { Username = "Jignesh Trivedi", EmailAddress = "test.btest#gmail.com" };
}
return user;
}
}
2.Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//...
app.UseMyMiddleware();
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseMvc();
}
3.custom MyMiddleware(the same as yours)
4.Authorize api:
[HttpGet]
[Authorize]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "High Time1", "High Time2", "High Time3", "High Time4", "High Time5" };
}
5.Result:
Related
I have been trying to fix this all day. I am making a test API to practice my development. I tried adding a bearer authentication error and now none of the methods work.
namespace WebApiTest
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddConsole();
});
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT Authorization header. \r\n\r\n Enter the token in the text input below."
});
c.OperationFilter<AddAuthorizationHeaderParameterOperationFilter>();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
{
try
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Web API Test");
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while configuring the application.");
throw;
}
}
}
public class AddAuthorizationHeaderParameterOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var authAttributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
.Union(context.MethodInfo.GetCustomAttributes(true))
.OfType<AuthorizeAttribute>();
if (authAttributes.Any())
{
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" },
},
new string[] {}
}
}
};
}
}
}
}
namespace AzureWebApiTest.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MainController : ControllerBase
{
private readonly LoginRequest loginInformation = new LoginRequest("username", "password");
[HttpPost("GetToken")]
public IActionResult GetToken([FromBody] LoginRequest loginRequest)
{
if (loginRequest == null)
{
return BadRequest("Bad Login Request");
}
if (loginRequest.Equals(loginInformation))
{
var token = GenerateBearerToken(loginRequest);
return Ok(new { token });
}
else
{
return Unauthorized("Incorrect Login Information");
}
}
[HttpGet("GetHello")]
[Authorize(AuthenticationSchemes = "Bearer")]
public IActionResult GetHello([FromQuery] string name)
{
try
{
var token = Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
var tokenHandler = new JwtSecurityTokenHandler();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("secretKey"))
};
var claimsPrincipal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
return Ok("Hello " + name);
}
catch (SecurityTokenExpiredException)
{
return Unauthorized("Token has expired.");
}
catch (SecurityTokenInvalidSignatureException)
{
return Unauthorized("Invalid token signature.");
}
catch (Exception)
{
return Unauthorized("Invalid token.");
}
}
private string GenerateBearerToken(LoginRequest loginRequest)
{
if (ValidateCredentials(loginRequest))
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("secretKey");
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
return null;
}
private bool ValidateCredentials(LoginRequest loginRequest)
{
if(loginRequest.Equals(loginInformation))
{
return true;
}
return false;
}
}
}
When I try the method in swagger, I am getting the response:
I've tried getting ChatGPT to fix it but I'm getting nowhere and it's going in circles. Anyone have any ideas?
Edit:
The Validate function returns false at the end, I changed it for testing purposes. Edited back.
You are trying to get authorization to work, but you lack a few things.
Authentication
services.AddAuthentication(..options..).AddJwtBearer(..options..)
You need to add authentication to the pipeline, by adding UseAuthentication() before UseAuthorization(), like:
app.UseAuthentication();
app.UseAuthorization();
You need to add / register the authorization service using
services.AddAuthorization(..options..);
I have a web API that allows registration, login and retrieval of an entity, students data in this case. I added Jwt token based authentication that issues a Jwt on successful login. The problem is I keep getting 401 Unauthorized either or not I included the token in my request to the server. I saw a similar issue here but that doesn't work for me.
The Controller class
[ApiController]
[Route("api/student")]
public class StudentController:ControllerBase
{
IStudentRepository _student;
IMapper _mapper;
IConvertFileToByteArray _fileConverter;
IAuthenticate _authenticate;
public StudentController(IMapper mapper, IStudentRepository context,IConvertFileToByteArray converter,IAuthenticate authenticate)
{
_student = context;
_mapper = mapper;
_fileConverter = converter;
_authenticate = authenticate;
}
[HttpGet("login")]
[PasswordAttribute]
[AllowAnonymous]
public async Task<IActionResult> Login([FromQuery] LoginCredential credential)
{
Student student;
try
{
student = await _student.GetStudentAsync(credential.Username);
if (!_authenticate.AuthenticateUser(student.Password, credential.Password))
{
return Unauthorized("Incorrect Username or Password");
}
var token=_authenticate.GenerateToken(credential.Username);
return Ok(new { Token = token });
}
catch (Exception e)
{
return Unauthorized(e.Message);
}
}
[HttpGet("usrname",Name ="GetStudentUsingUsername")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<ActionResult<ReturnStudent>> Get([FromQuery]string Username)
{
Console.WriteLine("GetReq");
Student student;
IFormFile passport=null;
try
{
student = await _student.GetStudentAsync(Username);
Console.WriteLine(student.FirstName);
if (student.ByteArrayofPassport.Length>1000)
{
passport = new FormFile(new MemoryStream(student.ByteArrayofPassport), 0, student.ByteArrayofPassport.Length, "ByteArrayOfPassport", "passport");
}
ReturnStudent returnStudent = _mapper.Map<ReturnStudent>(student);
returnStudent.Passport = passport;
return Ok(returnStudent);
}
catch (Exception e)
{
return Ok(e.Message);
}
}
Token is generated through the method
private readonly byte[] secret = Encoding.ASCII.GetBytes("REDACTED");
public string GenerateToken(string username)
{
var tokenHandler=new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, username) }),
Expires =DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(secret), SecurityAlgorithms.HmacSha256)
};
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return token;
}
Service Configuration
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
/*services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "StudentManagementSystem2", Version = "v1" });
});*/
services.AddDbContext<StudentContext>();
services.AddScoped<IStudentRepository,StudentRepository>();
services.AddScoped<IConvertFileToByteArray, ConvertFileToByteArray>();
services.AddScoped<IAuthenticate, Authenticate>();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("REDACTED"))
};
});
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseSwagger();
///app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "StudentManagementSystem2 v1"));
}
//app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
I am trying to use a bearer token at present and no matter what I do, I get a HTTP 401 unauthorized error.
I am following a guide on JWT implementation I have a extension method that handles the JWT.
public static class AuthenticationExtension
{
public static IServiceCollection AddTokenAuthentication(this IServiceCollection services, IConfiguration config)
{
var secret = config.GetSection("JwtConfig").GetSection("secret").Value;
var key = Encoding.ASCII.GetBytes(secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// ValidIssuer = "localhost",
//ValidAudience = "localhost"
};
});
return services;
}
}
Also the following is the way the token controller generates the token obv I should be getting values from the header instead email and a password for example.
As that code be replaced if found in code could it not.
var token = jwt.GenerateSecurityToken("fake#email.com");
In my StartUp.cs I simply have the following to add the middle ware to my config. In the services section I conduct a test
services.AddTokenAuthentication(Configuration);
But as you see I get HTTP 401 unauthorised returned.
This is the code from the api/token controller.
[Route("api/[controller]")]
[ApiController]
public class TokenController : ControllerBase
{
private IConfiguration _config;
public TokenController(IConfiguration config)
{
_config = config;
}
[HttpGet]
public string GetRandomToken()
{
var jwt = new JwtService(_config);
var token = jwt.GenerateSecurityToken("fake#email.com");
return token;
}
}
Token from api/token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImZha2VAZW1haWwuY29tIiwibmJmIjoxNTg2NjMzMTkwLCJleHAiOjE1ODY3MTk1OTAsImlhdCI6MTU4NjYzMzE5MH0.SPSErcPpD4f48sWFYQFVUBmTaVtCW8oDw4Np6Tncozo
This is my appSettings.json config I will of course be changing my secret once I have this setup.
Is this enough to secure a api or should you also use client id and secret in terms of api layer. HMAC style.
"JwtConfig": {
"secret": "PDv7DrqznYL6nv7DrqzjnQYO9JxIsWdcjnQYL6nu0f",
"expirationInMinutes": 1440
},
In my case I also failed like you did at first, eventually I followed this tutorial and with a few modifications I had a working code.
I used user Id as identifier and here are the parts in my code related to jwt authentication:
UserService:
public class UserService : IUserService
{
...
public IDataResult<AuthenticationResponse> Authenticate(AuthenticationRequest request)
{
UserDto userDto = _mapper.Map<UserDto>(user);
AccessToken token = generateJwtToken(userDto);
return new SuccessDataResult<AuthenticationResponse>(new AuthenticationResponse(userDto, token));
}
private AccessToken generateJwtToken(UserDto userDto)
{
AuthUser authUser = _mapper.Map<AuthUser>(userDto);
return _jwtHelper.CreateToken(authUser);
}
...
}
JWTHelper:
public class JwtHelper
{
...
public AccessToken CreateToken(AuthUser user)
{
_accessTokenExpiration = DateTime.Now.AddMinutes(_tokenOptions.ExpirationInMinutes);
var securityKey = SecurityKeyHelper.CreateSecurityKey(_tokenOptions.SecurityKey);
var signingCredentials = SigningCredentialsHelper.CreateSigningCredentials(securityKey);
var jwt = CreateJwtSecurityToken(_tokenOptions, user, signingCredentials);
var token = new JwtSecurityTokenHandler().WriteToken(jwt);
return new AccessToken
{
Token = token,
Expiration = _accessTokenExpiration
};
}
private JwtSecurityToken CreateJwtSecurityToken(TokenOptions tokenOptions, AuthUser user,
SigningCredentials signingCredentials)
{
var jwt = new JwtSecurityToken(
expires: _accessTokenExpiration,
claims: SetClaims(user),
signingCredentials: signingCredentials
);
return jwt;
}
private IEnumerable<Claim> SetClaims(AuthUser user)
{
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString());
return claims;
}
}
Startup.cs
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(
options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration.GetValue<string>("TokenOptions:SecurityKey")))
};
});
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(...);
...
}
}
I am trying to access a jwt controller in my dotnet core 3.1 solution.
Here is my jwt controller:
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class JwtController : ControllerBase
{
#region Variables
private readonly IUserAuthorisationServices _tokenService;
private readonly IOptions<JwtTokenOptions> jwtOptions;
private readonly ILogger logger;
private readonly JsonSerializerSettings _serializerSettings;
#endregion
public JwtController(IUserAuthorisationServices tokenService,
IOptions<JwtTokenOptions> jwtOptions,
ILoggerFactory loggerFactory)
{
if (loggerFactory is null) throw new ArgumentNullException(nameof(loggerFactory));
_tokenService = tokenService ?? throw new ArgumentNullException(nameof(tokenService));
jwtOptions = jwtOptions ?? throw new ArgumentNullException(nameof(jwtOptions));
logger = loggerFactory.CreateLogger<JwtController>();
_serializerSettings = new JsonSerializerSettings {
Formatting = Formatting.Indented
};
//loggingRepository = _errorRepository;
//ThrowIfInvalidOptions(this.jwtOptions);
}
[AllowAnonymous]
[Route("authenticate")]
[HttpPost]
public async Task<IActionResult> Authenticate([FromBody] LoginViewModel model)
{
var userContext = _tokenService.Authenticate(model.Email, model.Password);
if (userContext.Principal == null) {
logger.LogInformation($"Invalid username ({model.Email}) or password ({model.Password})");
return BadRequest(new { message = "Username or password is incorrect" });
}
return Ok(await _tokenService.CreateTokenAsync(userContext).ConfigureAwait(false));
}
[Route("JWTStatus")]
[HttpGet]
public static IActionResult GetJwtStatus()
{
// It made it here so it was authenticated.
return new OkResult();
}
}
I have stepped my way through the process and it gets to this middleware and then cannot find the controller returning immediately and at the same time providing the browser with a 404.
This is what I am using to access this controller action:
login(email: string, password: string) {
this.userLogin.Email = email;
this.userLogin.Password = password;
// Do the fetch!
const t = fetch("/api/authenticate",
{
method: "POST",
body: JSON.stringify(this.userLogin),
headers: new Headers({ "content-type": "application/json" })
})
.then(response => {
I note the fetch is using "api/authenticate" and when I look at the HttpRequest its got the same url yet it wont find it.
Here is the middleware that it gets to before just returning.
namespace JobsLedger.AUTHORISATION.API.SessionMiddleware
{
public class ConfigureSessionMiddleware
{
private readonly RequestDelegate _next;
public ConfigureSessionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext, IUserSession userSession, ISessionServices sessionServices)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
if (userSession == null)
{
throw new ArgumentNullException(nameof(userSession));
}
if (sessionServices == null)
{
throw new ArgumentNullException(nameof(sessionServices));
}
if (httpContext.User.Identities.Any(id => id.IsAuthenticated))
{
if (httpContext.Session.GetString("connectionString") == null) // Session needs to be set..
{
userSession.UserId = httpContext.User.Claims.FirstOrDefault(x => x.Type == "userId")?.Value;
userSession.ConnectionString = sessionServices.ConnectionStringFromUserId(userSession.UserId);
httpContext.Session.SetString("userId", userSession.UserId);
httpContext.Session.SetString("connectionString", userSession.ConnectionString);
}
else // Session set so all we need to is to build userSession for data access..
{
userSession.UserId = httpContext.Session.GetString("userId");
userSession.ConnectionString = httpContext.Session.GetString("connectionString");
}
}
// Call the next delegate/middleware in the pipeline
await _next.Invoke(httpContext).ConfigureAwait(false);
}
}
}
Its suppose to jump straight past this as there are no user identities as this hasnt been set yet. So really it goes straight to:
await _next.Invoke(httpContext).ConfigureAwait(false);
Then onto the controller action.. but it just goes straight out with a 404.
In the output window I am getting:
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request starting HTTP/2.0 POST https://localhost:44301/api/authenticate text/plain;charset=UTF-8 76
Microsoft.AspNetCore.Cors.Infrastructure.CorsService: Information: CORS policy execution successful.
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request finished in 17.1363ms 404
You can see the 404.
How is it cant find the action?
Is it possibly due to it being https and http 2.0?
For completeness here is the httpContext at the point of that middleware:
UPDATE...
I do not use MVC in the startup either just endpoints...
Maybe its the pipeline in Startup.cs.. here is my startup.cs
[assembly: NeutralResourcesLanguage("en")]
namespace JobsLedger.API {
public class Startup {
//private const string SecretKey = "needtogetthisfromenvironment";
//private readonly SymmetricSecurityKey
// _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
private readonly IWebHostEnvironment _env;
private readonly IConfiguration _configuration; // { get; }
public Startup(IWebHostEnvironment env, IConfiguration configuration) {
_env = env;
_configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
// Add framework services.
services.AddCors();
services.AddControllers();
services.AddOptions();
services.AddDbContext<CATALOGContext>(options => options.UseSqlServer(_configuration.GetConnectionString("CatalogConnection"), b => b.MigrationsAssembly("JobsLedger.CATALOG")));
services.AddDbContext<DATAContext>(options => options.UseSqlServer(_configuration.GetConnectionString("TenantDbConnection"), a => a.MigrationsAssembly("JobsLedger.DATA")));
// Make authentication compulsory across the board (i.e. shut
// down EVERYTHING unless explicitly opened up).
// Use policy auth.
services.AddAuthorization(options => {
options.AddPolicy("TenantAdmin", policy => policy.RequireClaim(ClaimTypes.Role, "TenantAdmin"));
options.AddPolicy("Admin", policy => policy.RequireClaim(ClaimTypes.Role, "Admin"));
options.AddPolicy("Employee", policy => policy.RequireClaim(ClaimTypes.Role, "Employee"));
});
//services.ConfigureApplicationInjection();
// Get jwt options from app settings
var tokenOptions = _configuration.GetSection("Authentication").Get<JwtTokenOptions>();
services.AddAuthentication(x => {
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
RequireExpirationTime = true,
ValidIssuer = tokenOptions.Issuer,
ValidAudience = tokenOptions.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(tokenOptions.SigningKey)),
ClockSkew = TimeSpan.Zero
};
});
services
.AddDistributedMemoryCache()
.AddSession()
// Repositories - DATA
.AddScoped<IClientDATARepository, ClientDATARepository>()
.AddScoped<ILoggingDATARepository, LoggingDATARepository>()
.AddScoped<IJobDATARepository, JobDATARepository>()
.AddScoped<IBrandDATARepository, BrandDATARepository>()
.AddScoped<ITypeDATARepository, TypeDATARepository>()
.AddScoped<IStateDATARepository, StateDATARepository>()
.AddScoped<IStatusDATARepository, StatusDATARepository>()
.AddScoped<ISuburbDATARepository, SuburbDATARepository>()
.AddScoped<ICounterDATARepository, CounterDATARepository>()
// Repositories - CATALOG
.AddScoped<ITenantCATALOGRepository, TenantCATALOGRepository>()
.AddScoped<IUserCATALOGRepository, UserCATALOGRepository>()
.AddScoped<IRoleCATALOGRepository, RoleCATALOGRepository>()
.AddScoped<ICounterCATALOGRepository, CounterCATALOGRepository>()
.AddScoped<ISuburbCATALOGRepository, SuburbCATALOGRepository>()
.AddScoped<IStateCATALOGRepository, StateCATALOGRepository>()
// Business services
// Services - API
.AddScoped<IClientServices, ClientServices>()
.AddScoped<IJobServices, JobServices>()
//Services - Catalog
.AddScoped<ITenantServices, TenantServices>()
.AddScoped<IUserServices, UserServices>()
//.AddScoped<IUserValidateService, UserValidateService>()
// Services - Shared
.AddScoped<IAddressDropdownServices, AddressDropdownServices>()
//Services - Auth
.AddScoped<ICryptoService, CryptoService>()
.AddScoped<IUserAuthorisationServices, UserAuthorisationServices>()
.AddScoped<IUserSession, UserSession>()
.AddScoped<ISessionServices, SessionServices>()
// CATALOG services - Initialisations.
.AddScoped<ICATALOGCounterInitialiser, CATALOGCounterInitialiser>()
.AddScoped<ICATALOGStateInitialiser, CATALOGStateInitialiser>()
.AddScoped<ICATALOGSuburbInitialiser, CATALOGSuburbInitialiser>()
.AddScoped<IRoleInitialiser, CATALOGRoleInitialiser>()
.AddScoped<ICATALOGTenantAndUserInitialisations, CATALOGTenantAndUserInitialiser>()
// DATA Services - Initialisations.
.AddScoped<IBrandInitialiser, BrandInitialiser>()
.AddScoped<IDATACounterInitialiser, DATACounterInitialiser>()
.AddScoped<IDATAStateInitialiser, DATAStateInitialiser>()
.AddScoped<IDATASuburbInitialiser, DATASuburbInitialiser>()
.AddScoped<IStatusInitialiser, StatusInitialiser>()
.AddScoped<ITypeInitialiser, TypeInitialiser>()
.AddScoped<IVisitTypeInitialiser, VisitTypeInitialiser>()
// TESTDATA Services - Initialisations.
.AddScoped<ITESTDATAInitialisations, TESTDATAInitialisations>()
.AddScoped<ITESTDATATenantAndUserInitialisations, TESTDATATenantAndUserInitialisations>()
.AddTransient<IDATAContextFactory, DATAContextFactory>()
.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); // For getting the user.
}
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
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.UseExceptionHandler(options => {
options.Run(async context => {
var ex = context.Features.Get<IExceptionHandlerPathFeature>();
if (ex?.Error != null) {
Debugger.Break();
}
});
});
app.UseRouting();
// global cors policy
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseFileServer();
app.UseStaticFiles();
app.UseSession();
app.UseAuthorization();
app.UseConfigureSession();
app.EnsureCATALOGMigrationAndInitialise();
app.EnsureDATAMigrationsAndInitialise();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
}
}
Okay! got it. According to your current route configuration, your routing endpoints are:
/authenticate // <-- not api/authenticate
/JWTStatus // <-- not api/JWTStatus
Because you are overriding the controller level route in action.
If you want api/authenticate and api/JWTStatus as your routing endpoints, then your action level route should be as follows:
[Authorize]
[ApiController]
//Remove the controller level route from here
public class JwtController : ControllerBase
{
[AllowAnonymous]
[Route("api/authenticate")] // <-- Here it is
[HttpPost]
public async Task<IActionResult> Authenticate([FromBody] LoginViewModel model)
{
.........
}
[Route("api/JWTStatus")] // <-- Here it is
[HttpGet]
public IActionResult GetJwtStatus()
{
.......
}
}
But my suggestion is to follow the following convention:
[Authorize]
[ApiController]
[Route("api/[controller]/[action]")] // <-- Here it is
public class JwtController : ControllerBase
{
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> Authenticate([FromBody] LoginViewModel model)
{
.......
}
[HttpGet]
public IActionResult GetJwtStatus()
{
......
}
}
Now no action level route is required anymore and your endpoints will be:
api/Jwt/Authenticate
api/Jwt/GetJwtStatus
Your controller level route attribute is creating problem.
Both route attributes, at controller and action level, need a change.
First, remove your controller's route attribute
Then, modify action level route attributes as below
[Route("api/authenticate")]
[Route("api/JWTStatus")]
I am getting an error in net core 2.1:
Bearer was not authenticated.
Failure message: No SecurityTokenValidator available for token: null
The asp net output window is:
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[7]
Bearer was not authenticated. Failure message: No SecurityTokenValidator available for token: null
info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4]
Policy execution successful.
The accounts controller code is here:
namespace quiz_backend.Controllers
{
public class Credentials
{
public string Email { get; set; }
public string Password { get; set; }
}
[Produces("application/json")]
[Route("api/Account")]
public class AccountController : Controller
{
readonly UserManager<IdentityUser> userManager;
readonly SignInManager<IdentityUser> signInManager;
public AccountController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager)
{
this.userManager = userManager;
this.signInManager = signInManager;
}
[HttpPost]
public async Task<IActionResult> Register([FromBody] Credentials credentials)
{
var user = new IdentityUser {
UserName = credentials.Email,
Email = credentials.Email
};
var result = await userManager.CreateAsync(user, credentials.Password);
if (!result.Succeeded)
return BadRequest(result.Errors);
await signInManager.SignInAsync(user, isPersistent: false);
// create a token
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is the secret phrase"));
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
var jwt = new JwtSecurityToken(signingCredentials: signingCredentials);
return Ok(new JwtSecurityTokenHandler().WriteToken(jwt));
}
}
}
Here is the startup.cs
namespace quiz_backend
{
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.AddCors(options => options.AddPolicy("Cors", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
services.AddDbContext<QuizContext>(opt =>opt.UseInMemoryDatabase("quiz"));
services.AddDbContext<UserDbContext>(opt => opt.UseInMemoryDatabase("user"));
services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<UserDbContext>();
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is the secret phrase"));
services.AddAuthentication(options =>{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(cfg => {
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = signingKey,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true
};
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("Cors");
app.UseMvc();
}
}
}
This is the front end auth code to attach the token to the header in ts:
export class AuthInterceptor implements HttpInterceptor {
constructor() {}
intercept(req, next) {
var token = localStorage.getItem('token')
var authRequest = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
})
return next.handle(authRequest)
}
}
Based on your code, it seems that the issue is that the token received is not valid (NULL).
Failure message: No SecurityTokenValidator available for token: null
First of all, you should make sure the token arrives as expected.