0
votes

I am writing an IdentityServer4 implementation and using the Quickstart project described here.

When you define an ApiResource (using InMemory classes for now) it looks like IdentityServer creates a Scope with the same name as the resource. For example

public static IEnumerable<ApiResource> GetApiResources()
{
    return new List<ApiResource>
    {
        new ApiResource("api", "My API")
    };
}

will create a Scope called "api" (this is done in the ApiResource constructor). If I add "api" as an allowed Scope on my Client object (using InMemoryClients for a proof of concept) and request this api Scope in the scope query string parameter in my auth request from my JavaScript client I get an invalid_scope error message.

I found by following this documentation you can add Scopes to the ApiResource through the Scopes property like so

new ApiResource
{
     Name = "api",
     DisplayName = "Custom API",
     Scopes = new List<Scope>
     {
          new Scope("api.read"),
          new Scope("api.write")
      }
 }

So now if I instead define my ApiResource like this and request the Scopes api.read and api.write (and add them to the AllowedScopes property on the Client Object) then everything works fine EXCEPT the consent page which shows duplicate Scopes. It shows api.read 2 times and api.write 2 times. See the consent screen here

enter image description here

The Client configuration is as follows:

new Client
{
     ClientId = "client.implicit",
     ClientName = "JavaScript Client",
     AllowedGrantTypes = GrantTypes.Implicit,
     AllowAccessTokensViaBrowser = true,
     RedirectUris = { "http://localhost:3000/health-check" },
     PostLogoutRedirectUris = { "http://localhost:3000" },
     AllowedCorsOrigins =     { "http://localhost:3000" },
     AllowedScopes = {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "customApi.read", "customApi.write"
                     }
}

Why is this happening? Am I doing something obviously wrong?

Update: Here a portion of the discovery document that shows the Scopes are only listed once...

enter image description here

3
How many API resources do you have? Their scopes will be duplicated. Please consider posting of scopes_supported contents from your /.well-known/openid-configuration endpoint as well.Ilya Chumakov
@IlyaChumakov I posted it.I definitely only have one API resource so I am lost as to why this is happeningRob L

3 Answers

2
votes

It looks like the problem is with the Quickstart UI... or with the Scope.cs class depending on how you look at it. Specifically, in the method and line shown in the class ConsentService.cs

The following code

vm.ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();

is not filtering out the duplicates. That is, even if two Scopes have the same name they are not considered equal. So if GetHashCode and Equals were overridden in Scope.cs (which is in IdentityServer4 - not the Quickstart) then it would solve this problem. In that case SelectMany would return a unique set. This is because the ApiResources property is implemented as a HashSet. Alternatively, you could write your own logic to make this return a unique set of Scopes. This is how I solved the problem. I wrote something very similar to Jon Skeet's answer in this post that filtered out the duplicate Scopes.

1
votes

The problem lies within IdentityService4 code in the implementation of InMemoryResourcesStore.FindApiResourcesByScopeAsync and was fixed with this commit. You can use the dev branch where it's included since June 22th 2017, but it was never released in any of the NuGET packages targeting .NET Standard 1.4, which is very annoying.

I created an issue and requested it to get patched: https://github.com/IdentityServer/IdentityServer4/issues/1470

For fixing the view, i added the line marked with Todo to ConsentService.cs

var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any()))
{
    // TODO: Hotfix to cleanup scope duplication:
    resources.ApiResources = resources.ApiResources.DistinctBy(p => p.Name).ToList();
    return CreateConsentViewModel(model, returnUrl, request, client, resources);
}

This solves the display problem, but the scope will still be included multiple times in the access token which makes it bigger since it squares the scope count for that API. I had 3 scopes, so each one was included 3 times, adding 6 unneeded scope copies. But at least it's usable until it get's fixed.

0
votes

There was a bug that was just fixed in 1.5 that addresses this: https://github.com/IdentityServer/IdentityServer4/pull/1030. Please upgrade and see if that fixes the issue for you. Thanks.