elsa-workflows / elsa-core

A .NET workflows library
https://v3.elsaworkflows.io/
MIT License
6.58k stars 1.21k forks source link

[BUG] Cannot use Elsa: `Scheme already exists: Bearer` #5385

Open douglasg14b opened 6 months ago

douglasg14b commented 6 months ago

Description

When trying to startup after following the Server docs. I receive the following exception:

System.InvalidOperationException: Scheme already exists: Bearer
   at Microsoft.AspNetCore.Authentication.AuthenticationOptions.AddScheme(String name, Action`1 configureBuilder)
   at Microsoft.Extensions.Options.ConfigureNamedOptions`1.Configure(String name, TOptions options)
   at Microsoft.Extensions.Options.OptionsFactory`1.Create(String name)
   at Microsoft.Extensions.Options.UnnamedOptionsManager`1.get_Value()
   at Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider..ctor(IOptions`1 options, IDictionary`2 schemes)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
   at Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.ReflectionMiddlewareBinder.CreateMiddleware(RequestDelegate next)
   at Microsoft.AspNetCore.Builder.ApplicationBuilder.Build()
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>b__15_1(IHostedService service, CancellationToken token)
   at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
   at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
   at Buzzscreens.Program.Main(String[] args) in

I'm honestly not sure what else needs to be fiddled with here to make this work :/

    public static IServiceCollection Install(IServiceCollection services, AppConfig appConfig)
    {
        services.AddElsa(elsa =>
        {
            // Configure Management layer to use EF Core.
            elsa.UseWorkflowManagement(management =>
                management.UseEntityFrameworkCore(ef => ef.UsePostgreSql(appConfig.DbConnectionString)));

            // Configure Runtime layer to use EF Core.
            elsa.UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ef.UsePostgreSql(appConfig.DbConnectionString)));

            // Default Identity features for authentication/authorization.
            elsa.UseIdentity(identity =>
            {
                identity.TokenOptions = options =>
                    options.SigningKey = "sufficiently-large-secret-signing-key"; // This key needs to be at least 256 bits long.
                identity.UseAdminUserProvider();
            });

            // Configure ASP.NET authentication/authorization.
            elsa.UseDefaultAuthentication(auth => auth.UseAdminApiKey());

            // Expose Elsa API endpoints.
            elsa.UseWorkflowsApi();

            // Setup a SignalR hub for real-time updates from the server.
            elsa.UseRealTimeWorkflows();

            // Enable C# workflow expressions
            elsa.UseCSharp();

            // Enable HTTP activities.
            elsa.UseHttp();

            // Use timer activities.
            elsa.UseScheduling();

            // Register custom activities from the application, if any.
            elsa.AddActivitiesFrom<Program>();

            // Register custom workflows from the application, if any.
            elsa.AddWorkflowsFrom<Program>();
        });
        return services;
    }
glime-ai[bot] commented 6 months ago

The exception you're encountering indicates that there is an attempt to add an authentication scheme with the name "Bearer" more than once. This can happen if AddAuthentication is called multiple times with the same scheme or if different parts of the code are trying to configure the same authentication scheme independently.

To resolve this issue, you should ensure that the "Bearer" scheme is only added once throughout your application's startup configuration. Here are some steps you can take:

  1. Search your codebase for all occurrences of AddAuthentication and AddJwtBearer to see if the "Bearer" scheme is being added more than once.
  2. If you are using external libraries or packages that add authentication schemes, make sure they are not conflicting with your own authentication configuration.
  3. Check the order of operations in your startup configuration. Sometimes the order in which services are added can cause issues.
  4. If you have multiple projects or modules that are being integrated, ensure that they are not each trying to add the same authentication scheme.

Without seeing the actual code, it's difficult to provide a precise solution. However, the general approach is to review and consolidate your authentication configuration to prevent duplicate scheme registrations.

If you continue to have trouble, please provide more details about your startup configuration, especially around the authentication and authorization setup, so that a more specific solution can be provided.

This comment was generated by Glime.

douglasg14b commented 6 months ago

From what I'm reading, perhaps elsa should chose a more specific name for it's authentication schema? 🤔

Most applications are, by default, going to be using JwtBearerDefaults.AuthenticationScheme since that's what is automatically configured for them if they have setup JWT auth. Which means the canonical JWT auth setup is in conflict with elsa.

This call can specify the schema name: https://github.com/elsa-workflows/elsa-core/blob/8289ae2cdfe54bfa848c1a89060de30ea44c33fc/src/modules/Elsa.Identity/Features/DefaultAuthenticationFeature.cs#L68

sfmskywalker commented 6 months ago

That may not be a bad idea. Originally, the default configuration is just there to get started quickly, but usually you'll want to handle authentication yourself, in which case you may not want to use the default setup other than perhaps as an example. However, that also means you'd need to handle setting up the claims identity such that it has the permissions claim. If we just offer a default setup using a different scheme, then perhaps it can work side by side with an application specific scheme (which probably is called Bearer as you mentioned)

douglasg14b commented 6 months ago

That's a good point actually 🤔 , I have not yet integrated my auth scheme into Elsa. Actually, I have no idea how even!

I agree that most will move away from the default, but to ease the onboarding pain the default may want to be named something different to avoid the conflcit!

douglasg14b commented 6 months ago

If elsa is not explicitly specifying the Authorization scheme to use, then I don't think it will work at all if an existing default one exists. Regardless of it one was to integrate their own identity or not no?

The IdentityFeature also appears tosupply defaults for some of these services, how can this be changed?

            .AddScoped<ISecretHasher, DefaultSecretHasher>()
            .AddScoped<IAccessTokenIssuer, DefaultAccessTokenIssuer>()
            .AddScoped<IUserCredentialsValidator, DefaultUserCredentialsValidator>()
            .AddScoped<IApplicationCredentialsValidator, DefaultApplicationCredentialsValidator>()
            .AddScoped<IApiKeyGenerator>(sp => sp.GetRequiredService<DefaultApiKeyGeneratorAndParser>())
            .AddScoped<IApiKeyParser>(sp => sp.GetRequiredService<DefaultApiKeyGeneratorAndParser>())
            .AddScoped<IClientIdGenerator, DefaultClientIdGenerator>()
            .AddScoped<ISecretGenerator, DefaultSecretGenerator>()
            .AddScoped<IRandomStringGenerator, DefaultRandomStringGenerator>()
            .AddScoped<DefaultApiKeyGeneratorAndParser>()
4lexKislitsyn commented 5 months ago

The IdentityFeature also appears tosupply defaults for some of these services, how can this be changed?

In your project after all registrations you can replace any of service descriptor. It's the easiest way without any changes to elsa-core library.

When you want to integrate Elsa with your own authentication I think you don't need Elsa.Identity package at all. You would like to add some Elsa claims to your user by default (e.g. based on role). It's possible that you will have to support some contracts from Elsa.Identity package. However FastEndpoints uses default asp.net authentication mechanism so the main goal is to authenticate user with some Elsa claims.