Search code examples
asp.netasp.net-web-apiowiniidentity

Get Owin IIdentity from IHttpHandler


Accepted answer note:

Although I have appreciated the help of creating my own OwinMiddleware to send images after doing some checks instead of IHttpModule, that doesn't solve the issue entirely.

The thing is I have added an Authorization header to the ajax requests, and inside that header I am sending my Bearer's Token so that I can get logged user information from Owin. So I have to add this header to the image requests either, to be able to get logged user information from image handler middleware.


Original Question:

I am following this blog post to create token based authentication for my web project. Because some resources of my Web API will be used by native mobile clients. And I have heard that token based authentication is the way to go for that. And in my own project I have a custom image request handler. And need the logged user information inside this handler. But when i try to extract user information from ticket I get null. And I am not sure about this but, I think I have 2 different IIdentity objects here, and I need the one stored inside Owin Context.

Here let me show you some codes;

My GrantResourceOwnerCredentials which is storing claims into ClaimsIdentity,

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
....
// checking user credentials and get user information into 'usr' variable
....

            var identity = new ClaimsIdentity(context.Options.AuthenticationType);
            identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
            identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
            identity.AddClaim(new Claim("sub", context.UserName));
            identity.AddClaim(new Claim(ClaimTypes.Sid, usr.UserId.ToString()));

            var props = new AuthenticationProperties(new Dictionary<string, string>
                {
                    {
                        "as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
                    },
                    {
                        "userId", usr.UserId.ToString()
                    }
                });

            var ticket = new AuthenticationTicket(identity, props);
            context.Validated(ticket);
}

Helper function to extract user id from the given IIdentity object

public class utils {
    public Guid? GetUserIdFromTicket(IIdentity identity)
    {
        var cId = (ClaimsIdentity)identity;
        var uid = cId.FindFirst(ClaimTypes.Sid);

        if (uid != null && Comb.IsComb(uid.Value))
            return new Guid(uid.Value);
        else
            return null;
    }
....
}

Now I can get the loggedUserId from my controller like,

    var loggedUserId = utils.GetUserIdFromTicket(User.Identity);

but if I call it from my IHttpHandler I get null,

    public class ImageHandler : IHttpHandler
    {
        public ImageHandler()
        {
        }

        public ImageHandler(RequestContext requestContext)
        {
            RequestContext = requestContext;
        }

        protected RequestContext RequestContext { get; set; }

        public utils utils = new utils(); // changed name for simplicity.

        public void ProcessRequest(HttpContext context)
        {
            var strUserId = RequestContext.RouteData.Values["userid"].ToString();
            var strContentId = RequestContext.RouteData.Values["contentid"].ToString();
            var fileName = RequestContext.RouteData.Values["filename"].ToString();
            var size = RequestContext.RouteData.Values["size"].ToString();

            var loggedUserId = utils.GetUserIdFromTicket(context.User.Identity);

....
image processing
....
            context.Response.End();
        }

}

Hope I didn't messed this up for good...

Solution:

I have implemented my own middleware to serv images to my users after doing some checks. Here is my Invoke task implementation. Everything else is just like as recommended in accepted answer. But as stated above, for this to work I have to send images with the Authorization header, or the loggedUserId will be null again.

public async override Task Invoke(IOwinContext context)
{
    // need to interrupt image requests having src format : http://www.mywebsite.com/myapp-img/{userid}/{contentId}/{fileName}/{size}/
    if (context.Request.Path.HasValue && context.Request.Path.Value.IndexOf("myapp-img") > -1)
    {
        // get values from url.
        var pathValues = context.Request.Path.Value.Split('/');
        var strUserId = pathValues[2].ToString();
        var strContentId = pathValues[3].ToString();
        var fileName = pathValues[4].ToString();
        var size = pathValues[5].ToString();

        // check if code returned a notfound or unauthorized image as response.
        var hasError = false;

        // get userId from static utils class providing current owin identity object
        var loggedUserId = ChildOnBlogUtils.GetUserIdFromTicket(context.Request.User.Identity);

        // save root path of application to provide error images.
        var rootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

        // assign content type of response to requested file type
        context.Response.ContentType = ChildOnBlogUtils.GetContentType(context.Request.Path.Value.ToString());

        // if user requested thumbnail send it without doing checks
        if (size == "thumb")
        {
            imgPath = "images/" + strUserId.ToLower() + "/thumbnail/" + fileName;
        }
        else
        {
            var canSee = false;

            // check if user can see the content and put the result into canSee variable
            // I am using loggedUserId inside these checks
            ...
            ...
            // end checks

            if (canSee)
            {
                // removed some more checks here for simplicity
                imgPath = "images/" + strUserId.ToLower() + "/" + fileName;
            }
            else
            {
                context.Response.ContentType = "Image/png";
                var imgData = File.ReadAllBytes(rootPath + "/images/unauthorized.png");
                await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
                hasError = true;
            }
        }

        if (!hasError) // if no errors have been risen until this point. try to provide the requested image to user.
        {
            try
            {
                var imgData = UserMediaContainer.GetFileContent(imgPath); // get file from storage account (azure)

                if (imgData.Length == 0)
                {
                    context.Response.ContentType = "Image/png";
                    imgData = File.ReadAllBytes(rootPath + "/images/notfound.png");
                    await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
                }
                else
                {
                    await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
                }
            }
            catch (Exception ex)
            {
                context.Response.ContentType = "Image/png";
                var imgData = File.ReadAllBytes(rootPath + "/images/notfound.png");
                await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
            }
        }
    }
    else if (context.Request.Path.HasValue && context.Request.Path.Value.IndexOf("profile-img") > -1)
    {
        // profile image provider. Same code as providing thumbnails.
    }
    else
    {
        // if it is not an image request to be handled. move to the next middleware.
        await Next.Invoke(context);
    }
}

Solution

  • I guess your ImageHandler is processed before everything else in the owin pipeline, which means it is processed before the authorization comes into place.

    Since you're using owin I would advise you to drop the IHttpHandler and use some custom owin middleware. Following this path will allow you to inject your module in the right place in the pipeline.

    Creating the middleware is quite easy:

    public class ImageProcessingMiddleware : OwinMiddleware
    {
        public ImageProcessingMiddleware(OwinMiddleware next): base(next)
        {
        }
    
        public async override Task Invoke(IOwinContext context)
        {
            string username = context.Request.User.Identity.Name;
    
            Console.WriteLine("Begin Request");
            await Next.Invoke(context);
            Console.WriteLine("End Request");
        }
    }
    

    Once you have defined your middleware you can create an extension method for the instantiation:

    public static class ImageProcessingExtensions
    {
        public static IAppBuilder UseImageProcessing(this IAppBuilder app)
        {
            return app.Use<ImageProcessingMiddleware>();
        }
    }
    

    Now you can plug-in your middleware in the pipeline:

    app.UseImageProcessing();
    

    If you have followed Taiseer sample, you would do that after you have configured the authorization module:

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

    Going back to the middleware, you might have noticed there's a method called Invoke:

    public async override Task Invoke(IOwinContext context)
    {
        string username = context.Request.User.Identity.Name;
    
        Console.WriteLine("Begin Request");
        await Next.Invoke(context);
        Console.WriteLine("End Request");
    }
    

    This is the entry-point of each middleware. As you can see I am reading the user's name authorized right after the authorization token has been verified and authorized.

    There's an interesting article about owin middleware which is worth reading.