Autofac dependency injection in implementation of OAuthAuthorizationServerProvider

前端 未结 3 1856
陌清茗
陌清茗 2020-12-15 18:24

I am creating a Web Api application and I want to use bearer tokens for the user authentication. I implemented the token logic, following this post and everything seems to w

相关标签:
3条回答
  • 2020-12-15 18:53

    I have had a similar problem.

    The problem here is that when you try to inject IUserService in your provider, Autofac detects that it has been registered as InstancePerRequest (that uses the well-known lifetime scope tag 'AutofacWebRequest') but the SimpleAuthorizationServerProvider is registered in the 'root' container scope where the 'AutofacWebRequest' scope is not visible.

    A proposed solution is to register dependencies as InstancePerLifetimeScope. This apparently solved the problem but introduces new ones. All dependencies are registered in the 'root' scope, that implies having the same DbContext and services instances for all the requests. Steven explains very good in this answer why is not a good idea to share the DbContext between requests.

    After deeper investigation tasks, I've solved the problem getting the 'AutofacWebRequest' from the OwinContext in the OAuthAuthorizationServerProvider class and resolving the services dependencies from it, instead of letting Autofac to inject them automatically. For this I've used the OwinContextExtensions.GetAutofacLifetimeScope() extension method from Autofac.Integration.Owin, see example below:

    using Autofac.Integration.Owin;
    ...
    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        ...
        // autofacLifetimeScope is 'AutofacWebRequest'
        var autofacLifetimeScope = OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext);
        var userService = autofacLifetimeScope.Resolve<IUserService>();
        ...
    }
    

    I've made OAuthAuthorizationServerProvider registration and injection inside ConfigureOAuth method in a similar way than proposed by Laurentiu Stamate in another response to this question, as SingleInstance(). I've implemented RefreshTokenProvider in the same way.

    EDIT

    @BramVandenbussche, this is my Configuration method in the Startup class, where you can see the order of middlewares added to the OWIN pipeline:

    public void Configuration(IAppBuilder app)
    {
        // Configure Autofac
        var container = ConfigureAutofac(app);
    
        // Configure CORS
        ConfigureCors(app);
    
        // Configure Auth
        ConfigureAuth(app, container);
    
        // Configure Web Api
        ConfigureWebApi(app, container);
    }
    
    0 讨论(0)
  • 2020-12-15 18:58

    I also tried @jumuro answer using the OwinContextExtensions.GetAutofacLifetimeScope that saves my day. Instead of registering the IUserService at runtime, this answer gives an option on validation/creating the instance service after request.

    I added some new answer because I can't comment yet because of my low reputations but added additional guide codes to help someone.

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
    
            try
            {
                if (service == null)
                {
                    var scope = Autofac.Integration.Owin.OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext);
                    service = scope.Resolve<IUserService>();
                }
                var user = await service.FindUserAsync(context.UserName);
                if (user?.HashedPassword != Helpers.CustomPasswordHasher.GetHashedPassword(context.Password, user?.Salt))
                {
                    context.SetError("invalid_grant", "The user name or password is incorrect.");
                    return;
                }
            }
            catch(Exception ex)
            {
                context.SetError("invalid_grant", ex.Message);
                return;
            }
    
            var identity = new ClaimsIdentity(context.Options.AuthenticationType);
            identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
    
            AuthenticationProperties properties = CreateProperties(context.UserName);
            AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
            context.Validated(ticket);
            context.Request.Context.Authentication.SignIn(identity);
    
        }
    
    0 讨论(0)
  • 2020-12-15 19:00

    To use dependency injection in SimpleAuthorizationServerProvider you have to register IOAuthAuthorizationServerProvider to the Autofac container just like any other type. You can do something like this:

    builder
      .RegisterType<SimpleAuthorizationServerProvider>()
      .As<IOAuthAuthorizationServerProvider>()
      .PropertiesAutowired() // to automatically resolve IUserService
      .SingleInstance(); // you only need one instance of this provider
    

    You also need to pass the container to the ConfigureOAuth method and let Autofac resolve your instance like this:

    var oAuthServerOptions = new OAuthAuthorizationServerOptions
    {
        AllowInsecureHttp = true,
        TokenEndpointPath = new PathString("/token"),
        AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
        Provider = container.Resolve<IOAuthAuthorizationServerProvider>()
    };
    

    You should always use single instances if your properties within the object don't change via external data (let's say you have a property which you set in the controller which dependents upon some information stored in the database - in this case you should use InstancePerRequest).

    0 讨论(0)
提交回复
热议问题