Sam
Sam

Reputation: 30388

ASP.NET Core API returning 401 on call from React client

I'm working on a brand new ASP.NET Core 2.1 SPA app with React/Redux front end. I've implemented jwt authentication which gets its token from Azure AD B2C.

When I analyze the network tab for my API call to the backend, I see that token is placed in the header -- see below:

enter image description here

Here's the code for my fetch call:

import { fetchOptionsGet, fetchOptionsPost, parseJSON } from '../../utils/fetch/fetch-options';

export const getData = () => {

    return (dispatch) => fetch("/api/accounts/test", fetchOptionsGet())
        .then((response) => {

            if (response.ok) {

                parseJSON(response)
                    .then(result => {
                        // Do something here...
                    })
            }
        })
};

Here's my fetch options:

export const fetchOptionsGet = () => {

    const token = authentication.getAccessToken();
    debugger
    return {
        method: 'GET',
        mode: 'cors',
        headers: {
            "Content-Type": "application/json",
            "Authentication": "Bearer " + token
        }
    }
}

Notice the debugger in the above code to make sure I'm getting the token which confirms I have the token -- not to mention it's my network call as well.

Here's the ConfigureServices() method in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
        services.AddAuthentication(options => {
             options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
         })
         .AddJwtBearer(jwtOptions => {
         jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
         jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
         jwtOptions.Events = new JwtBearerEvents
         {
              OnAuthenticationFailed = AuthenticationFailed
         };
     });

     services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

     // In production, the React files will be served from this directory
     services.AddSpaStaticFiles(configuration =>
     {
         configuration.RootPath = "ClientApp/build";
     });
}

Here's the Configure() method in Startup.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
       app.UseDeveloperExceptionPage();
    }
    else
    {
       app.UseExceptionHandler("/Error");
       app.UseHsts();
    }

    app.UseHttpsRedirection();

    ScopeRead = Configuration["AzureAdB2C:ScopeRead"];
    app.UseAuthentication();

    app.UseStaticFiles();
    app.UseSpaStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller}/{action=Index}/{id?}");
    });

    app.UseSpa(spa =>
    {
        spa.Options.SourcePath = "ClientApp";

        if (env.IsDevelopment())
        {
           spa.UseReactDevelopmentServer(npmScript: "start");
        }
     });
}

Here's the API controller:

[Produces("application/json")]
[Route("api/[controller]")]
[Authorize]
public class AccountsController : Controller
{
    [HttpGet("test")]
    public async Task<IActionResult> Test()
    {
        // Do something here...
    }
}

I put a break point right at the beginning of my Test() API method but I'm not hitting it. Without the [Authorize] attribute, I'm able to hit the Test() API method and get my data. So, something in the pipeline is blocking the call before I even hit the API method.

I also tried specifying the authorization scheme in my API controller with the following but that didn't make any difference. Still getting a 401 error.

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

Any idea where I'm making a mistake here?

Upvotes: 4

Views: 1658

Answers (1)

Brad
Brad

Reputation: 4553

The header name should be Authorization.

export const fetchOptionsGet = () => {

    const token = authentication.getAccessToken();
    debugger
    return {
        method: 'GET',
        mode: 'cors',
        headers: {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + token //<--
        }
    }
}

Upvotes: 3

Related Questions