20

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 work fine. NOTE: I am not using the ASP.NET Identity Provider. Instead I have created a custom User entity and services for it.

 public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        ConfigureOAuth(app);

        var config = new HttpConfiguration();
        var container = DependancyConfig.Register();
        var dependencyResolver = new AutofacWebApiDependencyResolver(container);
        config.DependencyResolver = dependencyResolver;

        app.UseAutofacMiddleware(container);
        app.UseAutofacWebApi(config);

        WebApiConfig.Register(config);
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
        app.UseWebApi(config);
    }

    public void ConfigureOAuth(IAppBuilder app)
    {
        var oAuthServerOptions = new OAuthAuthorizationServerOptions
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new SimpleAuthorizationServerProvider()
        };

        // Token Generation
        app.UseOAuthAuthorizationServer(oAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

    }
}

and this is my implementation of the SimpleAuthorizationServerProvider class

private IUserService _userService;
    public IUserService UserService
    {
        get { return (IUserService)(_userService ?? GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IUserService))); }
        set { _userService = value; }
    }

    public async override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated();
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

        var user = await UserService.GetUserByEmailAndPassword(context.UserName, context.Password);

        if (user == null)
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }

        var identity = new ClaimsIdentity(context.Options.AuthenticationType);
        identity.AddClaim(new Claim("sub", context.UserName));
        identity.AddClaim(new Claim("role", "user"));

        context.Validated(identity);

    }
}

After I call the /token url, I receive the following error

No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself

Is there a way to use dependency injection inside this class? I am using a repository pattern to access my entities, so I don't think that it is a good idea to make a new instance of the object context. What is the correct way to do this?

Ivan Stoyanov
  • 5,412
  • 12
  • 55
  • 71

3 Answers3

19

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);
}
Community
  • 1
  • 1
jumuro
  • 1,517
  • 15
  • 17
  • 1
    OwinContextExtensions.GetAutofacLifetimeScope returns null for me, was there anything else you set up ? – Bram Vandenbussche Apr 23 '16 at 10:26
  • @BramVandenbussche, was your project working before changes for calling this method? – jumuro Apr 25 '16 at 07:07
  • 1
    @BramVandenbussche, are you calling `app.UseAutofacMiddleware(container)`? As indicated by Alex Meyer-Gleaves in one of his [posts](http://alexmg.com/owin-support-for-the-web-api-2-and-mvc-5-integrations-in-autofac/): "This is required because along with enabling the middleware DI support, **this is also responsible for placing the lifetime scope in the OWIN context**". – jumuro Apr 25 '16 at 07:37
  • 3
    Thx for the reply. I did perform the call to `app.UseAutofacMiddleware(container)` but the article you linked me too helped me figure out that I called it too late. I had to move that line BEFORE the setup of the OAuth and that fixed that problem. I'm now stuck with a `A delegate registered to create instances of 'System.Security.Principal.IPrincipal' returned null` exception, but that's because there is no CurrentContext in the case of a `/token` call. – Bram Vandenbussche Apr 25 '16 at 08:21
  • 2
    @BramVandenbussche, see my `Configuration()` method in the `Startup` class, where you can see the order of middlewares added to the OWIN pipeline. As I cannot add format code in the comment, I edited my answer. – jumuro Apr 25 '16 at 08:39
  • Thx for the clarification. For future visitors & reference it might be useful if you were to include the import bits of the `ConfigureAutofac` `ConfigureAuth` and `ConfigureWebApi` methods, so that it's clear exactly which calls are being made. – Bram Vandenbussche Apr 25 '16 at 09:00
  • @BramVandenbussche, those are my methods declared in `Startup` class where I configure each middleware. Please let me know if you need more details. Anyway, I will try to upload my project to github, I'll let you know. – jumuro Apr 25 '16 at 09:10
12

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).

  • 1
    then you can use `private readonly IAuthService _authService; public SimpleAuthorizationServerProvider(IAuthService authService){ _authService = authService; }` – BotanMan Apr 27 '16 at 13:25
  • @ShalomDahan Have a look at this page: https://autofaccn.readthedocs.io/en/latest/resolve/ – Imdad Feb 07 '19 at 21:24
3

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);

    }
pampi
  • 517
  • 4
  • 8