ASP.NET MVC Authenticate before creating a controller instance

I have a controller where I insert the service interface into the constructor. A service has interfaces entered into its constructor. The IoC (Unity) container must use user information when building one of the classes that it returns for this interface.

What happens is that the controller is created before the [Authorize] attribute is evaluated and the user is authenticated. This forces Unity to inject dependencies and use user information before they log in. None of these problems was a problem when using integrated Windows authentication, but now we use OpenID Connect for Azure AD, and the user information isn’t there until they log in (which happens AFTER the controller is installed).

I heard (in other posts) that there is a way to configure my owin startup class to transfer authentication earlier in this process, but I cannot find examples of how to do this. I need authentication before the controller instance is created.

Here is a simplified example of what I have ...

Controller:

[Authorize] public class MyController : Controller { private readonly IMyService myService; public MyController(IMyService myService) { this.myService = myService; } // ... } 

Unity configuration:

 public class UnityBootstrap : IUnityBootstrap { public IUnityContainer Configure(IUnityContainer container) { // ... return container .RegisterType<ISomeClass, SomeClass>() .RegisterType<IMyService>(new InjectionFactory(c => { // gather info about the user here // eg var currentUser = c.Resolve<IPrincipal>(); var staff = c.Resolve<IStaffRepository>().GetBySamAccountName(currentUser.Identity.Name); return new MyService(staff); })); } } 

Starting OWIN (Startup.Auth.cs):

 public void ConfigureAuth(IAppBuilder app) { app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); app.UseCookieAuthentication(new CookieAuthenticationOptions()); app.UseOpenIdConnectAuthentication( new OpenIdConnectAuthenticationOptions { ClientId = this.clientID, Authority = this.authority, PostLogoutRedirectUri = this.postLogoutRedirectUri, Notifications = new OpenIdConnectAuthenticationNotifications { RedirectToIdentityProvider = context => { context.ProtocolMessage.DomainHint = this.domainHint; return Task.FromResult(0); }, AuthorizationCodeReceived = context => { var code = context.Code; var credential = new ClientCredential(this.clientID, this.appKey.Key); var userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value; var authContext = new AuthenticationContext(this.authority, new NaiveSessionCache(userObjectID)); var result = authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, this.graphUrl); AzureAdGraphAuthenticationHelper.Token = result.AccessToken; return Task.FromResult(0); } } }); } 
+6
source share
1 answer

After you did not find anything specifically to solve my problem on the Internet, I decided to look into the life cycle of an ASP.NET MVC 5 application. I found that I could create a custom IControllerFactory (or inherit from DefaultControllerFactory), where I could define a CreateController method.

During CreateController, I check if the user is verified. If they are, I just let the DefaultControllerFactory create the controller, as usual.

If the user is not authenticated, I create my (very) simple "Auth" controller instead of the requested controller (the one that has many levels of dependencies), while the RequestContext remains unchanged.

The Auth controller will be created without problems, since it has no dependencies. Note. No action is taken on the Auth controller. After creating the Auth controller, the global AuthorizeAttribute attribute is started and the user must authenticate (through OpenID Connect to Azure AD and ADFS).

After logging in, they are redirected back to my application with the original RequestContext, while still in tact. CusomControllerFactory sees the user as an authenticated and requested controller is created.

This method works fine for me, since my controllers have a large chain of dependencies that they introduce (i.e. the controller depends on ISomeService, which depends on many ISomeRepository, ISomeHelper, ISomethingEles ...), and none of the dependencies are resolved before until the user is logged in.

I can still definitely hear other (more elegant) ideas on how to achieve what I asked in my original question.


CustomControllerFactory.cs

 public class CustomControllerFactory : DefaultControllerFactory { public override IController CreateController(RequestContext requestContext, string controllerName) { var user = HttpContext.Current.User; if (user.Identity.IsAuthenticated) { return base.CreateController(requestContext, controllerName); } var routeValues = requestContext.RouteData.Values; routeValues["action"] = "PreAuth"; return base.CreateController(requestContext, "Auth"); } } 

Global.asax.cs

 public class MvcApplication : HttpApplication { protected void Application_Start() { // ... ControllerBuilder.Current.SetControllerFactory(typeof(CustomControllerFactory)); } // ... } 

AuthController.cs

 public class AuthController : Controller { public ActionResult PreAuth() { return null; } } 
+4
source

Source: https://habr.com/ru/post/981732/


All Articles