Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding custom Roles to Azure Mobile Services User (Google, Twitter, Facebook, Microsoft)

I have an .NET Azure Mobile Services project with some controllers I want to secure with the typical Authorize attribute. I can create a Roles table and a UserProfiles table and associate the various users authenticated through Google, Facebook, etc. with Roles in my Roles table.

My question is: How do a add the Roles claims to the ServiceUsers after the authentication is complete but before the Authorize filter's OnAuthorize method runs? Is this even possible?

Example of what I want to do:

[Authorize(Roles = "admin")]
public async Task<IHttpActionResult> Put(int id, MyDto dto){...}

Rough table examples:

Roles
id | name
1  | user
2  | admin

UserProfiles
id | externalLoginId         | favoriteColor
1  | Google:<ServiceUser.Id> | blue

UserRoles (Linking Table)
roleId | userId
2      | 1

Edit:

One option might be to create my own action filter overriding the Authorize filter attribute and in the OnAuthorize method I could query the UserProfiles and Roles tables to get the roles for the current user, then check against the roles specified in the Authorize object's Roles property to determine if the user has access. But somehow, it feels like there should be a place earlier in the pipeline that I can intercept to just add the Roles claims to the current user.

like image 600
Chris Swain Avatar asked May 17 '15 18:05

Chris Swain


2 Answers

I found the solution. It involves:

  1. Creating my own ServiceTokenHandler and overriding the CreateServiceUser method to add logic after the call to base.CreateServiceUser(claimsIdentity) that tries to find the roles in my UserProfile table that are linked to the user specified in the claimsIdentity. If it finds roles, it adds new Role Claims to the Claims collection of the claimsIdentity.

    public class RolesServiceTokenHandler : ServiceTokenHandler
    {
        public RolesServiceTokenHandler(HttpConfiguration config) : base(config)
        {
    
        }
    
        public override ServiceUser CreateServiceUser(ClaimsIdentity claimsIdentity)
        {
            var serviceUser = base.CreateServiceUser(claimsIdentity);
    
            if (serviceUser != null && serviceUser.Identity.IsAuthenticated)
            {
                var dataContext = new AllergenDerivativesContext();
                var userId = serviceUser.Id;
                var userProfile = dataContext.UserProfiles.Include(u => u.Roles).FirstOrDefault(u => u.ExternalUserId == userId);
    
                if (userProfile == null)
                {
                    //If the user profile does not exist, then create it and assign it to the User role by default.  
                    userProfile = new UserProfile();
    
                    //Set the ExternalUserId for the UserProfile to the current User's External Login Id.
                    userProfile.ExternalUserId = userId;
    
                    //Get the "User" Role Entity.
                    var userRole = dataContext.Roles.FirstOrDefault(r => r.Name == Roles.User);
    
                    //Initialize the roles collection for the new UserProfile
                    //And add the existing role to the collection.
                    userProfile.Roles = new List<Role> { userRole };
    
                    //Add the new UserProfile to the database and save the changes.  
                    dataContext.UserProfiles.Add(userProfile);
                    dataContext.SaveChanges();
                }
    
                //Get the roles for the UserProfile and add role claims to the ServiceUser's primary Identity.  
                foreach (var role in userProfile.Roles)
                {
                    ((ClaimsIdentity)serviceUser.Identity).AddClaim(new Claim(ClaimTypes.Role, role.Name));
                }
            }
    
            return serviceUser;
        }
    }
    
  2. Register the new RolesServiceTokenHandler with autofac as the IServiceTokenHandler so that when the app requests an IServiceTokenHandler, autofac returns the new RolesServiceTokenHandler.

Inside the WebApiConfig.cs file's static Register method, change the call to ServiceConfig.Initialize so that it looks like:

    HttpConfiguration config = ServiceConfig.Initialize(new ConfigBuilder(options, (httpConfig, autofac) =>
            {
                 autofac.RegisterType<RolesServiceTokenHandler>().As<IServiceTokenHandler>();
            }));
like image 153
Chris Swain Avatar answered Sep 20 '22 20:09

Chris Swain


You can add the Role Claim to the user Principal, with something like:

new ClaimsIdentity(new Claim[]
{
  new Claim(ClaimTypes.NameIdentifier, loginName),
  new Claim(ClaimTypes.Role, "admin"),
};

but then would you rely on just validating the claims information that gets in for providing role based authorization?

You need to handle anyway the authorization and validation via AuthorizeLevelAttribute, or some other custom attribute instead, to decorate your methods; and that you need to program the behavior that needs to be in place to validate user's role. How you validate the role membership then needs to be done there, in the OnAuthorization method override, and it's up to you: you can query your Db, Graph API, or else. I don't think there are, in this moment, other options of managing authorization, and the documentation and samples point also in this way

Here some good reference:

  • Get started with custom authentication
  • Field Engineer Sample
  • This session registration: Building Microsoft Azure Mobile Services with Visual Studio (from about minute 28)
like image 25
Massimo Prota Avatar answered Sep 18 '22 20:09

Massimo Prota