3

I have an ASP.NET Core 5.0 API that uses JWT authentication.

All I want it to do, for now, is to read tokens in the button

@Html.ActionLink("Test","Oper","Home")

and it's [Authorize] header and validate them against my criteria. I don't know what missed but it is always returning HTTP 401 code.

Test add this code

app.UseCors(x => x.AllowAnyHeader()
                  .AllowAnyMethod()
                  .WithOrigins("https://localhost:4200"));

Error:

System.InvalidOperationException: The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the CORS policy by listing individual origins if credentials needs to be supported.

The connection to the terminal's pty host process is unresponsive, the terminals may stop working

It's not an Angular project.

This is Startup.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.FileProviders;
using System.IO;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;

namespace JWTtokenMVC
{
    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.AddControllersWithViews();
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials().Build());
            });

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.IncludeErrorDetails = true;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType ="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
                    RoleClaimType ="http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
                    ValidateIssuer = true,
                    ValidateAudience = false,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = Configuration["Jwt:Issuer"],
                    ValidAudience = Configuration["Jwt:Issuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])

                    )
                };
            });
        }

        // 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();
            }
            else
            {
                app.UseExceptionHandler("/Home/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.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseStaticFiles(new StaticFileOptions
            {
                FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "Test_modules")),
                RequestPath = "/" + "Test_modules"
            });
            app.UseCookiePolicy();

            app.UseRouting();

            app.UseAuthentication();

            app.UseAuthorization();
            app.UseCors(x => x.AllowAnyHeader()
                              .AllowAnyMethod()
                              .WithOrigins("https://localhost:4200"));

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

This is HomeController.cs - login get Jwt Token is OK:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using JWTtokenMVC.Models;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.Extensions.Configuration;
using JWTtokenMVC.Models.Test;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;

namespace JWTtokenMVC.Controllers
{
    public class HomeController : Controller
    {
        private IConfiguration _config;

        public HomeController(IConfiguration config)
        {
            _config = config;
        }

        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }

        private string GenerateJSONWebToken(UserPaul userinfo)
        {
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
            var claims = new[]
            {
                new Claim(JwtRegisteredClaimNames.Sub,userinfo.Username),
                new Claim(JwtRegisteredClaimNames.Email,userinfo.Email),
                new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()),
            };
            var token = new JwtSecurityToken(
                issuer: _config["Jwt:Issuer"],
                audience: _config["Jwt:Issuer"],
                claims,
                expires: DateTime.Now.AddMinutes(10),
                signingCredentials: credentials
                );
            var encodetoken = new JwtSecurityTokenHandler().WriteToken(token);

            var cookieOptions = new CookieOptions();
            //cookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(12);//you can set this to a suitable timeframe for your situation
            cookieOptions.HttpOnly = true;
            cookieOptions.Expires = DateTime.Now.AddMinutes(1);
            //cookieOptions.Domain = Request.Host.Value;
            cookieOptions.Path = "/";
            Response.Cookies.Append("jwt", encodetoken, cookieOptions);

            return encodetoken;
        }

        [HttpPost]
        public IActionResult Login()
        {
            string AccountNumber="TestUser";
            JWTtokenMVC.Models.TestContext userQuery = new JWTtokenMVC.Models.TestContext();
            var query = userQuery.Testxxxx.Where(N => N.UserId ==AccountNumber).FirstOrDefault();
            IActionResult response = Unauthorized();

            if (query != null)
            {
                var tokenStr = GenerateJSONWebToken(query);
                response = Ok(new { token = tokenStr });
            }

            return response;
        }

        [Authorize]
        [HttpGet("Home/Oper")]
        public IActionResult Oper()
        {
            var authenticationCookieName = "jwt";
            var cookie = HttpContext.Request.Cookies[authenticationCookieName];
            List<Test_SHOW> sHOWs = new List<Test_SHOW>();
            JWTtokenMVC.Models.Test.TestContext userQuery= new JWTtokenMVC.Models.Test.TestContext();
            var query = userQuery.Test.Select(T => new Test_SHOW
            {
                number= T.number,
                name= T.name,
                mail= T.mail

            }).OrderBy(o => o.Iid);

            sHOWs.AddRange(query);

            return View("Views/Home/Oper.cshtml", sHOWs);
        }
    }
}

This is Test.cshtml:

@{
    ViewBag.Title = "Home Page";
}

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
    public string GetAntiXsrfRequestToken()
    {
        return Xsrf.GetAndStoreTokens(Context).RequestToken;
    }
}

<input type="hidden" id="RequestVerificationToken"
       name="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">


<form method="post" asp-antiforgery="false">
<!--form -->


    <div>
        <span style="color:red">@ViewBag.Msg</span>
    </div>

    <div class="col-md-4 select-outline">
</div>
    <button type="button" class="btn btn-light">@Html.ActionLink("Test","Oper","Home")</button>
  <button type="button" class="btn btn-light">@Html.ActionLink("TestLogin","Login","Home")</button>

</form>

And finally this is Oper.cshtml:

@using JWTtokenMVC.Models.Test
@model List<Test_SHOW>

@{
    ViewBag.Title = "test";
}

<h2>Test List</h2>

<table class="table table-hover">
    <tr>
        <th>
          number
        </th>
        <th>
           name
        </th>
        <th>
            mail
        </th>
    </tr>
    @foreach (var item in Model)
    {    
        <tr>
             <td>
                @Html.DisplayFor(modelItem => item.number)
            </td>
            <td>
                <span class='text-danger'>@Html.DisplayFor(modelItem => item.name)</span>
            </td>
            <td>
                  <span class='text-danger'>@Html.DisplayFor(modelItem => item.mail)</span>
            </td>
        </tr>
    }
</table>

Here is my appsettings.json file:

{
  "Logging": {
    "LogLevel": {
      "Default": "TestInformation",
      "Microsoft": "TestWarning",
      "Microsoft.Hosting.Lifetime": "TestInformation"
    }
  },
  "AllowedHosts": "*",
  "Jwt": {
    "Key": "TestProdigy",
    "Issuer": "Test.mail.com"
  }
}
Paul
  • 173
  • 1
  • 9
  • 1
    sorry, already updated the code – Paul Jun 07 '21 at 05:52
  • 1
    Change your Jwt key in appsettings.json, I think It should be a length of 16 characters. – Matt Qafouri Jun 07 '21 at 06:12
  • yea, `new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));` will require 16 character long for `SecurityAlgorithms.HmacSha256` .. just ran into that as well and reviewed on [idx10603 SO question](https://stackoverflow.com/questions/47279947/idx10603-the-algorithm-hs256-requires-the-securitykey-keysize-to-be-greater) – Brett Caswell Jun 07 '21 at 06:14
  • Try `app.UseCors("CorsPolicy");` – Yinqiu Jun 07 '21 at 06:27
  • i ref [idx10603 SO question ](https://stackoverflow.com/questions/47279947/idx10603-the-algorithm-hs256-requires-the-securitykey-keysize-to-be-greater) is already updated code but is to read tokens in the button @Html.ActionLink("Test","Oper","Home")and it's [Authorize] header and validate them against my criteria. I don't know what missed but it is always returning the HTTP 401 code. – Paul Jun 07 '21 at 06:33
  • actually.. I don't know why you're relating 2 separate issues in this question actually.. the 401 error is likely related to your response Cookies not be correct (i.e you'll need to define a domain with the port).. the Cors issue isn't related to 401 error at all. and can be handled conditional by handling the build in services.AddCors scope. – Brett Caswell Jun 07 '21 at 06:33
  • Try `app.UseCors("CorsPolicy");`Error: System.InvalidOperationException: The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the CORS policy by listing individual origins if credentials needs to be supported. – Paul Jun 07 '21 at 06:36
  • I tested `GenerateJSONWebToken` and it functions as expected.. I tested with Postman, using the token that is returned in that scope to a request to an authorize attributed endpoint that uses this `AddJwtBearer` configuration.. – Brett Caswell Jun 07 '21 at 06:36
  • Try to change code to `options.AddPolicy("CorsPolicy", builder => builder.AllowAnyMethod().AllowAnyHeader().AllowCredentials() .SetIsOriginAllowed(_ => true))` – Yinqiu Jun 07 '21 at 06:37
  • You can see this [thread](https://mykkon.work/how-to-setup-any-origin/). – Yinqiu Jun 07 '21 at 06:44
  • try `services.AddCors(options => {options.AddPolicy("CorsPolicy", builder => builder.AllowAnyMethod().AllowAnyHeader().AllowCredentials() .SetIsOriginAllowed(hostName => true)); }); ` onclick button ` @Html.ActionLink("Test","Oper","Home")` throws 401 – Paul Jun 07 '21 at 06:46
  • Remember also change code to `app.UseCors("CorsPolicy")` – Yinqiu Jun 07 '21 at 06:48
  • try add code `services.AddCors(options =>{options.AddDefaultPolicy(builder => builder.SetIsOriginAllowed(_ => true) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); });` is ok not error but onclick button ` @Html.ActionLink("Test","Oper","Home")` throws 401 – Paul Jun 07 '21 at 07:02

2 Answers2

3

@Patriom Sarkar's answer addresses your CORS issues/Error

In regards to your 401 Unauthorized Responses

Those are likely unrelated to CORS.

What you're having issues with here is that you have configured JwtBearer JSON Web Tokens to be present in requests for your Authorize endpoints. It will, by default behavior, use Bearer tokens that are present in your Authorization request header.

Which means, in order to navigate/call Oper(), you will need to ensure "Authorization: Bearer {token}" exists with a valid token (as a request header).

Currently, in your Login handling, you're generating the token and doing Set-Cookie so that the user-agent/client creates 'jwt' cookie with the token as a value; However, the user-agent is not going to automatically add that jwt cookie to the headers of subsequent requests as an Authorization: Bearer Token. So JwtBearer configuration on Authorize attributed endpoints aren't going to be valid.

Also worth noting, setting the headers in Xhr or Fetch operations is supported and normal in SPA frameworks (to include Cookie stored jwt_tokens). However, you are not doing Xhr or Fetch requests here, you're doing html form post workflow/mechanic - navigating pages/views. In this respect, setting Authorization Header client-side is (AFAIK) not possible to do.

To support page/view navigation here, you'll need to implement a solution on the server-side that sets the token using the passed jwt cookie.

That solution is covered in In ASP.NET Core read JWT token from Cookie instead of Headers by @Kirk Larkin

    .AddJwtBearer(options => {
            options.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    context.Token = context.Request.Cookies["jwt"];
                    return Task.CompletedTask;
                }
            };
        });

Additionally

context.Token is always null or empty in this scope. No preprocessing nor postprocessing will occur with this declaration and assignment. If you intend to support Authorization Header and Cookie, you should implement that conditionality in this OnMessageReceived assigned delegate.

you can review the default handling of JwtBearerHandler (aspnetcore 5.0) on GitHub.

Thanks again to @Kirk Larkin for providing this additional information in a reply comment on the linked question.

Brett Caswell
  • 1,486
  • 1
  • 13
  • 25
1

As a @Yinkiu mentioned. you have to use the app.UseCors("CorsPolicy") because it's not an angular project you mentioned already.Install NuGet package Microsoft.AspNetCore.Cors

I already answer your one question this related. it's another process.You actually cannot use both AllowAnyOrigin() and AllowCredentials() at the same time.But If you want AllowCredentials() and AllowAnyOrigin() together use this SetIsOriginAllowed(Func<string,bool> predicate)

About IsOriginAllowed

services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy",
                    builder => builder
                    .AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    );

                options.AddPolicy("signalr",
                    builder => builder
                    .AllowAnyMethod()
                    .AllowAnyHeader()

                    .AllowCredentials()
                    .SetIsOriginAllowed(hostName => true));
            });

Pritom Sarkar
  • 2,154
  • 3
  • 11
  • 26