Angular + .net Core 5 API + Auth0 : Http 401

Hello there,
I’m trying very hard to implement Auth0 in my Angular 9.1.14 application, but I can’t still figure out what the problem.

I’ve been able to add login the my my app, but I fail to implement call to my C# .Net Core 5 REST API.
I’ve flowed the tutorial many time, but still all call I made to my API end with * HTTP Error. 401*

Here more details:

Angular App config:
AppModule:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { HomeComponent } from './home/home.component';
import { DevicesComponent } from './Device/devices.component';
import { DeviceComponent } from './Device/device.component';
import { SelectListComponent } from './components/selectlist.component/select.list.component';    import { GeneralNoteComponent } from './components/generalNote.compoment/generalNote.compoment';    import { DeviceMaintenanceComponent } from "./components/devicemaintenance.component/devicemaintenance.component";    import { DeviceLoanComponent } from './components/deviceloan.component/deviceloan.component';
import { AuthModule } from '@auth0/auth0-angular';    import { LoggedUserComponent } from './components/loggedUser.Component/loggedUser.Component';
import { LoginComponent } from './components/login.component/login.component';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthHttpInterceptor, AuthGuard } from '@auth0/auth0-angular';
@NgModule({

  declarations: [
    AppComponent,
    NavMenuComponent,
    HomeComponent,
    DevicesComponent,
    DeviceComponent,
    SelectListComponent,
    DeviceMaintenanceComponent,
    DeviceLoanComponent,
    GeneralNoteComponent,
    LoggedUserComponent,
    LoginComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
    HttpClientModule,
    FormsModule,
    RouterModule.forRoot([
      { path: '', component: DevicesComponent, canActivate: [AuthGuard] },
      { path: 'login', component: LoginComponent },
      { path: 'device', component: DeviceComponent, canActivate: [AuthGuard] },
      { path: 'generalNote', component: GeneralNoteComponent, canActivate: [AuthGuard] }
    ]),
    AuthModule.forRoot({
      domain: 'dev-***.us.auth0.com',
      clientId: 'Sk*********dm',
      audience: 'https://dev-***.us.auth0.com/api/v2/',
      httpInterceptor: {
        allowedList: [
          {
            // Match any request that starts 'https://dev-sss.us.auth0.com/api/v2/' (note the asterisk)
            uri: 'https://dev-***.us.auth0.com/api/v2/*',
            tokenOptions: {
              // The attached token should target this audience
              audience: "https://dev-***.us.auth0.com/api/v2/",
              // The attached token should have these scopes
              scope: 'device:read'
            }
          }
        ]
      }
    })
  ],
  providers: [AuthGuard,
    { provide: HTTP_INTERCEPTORS, useClass: AuthHttpInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

API Call:

 const options = { params: new HttpParams() };    
 this.http.get<device[]>(this.apiUrl + "device/getall", options)
          .subscribe(
            result => this.devices = result,
            error => console.error(error)
          );

.Net Core 5 API

namespace Equipment.Api
{
    using System;
    using System.Threading.Tasks;
    using Common.Interfaces;
    using Laumie.Infrastructure.Web.Authorization;
    using Laumie.Infrastructure.Web.Middleware;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    using Microsoft.OpenApi.Models;
    using Presentation;
    using Presentation.Interface;
    using Services;

    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.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "Equipment.Api", Version = "v1" });
            });
            services.AddMvc(x => x.EnableEndpointRouting = false);

            services.AddCors(options =>
            {
                options.AddPolicy("AllowSpecificOrigin",
                    builder =>
                    {
                        builder
                            .WithOrigins("http://localhost:4200")
                            .AllowAnyMethod()
                            .AllowAnyHeader()
                            .AllowCredentials();
                    });
            });

            services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(options =>
                {
                    options.Authority = "dev-***.us.auth0.com";
                    options.Audience = "apikey://666fcd34*****";
                    options.RequireHttpsMetadata = false;
                    options.Events = new JwtBearerEvents
                    {
                        OnAuthenticationFailed = context =>
                        {
                            throw new Exception($"Authentication failed for MetadataAddress {context.Options.MetadataAddress} with exception: {context.Exception}");
                            var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(JwtBearerEvents));
                            logger.LogWarning($"Authentication failed for MetadataAddress {context.Options.MetadataAddress} with exception: {context.Exception}");
                            return Task.CompletedTask;
                        }
                    };
                });

            services.AddAuthorization(options =>
            {
                options.AddPolicy("device:read", policy => policy.Requirements.Add(new HasScopeRequirement("device:read", "dev-***.us.auth0.com")));
            });

            services.RegisterDataServices();

            services.AddSingleton<IAuthorizationHandler, HasScopeHandler>();
            services.AddTransient<SelectListService>();
            services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();

            services.AddScoped<HostNameProviderService>();

            services.AddTransient<IGeneralNoteService, GeneralNoteService>();
            services.AddTransient<IDeviceService, DeviceService>();
            services.AddTransient<ICountryService, CountryService>();
            services.AddTransient<IDeviceService, DeviceService>();
            services.AddTransient<IDeviceLoanService, DeviceLoanService>();
            services.AddTransient<DeviceMaintenanceService>();
            services.AddTransient<ICountryPresentationService, CountryPresentationService>();
            services.AddTransient<IDevicePresentationService, DevicePresentationService>();
        }

        // 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();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Equipment.Api v1"));
            }

            app.UseMiddleware<ErrorLoggerMiddleware>();
            app.UseMiddleware<TimedHttpCallMiddleware>();
            app.UseCors("AllowSpecificOrigin");
            app.UseAuthentication();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

API Controller:

namespace Equipment.Api.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Common.DtoModel;
    using Common.Interfaces;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;

    [ApiController]
    [Route("[controller]")]
    public class DeviceController : ControllerBase
    {
        private readonly IDeviceService _deviceService;

        public DeviceController(IDeviceService deviceService)
        {
            _deviceService = deviceService;
        }

        [Authorize]
        [HttpGet("GetAll")]
        public async Task<IEnumerable<Device>> GetAll()
        {
            return await _deviceService.GetAllAsync();
        }

        [HttpGet]
        public async Task<Device> Get(Guid id)
        {
            return await _deviceService.GetByIdAsync(id);
        }

        [HttpPost]
        public async Task Post([FromBody] Device device)
        {
            await _deviceService.UpdateAsync(device);
        }

        [HttpPut]
        public async Task Put([FromBody] Device device)
        {
            await _deviceService.Insert(device);
        }

    }
}

The error that I get from Chrome Debugger:

{
    "headers": {
        "normalizedNames": {},
        "lazyUpdate": null,
        "headers": {}
    },
    "status": 401,
    "statusText": "OK",
    "url": "https://localhost:44335/device/getall",
    "ok": false,
    "name": "HttpErrorResponse",
    "message": "Http failure response for https://localhost:44335/device/getall: 401 OK",
    "error": null
}

Auth0 Application API Config
- Identifier : “apikey://666fcd34*****”"
- Enable RBAC : On
- Permission: device:read

Auth0 Application Config
Domain: dev-*** .us.auth0.com
Client Id: Sk*********dm
Application Type : SPA
Token Endpoint: None
Grant Type: implicit, AuthorizationCode, RefreshToken

API URL: http://localhost:62816 / https://localhost:44335

Environement

  • Visual Studio 2019 (last update - 16.9.3)
  • Windows 10 (last update - 20H2)
  • Angular 9.1.14
  • .Net Core Framework 5.0

Hi @hugodufort,

Will you please print and decode the access token and confirm that the token audience matches the API identifier you are validating for in your .net app?

You can decode and inspect the access token with JWT.io.

If you are having trouble, you can DM me the token and I will take a look.

Thanks for your reply, since the application is a SPA, I can’t figure out how to get the Token.
How to get it when the Grant Type is: Implicit, Authorization Code, Refresh Token?

I might be easier just to send me a HAR of the entire transaction.

Please DM me the recorded HAR.

OK , I’ve created a Test EndPoint application with the “Test” Tab from the EndPoint config page.
And I have a token,
The header is:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "lSUjob6JrpUXVpMNF0aiL"
}

the payload is

{
  "iss": "https://dev-976r1e2b.us.auth0.com/",
  "sub": "WgWiktEzuBU9jDjnLGnEHrIbjrdkaRDV@clients",
  "aud": "apikey://666fcd34*****",
  "iat": 1618344383,
  "exp": 1618430783,
  "azp": "WgWiktEzuBU9jDjnLGnEHrIbjrdkaRDV",
  "gty": "client-credentials"
}

Are you good with that?

We need to see the token requested by your SPA. I am specifically looking for the aud claim. It looks like you are requesting a token in your SPA with a different audience than what your API is expecting.

I sent you the HAR file in DM