Morgan Bradford
Morgan Bradford

Reputation: 41

React - How to Send JWT via Headers

I have a .NET 7 Web API which has authentication/authorization via JWT. Currently the app correctly blocks API requests without a JWT and allows them with it as tested via SwaggerUI and Postman; however, I am unable to send the request correctly via React.

Method sending request:

const getTopics = () => {
    const url = API_ROUTES.TOPICS_GET_ALL_TOPICS;
    fetch(url, {
      method: "GET",
      headers: {
        'Authorization': `Bearer ${jwt}`
      }
    })
    .then(res => res.json())
    .then(topicsFromServer => {
      console.log(topicsFromServer);
      setTopics(topicsFromServer);
    })
    .catch((error => {
      console.error(error);
    }))
  }

If I console log the response above, I receive the following:

Response {type: 'cors', url: 'https://localhost:7174/Account/Login?ReturnUrl=%2Ftopic%2Fget-all-topics', redirected: true, status: 405, ok: false, …}
body
: 
(...)
bodyUsed
: 
false
headers
: 
Headers {}
ok
: 
false
redirected
: 
true
status
: 
405
statusText
: 
""
type
: 
"cors"
url
: 
"https://localhost:7174/Account/Login?ReturnUrl=%2Ftopic%2Fget-all-topics"
[[Prototype]]
: 
Response

The response url is not an existing endpoint, but I'm guessing it is auto-generated by .NET [Authorize] attribute.

Cors Policy in .NET:

builder.Services.AddCors(options =>
{
    options.AddPolicy("CORSPolicy", builder =>
    {
        builder
        .AllowAnyMethod()
        .AllowAnyHeader()
        .WithOrigins("http://localhost:3000", "https://localhost:3000");
    });
});

Not sure if this helps, but this is my Swagger Gen configuration:

builder.Services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("auth0", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey
    });

    options.OperationFilter<SecurityRequirementsOperationFilter>();
});

If there is any other info needed please let me know. Thank you!

Edit:

After further testing, it appears that the jwt is still not being utilized in anyway. Once a user logs into their account, a cookie is then stored in the browser (SwaggerUI) or Postman, allowing them access to the API without the JWT.

The cookie being utilized:

.AspNetCore.Identity.Application=ReallyLongString; Path=/; Secure; HttpOnly; Expires=Fri, 31 Mar 2023 23:07:00 GMT;

Here is Program.cs in case there is an issue there:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("CORSPolicy", builder =>
    {
        builder
        .AllowAnyMethod()
        .AllowAnyHeader()
        .WithOrigins("http://localhost:3000", "https://localhost:3000", "https://appname.asurestaticapps.net");
    });
});
// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("auth0", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey
    });

    options.OperationFilter<SecurityRequirementsOperationFilter>();
});

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

builder.Services.AddAuthorization();

builder.Services.AddDbContext<SdeResearchDbContext>();
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<SdeResearchDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddScoped<JwtService>();

builder.Services.Configure<IdentityOptions>(options =>
{
    // Password Settings.
    options.Password.RequireDigit= true;
    options.Password.RequireLowercase= true;
    options.Password.RequireNonAlphanumeric= true;
    options.Password.RequireUppercase= true;
    options.Password.RequiredLength= 8;
    options.Password.RequireLowercase= true;
    options.Password.RequiredUniqueChars = 6;

    // User settings
    options.User.AllowedUserNameCharacters =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+!";
    options.User.RequireUniqueEmail= true;
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors("CORSPolicy");
app.UseAuthentication();
app.UseAuthorization();


app.MapControllerRoute(
    name: "Topic",
    pattern: "topic/*{action}"
    );

app.Run();

TopicController has [Authorize] at the top of the controller.

Upvotes: 0

Views: 129

Answers (1)

Tahbaza
Tahbaza

Reputation: 9546

Here is an example of react using the axios package to do the API call against a C# API endpoint using what seem to be your variable names and endpoint routes.

import axios from 'axios';

const getTopics = () => {
  return axios.get(API_ROUTES.TOPICS_GET_ALL_TOPICS, {
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      Authorization: `Bearer ${jwt}`
    }
  }).then(response => {
    console.log(response.data);
    setTopics(response.data);
  }).catch(error => console.error(error));
};

Upvotes: 0

Related Questions