Search code examples
asp.net-mvcrazorasp.net-web-api2authorize

Consume Authorize WebAPI 2 from MVC


I have an ApiController and a Controller in the same ASP.NET project. The idea is that I want to expose a REST API to 3rd parties and build a website on top of the REST API that I'm exposing.

I want to consume the REST API (in ProfileApiController) from within my MVC controller (in ProfileController). Both controllers require to be authenticated and the response of the ProfileApiController depends on the User.Identity that is active.

How can I achieve this?

Code below:

namespace Controllers
{
    [Authorize]
    public class ProfileApiController : ApiController
    {

        [Route("api/profile/{param}")]
        [HttpGet]
        public async Task<IHttpActionResult> GetProfile(string param)
        {
            return this.Ok<IEnumerable<TransferObject>>( /* business logic */ );
        }
    }


    [Authorize]
    public class ProfileController : Controller
    {
        public async Task<ActionResult> GetProfile()
        {
            //Pseudocode -- this is what I'm looking for
            var api = (reference_to_profileapicontroller);
            api.Authenticate(User.Identity);
            var m = api.GetProfile("myparameter");
            //End Pseudocode

            return View(m):
        }
    }

}

I have already tried two approaches:

  • Call the WebApi through the HttpClient

        HttpClientHandler h = new HttpClientHandler();
        var client = new HttpClient(h);
        var response = client.GetAsync("http://localhost:4827/api/profile/param/").Result;
        var m = await response.Content.ReadAsAsync<List<TransferObject>>();
        return View(m);
    

but here I'm stuck with passing the identity from the Controller to the ApiController

  • Invoke the Controller directly

        var pc = DependencyResolver.Current.GetService<ProfileController>();
        var r = await pc.GetTenseProfile("param");
        var rr = await r.ExecuteAsync(System.Threading.CancellationToken.None);
        var m = await rr.Content.ReadAsAsync<List<TransferObject>>();
        return View(m);
    

but this turns into a mess as pc.Configuration and pc.Request need to be configured. This shouldn't be this hard?


Solution

  • I would go one of 3 routes, in this order.

    1. Move your logic that is common to both the Controller and ApiController into a class then use that class in your controller.

      [Authorize]
      public class ProfileApiController : ApiController
      {
          [Route("api/profile/{param}")]
          [HttpGet]
          public async Task<IHttpActionResult> GetProfile(string param)
          {
              // have all business logic in this class
              ProfileClass = newClass = new ProfileClass();
              IList<TransferObject> vm = newClass.GetData();  // from bus rules
      
              return this.Ok<IList<TransferObject>>(vm);
          }
      }
      
      [Authorize]
      public class ProfileController : Controller
      {
          public async Task<ActionResult> GetProfile()
          {
              // have all business logic in this class
              ProfileClass = newClass = new ProfileClass();
              IList<TransferObject> vm = newClass.GetData();  // from bus rules
      
              return View(vm):
          }
      }
      
    2. Consume your API via AJAX. This is more server round trips, but uses your API as it was designed. Use the parameter in the view to make the AJAX call to the API controller.

      [Authorize]
      public class ProfileController : Controller
      {
          public async Task<ActionResult> GetProfile()
          {
              return View("myparameter"):
          }
      }
      
    3. Use Claims based authentication which includes headers in your requests. If you're securing your API, then you're probably already doing this. Use HttpClient as you've listed above, then just add the bearer token in the header based on the user in MVC.

      HttpClient client = new HttpClient();
      client.DefaultRequestHeaders.Authorization = 
          new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
      

      This may also help: http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api

    Lots of redundant code in options 2 and 3. It's better for your controllers to be agnostic of the business logic and have your code consume that. I don't think it's a good practice to have to create HttpRequests all over the place in your MVC code in every Action. That's going to lead to alot of headaches down the road when you have to refactor things.