Search code examples
c#authenticationidentityserver4asp.net-core-webapiasp.net-core-identity

How to make ASP.Net Core Web API Identity return a 401 on unauthorized


I'm trying to get ASP.net core web api JSON Web Token authentication working properly. I've got to the point where I have successfully integrated IdentityServer4 with the app, and it is successfully processing logins based on ASP.net Core Identity.

However, whenever authentication fails, the API returns a 302 result trying to redirect the client to a login page. However, this is a pure Web API, there is no user pages, or anything that a user is supposed to directly interact with.

How can I get the system to return a 401 rather than trying to redirect to a login page?

The identity part of ConfigureServices looks like this:

       // ASP.net Identity
        var identityBackingStoreConnectionString = configuration["identity:backingStore"];

        services
            .AddIdentityWithMongoStoresUsingCustomTypes<MyApplicationUser, IdentityRole>(
                identityBackingStoreConnectionString)
            .AddDefaultTokenProviders();

        services.AddSingleton<IClientStore, MyIdentityStore>();

        // IdentityServer4
        services.AddIdentityServer().AddTemporarySigningCredential()
            .AddInMemoryApiResources(MyResourceStore.GetAllResources())
            .AddAspNetIdentity<MyApplicationUser>()
            .AddTemporarySigningCredential();

and the relevant (I think) part of Configure looks like this:

        app.UseIdentity();

        app.UseIdentityServer();
        app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions()
        {
            Authority = "http://localhost:5000/",
            RequireHttpsMetadata = false,
            ApiName = "myapi",
            AutomaticAuthenticate = true,
            JwtBearerEvents = new JwtBearerEvents()
            {
                OnAuthenticationFailed = async context => context.Response.StatusCode = 401
            }
        });

        app.UseMvc();

As you can see, I've tried overriding the OnAuthenticationFailed event, but to no avail.

Any suggestions as to how to get the system returning a 401 would be very much appreciated.


Solution

  • Identity server uses cookie authentication internally which converts 401 into 302.

    I don't think you can make app.UseIdentityServer() and app.UseIdentityServerAuthentication() live together because of it.

    However you can easily find a workaround.

    The best is to host identity server in separate application (e.g. identity server at localhost:5000 and application at localhost:5001). It fits better the concept of open id connect and you can enjoy tons of examples at official GitHub

    Alternatively you can try placing Identity server and API at different subpaths like localhost:5000/idsrv and localhost:5000/api using app.UseWhen. For example

    app.UseWhen(
        c => c.Request.Path.StartsWithSegments(new PathString("/api")), 
        branch => {
           branch.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions()
           {
               Authority = "http://localhost:5000/idsrv",
               RequireHttpsMetadata = false,
               ApiName = "myapi",
               AutomaticAuthenticate = true,
           } );
           branch.UseMvc();
       });
    
    app.UseWhen(
        c => c.Request.Path.StartsWithSegments(new PathString("/idsrv")), 
        branch => {
           branch.UseIdentityServer();
           branch.UseMvc();
        } );
    

    Again, this approach is more error-prone and I would rather consider separate applications.