No Authenticationscheme Was Specified, and There Was No Defaultchallengescheme Found With Default Authentification and Custom Authorization

No authenticationScheme was specified, and there was no DefaultChallengeScheme found with default authentification and custom authorization

Do not use authorization instead of authentication. I should get whole access to service all clients with header.

The working code is:

public class TokenAuthenticationHandler : AuthenticationHandler<TokenAuthenticationOptions> 
{
public IServiceProvider ServiceProvider { get; set; }

public TokenAuthenticationHandler (IOptionsMonitor<TokenAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IServiceProvider serviceProvider)
: base (options, logger, encoder, clock)
{
ServiceProvider = serviceProvider;
}

protected override Task<AuthenticateResult> HandleAuthenticateAsync ()
{
var headers = Request.Headers;
var token = "X-Auth-Token".GetHeaderOrCookieValue (Request);

if (string.IsNullOrEmpty (token)) {
return Task.FromResult (AuthenticateResult.Fail ("Token is null"));
}

bool isValidToken = false; // check token here

if (!isValidToken) {
return Task.FromResult (AuthenticateResult.Fail ($"Balancer not authorize token : for token={token}"));
}

var claims = new [] { new Claim ("token", token) };
var identity = new ClaimsIdentity (claims, nameof (TokenAuthenticationHandler));
var ticket = new AuthenticationTicket (new ClaimsPrincipal (identity), this.Scheme.Name);
return Task.FromResult (AuthenticateResult.Success (ticket));
}
}

Startup.cs:

#region Authentication
services.AddAuthentication (o => {
o.DefaultScheme = SchemesNamesConst.TokenAuthenticationDefaultScheme;
})
.AddScheme<TokenAuthenticationOptions, TokenAuthenticationHandler> (SchemesNamesConst.TokenAuthenticationDefaultScheme, o => { });
#endregion

And mycontroller.cs:

[Authorize(AuthenticationSchemes = SchemesNamesConst.TokenAuthenticationDefaultScheme)]
public class MainController : BaseController
{ ... }

I can't find TokenAuthenticationOptions now, but it was empty. I found the same class PhoneNumberAuthenticationOptions:

public class PhoneNumberAuthenticationOptions : AuthenticationSchemeOptions
{
public Regex PhoneMask { get; set; }// = new Regex("7\\d{10}");
}

You should define static class SchemesNamesConst. Something like:

public static class SchemesNamesConst
{
public const string TokenAuthenticationDefaultScheme = "TokenAuthenticationScheme";
}

No authenticationScheme was specified, and there was no DefaultChallengeScheme found - ASP.NET core 2.1

It is okay to send 500 with this exception to user end, when header not specified?

I'm afraid that's not a good idea.

The 500 status code indicates there's a server error. When clients send a request without a token , it make no sense to tell the client that "an internal error happens". A better way is to send 401 to challenge the user or send 403 to forbid .

How to handle this scenario and passing meaningful message "header is missing" or something?

Firstly, I have to say that I don't think using a AuthorizationFilter to authenticate user is a good option.

As the error describes , the error is thrown because there was no AuthenticationScheme was specified, and there was no DefaultChallengeScheme found.

To fix the bug , simply specify a authentication scheme. For example , if you're using JwtToken , you should add a AddAuthentication(JwtBearerDefaults.AuthenticationScheme) or use a [Authorize(AuthenticationSchemes ="JwtBearerDefaults.AuthenticationScheme")] attribute

Otherwise, if you would like to custom the way it authenticates user (for example , custom a token-based authentication), you should create a new Token Authentication Handler. There's already a built-in abstract AuthenticationHandler class :

public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler 
where TOptions : AuthenticationSchemeOptions, new()
{
// ...
}

Since the default HandleChallengeAsync() will send a 401 response, you could simply extend theAuthenticationHandler and override the HandleChallengeAsync() method to custom your own message to challenge user :

public class OurOwnAuthenticationHandler : AuthenticationHandler<ApiKeyAuthOpts>
{
public OurOwnAuthenticationHandler(IOptionsMonitor<ApiKeyAuthOpts> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}


protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
StringValues authorizationHeaders;
if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out authorizationHeaders))
return AuthenticateResult.NoResult();
// ... return AuthenticateResult.Fail(exceptionMessage);
// ... return AuthenticateResult.Success(ticket)
}

protected override Task HandleChallengeAsync(AuthenticationProperties properties)
{
Response.StatusCode = 401;
var message = "tell me your token";
Response.Body.Write(Encoding.UTF8.GetBytes(message));
return Task.CompletedTask;
}

protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
{
Response.StatusCode = 403;
var message = "you have no rights";
Response.Body.Write(Encoding.UTF8.GetBytes(message));
return Task.CompletedTask;
}

}

Lastly , you need also register the authentication handler :

services.AddAuthentication("OurOwnAuthN")
.AddScheme<OurOwnAuthNOpts,OurOwnAuthNHandler>("OurOwnAuthN","Our Own AuthN Scheme",opts=>{
// ...
});

If you don't want set the "OurOwnAuthN" as the default authentication scheme , you can use a [Authorize(AuthenticationSchemes ="OurOwnAuthN")] to protect your resources :

// your `ConfigureServices()`
services.AddAuthentication()
.AddScheme<OurOwnAuthNOpts,OurOwnAuthNHandler>("OurOwnAuthN","Our Own AuthN Scheme",opts=>{
// ...
});


// your action method :
// GET api/values/5
[Authorize(AuthenticationSchemes ="OurOwnAuthN")]
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}

if a user send a request without a token or with a incorrect token , the response from server will be :

HTTP/1.1 401 Unauthorized
Transfer-Encoding: chunked
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcMTBcMThcU08uYXV0aGVudGljYXRpb25TY2hlbWUsIE5vIERlZmF1bHRDaGFsbGVuZ2VTY2hlbWVcQXBwXEFwcFxhcGlcdmFsdWVzXDE=?=
X-Powered-By: ASP.NET

tell me your token

[Edit]

If you're using Jwt Token , your can use the following code to register JwtBearer Authentication :

services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});

[Edit2]

The JwtBearer AuthenticationHandler provides a Challenge to custom the WWW-Authenticate :

    .AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
options.Challenge ="tell me your token";;
})

and the response will be :

HTTP/1.1 401 Unauthorized
Server: Kestrel
WWW-Authenticate: tell me your token, error="invalid_token"
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcMTBcMThcU08uYXV0aGVudGljYXRpb25TY2hlbWUsIE5vIERlZmF1bHRDaGFsbGVuZ2VTY2hlbWVcQXBwXEFwcFxhcGlcdmFsdWVzXDE=?=
X-Powered-By: ASP.NET
Content-Length: 0

Note the WwW-Authenticate header .

Another way is to forward the challenge by :

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:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
options.ForwardChallenge = "OurOwnAuthN";
})
.AddScheme<OurOwnAuthNOpts,OurOwnAuthNHandler>("OurOwnAuthN","Our Own Authentication Scheme",opts=>{
// ...
});

and the response will be :

HTTP/1.1 401 Unauthorized
Transfer-Encoding: chunked
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcMTBcMThcU08uYXV0aGVudGljYXRpb25TY2hlbWUsIE5vIERlZmF1bHRDaGFsbGVuZ2VTY2hlbWVcQXBwXEFwcFxhcGlcdmFsdWVzXDE=?=
X-Powered-By: ASP.NET

tell me your token

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

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

No authenticationScheme was specified, and there was no DefaultForbidScheme found with custom policy based authorization

Authorization and authentication are closely linked in ASP.NET Core. When authorization fails, this will be passed to an authentication handler to handle the authorization failure.

So even if you don’t need actual authentication to identify your users, you will still need to set up some authentication scheme that is able to handle forbid and challenge results (403 and 401).

To do that, you need to call AddAuthentication() and configure a default forbid/challenge scheme:

services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = "scheme name";

// you can also skip this to make the challenge scheme handle the forbid as well
options.DefaultForbidScheme = "scheme name";

// of course you also need to register that scheme, e.g. using
options.AddScheme<MySchemeHandler>("scheme name", "scheme display name");
});

MySchemeHandler needs to implement IAuthenticationHandler and in your case, you especially need to implement ChallengeAsync and ForbidAsync:

public class MySchemeHandler : IAuthenticationHandler
{
private HttpContext _context;

public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_context = context;
return Task.CompletedTask;
}

public Task<AuthenticateResult> AuthenticateAsync()
=> Task.FromResult(AuthenticateResult.NoResult());

public Task ChallengeAsync(AuthenticationProperties properties)
{
// do something
}

public Task ForbidAsync(AuthenticationProperties properties)
{
// do something
}
}


Related Topics



Leave a reply



Submit