MarimerLLC / cslaforum

Discussion forum for CSLA .NET
https://cslanet.com
Other
31 stars 6 forks source link

How to implement Windows Auth on BlazorClient ? #825

Open drakeforte5 opened 5 years ago

drakeforte5 commented 5 years ago

I'm tracing the csla source code and I get lost as I came from the version 2.xx which was more straightforward to debug/follow, eventually upgraded. Recent versions are much more powerful and complex hence I need to ask the question.

HttpProxy, which is what the BlazorClient uses seem to retain the old-windows convention of not serializing the principal if AuthenticationType == "Windows". However the server-counterpart which I believe is the HttpPortalController/HttpPortal seems to just deserialize the principal.

I added Authorize attribute on the BlazorExample.Server.Controller.DataPortalController to test it out while switching the Enable Windows Auth on the server project.

`[Route("api/[controller]")]

[ApiController]

[Authorize()]

public class DataPortalController : Csla.Server.Hosts.HttpPortalController`

Also, I added this to the BlazorExample.Client to force windows auth.

`public void Configure(IComponentsApplicationBuilder app) { app.AddComponent("app");

  app.UseCsla((c) => 
    c.DataPortal().DefaultProxy(typeof(Csla.DataPortalClient.HttpProxy), "/api/DataPortal").
    **AuthenticationType("Windows")**
    );
}`

I am getting an error 500 and the Csla.ApplicationContext.User is null when debugged on the server-side.

Is there a setting or code that I need to implement? I was hoping that the HttpClient instance on the HttpProxy would just use HttpHandler with .UseDefaultCredentials when using Windows auth and that the client request would just work seamlessly. (https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.usedefaultcredentials?view=netframework-4.8)

Angular's HttpRequest has a similar concept where if you set withCredentials = true then the windows identity will get passed into to the client's request to the server. (https://angular.io/api/common/http/HttpRequest)

Please advise.

Thank you.

rockfordlhotka commented 5 years ago

Right now there's nothing in CSLA to support Blazor authentication.

There is support for authorization, in that if your server can create a valid IPrincipal representing the user (and make it serializable) then CSLA will happily make that available to client and server code via ApplicationContext.User.

But I haven't looked at Blazor authentication and how a Blazor client app might authenticate against the server such that the server would have the user's identity and so be able to create that principal object.

fwiw, CSLA has never done anything with authentication - I've always relied on the platform to do that work, and once the platform has done the hard work then the data portal is happy to ferry the identity between client and server.

drakeforte5 commented 5 years ago

I think I know the solution for using Wasm blazor client with Windows-authenticated server. The steps I took were:

  1. Mark the BlazorExample.Server to Enable Windows Authentication
  2. add app.UseAuthorization(); on the Startup.Config method
  3. assign the HttpContext.User to the Csla.ApplicationContext.User in the DataPortalController

` public override Task PostAsync([FromQuery] string operation) { Csla.ApplicationContext.User = HttpContext.User; // need this so Csla.ApplicationContext.User is not null

  return base.PostAsync(operation);
}`

My reservation on this is about the assignment of the HttpContext.User to the ApplicationContext.User. Is the ApplicationContext.User thread-safe? Is it guaranteed that the originating principal is correct when multiple users begin to hit the DataPortalController? Is this the correct approach to doing this?

I wanted this to work as simple as possible just like in Windows Forms with AuthenticationType="Windows".

Thank you.

drakeforte5 commented 5 years ago

In addition to this, should the Csla.Shared.Server.Hosts.HttpChannel.CriteriaRequest.Principal be updated to mimic what the Csla.Windows.Shared.ApplicationContextManager.GetUser() is doing where it returns a WindowsPrincipal if the AuthenticationType = "Windows"?

I was tracing the source code and what I understand is that the Csla.Web.Mvc.Shared.Server.Hosts.HttpPortal.Fetch/Update/Delete calls the ConvertRequest() which is a CriteriaRequest which in turn has the Principal property.

I know there must be a reason why you have not done this in the HttpPortal call stack but I would like to understand your wisdom behind it.

Thank you.

rockfordlhotka commented 5 years ago

My reservation on this is about the assignment of the HttpContext.User to the ApplicationContext.User. Is the ApplicationContext.User thread-safe? Is it guaranteed that the originating principal is correct when multiple users begin to hit the DataPortalController? Is this the correct approach to doing this?

Yes, this is expected behavior.

HttpContext is threadsafe - it exists on a per-server-request basis.

The context manager used within an ASP.NET server by CSLA is designed around the HttpContext. In fact, you shouldn't need to explicitly do the assignment you are talking about, because ApplicationContext.User should be pulling its value from HttpContext automatically.

If that is not automatic that seems to imply that the server-side isn't using the correct context manager, and that could be a serious problem! The server project needs to reference the Csla.AspNetCore package, not just the Csla package, otherwise it won't have access to the correct context manager.

rockfordlhotka commented 5 years ago

In addition to this, should the Csla.Shared.Server.Hosts.HttpChannel.CriteriaRequest.Principal be updated to mimic what the Csla.Windows.Shared.ApplicationContextManager.GetUser() is doing where it returns a WindowsPrincipal if the AuthenticationType = "Windows"?

These are two different things. The context manager is responsible for managing the User property value. That's not something a data portal channel should worry about.

But as I note in my previous post - that's not the right context manager for use on a web server, and that's a serious problem. The web server must use the context manager designed for ASP.NET servers.

drakeforte5 commented 5 years ago

I verified the BlazorExample.Server nuget packages and it does reference Csla.AspNetCore and I am setting the AuthenticationType to Windows:

app.UseCsla((c) => c.DataPortal().AuthenticationType("Windows"));

but still the Csla.ApplicationContext.User is null hence I added this line in the DataPortalController.PostAsync to solve the issue. Csla.ApplicationContext.User = HttpContext.User;

The code is from the CSLA Blazor sample just modified to use Windows Auth. Am I missing something else, some other setting perhaps?

drakeforte5 commented 5 years ago

One more observation. While inside the DataportalController.PosAsync I typed this in the VS debug command window:

? System.Type.GetType("Csla.Web.ApplicationContextManager, Csla.Web")

and I get null which translates to a null value for the Csla.ApplicationContext.WebContextManager .

This is supposedly mimicking line number 55 in the Csla.Shared.ApplicationContext where it is resolving the WebContextManager.

drakeforte5 commented 5 years ago

This might be bug Rocky.

This returns null: ? System.Type.GetType("Csla.Web.ApplicationContextManager, Csla.Web")

While this returns the correct type, it is because of the assembly name: ? System.Type.GetType("Csla.Web.ApplicationContextManager, Csla.AspNetCore.Mvc")

rockfordlhotka commented 5 years ago

There may be a bug. But first, I think you are misunderstanding the way AuthenticationType works. This is described in the Using CSLA 4 data portal book.

That confg value must be the same on client and server, because it controls how (or if) the data portal transports the user's identity from client to server on each data portal call.

The idea behind setting the value to Windows is that the user's identity is managed by something outside CSLA - originally Windows, but these days there are probably other options. The point being, if the value is set to Windows then the server (Windows or your code) are responsible for setting the principal before the data portal is invoked.

drakeforte5 commented 5 years ago

Thanks Rocky. Yes the AuthenticationType upon reviewing the source code has retained the concept when set to Windows ever since Csla 2.x and it looks like there might be a bug on the ApplicationContext just like I described. For now I am setting the ApplicationContext.User to Httpcontext.User to remediate the issue on the DaportalController.

On Mon, Oct 7, 2019, 7:29 PM Rockford Lhotka notifications@github.com wrote:

There may be a bug. But first, I think you are misunderstanding the way AuthenticationType works. This is described in the Using CSLA 4 data portal book.

That confg value must be the same on client and server, because it controls how (or if) the data portal transports the user's identity from client to server on each data portal call.

  • Csla means that the data portal transports the client-side principal to the server with each call, and sets the server's principal to match that from the client
  • Windows means that the data portal does not transport the client-side principal to the server, and does not set the server-side principal at all

The idea behind setting the value to Windows is that the user's identity is managed by something outside CSLA - originally Windows, but these days there are probably other options. The point being, if the value is set to Windows then the server (Windows or your code) are responsible for setting the principal before the data portal is invoked.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/MarimerLLC/cslaforum/issues/825?email_source=notifications&email_token=ALDSZ5EFJAG4SRT27A35IE3QNPV7HA5CNFSM4I5RM6M2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEASNM6Q#issuecomment-539285114, or mute the thread https://github.com/notifications/unsubscribe-auth/ALDSZ5B7ULLZUPRXMERO4GTQNPV7HANCNFSM4I5RM6MQ .

rockfordlhotka commented 5 years ago

@drakeforte5 totally agree with the bug. On it! 😄

rockfordlhotka commented 5 years ago

I just pushed a prerelease of v5.1.0 that should fix the issue.

drakeforte5 commented 5 years ago

I'll go ahead and test it and close this ticket after. Thanks.

On Thu, Oct 10, 2019, 1:10 PM Rockford Lhotka notifications@github.com wrote:

I just pushed a prerelease of v5.1.0 that should fix the issue.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/MarimerLLC/cslaforum/issues/825?email_source=notifications&email_token=ALDSZ5GYUTRMHE3H265ZI23QN6DZXA5CNFSM4I5RM6M2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEA5W62A#issuecomment-540766056, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALDSZ5C6XUFL76HG4KFEUN3QN6DZXANCNFSM4I5RM6MQ .

drakeforte5 commented 5 years ago

It is now working and the user is now being returned by Csla.ApplicationContext.User on pre-v5.1.0. A very important point however is that the HttpContextAccessor has to be injected during the BlazorExample.Server.Startup.ConfigureServices().

services.AddHttpContextAccessor();

I have conflicting information about the HttpContextAccessor as I thought it is now automatically injected but apparently not because If I don't include the AddHttpContextAccessor the Csla.Application.User returns UnauthenticatedPrincipal naturally because Csla.AspNetCore.Shared.ApplicationContextManager.HttpContext line 51:

var httpContextAccessor = (IHttpContextAccessor)_serviceProvider.GetService(typeof(IHttpContextAccessor));

will be null.

Anyway, it is now working with a caveat that AddHttpContextAccessor() must be called on the Startup.

Thanks!

rockfordlhotka commented 5 years ago

Yes, I found similarly confusing info. But what I gather is the latest/best approach is that if you want to access HttpContext (or if you want CSLA to be able to access it) in any aspnetcore server project, you need the following in Startup.ConfigureServices:

      services.AddHttpContextAccessor();
      services.AddCsla();

And then in Startup.Configure:

      app.UseCsla();
      Configuration.ConfigureCsla();

Inside of CSLA the IHttpContextAccessor is used as you describe, but it relies on the AddHttpContextAccessor method being called in ConfigureServices.