angularasp.net-coreidentityserver4oidc-client-js

identityserver4 adding api resource causing unauthorized requests


I have an application that is using identityServer4 for authentication and Angular10(with oidc-client-js) for front-end and an ASP.NET Core API as the resource API. everything was working fine until I added the following line to the API startup configuration , then after log in all my requests were returning 401.

 services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
        .AddIdentityServerAuthentication(options =>
        {
            options.Authority = Configuration.GetSection("ApplicationSettings").GetValue<string>("Authority");
         1-->   options.ApiName = "app-api";
         2-->   options.RequireHttpsMetadata = false;
         3-->   //options.ApiSecret = "apisecret";
        });

and in the IdSrv config I added

  public static IEnumerable<ApiResource> Apis =>
        new ApiResource[]
        {
       4-->     new ApiResource("app-api", "Application Secured API")
            {
                ApiSecrets = { new Secret("apisecret".Sha256()) }
            }
        };

    public static IEnumerable<ApiScope> ApiScopes =>
        new ApiScope[]
    5-->      { new ApiScope("app-api") }; 

  public static IEnumerable<Client> Clients =>
        new Client[]
        {
            new Client {
                ClientName="Test App",
                ClientId="client-spa",
                AllowedGrantTypes = GrantTypes.Code,
                AlwaysIncludeUserClaimsInIdToken = true,
                RedirectUris = new List<string>() { "https://localhost:44383/signin-callback" , "https://localhost:44383/assets/silent-callback.html"}, 
                PostLogoutRedirectUris = {"https://localhost:44383/signout-callback" },
                AllowedCorsOrigins = { "https://localhost:44383" },
                AccessTokenLifetime = 60*5,
                AllowedScopes = {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
             6-->       "app-api"
                },
                RequireClientSecret=false
            }
        };

Client Startup

 public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = null);

        services.AddSpaStaticFiles(configuration =>
        {
            configuration.RootPath = "ClientApp/dist/ClientApp";
        });

        services.AddDbContext<MasterDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("PortalMaster")));
        services.AddDbContext<LocalDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("PortalLocal")));
        services.AddDbContext<PropelIdentityContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Identity")));

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

        services.AddControllers(options => options.Filters.Add(new PortalHttpResponseExceptionFilter()));
        services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
        // configure Identity Server
        .AddIdentityServerAuthentication(options =>
        {
            options.Authority = "https://localhost:5001/";
            options.ApiName = "app-api";
        });
        
        //Register the Permission policy handlers
        services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
        services.AddSingleton<IAuthorizationHandler, PermissionHandler>();

    }

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

        app.UseHttpsRedirection();

        app.UseStaticFiles();
        if (!env.IsDevelopment())
            app.UseSpaStaticFiles();

        app.UseCors("AllRequests");

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

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

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

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

IdSrv Startup

 public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        //Connection String 
        string connectionString = Configuration.GetConnectionString("Identity");
        services.AddDbContext<AppIdentityContext>(options => options.UseSqlServer(connectionString));


        //AspNetIdentity Configuration 
        services.AddIdentity<AppUser, IdentityRole>(options =>
        {
            options.User.RequireUniqueEmail = false;
            options.Lockout.MaxFailedAccessAttempts = Configuration.GetSection("AppSettings").GetValue<Int32>("MaxFailedAccessAttempts");

            //options.SignIn.RequireConfirmedEmail = true;
            //options.Password.RequiredLength = 8;
            //options.Password.RequireDigit = true;
            //options.Password.RequireUppercase = true;
            //options.Password.RequireLowercase = true;
            //options.Password.RequireNonAlphanumeric = true; 
        })
          .AddUserManager<AppUserManager>()
          .AddEntityFrameworkStores<AppIdentityContext>()
          .AddDefaultTokenProviders();
        //IdentityServer Configuration 
        var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

        var builder = services.AddIdentityServer(options =>
        {
            options.Events.RaiseErrorEvents = true;
            options.Events.RaiseInformationEvents = true;
            options.Events.RaiseFailureEvents = true;
            options.Events.RaiseSuccessEvents = true;
            options.UserInteraction.LoginUrl = "/Account/Login";
            options.UserInteraction.LogoutUrl = "/Account/Logout";
            options.Authentication.CookieLifetime = TimeSpan.FromMinutes(15);
        }).AddAspNetIdentity<PropelUser>()
        .AddProfileService<ProfileService>()
        .AddConfigurationStore(options =>
        {
            options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
        })
        .AddOperationalStore(options =>
        {
            options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
            options.EnableTokenCleanup = true;
        });

        //CORS configuration
        services.AddCors(options =>
        {
            options.AddPolicy("AllowAllOrigins", builder =>
            {
                builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();

            });
        });


        //Signing Credentials. Reading from tempkey saved on project for development, and from SSL certificate on Release
        if (Environment.IsDevelopment())
        {
            builder.AddDeveloperSigningCredential();

        }
        else
        {
            builder.AddSigningCredential(LoadCertificateFromStore());
        }



    }

My question is what is the risk if I remove the ApiResource part(all the marked lines from 1 to 6)? and how can I fix the problem if I should to keep them?


Solution

  • The best practice is to keep your IdentityServer on a separate service, just so that you can reason about how it all works. When its is one place, its really hard to understand what is going on. I would start with a separate IdentityServer, and then merge when it works and when you fully understand what is going on.

    If you get the "You must either set Authority or IntrospectionEndpoint" exception in your API, then the Authority is not properly set. You have the source code for the exception here.

    In your Api startup, I would also set the default authentication scheme to:

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)

    See this page: