locked
ASP.Net Core 3.0 : How to validate JWT Bearer Tokens RRS feed

  • Question

  • User-247644627 posted

    Hello,

    I am working for a non-profit video-learning platform and try to use JWT bearer tokens.

    I am using a ASP.Net Core 3.0 API with EntityFramework Core as UserStorage.

    Here is the code I already have:

    Startup.cs:

    public void ConfigureServices(IServiceCollection services)
                {
                 using Microsoft.AspNetCore.Authentication.JwtBearer;
                 using Microsoft.AspNetCore.Builder;
                 using Microsoft.AspNetCore.Hosting;
                 using Microsoft.AspNetCore.Identity;
                 using Microsoft.AspNetCore.SpaServices.AngularCli;
                 using Microsoft.EntityFrameworkCore;
                 using Microsoft.Extensions.Configuration;
                 using Microsoft.Extensions.DependencyInjection;
                 using Microsoft.Extensions.Hosting;
                 using Microsoft.IdentityModel.Tokens;
                 using System;
                 using System.Collections.Generic;
                 using System.Linq;
                 using System.Text;
                 using System.Threading.Tasks;
                    .
                    .
                    .
    
                    //Add Identity Provider with EntityFramework
                    services.AddIdentity<User, IdentityRole>()
                    .AddEntityFrameworkStores<ApplicationDBContext>()
                    .AddDefaultTokenProviders();
    
                    //Initialize EntityFramework
                    services.AddDbContext<ApplicationDBContext>(options => options.UseSqlite(Configuration.GetConnectionString("localDB")));
    
                    //Initialize JWT Authentication
                    services.AddAuthentication(options => {
                        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                    }).AddJwtBearer(jwtBearerOptions =>
                    {
                        jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters()
                        {
                            ValidateIssuer = true,
                            ValidateAudience = true,
                            ValidateLifetime = true,
                            ValidateIssuerSigningKey = true,
    
                            ValidIssuer = "http://localhost:44352",
                            ValidAudience = "http://localhost:44352",
                            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration.GetSection("Secrets")["jwt"]))
                        };
                    }
                    );
                    services.AddMvc(options => options.EnableEndpointRouting = false)
                        .AddNewtonsoftJson();
    
                    // In production, the Angular files will be served from this directory
                    services.AddSpaStaticFiles(configuration =>
                    {
                        configuration.RootPath = "ClientApp/dist";
                    });
                }
    
                .
                .
                .
    
    
                    app.UseHttpsRedirection();
                    app.UseStaticFiles();
                    app.UseSpaStaticFiles();
    
                    //Enable Authentication
                    app.UseAuthentication();
                    app.UseAuthorization();
    
                    .
                    .
                    .
    
                    app.UseMvc(routes =>
                    {
                        routes.MapRoute(
                            name: "default",
                            template: "{controller}/{action=Index}/{id?}");
                    });
    
    
        .
        .
        .

     This is my code issuing a JWT token:

    public async Task<IActionResult> Login()
            {
                using (var reader = new StreamReader(Request.Body))
                {
                    var body = await reader.ReadToEndAsync();
                    var cred = JsonConvert.DeserializeObject<Credentials>(body);
                    var result = (await userService.LoginUser(cred.userName, cred.password));
                    if (result == 200)
                    {
    
                        var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetSection("Secrets")["jwt"]));
                        var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256Signature);
    
                        var roles = await userService.GetRoleFromUsername(cred.userName);
                        var rolesString = JsonConvert.SerializeObject(roles);
    
                        var tokeOptions = new JwtSecurityToken(
                                    issuer: "http://localhost:44352",
                                    audience: "http://localhost:44352",
                                    claims: new List<Claim>(new List<Claim> {
                                            new Claim("userName",cred.userName),
                                            new Claim("roles", rolesString)
                                    }),
                                    expires: DateTime.Now.AddHours(1),
                                    signingCredentials: signinCredentials
                        );

    This is my API call:

    [Route("api/videos/add")]
    [Authorize(Roles = "Admin")]
    [HttpPost]
    public async Task<IActionResult> AddVideo()
    {
        using (var reader = new StreamReader(Request.Body))
        {
            var body = await reader.ReadToEndAsync();
            var video = JsonConvert.DeserializeObject<Video>(body);
            await videoService.AddVideo(video);
            return Ok();
        }
    }

    My NuGet Packages are:

    • Microsoft.EntityFrameworkCore {3.0.0-preview5.19227.1}
    • Microsoft.EntityFrameworkCore.Sqlite {3.0.0-preview5.19227.1}
    • Microsoft.AspNetCore.Authentication.JwtBearer {3.0.0-preview4-19216-03}
    • Microsoft.EntityFrameworkCore.Sqlite.Core {3.0.0-preview5.19227.1}
    • Microsoft.NETCore.Platforms {3.0.0-preview4.19212.13}
    • Microsoft.AspNetCore.Mvc.NewtonsoftJson {3.0.0-preview5-19227-01}
    • Microsoft.AspNetCore.SpaServices.Extensions {3.0.0-preview5-19227-01}
    • Microsoft.AspNetCore.Identity.EntityFrameworkCore {3.0.0-preview5-19227-01}
    • runtime.win-x64.Microsoft.NETCore.DotNetAppHost {3.0.0-preview4-27615-11}

    The problem I have, is that if I call that API part, I get the error:

    Information: Bearer was not authenticated. Failure message: No SecurityTokenValidator available for token:

    Any help would be much appreciated, since I can't find the error

    Friday, June 7, 2019 1:38 PM

All replies

  • User-2054057000 posted

    Apply the breakpoint on your code line then move forward by pressing F10 key.

    Once you reach the line which gives the error, then update your question by providing the line which is giving the error and also the error text. 

    This way we can assist you in a good way. 

    Thank You..

    Saturday, June 8, 2019 9:31 AM
  • User-247644627 posted

    Thank you for your answer.

    Unfortunatelly, I cannot use breakpoints, since I get the error inside of ASP.Net code, not code written by me. 

    The only info I can give you is:

    Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker: Information: Route matched with {action = "AddVideo", controller = "Video", page = ""}. Executing action L_Tube.Controllers.VideoController.AddVideo (L-Tube)

    The error happens when I call VideoController.AddVideo() and it only happens, if I check for Authorization

    Saturday, June 8, 2019 4:13 PM
  • User-1764593085 posted

    Hi AtzederSerbe,

    Do you get a 403 error? Try to use `ClaimTypes.Role`  instead of `roles` if you want to add a Role as claim.

     var tokeOptions = new JwtSecurityToken(
                                    issuer: "http://localhost:44352",
                                    audience: "http://localhost:44352",
                                    claims: new List<Claim>(new List<Claim> {
                                            new Claim("userName",cred.userName),
    new Claim(ClaimTypes.Role, "Admin") }), expires: DateTime.Now.AddHours(1), signingCredentials: signinCredentials );

    Api:

    [Route("api/videos/add")]
    [Authorize(Roles = "Admin")]
    [HttpPost]
    public async Task<IActionResult> AddVideo()

    Refer to;

    https://stackoverflow.com/questions/55628357/asp-net-core-2-2-jwt-claims-identity-authentication-for-website

    Best Regards,

    Xing

    Monday, June 10, 2019 5:46 AM
  • User-247644627 posted

    Dear Xing Zou,

    thank you for your answer. Unfortunatelly, that did not do it. I got still the same error.

    Failed to load resource: the server responded with a status of 401 () [https://localhost:44352/api/videos/add]

    Best regards

    Monday, June 10, 2019 8:45 PM
  • User-1764593085 posted

    Hi Atzederserbe,

    How do you call the [Authorize] api after you receiving the token? Do you send the token in request header?if you use postman, you could choose Authorization->Type(Bearer Token)-> Input your token to test the api.

    Besides, do you have the same problem in asp.net core 2.2?

    Best Regards,

    Xing

    Tuesday, June 11, 2019 1:21 AM
  • User-247644627 posted

    Hello,

    I am using an Angular7 SPA with a HTTP Interceptor. If the authService gets a HTTP 200 back with the bearer token, it saves it in the LocalStorage, as well as the decrypted userName. If there are those entries in the LocalStorage, the HTTP Interceptor adds an authorization header to every HTTP request.

    Following is the request I am sending via Angular

    Request sent

    Using Postman, I think I found the error. It's the quotation marks at the bearer token. If I use Postman, I get a 403 Error, but I do not get the original error in the API. 

    So, the 401 error got a 403. But I guess this is because of an old bearer token.

    I will reply again after some testing.

    Best Regards

    Tuesday, June 11, 2019 1:43 PM
  • User-247644627 posted

    After some testing, it is not an old bearer token which causes this problem.

    Erasing the quotation marks worked, so that the token gets successfully validated, but there is still a 403 error. 

    This is what the API puts out

    Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request starting HTTP/2.0 POST https://localhost:44352/api/videos/add text/plain 133
    Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler: Information: Successfully validated the token.
    Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker: Information: Route matched with {action = "AddVideo", controller = "Video", page = ""}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] AddVideo() on controller L_Tube.Controllers.VideoController (L-Tube).
    Microsoft.AspNetCore.Authorization.DefaultAuthorizationService: Information: Authorization failed.
    Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker: Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
    Microsoft.AspNetCore.Mvc.ForbidResult: Information: Executing ForbidResult with authentication schemes ().
    Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler: Information: AuthenticationScheme: Bearer was forbidden.

    Tuesday, June 11, 2019 1:59 PM
  • User-247644627 posted

    I found the issue concerning error 403. It seems that you are only allowed to send ONE role in the bearer token for ASP.Net to validate it. The return type of userManager.GetRolesAsync suggests that a user can have multiple roles which can be included in a JWT bearer token.

    That means my issue is fixed.

    I want to thank Xing Zou for his/her answers. I would not have gotten it without you!

    Tuesday, June 11, 2019 9:13 PM
  • User475983607 posted

    I found the issue concerning error 403. It seems that you are only allowed to send ONE role in the bearer token for ASP.Net to validate it.

    I'm pretty sure all you need to do is add multiple Role types.

    new Claim(ClaimTypes.Role, rolesString1),
    new Claim(ClaimTypes.Role, rolesString2),
    new Claim(ClaimTypes.Role, rolesString3)

    Tuesday, June 11, 2019 9:20 PM
  • User-247644627 posted

    Thank you for your answer mgebhard, I will implement this if needed

    Wednesday, June 12, 2019 9:07 PM