'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 |
---|