2
votes

I am facing the following problem while hosting a web app built with asp.net core 3.1 and React.

We have used default visual studio template for React. ASP.NET Identity is used for authentication and authorization.

Authentication and Authorization work as expected as long as we host the website with an SSL certificate issued for single domain or CN. (e.g. example.com)

If we host he website with an SSL with multiple CNs (e.g. example.com, sub1.example.com, sub2.example.com), it works fine for any ONE of the domains. For the remaining domains we get the following behavior:

The login works as expected. The /connect/token path issues valid token. Once logged in, when we try to invoke any api (all apis are hosted under /api route), we get 401 unauthorized error. Error description in the header:

WWW-Authenticate: Bearer error="invalid_token", error_description="The issuer 'https://sub1.example.com' is invalid".

I also tried parsing the issued token on jwt.io. The iss field (issuer) is https://sub1.example.com which exactly matches the error description. I cannot fathom why identity engine refuses to identify the issuer for which it issued token for.

Here is relevant snippet from Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentityServer()
            .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
    services.AddAuthentication()
            .AddIdentityServerJwt();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseAuthentication();
    app.UseIdentityServer();
    app.UseAuthorization();
}

Any ideas?

3
One more observation, in case of multi CN SSL, the web work with the first domain the website is loaded with, for all other domains the /api calls are returned with 401 status code. I have also added relevant confirmation from Startup.cs for reference. - Ravi M Patel
@TarunLalwani, as per the documentation, AllowedHosts is used to "restrict" hosts (in X-forwarded header) to the specified values. So not specifying any should not cause this error. - Ravi M Patel
"it works fine for any ONE of the domains." -- what do you mean by this? Is there a different instance of IdentityServer on each domain and it only works when one of them is up? Are some of the domains pure API endpoints for which the access token works for one of them? - Randy

3 Answers

0
votes

This is probably happening as a result of receiving the token from an instance of IdentityServer4 on one CN, and trying to validate it with a request to IdentityServer4 using another CN. The IdentityServer component that's rejecting the token is TokenValidator's ValidateJwtAsync method. This method passes in the issuer into JwtSecurityTokenHandler's ValidateToken as a property of TokenValidationParameters. The issuer is retrieved from either the issuer configured on the IdentityServerOptions in the 'AddIdentityServer' extension method, or is dynamically generated from the request.

I can think of one way to resolve the validation problems, and that is to set the issuer on the IdentityServerOptions using the delegate passed into AddIdentityServer. This will result in the same issuer being set for all tokens issued, regardless of the CN it was accessed from. This would allow IdentityServer a single source of truth for issuer information, and will allow IdentityServer to know which issuer to verify against when a token comes in for validation.

Other solutions of trying to maintain the issuer are heavily restricted by the TokenValidator being an internal class that can't be inherited and easily replaced with an implementation that will validate against a list of valid issuers. Additionally, the IdentityServerOptions that's configured to have the issuer uri is registered as a singleton and cannot have its values changed. Other contrived implementation could be devised like attempting to dynamically change the host value on the HttpContext with a middleware (which I'm not sure is even possible since I've never tried), but anything that goes against IdentityServer4's design decision is not advised.

0
votes

Please check url http://{url}/.well-known/openid-configuration

This url is should be true

Following codes are worked different domain. Auth Startup

 services.AddIdentityServer(options =>
        {
            options.IssuerUri = Configuration["ServerSettings:Authority"].ToString();
            options.PublicOrigin = Configuration["ServerSettings:Authority"].ToString();
        })
            .AddDeveloperSigningCredential()
            .AddInMemoryApiResources(Config.GetApiResources())
            .AddInMemoryIdentityResources(Config.GetIdentityResources())
            .AddInMemoryClients(Config.GetClients())
            .AddProfileService<ProfileService>();

Api Startup

services.AddAuthentication("Bearer")
        .AddIdentityServerAuthentication(options =>
        {
            options.Authority = Configuration["ServerSettings:Authority"].ToString(); //"http://localhost:31864";
            options.RequireHttpsMetadata = false;
            options.ApiName = "api";
        });

Works in the same domain but if different domain you should specify this

0
votes

The new .Net (.net core) is highly configurable and modular. Usually the extension methods take a delegate which we can use to configure options. However, AddIdentityServerJwt method doesn't follow that convention.

I noticed long time ago that there is a property called ValidIssuers in TokenValidationParameters which can be configured with AddJwtBearer extension method. However, AddIdentityServerJwt extension method doesn't accept any options delegate as parameter.

It turns out that there is a special way to configure options.

services.AddAuthentication()
    .AddIdentityServerJwt();

services.Configure<JwtBearerOptions>(IdentityServerJwtConstants.IdentityServerJwtBearerScheme, options =>
{
    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
    {
        ValidIssuers = new string[] { "https://sub1.example.com", "https://sub2.example.com", "https://sub3.example.com" }
    };
});

Added this code and problem solved. Configuration can also be moved to appsettings.json.