Search code examples
c#.net.net-coreasp.net-identityduende-identity-server

Duende.IdentityServer from Blazor WebAssembly App protecting ASP.NET Core API using Client Credentials with custom allowed scope - invalid_scope


I have a Blazor WebAssembly App created with Microsoft Visual Studio with these specifications: Target Framework .NET 6.0, Authentication Type Individual Accounts and ASP.NET Core Hosted:

Using this answer I have been able to add Client Credentials flow

https://stackoverflow.com/a/67324222/3850405

I removed this from appsettings.json:

"Clients": {
  "WebApplication4.Client": {
    "Profile": "IdentityServerSPA"
  }
}

Edit Startup.cs or Program.cs:

services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
    {
        options.Clients.AddIdentityServerSPA("WebApplication4.Client", builder =>
        {
            builder.WithRedirectUri("/authentication/login-callback");
            builder.WithLogoutRedirectUri("/authentication/logout-callback");
        });
        options.Clients.Add(new Duende.IdentityServer.Models.Client
        {
            ClientId = "WebApplication4.Integration",
            AllowedGrantTypes = { GrantType.ClientCredentials },
            ClientSecrets = { new Secret("MySecretValue".Sha256()) },
            AllowedScopes = { "WebApplication4.ServerAPI"}
        });
    });

This request will work:

POST /connect/token HTTP/1.1
Host: localhost:44397
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=WebApplication4.Integration&client_secret=MySecretValue&scope=WebApplication4.ServerAPI

However I want this client to have its own AllowedScopes. If I then change AllowedScopes = { "WebApplication4.ServerAPI"} to AllowedScopes = { "WebApplication4.IntegrationAPI"} and of course modifying the request.

Server then responds with:

{
    "error": "invalid_scope"
}

If I look at logging I get the following history:

info: Duende.IdentityServer.Hosting.IdentityServerMiddleware[0]
      Invoking IdentityServer endpoint: Duende.IdentityServer.Endpoints.TokenEndpoint for /connect/token
info: Duende.IdentityServer.Events.DefaultEventService[0]
      {
        "ClientId": "WebApplication4.Integration",
        "AuthenticationMethod": "SharedSecret",
        "Category": "Authentication",
        "Name": "Client Authentication Success",
        "EventType": "Success",
        "Id": 1010,
        "ActivityId": "80000009-0014-d600-b63f-84710c7967bb",
        "TimeStamp": "2022-09-22T09:30:31Z",
        "ProcessId": 17768,
        "LocalIpAddress": "::1:44397",
        "RemoteIpAddress": "::1"
      }
fail: Duende.IdentityServer.Validation.DefaultResourceValidator[0]
      Scope WebApplication4.IntegrationAPI not found in store or not supported by requested resource indicators.
fail: Duende.IdentityServer.Validation.TokenRequestValidator[0]
      Invalid scopes requested, {
        "ClientId": "WebApplication4.Integration",
        "GrantType": "client_credentials",
        "AuthorizationCode": "********",
        "RefreshToken": "********",
        "Raw": {
          "grant_type": "client_credentials",
          "client_id": "WebApplication4.Integration",
          "client_secret": "***REDACTED***",
          "scope": "WebApplication4.IntegrationAPI"
        }
      }
info: Duende.IdentityServer.Events.DefaultEventService[0]
      {
        "ClientId": "WebApplication4.Integration",
        "Endpoint": "Token",
        "GrantType": "client_credentials",
        "Error": "invalid_scope",
        "Category": "Token",
        "Name": "Token Issued Failure",
        "EventType": "Failure",
        "Id": 2001,
        "ActivityId": "80000009-0014-d600-b63f-84710c7967bb",
        "TimeStamp": "2022-09-22T09:30:31Z",
        "ProcessId": 17768,
        "LocalIpAddress": "::1:44397",
        "RemoteIpAddress": "::1"
      }

What I take with me is this:

Scope WebApplication4.IntegrationAPI not found in store or not supported by requested resource indicators.

I then looked at these guides:

https://github.com/IdentityServer/IdentityServer4/issues/4632#issuecomment-654685880

https://docs.duendesoftware.com/identityserver/v5/quickstarts/1_client_credentials/

I therefore added this code:

public static class Config
{
    public static IEnumerable<ApiScope> ApiScopes =>
        new List<ApiScope>
        {
        new ApiScope("WebApplication4.IntegrationAPI", "Integration API")
        };
}

and

services.AddIdentityServer()
    .AddInMemoryApiScopes(Config.ApiScopes)
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>...

This still gave me the same error.

I then added a new list with clients:

public static IEnumerable<Duende.IdentityServer.Models.Client> Clients =>
    new List<Duende.IdentityServer.Models.Client>
    {
        new Duende.IdentityServer.Models.Client
        {
            ClientId = "WebApplication4.Integration",
            AllowedGrantTypes = { GrantType.ClientCredentials },
            ClientSecrets = { new Secret("MySecretValue".Sha256()) },
            AllowedScopes = { "WebApplication4.IntegrationAPI" },
        }
    };

Removed the old client from AddApiAuthorization and used this code instead:

services.AddIdentityServer()
    .AddInMemoryApiScopes(Config.ApiScopes)
    .AddInMemoryClients(Config.Clients)
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => ...

This gave me a new error when requesting a token:

{
    "error": "invalid_client"
}

Logs:

info: Duende.IdentityServer.Hosting.IdentityServerMiddleware[0]
      Invoking IdentityServer endpoint: Duende.IdentityServer.Endpoints.TokenEndpoint for /connect/token
info: Duende.IdentityServer.Events.DefaultEventService[0]
      {
        "ClientId": "WebApplication4.Integration",
        "Category": "Authentication",
        "Name": "Client Authentication Failure",
        "EventType": "Failure",
        "Id": 1011,
        "Message": "Unknown client",
        "ActivityId": "8000000a-0016-e700-b63f-84710c7967bb",
        "TimeStamp": "2022-09-22T09:54:08Z",
        "ProcessId": 10676,
        "LocalIpAddress": "::1:44397",
        "RemoteIpAddress": "::1"
      }
fail: Duende.IdentityServer.Validation.ClientSecretValidator[0]
      No client with id 'WebApplication4.Integration' found. aborting

If I look at https://localhost:44397/.well-known/openid-configuration I only see WebApplication4.ServerAPI in scopes_supported no matter the configuration.

I want to do it like this so that I can add a policy like this later:

services.AddAuthorization(options =>
{
    options.AddPolicy("IntegrationApiScope", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("scope", "WebApplication4.IntegrationAPI");
    });
});

Meaning that I only want Client Credentials flow to get the scope WebApplication4.IntegrationAPI and I don't want Authorization Code Grant, normal login flow via (authorization_code), to have this scope.


Solution

  • This will work:

    services.AddIdentityServer()
        .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
        {
            options.ApiScopes.Add(new ApiScope("scope1"));
            options.Clients.AddIdentityServerSPA("WebApplication4.Client", builder =>
            {
                builder.WithRedirectUri("/authentication/login-callback");
                builder.WithLogoutRedirectUri("/authentication/logout-callback");
            });
            options.Clients.Add(new Duende.IdentityServer.Models.Client
            {
                ClientId = "WebApplication4.Integration",
                AllowedGrantTypes = { GrantType.ClientCredentials },
                ClientSecrets = { new Secret("MySecretValue".Sha256()) },
                AllowedScopes = { "scope1" }
            });
        });
    

    However WebApplication4.Integration can still request the scope WebApplication4.ServerAPI and get a token with it.

    Using the following code:

    services.AddIdentityServer()
        .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
        {
            options.ApiScopes.Add(new ApiScope("scope1"));
            options.ApiScopes.Add(new ApiScope("scope2"));
            options.Clients.Add(new Duende.IdentityServer.Models.Client
            {
                ClientId = "WebApplication4.Integration",
                AllowedGrantTypes = { GrantType.ClientCredentials },
                ClientSecrets = { new Secret("MySecretValue".Sha256()) },
                AllowedScopes = { "scope1" }
            });
        });
    

    WebApplication4.Integration can get a token using scopes WebApplication4.ServerAPI and scope1 but not with scope2.

    See this issue for more info:

    https://github.com/dotnet/aspnetcore/issues/44122