Search code examples
c#asp.net-coreidentityserver4

MogoDB as store for IdentityServer: invalid_scope


I'm getting invalid_scope but not sure why.

This is how my Console app looks like

private static async Task Main()
{
    // discover endpoints from metadata
    var client = new HttpClient();

    var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
    if (disco.IsError)
    {
        Console.WriteLine(disco.Error);
        return;
    }

    // request token
    var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
    {
        Address = disco.TokenEndpoint,
        ClientId = "client",
        ClientSecret = "secret",

        Scope = "api1"
    });
    
    if (tokenResponse.IsError)
    {
        Console.WriteLine(tokenResponse.Error);
        return;
    }

    Console.WriteLine(tokenResponse.Json);
    Console.WriteLine("\n\n");

    // call api
    var apiClient = new HttpClient();
    apiClient.SetBearerToken(tokenResponse.AccessToken);

    var response = await apiClient.GetAsync("http://localhost:5001/identity");
    if (!response.IsSuccessStatusCode)
    {
        Console.WriteLine(response.StatusCode);
    }
    else
    {
        var content = await response.Content.ReadAsStringAsync();
        Console.WriteLine(JArray.Parse(content));
    }
}

This is how my IdentityServer project is configured

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{

   //...
    InitializeDatabase(app);

}

private static void InitializeDatabase(IApplicationBuilder app)
{
    bool createdNewRepository = false;
    var repository = app.ApplicationServices.GetService<IRepository>();

    //  --Client
    if (!repository.CollectionExists<Client>())
    {
        foreach (var client in Config.GetClients())
        {
            repository.Add<Client>(client);
        }
        createdNewRepository = true;
    }

    //  --IdentityResource
    if (!repository.CollectionExists<IdentityResource>())
    {
        foreach (var res in Config.GetIdentityResources())
        {
            repository.Add<IdentityResource>(res);
        }
        createdNewRepository = true;
    }


    //  --ApiResource
    if (!repository.CollectionExists<ApiResource>())
    {
        foreach (var api in Config.GetApiResources())
        {
            repository.Add<ApiResource>(api);
        }
        createdNewRepository = true;
    }

    // If it's a new Repository (database), need to restart the website to configure Mongo to ignore Extra Elements.
    if (createdNewRepository)
    {
        var newRepositoryMsg = $"Mongo Repository created/populated! Please restart you website, so Mongo driver will be configured  to ignore Extra Elements - e.g. IdentityServer \"_id\" ";
        throw new Exception(newRepositoryMsg);
    }
}

public class Config
{
    // scopes define the resources in your system
    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
        };
    }

    public static IEnumerable<ApiResource> GetApiResources()
    {
        var apiResource = new ApiResource("api1", "My API");
        apiResource.Scopes.Add("api1");


        return new List<ApiResource>
        {
            apiResource
        };
    }

    // clients want to access resources (aka scopes)
    public static IEnumerable<Client> GetClients()
    {
        // client credentials client
        return new List<Client>
        {
            new Client
            {
                ClientId = "client",
                AllowedGrantTypes = GrantTypes.ClientCredentials,

                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },
                AllowedScopes = { "api1" }
            },

            // resource owner password grant client
            new Client
            {
                ClientId = "ro.client",
                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,

                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },
                AllowedScopes = { "api1" }
            },

            // OpenID Connect hybrid flow and client credentials client (MVC)
            new Client
            {
                ClientId = "mvc",
                ClientName = "MVC Client",
                AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,

                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },

                RedirectUris = { "http://localhost:5002/signin-oidc" },
                PostLogoutRedirectUris = { "http://localhost:5002" },

                AllowedScopes =
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    "api1"
                },
                AllowOfflineAccess = true
            }
        };
    }

    public static List<TestUser> GetUsers()
    {
        return new List<TestUser>
        {
            new TestUser
            {
                SubjectId = "1",
                Username = "alice",
                Password = "password",

                Claims = new List<Claim>
                {
                    new Claim("name", "Alice"),
                    new Claim("website", "https://alice.com")
                }
            },
            new TestUser
            {
                SubjectId = "2",
                Username = "bob",
                Password = "password",

                Claims = new List<Claim>
                {
                    new Claim("name", "Bob"),
                    new Claim("website", "https://bob.com")
                }
            }
        };
    }
}

This is how the ApiResource looks in mongo

{"_id":{"$oid":"61126588ee05fcdcbf2bb885"},"Enabled":true,"Name":"api1","DisplayName":"My API","Description":null,"ShowInDiscoveryDocument":true,"UserClaims":[],"Properties":{},"ApiSecrets":[],"Scopes":["api1"],"AllowedAccessTokenSigningAlgorithms":[]}

Logs

2021-08-11 12:32:30.081 +03:00 [ERR] Scope api1 not found in store. 2021-08-11 12:32:30.081 +03:00 [ERR] Invalid scopes requested, {"ClientId":"ro.client","ClientName":null,"GrantType":"password","Scopes":null,"AuthorizationCode":"********","RefreshToken":"********","UserName":null,"AuthenticationContextReferenceClasses":null,"Tenant":null,"IdP":null,"Raw":{"grant_type":"password","username":"Alice","password":"***REDACTED***","scope":"api1","client_secret":"***REDACTED***","client_id":"ro.client"},"$type":"TokenRequestValidationLog"}

What could be the problem? The problem appeared when I've migrated from netcoreapp2.2 to net5.0 along with IdentityServer - here's some info about the resource changes - - https://github.com/IdentityServer/IdentityServer4.Quickstart.UI/commit/d787bf819964c42235f71d2580a26a8d739a5943.

Inspired from: https://github.com/souzartn/IdentityServer4.Samples.Mongo


Solution

  • ApiScope are required, and allow more granularity control than the old api resources:

    public static IEnumerable<ApiResource> GetApiResources()
            {
                return new List<ApiResource>
                {
                    new ApiResource("apiErp", "ERP BACKEND")
                    {
                        Scopes = new List<string>()
                        {
                            "api1"
                        }
                    }
                };
            }
    
            public static IEnumerable<ApiScope> GetApiScopes()
            {
                return new[]
                {
                    new ApiScope(name: "api1",   displayName: "Access API Backend")
                };
            }
    
    // And in Startup.cs
    
    services.AddIdentityServer(isrvconfig =>
                    {
                        isrvconfig.Events.RaiseErrorEvents = true;
                        isrvconfig.Events.RaiseInformationEvents = true;
                        isrvconfig.Events.RaiseFailureEvents = true;
                        isrvconfig.Events.RaiseSuccessEvents = true;
                    })
                    .AddInMemoryIdentityResources(IdentityDevelopmentConfig.GetIdentityResources())
                    .AddInMemoryApiResources(IdentityDevelopmentConfig.GetApiResources())
    // 🔴  👇
                    .AddInMemoryApiScopes(IdentityDevelopmentConfig.GetApiScopes())
                    .AddInMemoryClients(IdentityDevelopmentConfig.GetMainClients(configuration))
                    .AddAspNetIdentity<AppUser>();
    

    Original answer: https://github.com/IdentityServer/IdentityServer4/issues/4632