Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question about ASP.NET Core 3 Identity / Identity Server / SPA support for Resource Owner Password Grant Type #13919

Closed
jasontaylordev opened this issue Sep 12, 2019 · 4 comments
Labels
Milestone

Comments

@jasontaylordev
Copy link

@jasontaylordev jasontaylordev commented Sep 12, 2019

As per Authentication and authorization for SPAs I have created a new SPA with support for API authorization. You can view this here; https://github.com/JasonGT/SecureSpa/.

In order to support integration tests, I have added a new client (see appsettings.json) that is allowed the resource owner password grant type:

      "SecureSpa.IntegrationTests": {
        "Profile": "IdentityServerSPA",
        "AllowedGrantTypes": [ "password" ],
        "ClientSecrets": [ { "Value": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=" } ],
        "AllowedScopes": [ "SecureSpaAPI", "openid", "profile" ]
      }

Then within WeatherForecastControllerTests.cs I attempt to request the token as follows:

            var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
            {
                Address = disco.TokenEndpoint,
                ClientId = "SecureSpa.IntegrationTests",
                ClientSecret = "secret",

                Scope = "SecureSpaAPI",
                UserName = "demouser@securespa",
                Password = "Pass@word1"
            });

When running the test, I've tried many different combinations, however the results are usually the same (unauthorized_client). This is the relevant logged output from Identity Server:

IdentityServer4.Endpoints.TokenEndpoint: Debug: Start token request.
IdentityServer4.Validation.ClientSecretValidator: Debug: Start client validation
IdentityServer4.Validation.BasicAuthenticationSecretParser: Debug: Start parsing Basic Authentication secret
IdentityServer4.Validation.PostBodySecretParser: Debug: Start parsing for secret in post body
IdentityServer4.Validation.SecretParser: Debug: Parser found secret: PostBodySecretParser
IdentityServer4.Validation.SecretParser: Debug: Secret id found: SecureSpa.IntegrationTests
IdentityServer4.Stores.ValidatingClientStore: Debug: client configuration validation for client SecureSpa.IntegrationTests succeeded.
IdentityServer4.Validation.ClientSecretValidator: Debug: Public Client - skipping secret validation success
IdentityServer4.Validation.ClientSecretValidator: Debug: Client validation success
IdentityServer4.Events.DefaultEventService: Information: {
  "Name": "Client Authentication Success",
  "Category": "Authentication",
  "EventType": "Success",
  "Id": 1010,
  "ClientId": "SecureSpa.IntegrationTests",
  "AuthenticationMethod": "SharedSecret",
  "ActivityId": "0HLPN4PPDDMCJ",
  "TimeStamp": "2019-09-12T02:10:57Z",
  "ProcessId": 28948,
  "LocalIpAddress": "unknown",
  "RemoteIpAddress": "unknown"
}
IdentityServer4.Validation.TokenRequestValidator: Debug: Start token request validation
IdentityServer4.Validation.TokenRequestValidator: Debug: Start resource owner password token request validation
IdentityServer4.Validation.TokenRequestValidator: Error: Client not authorized for resource owner flow, check the AllowedGrantTypes setting{ client_id = SecureSpa.IntegrationTests }, details: {
  "ClientId": "SecureSpa.IntegrationTests",
  "ClientName": "SecureSpa.IntegrationTests",
  "GrantType": "password",
  "Raw": {
    "grant_type": "password",
    "username": "demouser@securespa",
    "password": "***REDACTED***",
    "scope": "SecureSpaAPI",
    "client_id": "SecureSpa.IntegrationTests",
    "client_secret": "***REDACTED***"
  }
}
IdentityServer4.Events.DefaultEventService: Information: {
  "Name": "Token Issued Failure",
  "Category": "Token",
  "EventType": "Failure",
  "Id": 2001,
  "ClientId": "SecureSpa.IntegrationTests",
  "ClientName": "SecureSpa.IntegrationTests",
  "Endpoint": "Token",
  "GrantType": "password",
  "Error": "unauthorized_client",
  "ActivityId": "0HLPN4PPDDMCJ",
  "TimeStamp": "2019-09-12T02:10:57Z",
  "ProcessId": 28948,
  "LocalIpAddress": "unknown",
  "RemoteIpAddress": "unknown"
}
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request finished in 212.96790000000001ms 400 application/json; charset=UTF-8

Is this approach is supported? If not, is there an alternative approach that can be used to get the token in order to write integration tests? I'm planning to set up test users along with the test client so that I can test lots of different behaviours.

Thanks!

@jasontaylordev

This comment has been minimized.

Copy link
Author

@jasontaylordev jasontaylordev commented Sep 12, 2019

I continued working on this issue and found that the allowed grant type of password was not being added when the profile is set to IdentityServerSPA. I couldn't see a way to add a client without a profile via appsettings, so I removed the configuration from appsettings and created the clients using this approach:

services.AddIdentityServer()
    //.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
    {
        options.Clients.AddIdentityServerSPA("SecureSpa", builder =>
        {
            builder.WithRedirectUri("https://localhost:44307/authentication/login-callback");
            builder.WithLogoutRedirectUri("https://localhost:44307/authentication/logout-callback");
        });
        options.Clients.Add(new Client
        {
            ClientId = "SecureSpa.IntegrationTests",
            AllowedGrantTypes = { GrantType.ResourceOwnerPassword },
            ClientSecrets = { new Secret("secret".Sha256()) },
            AllowedScopes = { "SecureSpaAPI", "openid", "profile" }
        });
    });

With that in place my tests now run. You can see the final solution here; https://github.com/JasonGT/SecureSpa/.

Everything works fine, however there seems to be a bug (or feature limitation) within DefaultClientRequestParametersProvider. See the 'GetClientParameters' method - if the specified client does not have an associated profile, an InvalidOperationException is thrown.

If this is not intended, let me know and I'll raise a bug issue.

@blowdart

This comment has been minimized.

Copy link
Contributor

@blowdart blowdart commented Sep 12, 2019

The initial release isn't targeting credential flow like this. It's on the list to examine for 5.0 for service to service auth, but it's not something we'd take now, it'd have to be part of the bigger feature expansion.

@blowdart blowdart added this to the Discussions milestone Sep 12, 2019
@jasontaylordev

This comment has been minimized.

Copy link
Author

@jasontaylordev jasontaylordev commented Sep 12, 2019

No worries. It's a great feature, looking forward to seeing it evolve. I'll continue working with the solution above and update as I streamline the approach.

@leastprivilege

This comment has been minimized.

Copy link

@leastprivilege leastprivilege commented Sep 13, 2019

Well - simply use the native config system of IdentityServer and you get full access to all flows and features...

@blowdart blowdart closed this Sep 18, 2019
@msftbot msftbot bot locked as resolved and limited conversation to collaborators Dec 2, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
3 participants
You can’t perform that action at this time.