DuendeSoftware / BFF

Framework for ASP.NET Core to secure SPAs using the Backend-for-Frontend (BFF) pattern
Other
341 stars 77 forks source link

Anonymous access not works #20

Closed rahul7720 closed 3 years ago

rahul7720 commented 3 years ago

Duende IdentityServer4 Donnet 6 Preview 4

Attribute [AllowAnonymous]not works in the controller and 401 error is returned instead.

  1. Created a Blazor Wasm Hosted App
  2. Configure the BFF (Server App/ ClientApp)
  3. Configure IdentityServer on the same app (Server App)
  4. Create an MVC controller and decorate it with [AllowAnonymous]
  5. Call the controller

Result: 401 Unauthorized exception when calling the MVC controller

Observation: When the below line is commented out from the Startup class the controller can be accessed anonymously. app.UseBff();

brockallen commented 3 years ago

Is there anything in the BFF host logs that explains the 401?

rahul7720 commented 3 years ago

As I mentioned below there is no dedicated IDS4 host or BFF host, but the Blazor Server app host. I have set the Serilog log level to .MinimumLevel.Override("Duende.Bff", LogEventLevel.Verbose) and navigated to the controller from the browser.

Below is the only information is written to the output console: crbug/1173575, non-JS module files deprecated.

Note: when app.UseBff(); is commented, then the above information is not written to the console.

The Startup Class (Blazor Host) :

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddBff().AddServerSideSessions();
            services.AddControllersWithViews();
            services.AddRazorPages();

            var builder = services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;
                options.EmitStaticAudienceClaim = true;
            })
                .AddTestUsers(TestUsers.Users);

            builder.AddInMemoryIdentityResources(Resources.Identity);
            builder.AddInMemoryApiScopes(Resources.ApiScopes);
            builder.AddInMemoryApiResources(Resources.ApiResources);
            builder.AddInMemoryClients(Clients.List);
            builder.AddJwtBearerClientAuthentication();

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = "cookie";
                options.DefaultChallengeScheme = "oidc";
                options.DefaultSignOutScheme = "oidc";
            })
            .AddCookie("cookie", options =>
            {
                options.Cookie.Name = "__Host-blazor";
                options.Cookie.SameSite = SameSiteMode.Strict;
            })
             .AddOpenIdConnect("Google", "Sign-in with Google", options =>
             {
                 options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                 options.ForwardSignOut = IdentityServerConstants.DefaultCookieAuthenticationScheme;

                 options.Authority = "https://accounts.google.com/";
                 options.ClientId = "708778530804-rhu8gc4kged3he14tbmonhmhe7a43hlp.apps.googleusercontent.com";
                 options.CallbackPath = "/signin-google";
                 options.Scope.Add("email");
             })
            .AddOpenIdConnect("oidc", options =>
            {
                options.Authority = "https://localhost:44304";
                options.ClientId = "interactive.confidential";
                options.ClientSecret = "secret";
                options.ResponseType = "code";
                options.ResponseMode = "query";

                options.MapInboundClaims = false;
                options.GetClaimsFromUserInfoEndpoint = true;
                options.SaveTokens = true;

                options.Scope.Clear();
                options.Scope.Add("openid");
                options.Scope.Add("profile");
                options.Scope.Add("api");
                options.Scope.Add("offline_access");
            });

        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseSerilogRequestLogging();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseWebAssemblyDebugging();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

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

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

            app.UseEndpoints(endpoints =>
            {

                endpoints.MapBffManagementEndpoints();
                endpoints.MapRazorPages();
                endpoints.MapControllers()
                    .RequireAuthorization()
                    .AsLocalBffApiEndpoint();

                endpoints.MapRemoteBffApiEndpoint("/remote", "https://localhost:44304/api/test")
                    .RequireAccessToken();
                endpoints.MapFallbackToFile("index.html");
            });
        }
    }
leastprivilege commented 3 years ago

Are you sure you want to mix Blazor, BFF and IdentityServer in the same host?

This is def. not how we anticipated how BFF will be used - and we have not tested for it either.

What's the use case?

...also - do you send the antiforgery header when calling the controller?

rahul7720 commented 3 years ago

I'm upgrading (rebuilding) a Dotnet 4.6 Webapi LOB application to the latest Dotnet Core (6 Preview 4) with small architectural changes.

Legacy App FrontEnd: WPF Desktop Application Backend: Asp.Net WebAPI Authentication: JWT token-based authentication (Owin, OAuth, Thinktecture.IdentityModel on the single host)

New Application Frontend: Blazor.Client (WASM - Asp.Net Core Hosted) Backend: Blazor.Server (Resource Server + Identity Server/BFF) Authentication: IdentityServer 4 BFF (SPA Blazor)

The new application will replace the WPF client with the Blazor WASM. So my plan is to build a Blazor Wasm (Asp.Net Core hosted ) application, where the server app serves as a resource server and the authentication server.

I prefer to combine the resource server and the authentication server into a single host to keep the existing architecture as it is and for maintainability reasons. Is there any drawback to this approach?

Note: The anti-forgery header is not included in the header, as the call was made from the browser anonymously. Use case: To display the login page to users (E.g AccountController/Login) anonymously using [AllowAnonymous] Attribute

leastprivilege commented 3 years ago

OK - as I said we haven't tested this - but could work. Let us know if you run into problems.

The antiforgery header is required for security reasons - see https://docs.duendesoftware.com/identityserver/v5/bff/apis/local/

rahul7720 commented 3 years ago

I tried to access the MVC controller (..Account/Login) using postman with an anti-forgery header and it works. The Blazor client application also adds an anti-forgery header to the calls made directly from the blazor client.

Problem The blazor app and the identity server shares the same root address which is https://localhost:44304since both are hosted in the same server app. When the Login link is clicked from the client it redirects to the URL https://localhost:44304/Account/Login which is served by the MVC controller, and it returns 401. The AccountController decorated with [AllowAnonymous] attribute and the anti-forgery header is not set since it's a browser redirect.

As per my understanding anti-forgery header is not necessary when redirecting to the AccountController, as it's the same as calling https://demo.duendesoftware.com/Account/Login. It seems adding app.UseBff(); restricts the anonymous access to the AccountController.

I understand this scenario is not tested, but what would be the possible solution to overcome this situation. Is there any way to disable the anti-forgery header requirement for the AccountController ?

leastprivilege commented 3 years ago

right - so the AsLocalBffEndpoint extensions makes the assumption that you are decorating API endpoints - but you are also adding this to your UI endpoints. So I would remove this extension to see if that brings you further.

You can then afterwards decorate individual API controllers with the [BffLocalApi] attribute.

The names will change soon - but let me know if this works.

rahul7720 commented 3 years ago

I have commented on the .AsLocalBffApiEndpoint extension, and now the controllers decorated with [AllowAnonymous] can be accessed without any issues. I understand this disables the Anti Forgery Check.

Highly appreciated it if you could create a sample project for the scenario mention: A Blazor Asp.net hosted where the server app includes the IdentityServer and the BFF endpoint.

leastprivilege commented 3 years ago

I understand this disables the Anti Forgery Check.

As I said - you can decorate your API controller individually with [BffLocalApi].

I will write the sample request down, but since this is a pretty unusual scenario, it will have to go to backlog for now.

leastprivilege commented 3 years ago

After discussing this internally - we came to the conclusion that we do not want to encourage this setup.

If there is only a single host in total, you do not need IdentityServer, and if you plan for more clients, IdentityServer should be in a separate host.

rahul7720 commented 3 years ago

Is there any specific reason to have a separate host for the identity server? Consider single host (Asp.net core Web API) and multiple clients scenario (Blazor Client, WPF Client, Mobile Client), so a single server app fulfils all the needs. This is very common for ERP & Business applications.

Diagram (1)

brockallen commented 3 years ago

That's a very simple picture and that would make you think the solution should also be simple, but if you think through the detail of everything that's actually exposed from your IdentityServer+API host, and all the interactions each of those inbound connections expect, and what's needed to secure each of those pieces (authenticate and mitigate against attacks), then you'll start to come to the realization that it's complex, and complexity is the enemy of security. It's far easier to split discrete pieces into their own hosts for separation of concerns and isolation of how each piece needs to be secured. I understand the desire to have a single host for all of these things, but it also doesn't help that ASP.NET Core doesn't allow proper DI/middleware isolation with a single host.

Given what I said above, our BFF library was not designed for this scenario (mainly you'd be missing the anti-forgery feature). What you're asking IS possible, you'd just need to tap into the primitives from ASP.NET Core to juggle the requirements you have. If you're interested in consulting services to help you with this, then let us know.

rahul7720 commented 3 years ago

Understood from the security perspective. When a business application needs to be hosted in Azure App Service, two different instances need to be run. From a maintenance perspective, this will be a hassle. Is there any way to logically seperate the functionality under the single deployment?

github-actions[bot] commented 3 years ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue.