Milen
Milen

Reputation: 674

Azure AD application roles

I'm trying to create a protected controller via Azure AD application roles.

Here is an exempt from Startup.Auth, which is basically provided by Visual Studio template:

public void ConfigureAuth(IAppBuilder app)
        {
            ApplicationDbContext db = new ApplicationDbContext();

            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());

            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    ClientId = clientId,
                    Authority = Authority,
                    PostLogoutRedirectUri = postLogoutRedirectUri,

                    Notifications = new OpenIdConnectAuthenticationNotifications()
                    {
                        // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
                       AuthorizationCodeReceived = (context) => 
                       {
                           var code = context.Code;
                           ClientCredential credential = new ClientCredential(clientId, appKey);
                           string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
                           AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
                           AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                           code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);

                           return Task.FromResult(0);
                       }
                    }
                });
        }

Tried ApiController having attributes like:

[Authorize(Roles = "Administrators")]
// GET: api/Questions
[ResponseType(typeof(Question))]
public IHttpActionResult GetQuestions()
{
    ....
}

and a MVC Controller:

  [Authorize(Roles = "Administrators")]
    public ActionResult Index()
    {
     ....    
    }

In Azure Application manifest defined the following:

  "appRoles": [
      { 
        "id": "B4531A9A-0DC8-4015-8CE5-CA1DA1D73754",
        "allowedMemberTypes": ["User"], 
        "description": "Administrators",
        "displayName": "Administrators",
        "value": "Administrators",
        "isEnabled": true,
        "origin": "Application"
      }
  ]

Now executing GET request for /api/Questions redirects to https://login.microsoftonline.com and user authentication seems to be successful, beside there is an infinite loop of requests between localhost and microsoft online. See below:

Request 1

Request 2

Request 3

Request 4

What is it that I am doing wrong?

Using [Authorize] works just fine.

Upvotes: 2

Views: 3234

Answers (3)

memz
memz

Reputation: 45

protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        if (httpContext.Request.Url.IsLoopback)
        {
            return true;
        }

        return base.AuthorizeCore(httpContext);
    }

The above code is aiming to dismiss the localhost calls. Hope it works out for you.

Upvotes: 0

SvenAelterman
SvenAelterman

Reputation: 1662

For me, the solution above did not work. Instead, I implemented a custom AuthorizeAttribute as described in https://identityserver.github.io/Documentation/docs/overview/mvcGettingStarted.html to avoid the infinite redirect loop.

Here is my code for the custom AuthorizeAttribute implementation (again, largely from the resource above):

public class RoleAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        // If the user is authenticated, but we ended up here, it means that
        // the user is not in a role that's allowed to use the controller being called
        if (filterContext.HttpContext.User.Identity.IsAuthenticated)
            filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden);
        else
            filterContext.Result = new HttpUnauthorizedResult();
    }
}

PS: I am not including the TokenValidationParameters code and I can use both the AuthorizeAttribute with and without Roles specified. However, when I specify Authorize(Roles = "SomeRole") and the already authenticated and authorized user is not in that role, I am faced with the infinite redirect loop.

Therefore, for those controller methods where I have to use roles-based authorization, I use RoleAuthorize(Roles = "SomeRole") instead. I also have a custom 403 error page that is styled like the application and provides an appropriate amount of detail and sign out link so the user can try to authenticate as a different user.

I am not fully satisfied with that solution as I feel that the built-in AuthorizeAttribute should be able to handle this scenario. I am just not able to get it to work.

Upvotes: 4

Milen
Milen

Reputation: 674

It turns out the following should be added to Startup.Auth:

TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
    {
        ValidateIssuer = true,
        // map the claimsPrincipal's roles to the roles claim
        RoleClaimType = "roles",
    }

It is actually configuring 'roles' claim type in order to map it with the default behavior. Excellent explanation is available at: https://samlman.wordpress.com/2015/03/09/using-roles-in-azure-applications/

Upvotes: 8

Related Questions