10

I'm implementing ASP.Net Identity 2 in a WebApi system. To manage email confirmation for new accounts, I had to create a custom ApplicationUserManager and register it so that it would be created for every request:

public class IdentityConfig{
    public static void Initialize(IAppBuilder app)
    {
        [snip]
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
    }

It works correctly inside an ApiController like this:

public class AccountController : ApiController
{
    public ApplicationUserManager UserManager
    {
        get
        {
            return HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
        }
    }

The problem I'm facing is that the ApplicationUserManager.Create method is not being called before I try to access it in the OAuth Token creation method:

public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var mgr = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();

In the above code, mgr is null because GetUserManager retrieves null

Is the token creation method somehow earlier in the pipeline such that the CreatePerOwinContext methods haven't beeen called yet? If so, what's the best way cache an ApplicationUserManager so that it can be used inside GrantResourceOwnerCredentials ?

LeftyX
  • 35,328
  • 21
  • 132
  • 193
Brian
  • 1,675
  • 3
  • 19
  • 29

3 Answers3

16

This one was tricky. It turns out that the startup code has to be done in a certain order.

This code will work.

public class IdentityConfig{
    public static void Initialize(IAppBuilder app)
    {   
        // correct... first create DB context, then user manager, then register  OAuth Provider
        app.CreatePerOwinContext(AuthContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new SimpleAuthorizationServerProvider()
        };
        app.UseOAuthAuthorizationServer(OAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
    }
}

If you change the order of how you register the code with the app, you can get unintended consequences. This code causes ApplicationUserManager to be null inside the token generation method GrantResourceOwnerCredentials

// wrong... these two lines must be after the ApplicationUserManager.Create line
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

[snip]

app.CreatePerOwinContext(AuthContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

While I'm at it, here's another thing that could trip someone up. These lines cause AuthContext to be null inside of the token generation method GrantResourceOwnerCredentials

// wrong... The AuthContext must be instantiated before the ApplicationUserManager
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext(AuthContext.Create);

[snip]

app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
Brian
  • 1,675
  • 3
  • 19
  • 29
5

Don't use HttpContext.Current.GetOwinContext() in GrantResourceOwnerCredentials as you already have a context: (OAuthGrantResourceOwnerCredentialsContext context)

Instead do this:

var mgr = context.OwinContext.GetUserManager<ApplicationUserManager>();

CreatePerOwinContext is created for each request so you should be able to fetch the object ApplicationUserManager. It should be called immediately, at Startup. Maybe your startup implementation is not correct. I wrote an answer here with some details for the implementation.

Community
  • 1
  • 1
LeftyX
  • 35,328
  • 21
  • 132
  • 193
  • I tried context.OwinContext too, but that didn't help. You were on the right track that it had something to do with the startup code though. The startup code needs to be run in a certain non-obvious order. Thanks for the tip that led me to the answer. – Brian Jan 03 '15 at 12:44
  • Should I dispose the `UserManager` somewhere in my `OAuthAuthorizationServerProvider` implementation? – Shimmy Weitzhandler Oct 16 '15 at 12:58
  • 1
    @Shimmy: There shouldn't be no need for that. `UserManager` implements `IDisposable` and `CreatePerOwinContext` registers the object only for the request. – LeftyX Oct 16 '15 at 14:21
  • @LeftyX, so I don't have to dispose it int the controllers either? Does the same apply to `DbContext`, `SignInManager`, and `UserStore`, if they're registered in OWIN as above? – Shimmy Weitzhandler Oct 17 '15 at 23:06
  • 2
    @Shimmy: I don't normally dispose in the controller. I don't dispose object which have a **per-request** life cycle. All those objects are nested so when the first one is disposed all the others are disposed too. – LeftyX Oct 18 '15 at 10:35
  • @LeftyX. Thanks. I wish I know where to find and prove this in OWIN's source code... – Shimmy Weitzhandler Oct 19 '15 at 00:00
1

You can also get a null returned when asking the OwinContext for the ApplicationUserManager if the app.UseWebApi(config); line is before the Oauth setup.

Below will cause most calls to HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();

to return null.

        HttpConfiguration config = new HttpConfiguration();

        WebApiConfig.Register(config);
        app.UseWebApi(config); <-- Bad location

        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
            Provider = new ApplicationOAuthProvider("clientId"),
            RefreshTokenProvider = new SimpleRefreshTokenProvider()
        };

        app.UseOAuthAuthorizationServer(OAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
        app.UseOAuthBearerTokens(OAuthOptions);

Moving app.UseWebApi(config); below the app.UseOAuth methods will allow you to get the ApplicationUserManager from the OwinContext.

This works for me:

        HttpConfiguration config = new HttpConfiguration();
        WebApiConfig.Register(config);

        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
            Provider = new ApplicationOAuthProvider("clientId"),
            RefreshTokenProvider = new SimpleRefreshTokenProvider()
        };

        app.UseOAuthAuthorizationServer(OAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
        app.UseOAuthBearerTokens(OAuthOptions);

        app.UseWebApi(config); // <- Moved
Chad Jessup
  • 540
  • 7
  • 15
  • So you mean do all of the identity startup ops before doing the Web API initialization? – Brian Apr 07 '15 at 11:35
  • I'll reword the answer, it was poorly worded. I had the same problem, but your accepted answer led me to my solution. For me, all calls to: `HttpContext.Current.GetOwinContext().GetUserManager();` would return null if I had `app.UseWebApi(config);` before my app.UseOAuth calls. Moving `app.UseWebApi(config);` to after my OAuth setup causes that GetUserManager call to then work. To answer your question directly, yes, that caused things to work for me. – Chad Jessup Apr 09 '15 at 05:52
  • `HttpContext.Current` introduces a dependency to `System.Web`, which is not available in self-hosted scenarios. `Request.GetOwinContext()` is injected, therefore allowing for test-ability. `Request.GetOwinContext()` is defined in the `Microsoft.AspNet.WebApi.Owin` nuget package. – Chef_Code Mar 25 '16 at 23:18