2
votes

I am attempting to allow only Azure Active Directory B2C users with role "Global Administrator" to access the following class (which is why I have included the Authorize command):

[Authorize(Roles = "admin")]
public class UserProfileController : Controller
{
    ... controller class ...
}

And my Startup class looks like this:

public partial class Startup
{
    private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
    private static string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
    private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
    private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
    private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
    // This is the resource ID of the AAD Graph API.  We'll need this to request a token to call the Graph API.
    private static string graphResourceId = "https://graph.microsoft.com";
    private static readonly string Authority = aadInstance + tenantId;
    public static GraphServiceClient graphClient = null;

    public static GraphServiceClient GetGraphServiceClient()
    {
        return graphClient;
    }

    public void ConfigureAuth(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            CookieSecure = CookieSecureOption.Always
        });

        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;

                       TokenCache userTokenCache = new ADALTokenCache(signedInUserID);

                       AuthenticationContext authContext = new AuthenticationContext(Authority, userTokenCache);
                       AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                           code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);

                       string token = result.AccessToken;

                       try
                       {
                           graphClient = new GraphServiceClient(
                               new DelegateAuthenticationProvider(
                                   (requestMessage) =>
                                   {
                                       requestMessage.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token);

                                       return Task.FromResult(0);
                                   }));
                       }
                       catch (Exception e)
                       {
                           System.Diagnostics.Debug.WriteLine("Failed to create graph client: " + e.Message);
                       }

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

The problem is: when I click on the button that instantiates the UserProfileController, then the the code inside of AuthorizationCodeReceived = (context) => line of code is called again and again in an infinite loop. How can I fix the infinite loop so that only Azure Active Directory B2C "Global Administrators" can instantiate the UserProfileController?

1

1 Answers

4
votes

[Authorize(Roles = "admin")]

Since your are using the Authorize attribute to check user's roles, you need to make sure the current user's Claims have the valid role claim. You could leverage the following code snippet to check your current user claims:

return Json((User.Identity as ClaimsIdentity).Claims.Select(c => new { key = c.Type, value = c.Value }),JsonRequestBehavior.AllowGet);

The problem is: when I click on the button that instantiates the UserProfileController, then the the code inside of AuthorizationCodeReceived = (context) => line of code is called again and again in an infinite loop.

You could override the HandleUnauthorizedRequest method under AuthorizeAttribute and define your custom authorize attribute as follows:

public class MyAuthorize : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        filterContext.Result = new ContentResult() { Content = "You don't have rights to take actions" };
    }
}

Then, you could decorate your UserProfileController controller as follows:

[MyAuthorize(Roles = "admin")]
public class UserProfileController : Controller
{
    //TODO:
}

I am attempting to allow only Azure Active Directory B2C users with role "Global Administrator" to access the following class

Under the AuthorizationCodeReceived delegate method, after you retrieve the access token, you need to leverage the Microsoft Graph Client library to check whether the current user is a Global Administrator / Company Administrator or not. If the current user is a Global Administrator / Company Administrator, then you need to specify the role claim as follows:

context.AuthenticationTicket.Identity.AddClaim(new Claim(context.AuthenticationTicket.Identity.RoleClaimType, "admin"));

Note: For checking whether a user is a Global Administrator, you could retrieve the roles under the current user's directory, then use the getMemberObjects API to retrieve the groups, roles that the current user is a member of, then check whether the Global Administrator role id is in current user's MemberObjects.

//List directory roles, https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/directoryrole_list
var roles=await graphClient.DirectoryRoles.Request().GetAsync();

//user: getMemberObjects ,https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_getmemberobjects

UPDATE:

I checked the implementation on my side. Here is the code to check the role for the current logged user.

var directoryRoles = await graphClient.DirectoryRoles.Request().GetAsync();
var userRoles = await graphClient.Me.MemberOf.Request().GetAsync();

var adminRole=directoryRoles.Where(role => role.DisplayName== "Company Administrator" || role.DisplayName == "Global Administrator").FirstOrDefault();
if (userRoles.Count(role => role.Id == adminRole.Id) > 0)
{
    context.AuthenticationTicket.Identity.AddClaim(new Claim(context.AuthenticationTicket.Identity.RoleClaimType, "admin"));
}
else
{
    context.AuthenticationTicket.Identity.AddClaim(new Claim(context.AuthenticationTicket.Identity.RoleClaimType, "user"));
}

Note: For adding multiple user roles, you could add multiple new Claim(context.AuthenticationTicket.Identity.RoleClaimType, "<role-name>") role claims.

Here is my modified custom AuthorizeAttribute:

public class MyAuthorize : AuthorizeAttribute
{
    private bool noPermission = false;

    public string Permissions { get; set; }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        if (!base.AuthorizeCore(httpContext))
            return false;

        var permissionArrs = Permissions.Trim().Split('|');

        if (permissionArrs.ToList().Exists(p=>httpContext.User.IsInRole(p)))
        {
            return true;
        }
        else
        {
            noPermission = true;
            return false;
        }
    }

    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        if (noPermission)
            filterContext.Result = new ContentResult() { Content = "You don't have rights to take actions" };
        else
            base.HandleUnauthorizedRequest(filterContext);
    }
}

Decorate the UserProfileController as follows:

[MyAuthorize(Permissions = "admin|co-admin")]
public class UsersController : Controller
{
   //TODO:
}