Search code examples
.netasp.net-coreidentityserver4

Getting a 404 when accessing an [Authorize] controller when authenticated


I'm trying to implement authentication and access control with IdentityServer4 on an ASP.NET MVC Core app (.NetCore 2). While it's not the first time I implement a backend, it's the first time with .net, and I'm struggling with some things.

I've followed the instructions at https://identityserver4.readthedocs.io/en/release/quickstarts/1_client_credentials.html as well as the page before that.

I have also added the sample IdentityController as they show:

using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace leafserver.Controllers
{
    [Route("/api/identity")]
    [Authorize]
    public class IdentityController : Controller
    {
        [HttpGet]
        public IActionResult Get()
        {
            return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
        }
    }
}

There are a few differences between my implementation and their example. As far as I can see:

  • I'm serving on my local network address (192.168.1.x) instead of localhost
  • They're using a "Web Application", where I'm using a "Web Api"
  • They seem to use ControllerBase instead of Controller as a superclass
  • I'm not sure whether there's a difference between the ASP.NET MVC they use and the one I use (I'm using core, they don't seem to, but normally it should still work...)

What I noticed is the following:

  • as long as I don't put a [Authorize], all is well. I get a 200 OK with the expected result
  • when the [Authorize] annotation is there, but I use no authentication bearer token, I am redirected to the login page (which doesn't work since this is a web api, but that's a problem for later)
  • when the [Authorize] annotation is there, and I use (what I think is) a correct authentication token, I get a 404 response.

I was expecting to have a 401 response instead. Why would my routing not work because I'm using an authentication token?

Also, I'm not getting any log from the server, which doesn't help...


Solution

  • Alright, I've found the problem.

    In my Startup.ConfigureServices, I modified the order in which I add the services.

        // https://identityserver4.readthedocs.io/en/release/quickstarts/1_client_credentials.html
        services.AddIdentityServer()
                .AddDeveloperSigningCredential()
                .AddInMemoryApiResources(Config.GetApiResources())
                .AddInMemoryClients(Config.GetClients())
                .AddTestUsers(Config.GetTestUsers()); // TODO Remove for PROD
    
        // This MUST stay below the AddIdentityServer, otherwise [Authorize] will cause 404s
        services.AddAuthentication("Bearer")
                .AddIdentityServerAuthentication(o =>
                {
                    o.Authority = "http://localhost:5000";
                    o.RequireHttpsMetadata = false; // TODO Remove for PROD
                    o.ApiName = "leaf_api";
                });
    

    If you add authentication before the identity server, then you'll get the 404s. In this order, it works just fine.

    Here's the full Startup.cs file for reference:

    using leafserver.Data;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Versioning;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace leaf_server
    {
        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.AddDbContext<LeafContext>(options => options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
    
                services.AddMvcCore()
                        .AddAuthorization()
                        .AddJsonFormatters();
    
                // https://identityserver4.readthedocs.io/en/release/quickstarts/1_client_credentials.html
                services.AddIdentityServer()
                        .AddDeveloperSigningCredential()
                        .AddInMemoryApiResources(Config.GetApiResources())
                        .AddInMemoryClients(Config.GetClients())
                        .AddTestUsers(Config.GetTestUsers()); // TODO Remove for PROD
    
                // This MUST stay below the AddIdentityServer, otherwise [Authorize] will cause 404s
                services.AddAuthentication("Bearer")
                        .AddIdentityServerAuthentication(o =>
                        {
                            o.Authority = "http://localhost:5000";
                            o.RequireHttpsMetadata = false; // TODO Remove for PROD
                            o.ApiName = "leaf_api";
                        });
    
                // https://dotnetcoretutorials.com/2017/01/17/api-versioning-asp-net-core/
                services.AddApiVersioning(o =>
                {
                    o.ReportApiVersions = true;
                    o.AssumeDefaultVersionWhenUnspecified = true;
                    o.DefaultApiVersion = new ApiVersion(1, 0);
                    o.ApiVersionReader = new HeaderApiVersionReader("x-api-version");
                });
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                    app.UseDatabaseErrorPage();
                    app.UseStatusCodePages();
                }
    
                app.UseIdentityServer();
                app.UseAuthentication();
    
                app.UseMvc();
            }
        }
    }