InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found - c#

We have a Net Core 2.1 API project. We use the request headers to retrieve API key which we check against our database to see if it matches one of the expected keys. If it does then we allow the request to continue, otherwise we want to send back Unauthorized response.
our startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("APIKeyAuth", policyCorrectUser =>
{
policyCorrectUser.Requirements.Add(new APIKeyAuthReq());
});
});
services.AddSingleton<Microsoft.AspNetCore.Authorization.IAuthorizationHandler, APIKeyAuthHandler>();
Our APIKeyAuthHandler.cs
public class APIKeyAuthReq : IAuthorizationRequirement { }
public class APIKeyAuthHandler : AuthorizationHandler<APIKeyAuthReq>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, APIKeyAuthReq requirement)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (requirement == null)
throw new ArgumentNullException(nameof(requirement));
var httpContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
var headers = httpContext.HttpContext.Request.Headers;
if (headers.TryGetValue("Authorization", out Microsoft.Extensions.Primitives.StringValues value))
{
using (DBContext db = new DBContext ())
{
var token = value.First().Split(" ")[1];
var login = db.Login.FirstOrDefault(l => l.Apikey == token);
if (login == null)
{
context.Fail();
httpContext.HttpContext.Response.StatusCode = 403;
return Task.CompletedTask;
} else
{
httpContext.HttpContext.Items.Add("CurrentUser", login);
context.Succeed(requirement);
return Task.CompletedTask;
}
}
}
}
}
and our controller.cs
[Route("api/[controller]/[action]")]
[Authorize("APIKeyAuth")]
[ApiController]
public class SomeController : ControllerBase
{
}
Everything works fine when a valid key exists but when it doesnt, there is a 500 internal error thrown for No authenticationScheme instead of 403.
We are relatively new to net core (coming from Net Framework/Forms Authentication) so if there is more accurate way of doing this sort of auth, please let me know.
Error Message:
InvalidOperationException: No authenticationScheme was specified, and
there was no DefaultChallengeScheme found.
Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext
context, string scheme, AuthenticationProperties properties)

Token based authentication is preferred. However, if you do need a custom ApiKeyAuth scheme, well, it's possible.
Firstly, it seems that Authorize("APIKeyAuth") does not make sense here, as we have to authenticate the user before authorization. When there's an incoming request, the server has no idea who the user is. So, let's move the ApiKeyAuth from Authorization to Authentication.
To do that, just create a dummy ApiKeyAuthOpts that can be used to hold options
public class ApiKeyAuthOpts : AuthenticationSchemeOptions
{
}
and a simple ApiKeyAuthHandler to handle authentication (I just copy some of your codes above):
public class ApiKeyAuthHandler : AuthenticationHandler<ApiKeyAuthOpts>
{
public ApiKeyAuthHandler(IOptionsMonitor<ApiKeyAuthOpts> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
private const string API_TOKEN_PREFIX = "api-key";
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string token = null;
string authorization = Request.Headers["Authorization"];
if (string.IsNullOrEmpty(authorization)) {
return AuthenticateResult.NoResult();
}
if (authorization.StartsWith(API_TOKEN_PREFIX, StringComparison.OrdinalIgnoreCase)) {
token = authorization.Substring(API_TOKEN_PREFIX.Length).Trim();
}
if (string.IsNullOrEmpty(token)) {
return AuthenticateResult.NoResult();
}
// does the token match ?
bool res =false;
using (DBContext db = new DBContext()) {
var login = db.Login.FirstOrDefault(l => l.Apikey == token); // query db
res = login ==null ? false : true ;
}
if (!res) {
return AuthenticateResult.Fail($"token {API_TOKEN_PREFIX} not match");
}
else {
var id=new ClaimsIdentity(
new Claim[] { new Claim("Key", token) }, // not safe , just as an example , should custom claims on your own
Scheme.Name
);
ClaimsPrincipal principal=new ClaimsPrincipal( id);
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
}
At last, we still need a little of configuration to make them to work:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication("ApiKeyAuth")
.AddScheme<ApiKeyAuthOpts,ApiKeyAuthHandler>("ApiKeyAuth","ApiKeyAuth",opts=>{ });
}
// 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();
app.UseHttpsRedirection();
app.UseMvc();
}
When you send a request to action method protected by [Authorize]:
GET https://localhost:44366/api/values/1 HTTP/1.1
Authorization: api-key xxx_yyy_zzz
the response will be HTTP/1.1 200 OK. When you send a request without the correct key, the response will be:
HTTP/1.1 401 Unauthorized
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcOVw5LTEyXFNPLkFwaUtleUF1dGhcQXBwXEFwcFxhcGlcdmFsdWVzXDE=?=
X-Powered-By: ASP.NET
Date: Wed, 12 Sep 2018 08:33:23 GMT
Content-Length: 0

Related

Dependency Injection - There is no argument given that corresponds to the required parameter

I found a couple of sample code implementations of JWT authentication on the internet but I'm having difficulties in making these codes work together (since I got these codes from various sources).
I'm trying to add JWT authentication to the generated WeatherForecast API project (Web API) in visual studio.
Below are the changes I made to the default project:
1. Create a JWT Service class: Create a new class that will handle creating and validating JWTs. This class should have methods for creating a JWT given a set of claims, and for validating a JWT and returning the claims contained within it.
public class JwtService
{
private readonly SymmetricSecurityKey _signingKey;
public JwtService(string secretKey)
{
_signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
}
public string CreateToken(ClaimsIdentity claims)
{
var jwt = new JwtSecurityToken(
issuer: "app",
audience: "app",
claims: claims.Claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddDays(7),
signingCredentials: new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256)
);
return new JwtSecurityTokenHandler().WriteToken(jwt);
}
public ClaimsPrincipal GetPrincipalFromToken(string token)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "app",
ValidateAudience = true,
ValidAudience = "app",
ValidateLifetime = true,
IssuerSigningKey = _signingKey
};
var handler = new JwtSecurityTokenHandler();
try
{
var principal = handler.ValidateToken(token, tokenValidationParameters, out var validatedToken);
return principal;
}
catch (SecurityTokenValidationException)
{
return null;
}
}
}
2. Create a JWT Authentication Service class: Create a new class that will handle authenticating users and returning a JWT. This class should have a method for authenticating a user given a username and password and returning a JWT if the authentication is successful.
public class JwtAuthenticationService
{
private readonly JwtService _jwtService;
public JwtAuthenticationService(JwtService jwtService)
{
_jwtService = jwtService;
}
public string Authenticate(string username, string password)
{
// authenticate user
var authenticated = username == "test" && password == "test";
if (!authenticated)
return null;
// create claims
var claims = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Role, "User")
});
// create token
return _jwtService.CreateToken(claims);
}
}
3. Create a JWT Authorization filter: Create a new class that will handle checking if the request contains a valid JWT and, if it does, adding the claims contained within the JWT to the current user's claims.
public class JwtAuthorizationFilter : AuthorizationFilterAttribute
{
private readonly JwtService _jwtService;
public JwtAuthorizationFilter(JwtService jwtService)
{
_jwtService = jwtService;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
// check if the request contains an Authorization header
var authHeader = context.HttpContext.Request.Headers["Authorization"].FirstOrDefault();
if (authHeader == null || !authHeader.StartsWith("Bearer "))
{
context.Result = new UnauthorizedResult();
return;
}
// get the token from the header
var token = authHeader.Substring("Bearer ".Length).Trim();
// validate the token and get the claims
var claimsPrincipal = _jwtService.GetPrincipalFromToken(token);
if (claimsPrincipal == null)
{
context.Result = new UnauthorizedResult();
return;
}
// set the current user's claims
var identity = (ClaimsIdentity)claimsPrincipal.Identity;
var claims = identity.Claims.ToList();
var genericIdentity = new GenericIdentity(identity.Name);
genericIdentity.AddClaims(claims);
var genericPrincipal = new GenericPrincipal(genericIdentity, new string[] { identity.RoleClaimType });
context.HttpContext.User = genericPrincipal;
}
}
4. Add the filter to the WeatherForecastController: Apply the filter to the WeatherForecastController class using the [JwtAuthorizationFilter] attribute.
[ApiController]
[Route("api/[controller]")]
[JwtAuthorizationFilter]
public class WeatherForecastController : ControllerBase
{
private readonly IWeatherForecastService _weatherForecastService;
public WeatherForecastController(IWeatherForecastService weatherForecastService)
{
_weatherForecastService = weatherForecastService;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
// check if the user is authorized
if (!User.IsInRole("User"))
{
throw new UnauthorizedAccessException();
}
// return the weather forecast data
return _weatherForecastService.GetWeatherForecasts();
}
}
5. Add the JWT service and authentication service to the Dependency Injection container: In the Startup.cs class, add the JwtService and JwtAuthenticationService classes to the Dependency Injection container so that they can be injected into other classes as dependencies.**
services.AddSingleton<JwtService>(new JwtService("your_secret_key"));
services.AddSingleton<JwtAuthenticationService>();
6. Add the JWT filter to the Dependency Injection container: In the Startup.cs class, add the JwtAuthorizationFilter to the Dependency Injection container so that it can be applied to controllers as a filter.**
services.AddSingleton<JwtAuthorizationFilter>();
Here's the final Startup.cs after the changes:
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<JwtService>(new JwtService("your_secret_key"));
services.AddSingleton<JwtAuthenticationService>();
services.AddSingleton<JwtAuthorizationFilter>();
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WeatherForecast", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WeatherForecast v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
The Error:
There is no argument given that corresponds to the required parameter 'jwtService' of 'JwtAuthorizationFilter.JwtAuthorizationFilter(JwtService)'
The error is encountered at the line where I added the [JwtAuthorizationFilter] attribute on the WeatherForecastController class:
[ApiController]
[Route("[controller]")]
[JwtAuthorizationFilter] // <-- Error occurs at this line
public class WeatherForecastController : ControllerBase { ...
I understand that JwtAuthorizationFilter has a constructor that accepts an argument of JwtService type but I'm not sure how exactly to supply it in the controller or in the Startup.cs.
You can't use constructor DI like this for an attribute. Instead, get the services you require in inside the attribute. For example:
public class JwtAuthorizationFilter : AuthorizationFilterAttribute
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var jwtService = context.HttpContext.RequestServices
.GetRequiredService<JwtService>();
//etc
}
}

How to get failed AuthorizeAttribute to return HTTP401?

I'm trying to add authentication to a ASP.NET Core 3.1 web service, that looks for a specific custom request header:
[Route("api/[controller]")]
[ApiController]
public class MyController : ControllerBase
{
[HttpPut]
[Authorize(Policy = "MustSupplyAuthenticationToken")]
public async Task<ActionResult> putMyStuff()
{
// ...
I've configured "MustSupplyAuthenticationToken" to use my AuthenticationTokenRequirement class, and wired IAuthorizationHandler to use my AuthenticationTokenHandler (and wired up HttpContextFactory because I'm going to need it.):
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHttpContextAccessor();
services.AddAuthorization(options =>
{
options.AddPolicy("MustSupplyAuthenticationToken",
policy => policy.Requirements.Add(new AuthenticationTokenRequirement("MY_SECRET_TOKEN")));
});
services.AddTransient<IAuthorizationHandler, AuthenticationTokenHandler>();
}
My AuthenticationTokenRequirement is simple:
public class AuthenticationTokenRequirement : IAuthorizationRequirement
{
public readonly string authenticationToken;
public AuthenticationTokenRequirement(string authenticationToken)
{
this.authenticationToken = authenticationToken;
}
}
And my AuthenticationTokenHandler isn't much more complicated:
public class AuthenticationTokenHandler : AuthorizationHandler<AuthenticationTokenRequirement>
{
private readonly IHttpContextAccessor httpContextAccessor;
public AuthenticationTokenHandler(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
AuthenticationTokenRequirement requirement)
{
var httpContext = this.httpContextAccessor.HttpContext;
var request = httpContext.Request;
var authenticationToken = request.getRequestHeader("authenticationToken");
if (authenticationToken == null)
context.Fail();
else if (authenticationToken != requirement.authenticationToken)
context.Fail();
else
context.Succeed(requirement);
return Task.CompletedTask;
}
}
And the surprising thing is that it all works. HandleRequirementAsync() is called, and when I call context.Fail() access is denied and when I call context.Succeed() access is allowed.
The only problem is that when I call context.Fail() the response is coming back with HTTP 500 - and I need it to come back with HTTP 401. (And when I get this working, I'm going to need a different policy that returns a 403.)
Am I doing something wrong, and am getting a 500 because of some other error?
Or is a failed authentication policy is supposed return a 500?
What do I need to do to get it to return 401?
FWIW: I'm seeing this in my server logs:
2019-12-27T15:49:04.2221595-06:00 80000045-0001-f900-b63f-84710c7967bb [ERR] An unhandled exception has occurred while executing the request. (48a46595)
System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).
at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.<Invoke>g__AwaitMatcher|8_0(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task`1 matcherTask)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
This could be why I'm seeing 500.
But only when I call context.Fail(). When I call context.Succeed() I don't.
So why am I getting "No authenticationScheme was specified" when I fail the requirement?
OK, for what I'm trying to do policies are not at all the correct approach.
What I need to do is to configure an authentication scheme.
After pulling out all of the authorization policy stuff above, I added this.
In the AuthorizeAttribute, I specify an authentication scheme:
[HttpPut]
[Authorize(AuthenticationSchemes = "AuthenticationTokenScheme")]
public async Task<ActionResult> putTicketDeliveryModel()
{
// ...
In ConfigureServices, I add the scheme:
services.AddAuthentication()
.AddScheme<AuthenticationTokenOptions, AuthenticationTokenHandler>("AuthenticationTokenScheme", _ => { });
And then I implement my AuthenticationTokenOptions and AuthenticationTokenHandler classes:
public class AuthenticationTokenOptions : AuthenticationSchemeOptions
{
}
public class AuthenticationTokenHandler : AuthenticationHandler<AuthenticationTokenOptions>
{
private readonly string expectedAuthenticationToken;
public AuthenticationTokenHandler(IOptionsMonitor<AuthenticationTokenOptions> optionsMonitor,
ILoggerFactory loggerFactory, UrlEncoder urlEncoder, ISystemClock systemClock,
IConfiguration config)
: base(optionsMonitor, loggerFactory, urlEncoder, systemClock)
{
this.expectedAuthenticationToken = config.GetSection("ExpectedAuthenticationToken").Get<string>();
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var authenticationToken = this.Request.getRequestHeader("authenticationToken");
if (string.IsNullOrWhiteSpace(authenticationToken))
return Task.FromResult(AuthenticateResult.NoResult());
if (this.expectedAuthenticationToken != authenticationToken)
return Task.FromResult(AuthenticateResult.Fail("Unknown Client"));
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(Enumerable.Empty<Claim>(), Scheme.Name));
var authenticationTicket = new AuthenticationTicket(claimsPrincipal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(authenticationTicket));
}
}
This returns 401, if I pass the wrong token.
Still haven't figured out how to return 403.

Asp.net Core action does not return 401 when using Authorize attribute on a thread without a principle?

I wanted to add a principle onto the thread by myself , without using the Identity mechanism which really reminds me the old membership/forms authentication mechanics.
So I've managed (successfully) to create a request with principle :
MyAuthMiddleware.cs
public class MyAuthMiddleware
{
private readonly RequestDelegate _next;
public MyAuthMiddleware(RequestDelegate next )
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var claims = new List<Claim>
{
new Claim("userId", "22222222")
};
ClaimsIdentity userIdentity = new ClaimsIdentity(claims ,"MyAuthenticationType");
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
httpContext.User = principal;
await _next(httpContext);
}
}
The Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//app.UseAuthentication(); //removed it. I will set the thread manually
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseMyAuthMiddleware(); // <---------- Myne
app.UseMvc();
app.Run(async context => { await context.Response.WriteAsync("Hello World!"); });
}
And here is the Action in the Controller: (Notice Authorize attribute)
[HttpGet]
[Route("data")]
[Authorize]
public IActionResult GetData()
{
var a=User.Claims.First(f => f.Type == "userId");
return new JsonResult(new List<string> {"a", "b",a.ToString() , User.Identity.AuthenticationType});
}
Ok Let's try calling this method, Please notice that this does work :
So where is the problem 😊?
Please notice that there is an [Authorize] attribute. Now let's remove
setting principle on the thread ( by removing this line ) :
//httpContext.User = principal; // line is remarked
But now when I navigate to :
http://localhost:5330/api/cities/data
I'm being redirected to :
http://localhost:5330/Account/Login?ReturnUrl=%2Fapi%2Fcities%2Fdata
But I'm expecting to see Unauthorized error.
I'm after WebApi alike responses. This is not a website but an API.
Question:
Why don't I see the Unauthorized error ? And how can I make it appear?
Nb here is my ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication( CookieAuthenticationDefaults.AuthenticationScheme )
.AddCookie( CookieAuthenticationDefaults.AuthenticationScheme, a =>
{
a.LoginPath = "";
a.Cookie.Name = "myCookie";
});
services.AddMvc();
}
EDIT
Currently What I've managed to do is to use OnRedirectionToLogin :
But it will be really disappointing if that's the way to go. I'm expecting it to be like webapi.
The default implementation of the OnRedirectToLogin delegate looks like this:
public Func<RedirectContext<CookieAuthenticationOptions>, Task> OnRedirectToLogin { get; set; } = context =>
{
if (IsAjaxRequest(context.Request))
{
context.Response.Headers["Location"] = context.RedirectUri;
context.Response.StatusCode = 401;
}
else
{
context.Response.Redirect(context.RedirectUri);
}
return Task.CompletedTask;
};
As is clear from the code above, the response that gets sent to the client is dependent upon the result of IsAjaxRequest(...), which itself looks like this:
private static bool IsAjaxRequest(HttpRequest request)
{
return string.Equals(request.Query["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal) ||
string.Equals(request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal);
}
This means that the response will be a 401 redirect if either a X-Requested-With request header or query-string value is set to XMLHttpRequest. When you hit your endpoint directly from the browser or from within e.g. Fiddler, this value is not set and so the response is a 302, as observed. Otherwise, when using XHR or Fetch in the browser, this value gets set for you as a header and so a 401 is returned.

Caching Claims in .net core 2.0

Looked up everywhere but looks like I am stuck right now. I am using Windows Active Directory in my application for authentication.
For authorization, I am using claims. After searching through the limited .net core documentation, this is how my code looks like.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IPrincipal>(
provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);
services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
}
ClaimsTransformer.cs
class ClaimsTransformer : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
// call to database to get more claims based on user id ClaimsIdentity.Name
((ClaimsIdentity)principal.Identity).AddClaim(new Claim("now",DateTime.Now.ToString()));
return Task.FromResult(principal);
}
}
But the problem is, this code is called with every request and claims are loaded from the db every time which is absolutely wrong. Is there any way I can cache it? I was able to create a cookie of claims and use that cookie for any further calls in .net 4.0. I can't seem to find a way in the core. Any documentation I check, is incomplete or it does not cover my scenario. I am able to claims further in my application just how the documentation says here: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/claims
But there is no mention about caching the claims.
Anyone in the same boat? Or knows the way out of it?
You can inject the IMemoryCache service in your ClaimsTransformer constructor.
using Microsoft.Extensions.Caching.Memory;
public class ClaimsTransformer : IClaimsTransformation
{
private readonly IMemoryCache _cache;
public ClaimsTransformer(IMemoryCache cache)
{
_cache = cache;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var cacheKey = principal.FindFirstValue(ClaimTypes.NameIdentifier);
if (_cache.TryGetValue(cacheKey, out List<Claim> claims)
{
((ClaimsIdentity)principal.Identity).AddClaims(claims);
}
else
{
claims = new List<Claim>();
// call to database to get more claims based on user id ClaimsIdentity.Name
_cache.Set(cacheKey, claims);
}
return principal;
}
}
I am not doing the exact same thing, but I am using cookie Authentication/Authorization. Most of what I learned comes from this microsoft doc but as you said the documentation doesn't seem to take you all the way there. Here is what is working for me:
in startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthentication("tanushCookie")
.AddCookie("tanushCookie", options => {
options.AccessDeniedPath = "/api/Auth/Forbidden";
options.LoginPath = "/";
options.Cookie.Expiration = new TimeSpan(7,0,0,0);
});
}
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
...
app.UseAuthentication();
}
And then in your controller that handles authentication:
[HttpPost()]
[Route("api/[Controller]/[Action]/")]
public async Task<JsonResult> Login([FromBody]Dictionary<string, string> loginData)
{
try
{
var loggedIn = true;
if (loggedIn)
{
var claims = new List<Claim> {
new Claim(ClaimTypes.Name, "tanush")
};
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaims(claims);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(
"tanushCookie",
principal,
new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddDays(7)
});
}
return new JsonResult(logRtn);
}
catch (Exception ex)
{
return new JsonResult(ex.Message);
}
}
I am unsure if you can use cookies with windows authentication. However if you can authenticate and assign loggedIn the result of your authentication request, you should be able to store some sort of claim(s) in the cookie. You can then recall that claim in a controller that might be doing authorization/recalling values using the following:
[HttpGet("[Action]", Name = "GetSomething")]
[Route("[Action]")]
public JsonResult something()
{
try
{
var loggedInUser = HttpContext.User;
var claym = loggedInUser.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name);
if (claym != null)
{
return new JsonResult(claym.Value);
// returns "tanush"
}
else
{
return new JsonResult("");
}
}
catch (Exception ex)
{
return new JsonResult(ex.Message);
}
}

How can I validate Request.Headers["Authorization"] for all controller at a single place?

[HttpGet]
public IActionResult Get()
{
string token = Request.Headers["Authorization"];
// Validate token.
}
[HttpPost]
public IActionResult Post(int id)
{
string token = Request.Headers["Authorization"];
// Validate token.
}
How can I validate Request.Headers["Authorization"] for all controller at a single place?
You can create and use custom middleware where you can check header and validate if it should be passed to controller or not.
To achive that create middleware class and regiester it in Startup.cs as below:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IConnectionManager conn, ILoggerFactory loggerFactory)
{
app.UseMiddleware<YourMidllewareClass>();
}
Create Invoke method in middleware class. This method will get called before each request jump into any of your contoller.
public async Task Invoke(HttpContext context)
{
string token = context.Request.Headers["Authorization"];
//do the checking
if (token == null)
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Access denied!");
return;
}
//pass request further if correct
await _next(context);
}
As far as I rember you must regiester your middleware before UseMvc() method to make sure your Invoke() will be called before Mvc pipeline.
For ASP.NET Core 2.0, some things have changed and you can also use an AuthenticationHandler.
Good documentation to get you started is at https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x.
An example of custom authentication I'm using in my current project:
Startup.ConfigureServices:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Custom Scheme";
options.DefaultChallengeScheme = "Custom Scheme";
}).AddCustomAuth(o => { });
Startup.Configure:
app.UseAuthentication();
And finally:
internal class CustomAuthenticationHandler :
AuthenticationHandler<CustomAuthenticationOptions>
{
public CustomAuthenticationHandler(IOptionsMonitor<CustomAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) :
base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
try
{
// Your auth code here
// Followed by something like this:
return AuthenticateResult.Success(
new AuthenticationTicket(
new ClaimsPrincipal(
new ClaimsIdentity(
new List<Claim>() { new Claim(ClaimTypes.Sid, Id.ToString()) },
Scheme.Name)),
Scheme.Name));
}
catch
{
return AuthenticateResult.Fail("Error message.");
}
}
}
This way, all calls to your controllers will go through the authentication middleware, and you can ignore it, if necessary, by using the [AllowAnonymous] attribute on the controller.
Or you could use Attributes on the class or method to validate and check the header elements. Depends on the need.
Middleware
Authentication Handler (similar to middleware)
Custom Attributes on the Class or Method.

Categories