Saturday, May 21, 2016

Microsoft 70-486: Configure and apply authorization

Exam Objectives


Create roles, authorize roles by using configuration, authorize roles programmatically, create custom role providers, implement WCF service authorization

Quick Overview of Training Materials



Create roles


Identity management has been it a state of flux for the last few iterations of ASP.NET, and as a result there are myriad ways to create roles in an application, depending on which particular flavor of authentication is used by the app.


Windows Authentication


When using Windows Authentication, roles are mapped to the Windows groups the user is a member of, based on the name, SID, or RID of the group.  It is possible to get information about groups from wmic in the console:


Creating "Roles" for users in this scenario is a matter of creating Windows groups and adding the user to those groups, which is pretty straightforward with the Microsoft Management Console "Local Users and Groups" snap-in:




Provider Based (ASP.NET >= 2)


Creating roles in the classic Provider based user management framework can be accomplished a number of ways.  Visual Studio includes a tool called "Web Site Administration Tool" which provides a simple web based administration page that will allow you to enable/disable roles, create roles, and add users to roles.  Roles can also be managed programmatically using the RolesManager, which is called using the System.Web.Security.Roles static methods.  Finally, roles can be added directly to the database, whether using SQL or the tooling built into Visual Studio for data management.



Using the Web Site Administration Tool

The Web Site Administration Tool (WSAT) is started through the PROJECT top level menu.  In Visual Studio 2012 it will appear at the bottom (at least in my configuration...):


Once open, the interface is pretty self explanatory.  Roles and users can be managed on the "Security" tab:




Programmatically with System.Web.Security.Roles

The System.Web.Security.Roles class exposes a number of methods for managing roles in the application (I fudged the plurality variations, you get the idea):

  • AddUser(s)ToRole(s)
  • CreateRole
  • DeleteRole
  • FindUsersInRole
  • GetAllRoles
  • GetRolesForUser(s)
  • GetUsersInRole
  • IsUserInRole
  • RemoveUser(s)FromRole(s)
  • RoleExists
So if we want to ensure a role exists when the application is first initialized, we can add a call to Roles.CreateRole in Application_Start.  If the role already exists and you try to create it again, the Provider will throw an exception, so it's pretty much universal to throw an if(!Roles.RoleExists("role name") before calling CreateRole():


protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
 
    // Use LocalDB for Entity Framework by default
    Database.DefaultConnectionFactory = new SqlConnectionFactory(@"...");
 
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
 
    //Create role
    Roles.CreateRole("testRole");
}

In order to use this functionality, however, roleManager must be enabled in web.config.  The following example is configured to use the Universal Provider:

  <roleManager enabled="true" defaultProvider="DefaultRoleProvider">
    <providers>
      <add connectionStringName="DefaultConnection" applicationName="/"
        name="DefaultRoleProvider" type="System.Web.Providers.DefaultRoleProvider, 
        System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </providers>
  </roleManager>



Directly in the database

Since, ultimately, roles and and user assignments to roles are represented in a database, it's possible to create these roles at the source.  In Visual Studio this is easily accomplished through the Server Explorer.  Navigate through the data connections, open the appropriate table, and select "Show Table Data".  Simply double click a field and type to change the data.  Not the most elegant solution but super simple for just a small amount of data:


For more serious data manipulation, you can also use SQL statements.  None of the columns are handled automatically when you go this route, so if you create a new Role, either through the data view or through a script, you'll have to assign a new GUID to the RoleId column, and ensure that the ApplicationId column value matches your application's value from the Applications table.

INSERT INTO Roles(RoleName,Description,ApplicationId,RoleId)
VALUES (N'SqlRole', 
        N'Inserted with a script', 
 N'5c3e60f5-be36-4bc9-8a5a-c1a854d6b3cf',
 NEWID());


SimpleMembershipProvider Based (MVC >= 4)


The SimpleMembershipProvider introduced in MVC 4 no longer supports the use of the Web Site Administration Tool (the option to start it was removed in Visual Studio 2013, though apparently there is a workaround to get it back).  Roles can still be created using the RoleManager, though it will require some configuration changes to the default template and casting to a SimpleRoleProvider to make work.

The other consideration with using RoleManager is that it can only be called after the database connection has been initialized using WebSecurity.InitializeDatabaseConnection(), which in the default template lives in the InitializeSimpleMembership filter.  Here is the default initializer after tweaking it to create a couple default roles if they don't already exist (thanks to StackOverflow), followed by the bit of configuration (thanks to StackOverflow again) that needs to be added to system.web in Web.config:

private class SimpleMembershipInitializer
 {
     public SimpleMembershipInitializer()
     {
         Database.SetInitializer<UsersContext>(null);
 
         try
         {
             using (var context = new UsersContext())
             {
                 if (!context.Database.Exists())
                 {
                     // Create the SimpleMembership database 
                     // without Entity Framework migration schema
                     ((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
 
                 }
             }
 
             WebSecurity.InitializeDatabaseConnection("DefaultConnection", 
                 "UserProfile""UserId""UserName", autoCreateTables: true);
 
             var roles = (SimpleRoleProvider)System.Web.Security.Roles.Provider;
 
             if (!roles.RoleExists("Admin"))
                 roles.CreateRole("Admin");
 
             if (!roles.RoleExists("User"))
                 roles.CreateRole("User");
         }
         catch (Exception ex)
         {
             throw new InvalidOperationException("Doh! ", ex);
         }
     }
 }

<profile defaultProvider="SimpleProfileProvider">
  <providers>
    <add name="SimpleProfileProvider" type="WebMatrix.WebData.SimpleMembershipProvider, 
          WebMatrix.WebData" connectionStringName="DNMXEntities" applicationName="/" />
  </providers>
</profile>
<membership defaultProvider="SimpleMembershipProvider">
  <providers>
    <add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, 
          WebMatrix.WebData" />
  </providers>
</membership>
<roleManager defaultProvider="SimpleRoleProvider">
  <providers>
    <add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider,
          WebMatrix.WebData" />
  </providers>
</roleManager>

As with the older Provider based implementations, it is still possible to just manipulate the database directly.  The schema for roles in SimpleMembership is simplified, consisting of just an integer id and a role name (no more description or applicationId).  The table that stores these roles is called webpages_Roles:



ASP.NET Identity Based (MVC >= 4.5)


Identity changed a lot about how things work, though ultimately creating roles is pretty much the same process as with SimpleMembership.  Identity no longer uses the RoleManager, which relies on a Role Provider, but instead uses the RoleStore.  I added a bit of code (that I found on Anders Nordby's blog) to Startup.cs to create "User" and "Admin" roles if they don't already exist.  Ander's example actually creates all the necessary views and endpoints to create and manage roles through the app's own Admin interface, using RoleStore (and UserStore).

public void Configuration(IAppBuilder app)
{
    ConfigureAuth(app);
 
    using (var context = new ApplicationDbContext())
    {
        var roleStore = new RoleStore<IdentityRole>(context);
        var roleManager = new RoleManager<IdentityRole>(roleStore);
 
        if(!roleManager.RoleExists("Admin"))
            roleManager.Create(new IdentityRole("Admin"));
 
        if (!roleManager.RoleExists("User"))
            roleManager.Create(new IdentityRole("User"));
 
        context.SaveChanges();
    }
 
}

New system, new table names, but again a pretty simple schema.  Roles are stored in the AspNetRoles table, the Id is a GUID, and the name is the only other column:




Authorize roles using configuration


The Exam Ref takes this section to mean "how to configure a role provider", which isn't really the way I'd read it at all.  It is possible to authorize roles using configuration, it just isn't recommended because of the way MVC works.  Authorizing roles in configuration is a path based mechanism, but with routing, multiple paths can map to the same resource.

With that caveat in mind, there is a post on the asp.net blog on setting authorization rules in web.config that provides some concrete examples.  Say we wanted to deny access to anonymous users for the whole site, except we wanted them to have access to the login and registration pages (so they could become non-anonymous), and we also wanted to allow only "Admin" users to have access to the Admin pages, our configuration might look something like this:

  <system.web>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
 
    <authorization>
      <deny users="?"/>
    </authorization>
 
  </system.web>
 
  <location path="Account">
 
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
 
  </location>
 
  <location path="Admin">
 
    <system.web>
      <authorization>
        <allow roles="Admin" />
        <deny users="*"/>
      </authorization>
    </system.web>
 
  </location>

The order of deny and allow are important.  If they were reversed in the Admin section, then everyone would be denied access, irrespective of the <allow roles="Admin" />.  This example was included in an MVC5 project and behaved as expected.  However, just because it's possible doesn't make it a good idea.  This is not a secure scheme.  The best way to keep resources in an MVC project secure is to use the [Authorize] attribute.


Authorize roles programmatically


There are two ways to do programmatic authorization checks:  Using attributes, and using explicit code checks.

There are a number of attributes that support authorization. The most obvious of these is the [Authorize] attribute.  By itself, this attribute prevents access to anonymous users, but otherwise, any logged in user is allowed to access the resource.  Restrictions can be set in two ways: by Users and by Roles.  If the attribute is set at the controller level, then all methods of that controller will have the same level of access granted.  If one or two methods needs to made available to anonymous users (like, say, the login and register methods on the Account controller), the the [AllowAnonymous] attribute can be applied to them.

[Authorize] - Allow all logged in users
[Authorize(Roles="Admin, User")] - Allow all users in roles "Admin" or "User"
[Authorize(Users="troy@example.com")] - Allow user "troy@example.com" only
[AllowAnonymous] - Allow any user access, useful for applying after one of the above.

Two other attribute classes can be used to manage role access: the PrincipalPermission and ClaimsPrincipalPermisssion classes.  The [PrincipalPermission] attribute uses a code level security check to ensure that the user has permission to every class and method in the stack.  If not, the permission will fail and throw a security exception.  The syntax differs from the

[PrincipalPermission(SecurityAction.Demand, Role = "Admin")] - demand that the principal belong to the "Admin" role.  Only one role may be set for each attribute.

ClaimsPrincipal takes a much different approach.  Instead of specifying the roles the current Principal must belong to, it instead describes the resource and operation, and the configured ClaimsAuthorizationManager checks whether the claims attached to the current principal are sufficient to grant the user access

[ClaimsPrincipalPermission(SecurityAction.Demand, Resource = "widget", Operation = "add")] - the ClaimsAuthorizationManager will check whether the user's claims grant them access to perform the "add" operation on the "widget" resource.  This type of claims based access control is not something that is baked in to any of the default templates or providers, you'll have to subclass ClaimsAuthorizationManager and implement the security logic yourself.

I added a little bit of code to a default MVC5 template just to see how this all worked:

Added the attribute to the HomeController class:
[ClaimsPrincipalPermission(SecurityAction.Demand, Resource = "Home", Operation = "View")]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

Added the necessary configuration to the web.config file:
<configuration>
  <configSections>
    ...
    <!-- WIF configuration sections -->
    <section name="system.identityModel" 
             type="System.IdentityModel.Configuration.SystemIdentityModelSection, 
             System.IdentityModel, Version=4.0.0.0, Culture=neutral, 
             PublicKeyToken=B77A5C561934E089"/>
    <section name="system.identityModel.services" 
             type="System.IdentityModel.Services.Configuration.
                SystemIdentityModelServicesSection, 
             System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, 
             PublicKeyToken=B77A5C561934E089"/>
  </configSections>
  ...
  
  <system.identityModel>
    <identityConfiguration>
      <claimsAuthorizationManager 
        type ="ClaimsPrincipalPermissionDemo.MyClaimsAuthorizationManager, 
        ClaimsPrincipalPermissionDemo"/>
    </identityConfiguration>
  </system.identityModel>
 
</configuration>

And finally, a dummy ClaimsAuthorizationManager(CAM) subclass (it logs activity and just returns true):
public class MyClaimsAuthorizationManager : ClaimsAuthorizationManager
{
    public override bool CheckAccess(AuthorizationContext context)
    {
        Trace.WriteLine("Claims AuthZ manager called...");
        return true;
    }
}

When the home controller is hit, the "CheckAccess" method is called on the CAM.  The AuthorizationContext includes the Resource and Action being checked, as well as the Principal being authorized.  In a real scenario, claims (or other attributes) on the passed Principal could be used to make authorization decisions, and "true" would be returned if the requesting Principal had permission to that resource and action.



So, I'd mentioned that there are two ways to perform programmatic authorization.  The second way is to make these checks inline.

Naturally the way these checks are done can vary, depending on which Identity framework you are using.  I say can vary because one way of checking is a user is in a role seems to be pretty consistent across MVC versions, and that is using User.IsInRole() within the controller.

if(User.IsInRole("Admin"))
    Trace.WriteLine("User is in the role!");
else
    Trace.WriteLine("User is not in the role!");

In Provider based identity, it is possible to interact with the RoleProvider as long as roleManager is enabled.  The role provider class includes methods such as GetRolesForUsers and IsUserInRole that can be used to check is a user is authorized.  Setting this up is done much the same way as was done for creating roles, so I won't rehash that here.

In addition to using the provider directly, with SimpleMembership you can also use the WebSecurity class.  The RequireRoles() method will set the Http status to 401 if the user is not in all the listed roles.  RequiresAuthenticatedUser() just requires that the user is logged in, similar to the unmodified [Authorize] attribute.  Finally, RequiresUser() will return 401 for any user besides the specified one.

With ASP.NET Identity, these kinds of checks must be done using the UserManager.  Here is a snippet demonstrating how a user is looked up based on the User.Identity.Name and their roles pulled and logged:

using (var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
    var userId = userManager.FindByName(User.Identity.Name).Id;
    var rolesForUser = userManager.GetRolesAsync(userId).Result;
 
    foreach (var role in rolesForUser)
    {
        Trace.WriteLine(role);
    }
}

Dominick Baier mentions in a blog post about Approaches to Authorization that this kind of inline permission check is an anti-pattern, as it entangles your business logic with your security logic.  He makes a strong case for attribute based (and in particular, claims based) authorization using [Authorize], [ClaimsPrincipalPermission], or similar functionality.


Create custom role provider


As a learning exercise I created an InMemoryRoleProvider to complement the InMemoryProvider I created for Membership, following a similar TDD approach.  There aren't as many methods that need to be implemented on this provider (they are pretty self explanatory):

  • AddUsersToRoles
  • CreateRole
  • DeleteRole
  • FindUsersInRole
  • GetAllRoles
  • GetRolesForUser
  • GetUsersInRole
  • IsUserInRole
  • RemoveUsersFromRoles
  • RoleExists
The MSDN pages for RoleProvider Methods provided some useful guidance on expected behavior and return values.  Any method that takes a username or role name should throw an exception if they are null or empty.  If a user or role is not found, it should throw a ProviderException. There was some inconsistency when dealing with array return values, some examples showed them returning empty arrays, others null.  I'm not a big fan of returning null in those cases, but I followed the examples I found.  Once it's implemented, your custom role provider is specified using web.config:

    <roleManager defaultProvider="InMemoryRoleProvider">
      <providers>
        <add name="InMemoryRoleProvider"
             type="CustomMembershipProvider.Provider.InMemoryRoleProvider, 
                   CustomMembershipProvider"
        />
      </providers>
    </roleManager>

The code for my sample can be found in the same Github repo as the  custom membership provider (tests as well).

SimpleMembership uses a SimpleRoleProvider, which inherits from RoleProvider and overrides all it's methods.  Since it presents the same interface as RoleProvider, implementing a custom provider should be similar.  As with MembershipProvider, be sure to override the Initialize method from the Provider base class.

As far as customizing roles in ASP.NET identity, I did this as part of my InMemoryIdentity demo. I implemented a RoleStore and various in memory tables for storing that information. This role store is then used by the ASP.NET Identity RoleManager to create, delete, etc. roles in the application. The IQueryableRoleStore interface is pretty simple really:

  • CreateAsync
  • DeleteAsync
  • FindByIdAsync
  • FindByNameAsync
  • UpdateAsync

Assigning users to role is done by the UserRolesTable class, which feeds a Roles collection on the IdentityUser class (not the most elegant of solutions, I know...).  Users are added to roles through the UserStore.

An interesting real world application of a custom role provider involved using ActiveDirectoryMembershipProvider with Forms authentication.  The WindowsTokenRoleProvider (which provides roles when using Windows authentication) is not compatible with the AD membership provider.  One solution is to create a custom role provider that reads the Windows groups from AD and assigns them as roles (Active Directory Role Provider source blog post).


Implement WCF service authorization


I'm a bit surprised that WCF is included as part of 70-486, especially since it makes us such a large part of the next test (70-487).  It really just doesn't seem to fit the mold of everything else going into this test, but oh well.

The Exam Ref gives the subject some lip service.  He gives some overview that is way too general to really be on any use, but he also includes a couple examples of how to use credentials with the WCF service client.  For the uninitiated, in order to interact with a WCF service, you have to create a client.  This can be done in the command line using svcutil.exe, or by using "Add Service Reference" in Visual Studio.


Once the service reference is added, you'll be able to instantiate the client.  This will be in the namespace you defined when you created the service reference.  Below is the code corresponding to the above screenshot:

var client = new ServiceReference1.Service1Client();
client.ClientCredentials.UserName.UserName = "troy";
client.ClientCredentials.UserName.Password = "P@ssw0rd";

This method sets a credential based on a username and password.  It is possible to send a Windows credential as well using the NetworkCredential class:

var cred = new NetworkCredential();
cred.Domain = "Domain";
cred.UserName = "troy";
cred.Password = "P@ssw0rd";
client.ClientCredentials.Windows.ClientCredential = cred;

This is as far as the Exam Ref goes.  I wanted more a more in-depth look at this particular subject, so I googled about and found two resources on MSDN:  Authentication and Authorization in WCF Services, and Chapter 5: Authenticaiton, Authorization, and Identities in WCF, from the Microsoft Patterns and Practices book Improving Web Services Security: Scenarios and Implementation Guidance for WCF (thats a mouthful...).  I'll start with the first article...

In addition to the Windows and Basic authentication methods shown above, it is also possible to authenticate users using NTLM, X.509 Certificates, and if you are using a Secure Token Service you can use the token issed by that service.

Access to the service can be controlled using role, claims (typical when using STS tokens), and Windows Access Control Lists ("Resource Based").  Roles can be determined using Windows groups or an ASP.NET Role Provider.

The client generated in Visual Studio makes a request of the service, it passes the users credential along to the service.  The service can then access the user's principal with Thread.CurrentPrincipal.  For declarative security (analogous to the [Authorize] attribute in MVC) you can use the PrincipalPermission and ClaimsPrincipalPermission attributes as described is the Authorize Role Programmatically section.  Because the service has access to the requesting user's Principal, it is possible to call IsInRole on the current principal to determine if a user is in a role.

Part 2 of the AuthN and AuthZ in WCF Services article explores using Basic and Certificate based authentication.  It walks through the process of creating a self signed certificate and binding it to the service.  I ain't gonna lie, I skimmed most of this stuff.

Chapter 5 of the Patt & Prac book is mostly the same content as the above article, with maybe a tiny bit better presentation, but rather than the step-by-step walk-throughs, it takes a more high level approach, supported with several diagrams.  My guess is that the purpose of the two part article was to demo the concepts discussed in the book.  Since I'm not a masochist, I'm not going to reproduce the demo.



No comments:

Post a Comment