DuendeSoftware / Support

Support for Duende Software products
21 stars 0 forks source link

Authentication failed when trying to get token for a secondary client #184

Closed dobrinsky closed 2 years ago

dobrinsky commented 2 years ago

Which version of Duende IdentityServer are you using? 5.2.0 Which version of .NET are you using? 6.0.400 Describe the bug

In a project that was started with the template from Visual Studio ASP.NET Core with Angular with the newest version of the template, a new requirement has surfaced to communicate with a Windows Service.

Everything works fine in the web project. The service needs to call an API and POST data so, of course, it needs to login before actually posting the data. This means that it needs to request a token before sending the data.

The problem is that no matter what changes are made to the project or configuration, Postman only receives the answer: "Authentication failed". The configuration of the project was done according the Documentation and according to some other tutorials.

This does not work in new projects also.

Program.cs is:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddIdentityServer(options =>
{
    options.Events.RaiseErrorEvents = true;
    options.Events.RaiseInformationEvents = true;
    options.Events.RaiseFailureEvents = true;
    options.Events.RaiseSuccessEvents = true;

    // see https://docs.duendesoftware.com/identityserver/v6/fundamentals/resources/
    options.EmitStaticAudienceClaim = true;
})
    .AddInMemoryIdentityResources(Config.IdentityResources)
    .AddInMemoryApiScopes(Config.ApiScopes)
    .AddInMemoryClients(Config.Clients)
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();

app.MapFallbackToFile("index.html"); ;

app.Run();

The Config.cs is:

using Duende.IdentityServer.Models;

namespace Project1
{
    public class Config
    {
        public static IEnumerable<IdentityResource> IdentityResources =>
        new IdentityResource[]
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
        };

        public static IEnumerable<ApiScope> ApiScopes =>
            new ApiScope[]
            {
                new ApiScope("scope1"),
                new ApiScope("scope2"),
                new ApiScope("api1"),
            };

        public static IEnumerable<Client> Clients =>
            new Client[]
            {
                // m2m client credentials flow client
                new Client
                {
                    ClientId = "postman",
                    ClientName = "Client Credentials Client",

                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) },

                    AllowedScopes = { "api1" }
                },

                // m2m client credentials flow client
                new Client
                {
                    ClientId = "m2m.client",
                    ClientName = "Client Credentials Client",

                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) },

                    AllowedScopes = { "scope1" }
                },

                // interactive client using code flow + pkce
                new Client
                {
                    ClientId = "interactive",
                    ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },

                    AllowedGrantTypes = GrantTypes.Code,

                    RedirectUris = { "https://localhost:44300/signin-oidc" },
                    FrontChannelLogoutUri = "https://localhost:44300/signout-oidc",
                    PostLogoutRedirectUris = { "https://localhost:44300/signout-callback-oidc" },

                    AllowOfflineAccess = true,
                    AllowedScopes = { "openid", "profile", "scope2" }
                },
            };
    }
}

The answer in Postman is:

image

Postman does receive an answer on .well-known/openid-configuration:

image

Which clearly shows that "api1" scope is not added. Something definetly does not work.

No matter what changes are done, it is like the Clients in Config are not loaded because the ones in the database are loaded.

The only difference between our project Program.cs and the documentation (https://docs.duendesoftware.com/identityserver/v5/quickstarts/5_aspnetid/) is that builder.Services.AddIdentityServer() is called there with .AddAspNetIdentity(); but that cannot be changed in an Angular template since the Browser login throws the following error:

System.InvalidOperationException: No service for type 'Duende.IdentityServer.Stores.ISigningCredentialStore' has been registered.

What is the solution to communicate with other types of Clients when Angular project template is used and how can a token can be requested from C#, meaning doing what Postman does?

To Reproduce

Create a new project in Visual Studio template with Angular.

Configure Program.cs and add Config.cs file with the 4 lists.

Run project

Use Postman to get a new token.

Expected behavior

A new token should be received when calling /connect/token

Log output/exception with stacktrace

There is no data, just the above console output result and in Postman:

image

dobrinsky commented 2 years ago

Is this a license problem? In order to make a second client or a client that is not the main app, a license is required?

brockallen commented 2 years ago

Hmm, looks like you're mixing Microsoft's template and our base API for adding IdentityServer -- those do not mix well. We wrote a bit about it here:

https://docs.duendesoftware.com/identityserver/v6/upgrades/spa_to_duende/

The short of it is that if you use Microsoft's template and their config APIs, then you will have to use their abstraction for adding config (scopes, etc). You'd have to check their doc on how to do that (I honestly remember how they expose it -- some custom JSON somewhere?), or the other option is to do as we suggest here:

https://docs.duendesoftware.com/identityserver/v6/upgrades/spa_to_duende/#migrating

Which is to say, rip out the Microsoft abstraction on our library and configure things as per our docs/quickstarts.

dobrinsky commented 2 years ago

Hi @brockallen, thank you for your response. I understand now why it does not work, because Microsoft did a custom integration, which it seems only supports SPA authentication and authorization.

Indeed, the clients are configured in appsettings.json, the main config file of the project template, but I cannot make it to issue token with client_credentials, since it does not seem to support as it can be seen here:

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

I have also written before a StackOverflow question, which I will update in case someone will bump into this in the future.

Thank you