Identityserver4: HttpContextExtensions.GetIdentityServerIssuerUri lower case the URL

Created on 8 Sep 2017  路  19Comments  路  Source: IdentityServer/IdentityServer4

This GetIdentityServerIssuerUri calls the ToLowerInvariant to return lower case URL

        public static string GetIdentityServerIssuerUri(this HttpContext context)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));

            // if they've explicitly configured a URI then use it,
            // otherwise dynamically calculate it
            var options = context.RequestServices.GetRequiredService<IdentityServerOptions>();
            var uri = options.IssuerUri;
            if (uri.IsMissing())
            {
                uri = context.GetIdentityServerBaseUrl();
                if (uri.EndsWith("/")) uri = uri.Substring(0, uri.Length - 1);
                uri = uri.ToLowerInvariant();
            }

            return uri;
        }

I set up the IS4 service name with mixed case (e.g. MyIS)

When it browse the discovery end point, only the issuer field returns all lower case url. The other URL shows the mixed case:

http://myserver/MyIS/.well-known/openid-configuration

{
issuer: "http://myserver/myis",
jwks_uri: "http://myserver/MyIS/.well-known/openid-configuration/jwks",
authorization_endpoint: "http://myserver/MyIS/connect/authorize",
token_endpoint: "http://myserver/MyIS/connect/token",
userinfo_endpoint: "http://myserver/MyIS/connect/userinfo",
end_session_endpoint: "http://myserver/MyIS/connect/endsession",
check_session_iframe: "http://myserver/MyIS/connect/checksession",
revocation_endpoint: "http://myserver/MyIS/connect/revocation",
introspection_endpoint: "http://myserver/MyIS/connect/introspect",

The ValidateIssuer function in the Microsoft.IdentityModel.Tokens.Validators validate the issuer case insensitively so my token is treated as invalid and I have to explicitly set the TokenValidationParameters.ValidIssuer to lower case to make them matched.

            if (string.Equals(validationParameters.ValidIssuer, issuer, StringComparison.Ordinal))
            {
                IdentityModelEventSource.Logger.WriteInformation(LogMessages.IDX10236, issuer);
                return issuer;
            }

Can you remove this line? Don't know why the issuer URL need to be lower case?

uri = uri.ToLowerInvariant();

question

All 19 comments

I just double check my JWT token, its "iss" is really all lower case. I think it is because the DefaultTokenService.cs is calling the same GetIdentityServerIssuerUri to create the Issuer.

My token validation fails because I don't set the Issuer value returned from the Discovery Endpoint to the TokenValidationParameters.ValidIssuer. Instead, I set it by a config value. I think I do it wrong.

According to the spec, the "iss" value is a case-sensitive string containing a StringOrURI value. I still think it is not a good idea to force it to lower case when the comparison is case sensitive.

If the token creation and Discovey endpoint both calls that same GetIdentityServerIssuerUri, then the "Iss" in the JWT token and the one set to the TokenValidationParameters.ValidIssuer will be the same no matter if it is lower case or not.

If it is by design, you can close this issue.

It's by design - to minimize validation problems based on casing. IIS seems to be a bit special sometimes when it comes to that...

I just find out a real issue.

    var discoveryClient = new DiscoveryClient("http://myserver/MyIS/");
    var disco = await discoveryClient.GetAsync();

DiscoveryResponse will return this error:

    var isValid = ValidateIssuerName(Issuer.RemoveTrailingSlash(), policy.Authority.RemoveTrailingSlash());
    if (!isValid) return "Issuer name does not match authority: " + Issuer;

My workaround is to pass the ToLowerInvariant authority url to the DiscoveryClient constructor.

I think it can fix the DiscoveryClient.ParseUrl to lower case the Authority. But it needs to check if it has similar mismatched Authority issues in other classes.

You may consider to change the "demo.identityserver.io" to mixed case site to see if the DiscoveryClient unit test pass.

Could you please explain what is the special cases of IIS you mean?

I think this is no good behavior. My identity service is running on http://localhost:19081/MyApp/IdentityService and this URL is case sensitive, so i need to use exactly this url as authority. But Identity server in discovery document returns lowered issuer- "issuer":"http://localhost:19081/myapp/identityservice"and IdentityModel's DiscoveryClient fails validating it.

var disco = new DiscoveryClient("http://localhost:19081/MyApp/IdentityService"); var doc = await disco.GetAsync(); //Issuer name does not match authority: http://localhost:19081/myapp/identityservice
So either Identity Server should not lowercase issuer, either DiscoveryClient should do the same.
Or maybe only domain name should be lowered?

If this breaks because of the casing, I agree that this is not ideal. This is what we tried to work around with by lower-casing.

Why do you setup your vdirs in mixed case in the first place?

I'm developing azure service fabric application and identity server runs behind service fabric's reverse proxy. So idenity server's external url is based on my SF application name and serviCe type name that i give to identity serVer.

At this moment I was able to workaround this by turning off DiscoveryClient's issuer validation policy,

var disco = new DiscoveryClient("http://localhost:19081/MyApp/IdentityService"); disco.Policy.RequireHttps = false; disco.Policy.ValidateIssuerName = false;
but it would be nice to have method to specify correct endpoint on discoveryclient or control identityserver's "lowercasing" behavior.
I could add this and make PR if this solution is OK?

@Soarc , I think you can do this as workaround for the DiscoveryClient

var disco = new DiscoveryClient("http://localhost:19081/MyApp/IdentityService".ToLowerInvariant());

@leastprivilege , for your question why setup the vdirs in mixed case.

Firstly, the specification allows mixed case so we test this case.

Secondly, it is not just the vdirs, but the host name in the URL too. We cannot control what our customer uses as their hostname.

Thirdly, if you assume that all authority URL is in lower case, then that uri = uri.ToLowerInvariant() is an unnecessary call. If this assumption is wrong, then we need to support mixed case URL.

Could you please show some codes to explain what it is trying to solve? So far, you just say it is trying to solve some special cases of IIS, but I still don't get what the special cases are.

@kevinlo This will not work, because reverse proxy is case sensitive and will return 404 for lowered
http://localhost:19081/MyApp/IdentityService/.well-known/openid-configuration url.

@Soarc , how about this?


 var disco = new DiscoveryClient("http://localhost:19081/MyApp/IdentityService"); 
disco.Authority = disco.Authority.ToLowerInvariant(); 
var disco = await discoveryClient.GetAsync();

From the DiscoveryClient.cs, the Policy.Authority is set at the GetAsync call and DiscoveryResponse uses that Policy.Authority to validate the issuer. If it lower case the Authoity before the GetAsync call, it should work. But I have not tested it myself, just base on reading the codes:

        public async Task<DiscoveryResponse> GetAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            Policy.Authority = Authority;

@kevinlo this will not compile. DiscoveryClient's Authority property does not have public setter.
either setting disco.Policy.Authority will not work, because it's value will reset after disco.GetAsync() call.

@Soarc You are right both Authority and Url are read only. I did not check that. :(

The constructor of the DiscoveryClient has the HttpMessageHandler innerHandler parameter.

Do you think you can create a HttpClientHandler derived class, pass the lower case url to the DiscoveryClient for the Authority and override the SendAsync to correct the request url with the one with right case for the reverse proxy?

    public class HttpClientHandlerProxy : HttpClientHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            // Replace the request.RequestUri with the one with correct case
            return base.SendAsync(request, cancellationToken);
        }
    }

This may be ugly workaround and that is the only one I can think of.

It es required to send request to CORRECT url or 404 response will be received. So this will not work.
If You really want to check issuer You can do it manually.


            var authority = "http://localhost:19081/PigeonApplication/IdentityService";
            var disco = new DiscoveryClient(authority);

            disco.Policy.RequireHttps = false;
            disco.Policy.ValidateIssuerName = false;

            var doc = await disco.GetAsync();
            if (doc.Issuer != authority.ToLowerInvariant())
                throw new Exception("Issuer is not valid");

My question is about adding new behavior to IdetityServer4, not about workaround this. So i wont to know @leastprivilege 's opinion about adding configuration to allow controlling issuer name creation.

We'll discuss it.

We added an option in DiscoveryClient to ignore case. Will be published soon.

Generally speaking - when you configure the issuer name statically for validation - you need to use whatever issuer is set in the discovery document as the issuer and not the IIS vdir name (with its weird casing behavior).

This is great! Thank you.
Yes, but untill this moment we havn't way to set it :)

I published a new version of IdentityModel that includes DiscoveryClient. This allows you to set the comparison mode on the DiscoveryPolicy.

Add this DiscoveryClient issue for reference.

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

eshorgan picture eshorgan  路  3Comments

garymacpherson picture garymacpherson  路  3Comments

osmankibar picture osmankibar  路  3Comments

ekarlso picture ekarlso  路  3Comments

chrisrestall picture chrisrestall  路  3Comments