locked
OWIN Mvc5 + Web Api RRS feed

  • Question

  • User280723817 posted

    Hi

    I have a question about getting both mvc & web api working correctly with ADFS. Looks like I have post the question in the wrong place.

    http://forums.asp.net/t/2079851.aspx?OWIN+Mvc5+WebApi

    Hopefully more security experts are watching this category.

    Any help would be appreciated.

    Thanks
    Ras

    Thursday, December 10, 2015 9:50 PM

Answers

  • User280723817 posted

    Ok. I got further. I was able to check the "/api" path before redirecting to the identity provider like this:

     app.UseWsFederationAuthentication(
                    new WsFederationAuthenticationOptions
                    {
                        Wtrealm = realm,
                        MetadataAddress = adfsMetadata,
                        Notifications = new WsFederationAuthenticationNotifications
                        { 
                           RedirectToIdentityProvider = notification =>
                           {
                               if (notification.OwinContext.Request.Path.StartsWithSegments(new PathString("/api")))
                               {
                                   notification.HandleResponse();
                               }
                               return Task.FromResult(0);
                           }
                        }
                                           
                    });

    Is this an acceptable solution, security-wise? Seems a bit hacky with the hard-coded path but I'm more concerned about the implications of .HandleResponse() if that could do something bad in terms of security.

    Thanks!

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, December 11, 2015 6:24 PM

All replies

  • User-2057865890 posted

    Hi,Ras

    Welcome to asp.net forum.

    An authentication filter validates access tokens, and the [Authorize] attribute is used to protect a resource.

    When a controller or action has the [Authorize] attribute, all requests to that controller or action must be authenticated.

    Otherwise, authorization is denied, and Web API returns a 401 (Unauthorized) error.

    // If we already have a bearer token, set the Authorization header.
    var token = sessionStorage.getItem(tokenKey);
    var headers = {};
    if (token) {
        headers.Authorization = 'Bearer ' + token;
    }
    
    $.ajax({
        type: 'GET',
        url: 'api/values/1',
        headers: headers
    }).done(function (data) {
        self.result(data);
    }).fail(showError);

    More information,please see

    Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2

    Best regards,

    Chris Zhao

    Friday, December 11, 2015 1:50 AM
  • User280723817 posted

    Hi Chris, Thanks for the response. That part is all good for me. To explain my scenario better, I created a sample app:

    1. In VS2015 I created a new ASP.NET Web Application (4.5.2) and chose MVC with Web API

    2. For Authentication, I picked Work And School Accounts and used On-Premises option with ADFS metadata and App ID.

    3. In Startup.Auth it already had app.UseWsFederationAuthentication and I added app.UseActiveDirectoryFederationServicesBearerAuthentication() for the API. So my code looks like this:

    public partial class Startup
        {
            private static string realm = ConfigurationManager.AppSettings["ida:Wtrealm"];
            private static string adfsMetadata = ConfigurationManager.AppSettings["ida:ADFSMetadata"];
    
            public void ConfigureAuth(IAppBuilder app)
            {
                app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
    
                app.UseCookieAuthentication(new CookieAuthenticationOptions());
    
                app.UseWsFederationAuthentication(
                    new WsFederationAuthenticationOptions
                    {
                        Wtrealm = realm,
                        MetadataAddress = adfsMetadata
                    });
    
                var options = new ActiveDirectoryFederationServicesBearerAuthenticationOptions
                {
                    MetadataEndpoint = adfsMetadata,
                    TokenValidationParameters = new TokenValidationParameters()
                    {
                        ValidAudience = realm
                    },
                };
            
                app.UseActiveDirectoryFederationServicesBearerAuthentication(options);
            }
        }

    4. My MVC HomeController has MVC AuthorizeAttribute

     [Authorize]
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                return View();
            }      
        }

    5. Then I added an API Controller and added protected it with the Http Authorize attribute.

        [Authorize]
        public class ValuesController : ApiController
        {
            // GET: api/Values
            public IEnumerable<string> Get()
            {
                return new string[] { "value1", "value2" };
            }
        }

    6. I run my MVC home page and I get authenticated correctly by ADFS and that's good. 

    7. Then I generate a Bearer token and call the API and I can get values back. That's also good. I get ["value1","value2"] back.

                    HttpClient client = new HttpClient();
                    HttpRequestMessage request =
                        new HttpRequestMessage(HttpMethod.Get, "https://localhost:44329/api/values");
                    request.Headers.TryAddWithoutValidation("Authorization", access.Token);
                    HttpResponseMessage response = client.SendAsync(request).Result;
                    string responseString = response.Content.ReadAsStringAsync().Result;
                    Console.WriteLine(responseString);

    8. Now I add a role to the API Authorize attribute for a scenario where caller does not have access:

     [Authorize(Roles = "user-does-not-have-this-role")]
        public class ValuesController : ApiController
        {
            // GET: api/Values
            public IEnumerable<string> Get()
            {
                return new string[] { "value1", "value2" };
            }
        }

    9. This is where the issue is. Now the client code (Step #7) is returning an HTML content back which seems like an error page from the ADFS saying you don't have JavaScript. The issue is that when the Web API threw a 401, the MVC part of the OWIN Auth logic kicked in and redirected the call to ADFS instead of returning a 401 like it should.

    10. Now if I comment out the 'app.UseWsFederationAuthentication' part, I correctly get the 401 Unauthorized message for the API client (step 7)

    {"Message":"Authorization has been denied for this request."}

    I want to use both authentication options but I want to make it so that the passive authentication for MVC does not mess with the API 401s.

    How can I do that?

    Friday, December 11, 2015 4:21 AM
  • User280723817 posted

    I guess another way to look at this, I want the Federation authentication middleware for everything EXCEPT /api/ path. And the Bearer token middleware ONLY for /api/ path. Maybe there's a different way to handle this? Anyone else had to handle this same situation?

    Thanks,

    Ras

    Friday, December 11, 2015 5:30 PM
  • User280723817 posted

    Ok. I got further. I was able to check the "/api" path before redirecting to the identity provider like this:

     app.UseWsFederationAuthentication(
                    new WsFederationAuthenticationOptions
                    {
                        Wtrealm = realm,
                        MetadataAddress = adfsMetadata,
                        Notifications = new WsFederationAuthenticationNotifications
                        { 
                           RedirectToIdentityProvider = notification =>
                           {
                               if (notification.OwinContext.Request.Path.StartsWithSegments(new PathString("/api")))
                               {
                                   notification.HandleResponse();
                               }
                               return Task.FromResult(0);
                           }
                        }
                                           
                    });

    Is this an acceptable solution, security-wise? Seems a bit hacky with the hard-coded path but I'm more concerned about the implications of .HandleResponse() if that could do something bad in terms of security.

    Thanks!

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, December 11, 2015 6:24 PM