2

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?

David Liang
  • 20,385
  • 6
  • 44
  • 70
Ravi M Patel
  • 2,905
  • 2
  • 23
  • 32
  • 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 Feb 16 '20 at 13:58
  • Did you try with allowedHosts? https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-3.1 – Tarun Lalwani Feb 17 '20 at 07:40
  • @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 Feb 17 '20 at 10:08
  • Does this help? https://stackoverflow.com/questions/273732/how-can-i-share-a-session-across-multiple-subdomains-in-asp-net – Tarun Lalwani Feb 17 '20 at 12:32
  • "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 Feb 17 '20 at 15:28
  • @RaviMPatel Can you confirm that your host support .net core and ReactJS? – Mark Spencer Feb 17 '20 at 15:45
  • @Randy, we don't have different instances of Identity Server. Clarification on "it works fine for ONE of the domains" : Let say three domains d1.com, d2.com and d3.com are pointing to single IP address and hence the website. The site works with ONLY one of them. And lucky domain is the one with which we tried to load the site for the first time after application pool recycle. It is necessary that all three domains be part of single SSL certificate. – Ravi M Patel Feb 18 '20 at 06:02
  • @MarkSpencer, We are using Azure. So yes it does support. To reproduce we also hosted the site on IIS. Please note that the site works as expected with one of the domains. – Ravi M Patel Feb 18 '20 at 06:06

3 Answers3

1

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.

Ravi M Patel
  • 2,905
  • 2
  • 23
  • 32
0

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.

Randy
  • 1,212
  • 8
  • 14
  • We have tried to set TokenValidationParameters to set issuers several times. No luck so far. – Ravi M Patel Feb 19 '20 at 19:11
  • Where (in what context) did you set the TokenValidationParameters? – Randy Feb 19 '20 at 22:32
  • we tried to set `IssuerUri` property in `AddIdentityServer` extension method. It did not help. It works only for the uri we specify here. Not for the others. Thanks. – Ravi M Patel Feb 21 '20 at 10:58
0

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

ismail ERDEN
  • 138
  • 1
  • 9