in my .net core 2.2 microservice, I try to extract claims from a JWT token to do some authorization. authentication is done on another part of the system so I don't need to do it at this point.
I am using this code in the Startup.cs:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var signingKey = Encoding.UTF8.GetBytes("SECRET_KEY");
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(signingKey)
};
});
On the controller I have this code:
[Authorize]
[HttpPost]
public async Task<ActionResult<CreateResponse>> Create()
{
var userIdClaim = HttpContext.User.Claims.Where(x => x.Type == "empId").SingleOrDefault();
return Ok($"Your User ID is {userIdClaim.Value} and you can create invoices!");
}
I always get this error message and "Unauthorized" response:
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10503: Signature validation failed. Keys tried: '[PII is hidden]'.
Exceptions caught:
'[PII is hidden]'.
token: '[PII is hidden]'.
You can see the hidden details in development by adding the following to Configure() in the Startup class:
if (env.IsDevelopment())
{
IdentityModelEventSource.ShowPII = true;
}
Once you have the full message check the key being used is correct for the token.
Related
I am trying to validate a valid JWT using this code below but am getting a strange error
[Error] IDX10516: Signature validation failed. Unable to match key:
kid: '-KI3Q9nNR7bRofxmeZoXqbHZGew'.
Number of keys in TokenValidationParameters: '1'.
Number of keys in Configuration: '0'.
Exceptions caught:
'[PII of type 'System.Text.StringBuilder' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
token: '[PII of type 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. Valid Lifetime: 'True'. Valid Issuer: 'False'
Code:
I had seen a lot of reference links on this issue but still not getting what am I missing?
I tried to test locally, its worked perfectly but after deployed to Azure Function its failing.
Configuration Settings:
"JwtSettings:TenantName": "OSHDev.onmicrosoft.com",
"JwtSettings:TenantId": "5492b240-96ee-44a1-bdcb-fa0ba0200111",
"JwtSettings:AadB2cInstance": "https://OSHDev.b2clogin.com/{0}/v2.0/",
"JwtSettings:OpeinConfigUrl":"https://OSHDev.b2clogin.com/OSHDev.onmicrosoft.com/B2C_1A_SIGNUP_SIGNIN/v2.0/.well-known/openid-configuration",
public async Task<ClaimsPrincipal> ValidateAccessToken(string accessToken, JwtSettings jwtSettings, ILogger logger)
{
var audience = jwtSettings.Audience;
var tenant = jwtSettings.TenantName;
var tenantid = jwtSettings.TenantId;
var aadb2cInstance = jwtSettings.AadB2cInstance;
var openidconfigurl = jwtSettings.OpeinConfigUrl;
//Debugging purposes only, set this to false for production
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = false;
ConfigurationManager<OpenIdConnectConfiguration> configManager =
new ConfigurationManager<OpenIdConnectConfiguration>(
openidconfigurl,
new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config;
config = await configManager.GetConfigurationAsync();
//Microsoft Identity to override claim names . If we remove below code line, "sub" claim will not be visible. Its visible under "nameidentifier"
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler? tokenValidator = new JwtSecurityTokenHandler();
// Initialize the token validation parameters
TokenValidationParameters validationParameters = new TokenValidationParameters
{
// App Id URI and AppId of this service application are both valid audiences.
ValidateAudience = true,
ValidAudiences = new[] { audience },
ValidateIssuer= true,
ValidIssuers = new List<string>()
{
string.Format(CultureInfo.InvariantCulture, aadb2cInstance, tenantid),
string.Format(CultureInfo.InvariantCulture, aadb2cInstance, tenant)
},
ValidateIssuerSigningKey = true,
// Support Azure AD V1 and V2 endpoints.
IssuerSigningKeys = config.SigningKeys,
RequireSignedTokens = true,
//Debugging purposes only, set this to true for production
ValidateLifetime = true
};
try
{
//Validate JwTToken and return Claims Prinicpals
ClaimsPrincipal? claimsPrincipal = tokenValidator.ValidateToken(accessToken, validationParameters, out SecurityToken securityToken);
return claimsPrincipal;
}
catch (Exception ex)
{
logger.LogError(ex.Message);
}
return null;
}
Like the error said, your token validator try to find the public key that is used to sign your token in the jwks_uri, you can find it in your well-known URL.
In each token there is a header "KID", this header indicate the ID of the public key used to sign your token.
When i check your jwks_uri there is no KID with ID :
"-KI3Q9nNR7bRofxmeZoXqbHZGew"
But:
"veMP2TrHLgs4XvKYJhumvhPW6O-WpbdSGqKdetRmvxI"
I created a default ASP.NET Core (2.1) empty web application, and added JWT bearer authentication. The Startup.cs class looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var keyByteArray = Convert.FromBase64String(Constants.JwtSecretKey);
var signinKey = new SymmetricSecurityKey(keyByteArray);
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = Constants.Audience,
ValidateIssuer = true,
ValidIssuer = Constants.Issuer,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = signinKey
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseMvc();
}
The controller looks like this:
[Route("values")]
[ApiController]
public class ValuesController : Controller
{
[HttpGet("")]
public IActionResult Get()
{
return new StatusCodeResult(StatusCodes.Status200OK);
}
}
I would like my endpoint to return a 401 HTTP status code when the Authorization header is present but invalid (with an error message containing the failure reason) - but NOT when the header is missing. Is it possible to configure the middleware in such a way?
I tried fiddling with the OnAuthenticationFailed event from JwtBearerEvents, but couldn't get anything done with it.
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
// Not fired when the Authorization header is "Bearer foo",
// but fired when the header is "Bearer foo.bar.baz"
return Task.CompletedTask;
}
};
According to this document, JwtBearerEvents only support the below 4 kind of event
OnAuthenticationFailed(this one only triggered after Token failed to be Authenticated, in your case I think you need a validation instead of authentication)
OnChallenge
OnMessageReceived
OnTokenValidated(this one however only triggered after Token is successfully validated, that's why I think it does not work for your case)
What you want should be something like OnTokenValidateFailed but it is not there, one workaround would be register the OnMessageReceived events and try to validate token there
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
options.Events=new JwtBearerEvents(){
OnMessageReceived =context=>{
var header =context.Request.Headers["Authorization"];
//Your validation logic here
//if validate failed
//{
//context.Response.StatusCode=401;
//}
return Task.CompletedTask;
}
};
});
This way you can validate token by yourself however this approach might not short circuit the whole request pipeline and might conflict with other middleware(Not tested tested with other middleware but I assume this might happen)
Another workround would be like below
Add below code in your Config of start.cs in your API project
app.Use(async(context,next)=>{
var authHeader=context.Request.Headers["Authorization"];
//Your validation here
if(validation failed)
{
context.Response.StatusCode=401;
await context.Response.CompleteAsync();
}
else{
await next.Invoke();}
});
Add this before UseAuthenticate to overwrite default Authernticate middleware
First workaournd would trigger when it comes to JwtBearerToken authenticate while the second one will always be triggered since it is registered in the asp.net core middleware pipeline, so it is your choice to decidede to use which one
I have an ASP.NET Core Web API targeting 2.2, and I'm having trouble handling an authentication request in the event that it has failed. The authentication request needs to pass a valid refresh token. My code is handling the failed event when the JWT has expired (I also provide a lifetime validator), and then issue a new JWT and refresh token when the provided refresh token is correctly validated, and thus consumed. However, the pipeline does not continue executing and instead returns a 403 regardless of my code calling Success() on the context.
Here is the relevant Startup.cs section:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Authentication:JwtSecret"])),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
LifetimeValidator = l10n.Api.Code.Auth.Bearer.JwtBearerExtensions.LifetimeValidator
};
options.Authority = Configuration["Authentication:JwtAuthority"];
options.ClaimsIssuer = Configuration["Authentication:JwtIssuer"];
options.Events = new JwtBearerEvents();
options.Events.OnAuthenticationFailed = async context => await context.AuthenticationFailed();
})
And the fail handler:
public static async Task AuthenticationFailed(this AuthenticationFailedContext context)
{
ILoggerFactory loggerFactory = context.HttpContext.RequestServices.GetService<ILoggerFactory>();
ILogger logger = loggerFactory.CreateLogger(nameof(JwtBearerExtensions));
string refreshToken = context.HttpContext.Request.Cookies[Defaults.RefreshTokenCookie];
if (string.IsNullOrEmpty(refreshToken))
{
logger.LogWarning("No refresh token supplied with invalid JWT, Cookies are {0}", string.Join(", ", context.HttpContext.Request.Cookies.Keys));
return;
}
logger.LogInformation("Processing refresh token '{0}'.", refreshToken);
IConfiguration configuration = context.HttpContext.RequestServices.GetService<IConfiguration>();
IUserService userService = context.HttpContext.RequestServices.GetService<IUserService>();
ITokenHandler handler = context.HttpContext.RequestServices.GetService<ITokenHandler>();
long? userId = await handler.ValidateRefreshToken(refreshToken);
if (userId.HasValue)
{
User user = await userService.GetUserAsync(userId.Value);
refreshToken = await handler.GenerateRefreshToken(userId.Value);
string jwtToken = BuildJwtToken(user, configuration);
context.HttpContext.Response.AddBearerAuthorization(jwtToken);
context.HttpContext.Response.AddRefreshTokenCookie(refreshToken);
context.Principal = new ClaimsPrincipal(BuildClaimsIdentity(user));
context.Success();
}
}
When inspecting the 403 result in Postman, I can see the new JWT in the Authorization header, and the new refresh token in the cookie. Those are correctly attributed to the Response object. It's just the pipeline that abandons the rest of the processing, and my controller action is never invoked.
How do I allow the request to continue and complete, returning the expected JSON and also issuing (or refreshing) a new auth session?
The solution was to use the correct constructor overload for the ClaimIdentity by passing in the authentication type. More information at this github issue.
For those interested, in this solution, the 403 result happened because of app.UseAuthorization(), in which a policy was failing to pass as a result of the authentication issue. In the github issue post, the purpose-built repro did not have this and resulted in the 401 instead.
Is it possible to support multiple JWT Token issuers in ASP.NET Core 2?
I want to provide an API for external service and I need to use two sources of JWT tokens - Firebase and custom JWT token issuers. In ASP.NET core I can set the JWT authentication for Bearer auth scheme, but only for one Authority:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://securetoken.google.com/my-firebase-project"
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "my-firebase-project"
ValidateAudience = true,
ValidAudience = "my-firebase-project"
ValidateLifetime = true
};
}
I can have multiple issuers and audiences, but I can't set several Authorities.
You can totally achieve what you want:
services
.AddAuthentication()
.AddJwtBearer("Firebase", options =>
{
options.Authority = "https://securetoken.google.com/my-firebase-project"
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "my-firebase-project"
ValidateAudience = true,
ValidAudience = "my-firebase-project"
ValidateLifetime = true
};
})
.AddJwtBearer("Custom", options =>
{
// Configuration for your custom
// JWT tokens here
});
services
.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("Firebase", "Custom")
.Build();
});
Let's go through the differences between your code and that one.
AddAuthentication has no parameter
If you set a default authentication scheme, then on every single request the authentication middleware will try to run the authentication handler associated with the default authentication scheme. Since we now have two possible authentication schemes, there's no point in running one of them.
Use another overload of AddJwtBearer
Every single AddXXX method to add an authentication has several overloads:
One where the default authentication scheme associated with the authentication method is used, as you can see here for cookies authentication
One where you pass, in addition to the configuration of the options, the name of the authentication scheme, as on this overload
Now, because you use the same authentication method twice but authentication schemes must be unique, you need to use the second overload.
Update the default policy
Since the requests won't be authenticated automatically anymore, putting [Authorize] attributes on some actions will result in the requests being rejected and an HTTP 401 will be issued.
Since that's not what we want because we want to give the authentication handlers a chance to authenticate the request, we change the default policy of the authorization system by indicating both the Firebase and Custom authentication schemes should be tried to authenticate the request.
That doesn't prevent you from being more restrictive on some actions; the [Authorize] attribute has an AuthenticationSchemes property that allows you to override which authentication schemes are valid.
If you have more complex scenarios, you can make use of policy-based authorization. I find the official documentation is great.
Let's imagine some actions are only available to JWT tokens issued by Firebase and must have a claim with a specific value; you could do it this way:
// Authentication code omitted for brevity
services
.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("Firebase", "Custom")
.Build();
options.AddPolicy("FirebaseAdministrators", new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("Firebase")
.RequireClaim("role", "admin")
.Build());
});
You could then use [Authorize(Policy = "FirebaseAdministrators")] on some actions.
A final point to note: If you are catching AuthenticationFailed events and using anything but the first AddJwtBearer policy, you may see IDX10501: Signature validation failed. Unable to match key... This is caused by the system checking each AddJwtBearer in turn until it gets a match. The error can usually be ignored.
This is an extension of Mickaƫl Derriey's answer.
Our app has a custom authorization requirement that we resolve from an internal source. We were using Auth0 but are switching to Microsoft Account authentication using OpenID. Here is the slightly edited code from our ASP.Net Core 2.1 Startup. For future readers, this works as of this writing for the versions specified. The caller uses the id_token from OpenID on incoming requests passed as a Bearer token. Hope it helps someone else trying to do an identity authority conversion as much as this question and answer helped me.
const string Auth0 = nameof(Auth0);
const string MsaOpenId = nameof(MsaOpenId);
string domain = "https://myAuth0App.auth0.com/";
services.AddAuthentication()
.AddJwtBearer(Auth0, options =>
{
options.Authority = domain;
options.Audience = "https://myAuth0Audience.com";
})
.AddJwtBearer(MsaOpenId, options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = "00000000-0000-0000-0000-000000000000",
ValidateIssuer = true,
ValidIssuer = "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0",
ValidateIssuerSigningKey = true,
RequireExpirationTime = true,
ValidateLifetime = true,
RequireSignedTokens = true,
ClockSkew = TimeSpan.FromMinutes(10),
};
options.MetadataAddress = "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0/.well-known/openid-configuration";
}
);
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes( Auth0, MsaOpenId )
.Build();
var approvedPolicyBuilder = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes(Auth0, MsaOpenId)
;
approvedPolicyBuilder.Requirements.Add(new HasApprovedRequirement(domain));
options.AddPolicy("approved", approvedPolicyBuilder.Build());
});
The solution to your question, is available in following blog post https://oliviervaillancourt.com/posts/Fixing-IDX10501-MultipleAuthScheme
Basically the solutions exists of overriding the regular JWTBearer handler with you own generic handler that can check through the JWTBearerConfig if the issuer in the cfg is the same to the isseur in your token.
The blog post suggests to use seperate handlers for each scheme, that doesn't seem to be needed, a generic class JWTAuthenticationHandler that overrides the HandleAuthenticateAsync method seems to suffice!
Code wise you could implement your startup like this:
//Using multiple schemes can cause issues when validating the issuesSigningKey therefore we need to implement seperate handlers for each scheme! => cfr: https://oliviervaillancourt.com/posts/Fixing-IDX10501-MultipleAuthScheme
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());
services.AddAuthentication()
//Set the authenticationScheme by using the identityServer helper methods (we are using a Bearer token)
.AddScheme<JwtBearerOptions, JWTAuthenticationHandler>(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
{
//TO DO Get the origin url's from configuration file, instead of setting all url's here
options.Authority = _identityServerSettings.Authority;
options.Audience = _identityServerSettings.Audience;
options.Events = new JwtBearerEvents
{
OnChallenge = context =>
{
return Task.CompletedTask;
},
//When using multiple JwtBearer schemes we can run into "OnAuthenticationFailed" for instance when logging in via IdentityServer the AuthenticationHandler will still check in these events, this can be ignored...
//Cfr => https://stackoverflow.com/questions/49694383/use-multiple-jwt-bearer-authentication
//If you are catching AuthenticationFailed events and using anything but the first AddJwtBearer policy, you may see IDX10501: Signature validation failed.Unable to match key... This is caused by the system checking each AddJwtBearer in turn until it gets a match. The error can usually be ignored.
//We managed to fix this issue by adding seperate AuthenticationHandlers for each type of bearer token... cfr: https://oliviervaillancourt.com/posts/Fixing-IDX10501-MultipleAuthScheme
OnAuthenticationFailed = context =>
{
return Task.CompletedTask;
},
OnMessageReceived = context =>
{
return Task.CompletedTask;
},
OnForbidden = context =>
{
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
return Task.CompletedTask;
}
};
})
//Set the authentication scheme for the AzureAd integration (we are using a bearer token)
.AddScheme<JwtBearerOptions, JWTAuthenticationHandler>("AzureAD", "AzureAD", options =>
{
options.Audience = _azureAdSettings.Audience; //ClientId
options.Authority = _azureAdSettings.Authority; //"https://login.microsoftonline.com/{tenantId}/v2.0/"
options.TokenValidationParameters = new TokenValidationParameters
{
//Set built in claimTypes => Role
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
};
options.Events = new JwtBearerEvents
{
OnChallenge = context =>
{
return Task.CompletedTask;
},
//When using multiple JwtBearer schemes we can run into "OnAuthenticationFailed" for instance when logging in via IdentityServer the AuthenticationHandler will still check in these events, this can be ignored...
//Cfr => https://stackoverflow.com/questions/49694383/use-multiple-jwt-bearer-authentication
//A final point to note: If you are catching AuthenticationFailed events and using anything but the first AddJwtBearer policy, you may see IDX10501: Signature validation failed.Unable to match key... This is caused by the system checking each AddJwtBearer in turn until it gets a match. The error can usually be ignored.
//We managed to fix this issue by adding seperate AuthenticationHandlers for each type of bearer token... cfr: https://oliviervaillancourt.com/posts/Fixing-IDX10501-MultipleAuthScheme
OnAuthenticationFailed = context =>
{
return Task.CompletedTask;
},
OnMessageReceived = context =>
{
return Task.CompletedTask;
},
OnForbidden = context =>
{
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
return Task.CompletedTask;
}
};
});
}
The JWTAuthenticationHandlerClass can look like this
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
namespace WebAPI.Auth
{
public class JWTAuthenticationHandler: JwtBearerHandler
{
public JWTAuthenticationHandler(IOptionsMonitor<JwtBearerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{ }
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
//Fetch OIDC configuration for the IDP we are handling
var authorityConfig = await this.Options.ConfigurationManager.GetConfigurationAsync(this.Context.RequestAborted);
//Determine the issuer from the configuration
var authorityIssuer = authorityConfig.Issuer;
var jwtToken = this.ReadTokenFromHeader();
var jwtHandler = new JwtSecurityTokenHandler();
//Check if we can read the token as a valid JWT, if not let the JwtBearerHandler do it's thing...
if (jwtHandler.CanReadToken(jwtToken))
{
//Read the token and determine if the issuer in config is the same as the one in the token, if this is true we know we want to let the JwtBearerHandler continue, if not we skip and return noResult
//This way the next IDP configuration will pass here until we find a matching issuer and then we know that is the IDP we are dealing with
var token = jwtHandler.ReadJwtToken(jwtToken);
if (string.Equals(token.Issuer, authorityIssuer, StringComparison.OrdinalIgnoreCase))
{
return await base.HandleAuthenticateAsync();
}
else
{
// return NoResult since the issuer in cfg did not match the one in the token, so no need to proceed to tokenValidation
this.Logger.LogDebug($"Skipping jwt token validation because token issuer was {token.Issuer} but the authority issuer is: {authorityIssuer}");
return AuthenticateResult.NoResult();
}
}
return await base.HandleAuthenticateAsync();
}
//Fetch the bearer token from the authorization header on the request!
private string ReadTokenFromHeader()
{
string token = null;
string authorization = Request.Headers["Authorization"];
//If we don't find the authorization header return null
if (string.IsNullOrEmpty(authorization))
{
return null;
}
//get the token from the auth header
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}
return token;
}
}
}
One thing that was missing in Mickael's answer is that scheme needs to be specified in Authorize attribute (If you want to use authorization)
[Authorize(AuthenticationSchemes = "Firebase,Custom", Policy ="FirebaseAdministrators")]
Without AuthenticationSchemes provided, and AddAuthentication() has no parameter, NetCore fails to Authenticate and Request.HttpContext.User.Identity.IsAuthenticated is set to false
This is the page where I "learned" how to do it: https://stormpath.com/blog/token-authentication-asp-net-core
But for me this is not working (doesn't work with Fiddler, too)
There is this controller for my ApplicationUser-model:
[Authorize] //works when it's not set, doesn't work when it's set
[Route("api/[controller]")]
public class ApplicationUserController : Controller
{
private IRepository<ApplicationUser> _applicationUserRepository;
public ApplicationUserController(IRepository<ApplicationUser> applicationUserRepository)
{
_applicationUserRepository = applicationUserRepository;
}
[HttpGet("{id}")]
public ApplicationUser Get(int id)
{
return _applicationUserRepository.Get(id);
}
}
and there's my wrapper for RestSharp to get all applicationusers:
public Task<T> GetResponseContentAsync<T>(string resource, int id) where T : new()
{
RestRequest request = new RestRequest($"{resource}/{{id}}", Method.GET);
request.AddUrlSegment("id", id);
if (!AuthenticationToken.IsNullOrEmpty(true))
{
request.AddHeader("Authorization", string.Format("Bearer {0}", AuthenticationToken));
_client.Authenticator = new JwtAuthenticator(AuthenticationToken);
_client.Authenticator.Authenticate(_client, request);
}
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
_client.ExecuteAsync<T>(request, response =>
{
tcs.SetResult(response.Data);
});
return tcs.Task;
}
From my web-client application I want to login with JWT (Token-Authentication) what works. After login I get e.g. this access_token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJURVNUIiwianRpIjoiZTBjYjE0NjgtYzBmOS00ZTM4LTg4ZjgtMGM4ZjNmYjMyNjZmIiwiaWF0IjoxNDcwOTUwMTA0LCJuYmYiOjE0NzA5NTAxMDQsImV4cCI6MTQ3MDk1MDQwNCwiaXNzIjoiRXhhbXBsZUlzc3VlciIsImF1ZCI6IkV4YW1wbGVBdWRpZW5jZSJ9.a9_JK2SG3vzc6NSOB0mZXqHlM9UAEXUHHrrijAQUsX0
without the Authorize-attribute I get the ApplicationUser, but when setting the Attribute, the result is null (since the web-api is not getting called)
the wrapper-call looks like this:
//this works, token-value is set
string token = new RepositoryCall("http://localhost:54008/").Login("token", "TEST", "TEST123");
string accessToken = JsonConvert.DeserializeObject<Dictionary<string, string>>(token)["access_token"];
ViewData["Result"] = accessToken;
ApplicationUser userAfterLogin = await new RepositoryCall("http://localhost:54008/api")
{ AuthenticationToken = accessToken }
.GetResponseContentAsync<ApplicationUser>("ApplicationUser", 2);
and here userAfterLogin is null.
I'm trying to get the login since two weeks but I still don't get it right..
Any idea what I'm doing wrong? Maybe a wrong request-header-value for authorization?
Update
this is my Startup.Configure where I configured to use the Bearer / JWT:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentity();
var secretKey = "mysupersecret_secretkey!123";
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
var options = new TokenProviderOptions
{
Audience = "ExampleAudience",
Issuer = "ExampleIssuer",
SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
};
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = "ExampleIssuer",
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = "ExampleAudience",
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookie",
CookieName = "access_token",
TicketDataFormat = new CustomJwtDataFormat(
SecurityAlgorithms.HmacSha256,
tokenValidationParameters)
});
app.UseMiddleware<TokenProviderMiddleware>(Options.Create(options));
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
If you recive authorizacion error or using postman you realize that you are being asked to redirect to login just decorate your class with:
[Authorize(AuthenticationSchemes = "Bearer")]
By default .Net uses cookie based auth, with that annotation you swicht to token based one
So you are using 2 middlewares for identity. One provided by asp.net identity (cookie based) and another token based. Now both of the middleware use the same attribute for
handling the request [Authorize]. More precisely look at the code here
https://github.com/aspnet/Security/blob/dev/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs
for JWTBearer
and
https://github.com/aspnet/Security/blob/dev/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs
for Cookie
Because both are activated in middleware pipeline the principal will have the data when you send auth token or cookie.
But because both of them are active either of them will return Unauthorized for the request that doesnt have cookie or JwtBearer.
For the solution you are looking for you need to create a middleware on top of existing cookie and token based to route the request to either based on if authorization header is present.
In Fiddler you would see, if you are redirected to login page (it would report 2 Results, one with 302 (redirect) and then the 404 - is that the case?
You have DebugLogger activated, so try AddDebug(LogLevel.Trace) and view the Debug output window, it is very helpful in analysing which of authentication steps fail. It also shows if authentication fails or authorization, and if has a valid token etc. So it points to the direction to look for problems.