IdentityServer4 Windows Authentication without UI

Viewed 607

I am wanting to be able to use IdentityServer4 as the STS for applications using Windows Authentication. The majority of my applications do not have a UI component (they are just ASP.NET Core APIs). I have been able to get my IdentityServer4 Windows Authentication working with a MVC (a UI) client by following this guide and using the "is4inmem" identity server template. The MVC client I got it working with was this sample client. I am now attempting to set up my api client. All of my applications are hosted using kestrel with IIS underneath in order to enable Windows Auth. Below is what my Startup.cs for my api client looks like, which I believe is the issue.

    public class Startup
    {
        public IWebHostEnvironment HostingEnvironment { get; private set; }
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration, IWebHostEnvironment env)
        {
            HostingEnvironment = env;
            Configuration = configuration;
        }   

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddAuthentication("token")
                .AddJwtBearer("token", options => {
                    options.Authority = "https://localhost:44353";
                    options.TokenValidationParameters.ValidateAudience = false;
                    options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
                });

            services.AddAuthorization(options => {
                options.AddPolicy("scope", policy => {
                    policy.AddAuthenticationSchemes("token")
                        .RequireAuthenticatedUser()
                        .RequireClaim("scope", "api1");
                });
            });

            AppServSettings.WebHostEnvironment = HostingEnvironment;

            var appSettingsSection = Configuration.GetSection("appSettings");
            var connectionStringsSection = Configuration.GetSection("appSettings");

            services.Configure<AppSettings>(appSettingsSection)
                    .Configure<ConnectionStrings>(connectionStringsSection);

            services.AddControllersWithViews(options => {
                options.InputFormatters.Add(new ProtobufInputFormatter());
                options.OutputFormatters.Add(new ProtobufOutputFormatter());
            });

            var appSettingsOptions = new AppSettings();
            appSettingsSection.Bind(appSettingsOptions);

            var connectionStringsOptions = new ConnectionStrings();
            connectionStringsSection.Bind(connectionStringsOptions);

            AppServSettings.AppSettings = new AppEnvSettings(Options.Create<AppSettings>(appSettingsOptions), Options.Create<ConnectionStrings>(connectionStringsOptions));

            AppServ.Preload();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.UseRouting();

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

            app.UseEndpoints(
                endpoints => endpoints.MapControllers()
            );
        }
    }

I am also including my IdentityServer Config.cs to give a better picture of my project.

    public static class Config
    {
        public static IEnumerable<IdentityResource> Ids =>
            new IdentityResource[]
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email(),
                new IdentityResources.Address(),
            };


        public static IEnumerable<ApiResource> Apis =>
            new ApiResource[]
            {
                // new ApiResource("api1", "My API #1")

                new ApiResource("api1", "My API", new[] { JwtClaimTypes.Subject, JwtClaimTypes.Email, JwtClaimTypes.Address, "upn_custom"})
            };


        public static IEnumerable<Client> Clients =>
            new Client[]
            {
                // client credentials flow client
                new Client
                {
                    ClientId = "identity.server",
                    ClientName = "Identity Server Client",

                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    AlwaysIncludeUserClaimsInIdToken = true,
                    ClientSecrets = { new Secret("secret".Sha256()) },

                    AllowedScopes = { "openid", "profile", "email", "address", "api1", "upn_custom" }
                },

                // MVC client using code flow + pkce
                new Client
                {
                    //ClientId = "mvc",
                    ClientId = "mvc.code",
                    ClientName = "MVC Client",

                    // Note
                    AlwaysIncludeUserClaimsInIdToken = true,
                    
                    AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
                    //RequirePkce = true,
                    RequirePkce = false,
                    //ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },
                    ClientSecrets = { new Secret("secret".Sha256()) },

                    //RedirectUris = { "https://localhost:5003/signin-oidc" },
                    RedirectUris = { "https://localhost:5003/signin-oidc" },
                    FrontChannelLogoutUri = "https://localhost:5003/signout-oidc",
                    PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc" },

                    AllowOfflineAccess = true,
                    AllowedScopes = { "openid", "profile", "email", "address", "api1", "upn_custom" }
                },

                // MVC client using code flow + pkce
                new Client
                {
                    //ClientId = "mvc",
                    ClientId = "ptp.appserv",
                    ClientName = "PTP AppServ",

                    // Note
                    AlwaysIncludeUserClaimsInIdToken = true,

                    AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
                    //RequirePkce = true,
                    RequirePkce = false,
                    //ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },
                    ClientSecrets = { new Secret("secret".Sha256()) },

                    //RedirectUris = { "https://localhost:30001/signin-oidc" },
                    RedirectUris = { "https://localhost:30001/signin-oidc" },
                    FrontChannelLogoutUri = "https://localhost:30001/signout-oidc",
                    PostLogoutRedirectUris = { "https://localhost:30001/signout-callback-oidc" },

                    AllowOfflineAccess = true,
                    AllowedScopes = { "openid", "profile", "email", "address", "api1", "upn_custom" }
                },

                // MCW Appserver
                new Client
                {
                    //ClientId = "mvc",
                    ClientId = "mcw.appserver",
                    ClientName = "MCW AppServer",

                    // Note
                    AlwaysIncludeUserClaimsInIdToken = true,

                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    RequirePkce = true,
                    //RequirePkce = false,
                    //ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },
                    ClientSecrets = { new Secret("secret".Sha256()) },

                    //RedirectUris = { "http://localhost:16835/signin-oidc" },
                    //RedirectUris = { "https://localhost:16385/signin-oidc" },
                    //FrontChannelLogoutUri = "https://localhost:16835/signout-oidc",
                    //PostLogoutRedirectUris = { "https://localhost:16835/signout-callback-oidc" },

                    AllowOfflineAccess = true,
                    AllowedScopes = { "openid", "profile", "email", "address", "api1", "upn_custom" }
                },

                // SPA client using code flow + pkce
                new Client
                {
                    ClientId = "spa",
                    ClientName = "SPA Client",
                    ClientUri = "http://identityserver.io",

                    AllowedGrantTypes = GrantTypes.Code,
                    RequirePkce = true,
                    RequireClientSecret = false,

                    RedirectUris =
                    {
                        "http://localhost:5002/index.html",
                        "http://localhost:5002/callback.html",
                        "http://localhost:5002/silent.html",
                        "http://localhost:5002/popup.html",
                    },

                    PostLogoutRedirectUris = { "http://localhost:5002/index.html" },
                    AllowedCorsOrigins = { "http://localhost:5002" },

                    AllowedScopes = { "openid", "profile", "api1" }
                }
            };
    }

I am able to hit my API but when it tries to Authorize I get a HTTP 401 returned along with an HTML page for invalid login. Hopefully someone can point out what I am doing wrong here. I appreciate any help.

EDIT:

The endpoint I am hitting in my API is shown below.

    [Route("api/[controller]")]
    [Authorize]
    [ApiController]
    public class IdentityController : ControllerBase
    {
        public IActionResult Get()
        {
            return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
        }
    }

The client I am trying to use to hit that endpoint is Postman using NTLM Authorization Scheme. Additionally I have the cookies ".AspNetCore.Antiforgery.WxKm21GkYM0" and "idsrv.external" set. I am also currently using IdentityServer4 version 3.1.3 from NuGet.

Below is the request I am sending with Postman with the body tab open. As you can see I set the grant_type, scope, client_id, and client_secret.

enter image description here

0 Answers
Related