6

I am using the Cookie Middleware to authenticate the user. I have been following this official tutorial.

Inside my Startup class, an excerpt from my Configure method looks like this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
  // ...

  // Cookie-based Authentication
  app.UseCookieAuthentication(new CookieAuthenticationOptions()
  {
    AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,        
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    Events = new CustomCookieAuthenticationEvents(app),
  });

  // ...
}

The CustomCookieAuthenticationEvents class is defined as follows:

public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
  private IApplicationBuilder _app;
  private IMyService _myService = null;
  private IMyService MyService
  {
    get
    {
      if(_myService != null)
      {
        return _myService;
      } else
      {
        return _myService = (IMyService) _app.ApplicationServices.GetService(typeof(IMyService));
      }
    }
  }

  public CustomCookieAuthenticationEvents(IApplicationBuilder app)
  {
    _app = app;
  }

  public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
  {
    string sessionToken = context.Principal.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value;
    LogonSession response = null;

    var response = await MyService.CheckSession(sessionToken);

    if (response == null)
    {
      context.RejectPrincipal();
      await context.HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    }
  }
}

Since the dependency injection is not available at Startup.Configure (the services are not even registered at that point), I made a bit of a workaround:

  1. Pass IApplicationBuilder service to the CustomCookieAuthenticationEvents class
  2. Fetch IMyService upon first request inside a read-only property (singleton pattern)

tl;dr

My solution works, but it's ugly. There is no dependency injection involved, as it is not possible at that time.

The essence of the problem is that I must instantiate CustomCookieAuthenticationEvents. As far as I have read the source code, there is no way around this, because the UseCookieAuthentication throws an exception if I omit the options parameter.

Any suggestion how can one make my current solution nicer?

2
  • 1
    What's Service.Configure? Commented Apr 14, 2017 at 12:19
  • This is a typo. I meant Startup.Configure. Commented Apr 14, 2017 at 12:22

2 Answers 2

13

Startup.ConfigureServices() is called before Startup.Configure() (see https://learn.microsoft.com/en-us/aspnet/core/fundamentals/startup for more information). So Dependency Injection is available at that time ;)
As a consequence, you can resolve your dependence in your configure method like this:

app.ApplicationServices.GetRequiredService<CustomCookieAuthenticationEvents>()
Sign up to request clarification or add additional context in comments.

3 Comments

You're right. I don't know why I thought the Configure is called before ConfigureServices. But I am glad it isn't.
this approach uses the service locator pattern, you don't need to do that, you can simply add what you need into the Startup.Configure method signature and it will be injected into the method
You are right, I have just shown how to get a dependence from the Configure method if you really need it ;)
4

You should be really careful when you resolve services inside middleware. Your current approach (and the one suggested by @arnaudauroux) can result in difficulties when you use/need/require scoped services (i.e. usage of DbContext).

Resolving via app.ApplicationServices results in static (singleton) services, when the service is registered as scoped (transient are resolved per call, so they are not affected). It would be better to resolve your service during the request from HttpContext inside ValidatePrincipal method.

public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
    string sessionToken = context.Principal.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value;
    LogonSession response = null;

    var myService = context.HttpContext.RequestServices.GetService<IMyService >();
    var response = await myService.CheckSession(sessionToken);

    if (response == null)
    {
        context.RejectPrincipal();
        await context.HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    }
}

With this approach you don't need to pass any dependencies inside your CustomCookieAuthenticationEvents class at all. HttpContext.RequiredServices is made specifically for such classes (any other can be solved via constructor injection, but not middleware and http context related pipeline, as there is no other otherway to correctly resolve scoped services in middlewares - Middleware instance is static and only instantiated once per request)

This way you won't have lifetime issues with your scoped services. When you resolve transient services, they will be disposed at the end of request. Whereas transient services resolved via app.ApplicationServices will be resolved at some point in future after the request is finished and when garbage collection triggers (means: your resources will be freed at the earliest possible moment, which is when the request ends).

8 Comments

The CustomCookieAuthenticationEvents is a singleton, so it's OK if I pass it at constructor time via dependency injection. But will probably use your approach for scoped variables.
@alesc: Yes, CustomCookieAuthenticationEvents is a singleton, but IMyService and it's dependencies may not be singletons. That's the point I was going for. That's why you should resolve requesttime dependencies from HttpContext.RequestServices
One issue you may encounter is that scoped services become singletons by this usage,because when it's first resolved by app.ApplicationServices it gets stored in the parent container (which has lifetime that equals the lifetime of the application, since the parent container is only created and disposed when the app starts).When you now resolve that service via HttpRequest.RequestServices you work on child container and the child looks in the parent containers lookup table. If it finds a scoped/singleton there,it uses it instead of creating it.As such it doesn't dispose it at end of request
@Cubelaster: Common/recommended Seeding pattern for EF Core can be seen in this answer rather than inside Startup
@Cubelaster: The scoped container is resolved in program.cs (or rather the extension method), then the service from it. Once done, the scope (and all services that were initialized by it) get disposed. Notice the line with using (var scope = webhost.Services.GetService<IServiceScopeFactory>().CreateScope()) { ... }
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.