Search code examples
c#asp.net-coreoauth-2.0identityserver4

Identity Server OAuth 2.0 Code Grant - How To Request Permission For Custom Scopes In Consent Screen


I've implemented Identity Server and it's working also.

One of my client is an MVC client and during authentication, I want to show the consent screen. For this on the client config I added 'RequireConsent=true'

Now it shows consent screen but the issue is, There it shows only permissions for 'openid' and 'profile' scopes.

I have several other custom scopes like 'Api1.read', 'Api1.write' which are not fetching on Authorization request while Identity Server builds view modal for concent screen.

What I'm doing wrong. On the client AllowedScopes contains = { 'openid', 'profile', 'Api1.read', 'Api1.write' }

When it hits the consent page ApiResources and ApiScopes are empty but openid and profile are available inside IdentityResources

enter image description here

This is how I'm configuring IdentityServer on Startup

 services.AddIdentityServer(options =>
                {
                    options.Authentication.CookieLifetime = TimeSpan.FromSeconds(config.IdentityServerCookieLifetime);
                })
                .AddDeveloperSigningCredential()
                .AddCorsPolicyService<MyCORSPolicy>()
                .AddResourceStore<MyResourceStore>()
                .AddClientStore<MyClientStore>()
                .AddProfileService<ProfileService>()
                .AddDeveloperSigningCredential();

I'm using IClientStore and IResourceStore to implement fetching details from a database instead of static configuration in appsettings.json

I also don't want to use Entity Framework core for this. I prefer using my own custom table schemas and Dapper.

Here's the Startup config on MVC application

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            //Add Support for OAuth 2.0 Code-Grant With Identity Server 4
            services.AddAuthentication(opt =>
            {
                opt.DefaultScheme = "Cookies";
                opt.DefaultChallengeScheme = "oidc";
            })
            .AddCookie("Cookies")
            .AddOpenIdConnect("oidc", opt =>
            {
                opt.SignInScheme = "Cookies";
                opt.Authority = "https://localhost:5005";
                opt.ClientId = "mvc-client";
                opt.ResponseType = "code";                
                opt.ClientSecret = "MVCSecret";
                opt.UseTokenLifetime = true;
                opt.SaveTokens = true;
            });
        }

This is my ResourceStore Implementation

public class MyResourceStore : IResourceStore
{
    private readonly IConfiguration config;
    private readonly string connectionString;

    public MyResourceStore(IConfiguration config)
    {
        this.config = config;
        this.connectionString = config.GetConnectionString("AuthConfigDatabase");
    }

    public async Task<IEnumerable<IdentityServer4.Models.ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames)
    {
        var apis = SqlHelper.Query<AuthApiResources>($"SELECT * FROM AuthApiResources WHERE Name='{apiResourceNames}' AND IsActive=1", connectionString);
        if (apis != null)
        {
            var result = new List<IdentityServer4.Models.ApiResource>();
            foreach (var api in apis)
            {
                var availableScopes = new List<string>() { "openid", "profile" };
                availableScopes.AddRange(api.SupportedScopes.Split(",").ToList());
                result.Add(new IdentityServer4.Models.ApiResource
                {
                    Name = api.Name,
                    DisplayName = api.DisplayName,
                    Scopes = availableScopes
                });
            }
            return result;
        }
        return null;
    }

    public async Task<IEnumerable<IdentityServer4.Models.ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopesList)
    {
        var scopeNames = scopesList.ToList();
        var likeStatements = "";
        for (var i = 0; i < scopeNames.Count(); i++)
        {
            if (i == scopeNames.Count() - 1)
            {
                likeStatements += $"SupportedScopes LIKE '%{scopeNames[i]}%'";
            }
            else
            {
                likeStatements += $"SupportedScopes LIKE '%{scopeNames[i]}%' OR ";
            }
        }
        var apis = SqlHelper.Query<AuthApiResources>($"SELECT * FROM AuthApiResources WHERE ({likeStatements}) AND IsActive=1", connectionString);
        if (apis != null)
        {
            var result = new List<IdentityServer4.Models.ApiResource>();
            foreach (var api in apis)
            {
                var availableScopes = new List<string>() { "openid", "profile" };
                availableScopes.AddRange(api.SupportedScopes.Split(",").ToList());
                result.Add(new IdentityServer4.Models.ApiResource
                {
                    Name = api.Name,
                    DisplayName = api.DisplayName,
                    Scopes = availableScopes
                });
            }
            return result;
        }
        return null;
    }

    public async Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopesList)
    {
        var scopeNames = scopesList.ToList();
        var likeStatements = "";
        for (var i = 0; i < scopeNames.Count(); i++)
        {
            if (i == scopeNames.Count() - 1)
            {
                likeStatements += $"ScopeName='{scopeNames[i]}'";
            }
            else
            {
                likeStatements += $"ScopeName='{scopeNames[i]}' OR ";
            }
        }
        var scopes = SqlHelper.Query<AuthScope>($"SELECT * FROM AuthScopes WHERE ({likeStatements})", connectionString);
        if (scopes != null)
        {
            var result = new List<IdentityServer4.Models.ApiScope>();
            foreach (var scope in scopes)
            {
                result.Add(new IdentityServer4.Models.ApiScope
                {
                    Name = scope.ScopeName,
                    DisplayName = scope.ScopeDescription
                });
            }
            return result;
        }
        return null;
    }

    public async Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames)
    {
        return new List<IdentityResource>
         {
              new IdentityResources.OpenId(),
              new IdentityResources.Profile()
         };
    }

    public async Task<Resources> GetAllResourcesAsync()
    {
        var allResources = new Resources();
        allResources.IdentityResources =
         new List<IdentityResource>
         {
              new IdentityResources.OpenId(),
              new IdentityResources.Profile()
         };
        var apis = SqlHelper.Query<AuthApiResources>($"SELECT * FROM AuthApiResources WHERE IsActive=1", connectionString);
        if (apis != null)
        {
            var result = new List<IdentityServer4.Models.ApiResource>();
            foreach (var api in apis)
            {
                var availableScopes = new List<string>() { "openid", "profile" };
                availableScopes.AddRange(api.SupportedScopes.Split(",").ToList());
                result.Add(new IdentityServer4.Models.ApiResource
                {
                    Name = api.Name,
                    DisplayName = api.DisplayName,
                    Scopes = availableScopes
                });
            }
            allResources.ApiResources = result;
        }

        var scopes = SqlHelper.Query<AuthScope>($"SELECT * FROM AuthScopes", connectionString);
        if (scopes != null)
        {
            var result = new List<IdentityServer4.Models.ApiScope>();
            foreach (var scope in scopes)
            {
                result.Add(new IdentityServer4.Models.ApiScope
                {
                    Name = scope.ScopeName,
                    DisplayName = scope.ScopeDescription
                });
            }
            allResources.ApiScopes = result;
        }

        return allResources;
    }
}

And here's a sample of database schema enter image description here

What am I doing wrong


Solution

  • In your client, inside the AddOpenIdConnect method, you need to also define what scopes you want to have access to, like:

    .AddOpenIdConnect(options =>
            {
                ...
    
                options.Scope.Clear();
                options.Scope.Add("openid");
                options.Scope.Add("profile");
                options.Scope.Add("email");
                options.Scope.Add("employee_info");
                ...
             }