'Identity Server 4 connect/token works when self hosted but not when deployed

I built an .NET Core 3.1 IdentityServer4 to protect some API endpoints:

public static class Config
{
    public static IConfiguration Configuration { get; }
    public static IEnumerable<IdentityResource> IdentityResources =>
               new IdentityResource[]
               {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
               };

    public static IEnumerable<ApiScope> ApiScopes =>
        new ApiScope[]
        {
            new ApiScope("DEMO_SCOPE"),
            new ApiScope("SCOPE1")
        };

    public static IEnumerable<Client> Clients =>
        new Client[]
        {
            new Client
            {
                ClientId = "DemoClientId",
                ClientName = "Client setup for demo and test purposes.",

                AllowedGrantTypes = GrantTypes.ClientCredentials,
                ClientSecrets = { new Secret("11111111-1111-1111-1111-111111111111".Sha256()) },

                AllowedScopes = { "DEMO_SCOPE" }
            },

            new Client
            {
                ClientId = Configuration.GetConnectionString("ClientId"),
                ClientName = "Live Client",

                AllowedGrantTypes = GrantTypes.ClientCredentials,
                ClientSecrets = { new Secret(Configuration.GetConnectionString("ClientSecrets").Sha256()) },

                AllowedScopes = { "SCOPE1" }
            }
        };
}

Then created a self signed certificate both locally and in the server that the IdentityServer4 app is being hosted:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    var builder = services.AddIdentityServer(options =>
    {
        options.Events.RaiseErrorEvents = true;
        options.Events.RaiseInformationEvents = true;
        options.Events.RaiseFailureEvents = true;
        options.Events.RaiseSuccessEvents = true;

        // see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
        options.EmitStaticAudienceClaim = true;
    })
        .AddInMemoryIdentityResources(Config.IdentityResources)
        .AddInMemoryApiScopes(Config.ApiScopes)
        .AddInMemoryClients(Config.Clients)
        .AddAspNetIdentity<ApplicationUser>();

    // not recommended for production - you need to store your key material somewhere secure
    //builder.AddDeveloperSigningCredential();

    //Used Self Signed Certificate
    X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
    var certificate = new X509Certificate2();

    try
    {
        store.Open(OpenFlags.OpenExistingOnly);

        foreach (var cert in store.Certificates)
            if (cert.ToString().Contains("InternalIdServerCert"))
                certificate = cert;
    }
    catch (CryptographicException)
    {
        Console.WriteLine("Could Not Open or Find x509 Certificate Store " + store.Name, store.Location);
    }

    var key = new X509SecurityKey(certificate);
    var credential = new SigningCredentials(key, "RS256");

    builder.AddSigningCredential(credential);

    services.AddAuthentication()
        .AddGoogle(options =>
        {
            options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;

            // register your IdentityServer with Google at https://console.developers.google.com
            // enable the Google+ API
            // set the redirect URI to https://localhost:5001/signin-google
            options.ClientId = "copy client ID from Google here";
            options.ClientSecret = "copy client secret from Google here";
        });
}

Proceeded by implementing the code to protect the API endpoint written in .NET 6:

//IdentityServer4 Implementation
builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority = "https://myserver";
        //options.Authority = "https://localhost:5001";

        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false
        };
    });

//IdentityServer4 Implementation
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ApiScope", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("scope", "DEMO_SCOPE"); //Demo Testing Scope
    });
});

Then tested my work by creating a .NET 6 app that calls my protected API endpoint:

//Setup Http Client, use default credentials for Active Directory
HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });


//Identity Server 4 implementation - discover endpoints from metadata
var disco = await client.GetDiscoveryDocumentAsync("https://myserver");
//var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001/");

if (disco.IsError)
{
    Console.WriteLine(disco.Error);
    return;
}

//Identity Server 4 implementation - request tokenappsettings.json
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
    Address = disco.TokenEndpoint,

    ClientId = "DemoClientId",
    ClientSecret = "11111111-1111-1111-1111-111111111111",
    Scope = "DEMO_SCOPE"

});

if (tokenResponse.IsError)
{
    Console.WriteLine(tokenResponse.Error);
    return;
}

Console.WriteLine(tokenResponse.Json);

The application works great when I run all 3 apps in localhost, however, when I deployed my IdentityServer4 app into a different hosting environment, I can connect to https://myserver/.well-known/openid-configuration just fine, but requesting the token from connect/token returns a 400 bad request with a message of "invalid_client". I'm confused as to why it all works when self hosted but then making the same exact request, except for this time I'm calling https://myserver/connect/token instead of https://localhost:5001/connect/token, all of the sudden there's something wrong with the request.

Here's what the log says:

    No secret in post body found 
    Parser found no secret
    
   {"ClientId":"unknown","Category":"Authentication","Name":"Client Authentication Failure","EventType":"Failure","Id":1011,"Message":"No client id found","ActivityId":"80003280-0001-e100-b63f-84710c7967bb","TimeStamp":"2022-04-27T16:28:04.0000000Z","ProcessId":11316,"LocalIpAddress":"","RemoteIpAddress":"","$type":"ClientAuthenticationFailureEvent"} 
    No client identifier found

Any help or comments are greatly appreciated. Thanks all.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source