Customizing LightSwitch User Management
When you enable Windows or Forms authentication in a LightSwitch app, a set of SQL tables are used to store the user and role data. But did you know that you can customize how and where LightSwitch gets the user and role data? You don’t have to use the default set of SQL tables that LightSwitch generates in its intrinsic database. This post describes in detail how you can go about customizing the management of users and roles in your LightSwitch app.
Extensible Design
LightSwitch makes use of many APIs within ASP.NET. Among those are the provider classes for user and role management. You can learn more about these providers and their associated APIs on MSDN:
The great thing about this provider model is that it is configurable outside of source code. You can configure your app, defining which providers to use and their settings, by editing your web.config file. And since all providers have a common set of base classes, LightSwitch doesn’t need to know which provider type it’s using; the web.config file and ASP.NET dictate which providers LightSwitch uses.
There are a set of base provider classes that we’re interested in here:
- System.Web.Security.MembershipProvider: This class defines the interface used to access user information such as the login name and password of a user.
- System.Web.Security.RoleProvider: This class defines the interface used to access role information (e.g. SalesPerson or Manager).
- System.Web.Profile.ProfileProvider: This class defines the interface used to access custom user-specific data. This is data that the application itself wants to track along with the user. ASP.NET doesn’t track any data of its own here; this is all application-specific.
LightSwitch makes use of each of these provider types and each of them can have a custom implementation that LightSwitch will invoke.
How LightSwitch Works
Before I get into how you can customize the user and role management, I wanted to describe how LightSwitch configures things. Understanding this will allow you to be better informed when time comes to do the customizing.
LightSwitch makes use of the following implementations of the previously mentioned base provider classes:
- System.Web.Security.SqlMembershipProvider
- System.Web.Security.SqlRoleProvider
- System.Web.Profile.SqlProfileProvider
Each of these classes are implementations of the base provider interface such that they make use of a set of SQL tables to store and retrieve their respective data. For example, the SqlMembershipProvider implementation makes use of a combination of the aspnet_Membership and aspnet_Users tables contained in the LightSwitch intrinsic database.
If you enable authentication in your LightSwitch app, save, and open your web.config file, you’ll see how LightSwitch configures the use of these providers. For more information on configuring authentication in your LightSwitch app, see LightSwitch Authentication and Authorization.
Membership Provider
LightSwitch uses the membership provider to store the users that have access to the application. When you log into a LightSwitch app with your username and password, for example, LightSwitch makes use of the membership provider to ensure 1) you exist as an application user and 2) your password matches.
1: <membership defaultProvider="AspNetMembershipProvider">
2: <providers>
3: <clear />
4: <add
5: name="AspNetMembershipProvider"
6: type="System.Web.Security.SqlMembershipProvider"
7: connectionStringName="_IntrinsicData"
8: applicationName="Application10"
9: requiresUniqueEmail="false"
10: requiresQuestionAndAnswer="false" />
11: </providers>
12: </membership>
The snippet above shows how LightSwitch configures the membership provider when Forms authentication is being used. I’ll explain each of the lines in that snippet so it all makes sense:
Line #1: The membership element configures the ASP.NET membership management. It contains a defaultProvider attribute which references the name of membership provider defined below that should be used by default.
Line #2: The providers element defines the collection of membership providers and their configurations. Obviously, this implies you can define multiple membership providers in this section which ASP.NET supports. LightSwitch does not support multiple providers. Only the membership provider marked as the default (via the defaultProvider attribute on the membership element) will be used.
Line #3: The clear element simply ensures that the providers element collection is cleared prior to adding the membership provider. This isn’t strictly necessary but it ensures that there are no extraneous providers included in the collection that may have been added by fancy things like web.config inheritance.
Line #4: The add element defines which membership provider should be added to the collection and how it should be configured. It has several attributes that are configured by LightSwitch by default.
Line #5: The name attribute is an identifier for the provider within the web.config so that it can be referenced as the default provider.
Line #6: The type attribute is the .NET type name of the membership provider.
Line #7: The connectionStringName attribute is the name of the connection string that this membership provider should used when accessing the database tables.
Line #8: The applicationName attribute is the name of the application with which to associate the membership data. ASP.NET’s implementation of the SqlMembershipProvider allows you to have multiple applications all share the same database but partition, or share depending on how things are configured, the user information between those applications. Applications that have membership providers configured with the same application name will share the membership data; if they are different application names, the membership data will be separate between the apps.
Line #9: The requiresUniqueEmail attribute indicates whether a unique e-mail address is required for the user. By default, SqlMembershipProvider sets this value to true. LightSwitch doesn’t collect an e-mail address for a user, so this is set to false.
Line #10: The requiresQuestionAndAnswer attribute indicates whether a password question/answer pair is required for the user to allow for password reset and retrieval. By default, SqlMembershipProvider sets this to true. Again, since LightSwitch doesn’t use e-mail addresses, this is set to false.
1: <membership defaultProvider="AspNetMembershipProvider">
2: <providers>
3: <clear />
4: <add name="AspNetMembershipProvider" type="Microsoft.LightSwitch.Security.ServerGenerated.Implementation.WindowsUserMembershipProvider, Microsoft.LightSwitch.Base.Server, Version=11.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="_IntrinsicData" applicationName="Application10" />
5: </providers>
6: </membership>
The snippet above shows how LightSwitch configures the membership provider when Windows authentication is being used. For Windows authentication, LightSwitch continues to use SQL-based storage to track which Windows users have access. Even in the case where the app has been configured to allow access to any authenticated Windows user, LightSwitch will still make use of this storage to track which app-defined roles are assigned to any of those users. LightSwitch makes use of a custom implementation membership provider, derived from SqlMembershipProvider that adds some extra logic specific to handling Windows users and also correctly defaults most of the settings which is why you don’t see the requiresUniqueEmail and requiresQuestionAndAnswer settings here. Note that the type attribute is set using an assembly-qualified type name since this is a custom type that is not defined by ASP.NET.
Role Provider
LightSwitch uses the role provider to store the roles that have been defined within the app and to map those roles to users.
1: <roleManager enabled="True" defaultProvider="AspNetRoleProvider">
2: <providers>
3: <clear />
4: <add name="AspNetRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="_IntrinsicData" applicationName="Application10" />
5: </providers>
6: </roleManager>
The snippet above shows how LightSwitch configures the role provider when any authentication is being used. It’s pretty much the same structure as the membership snippet. The only real difference is that the roleManager element has an enabled attribute. That attribute needs to be set to true in order to enable the use of roles in the ASP.NET API. Just like with the membership provider, LightSwitch only makes use of the role provider that is defined as the default provider.
Profile Provider
LightSwitch uses the profile provider to store some extra information about the user in the case where Forms authentication is being used. In general, the profile provider can be used to track other custom data for a user that is not represented in the membership provider/SQL schema. In the case of LightSwitch, it keeps track of the full name (display name) of a user, so the profile provider is configured to store this data.
1: <profile enabled="True" defaultProvider="AspNetProfileProvider">
2: <providers>
3: <clear />
4: <add name="AspNetProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="_IntrinsicData" applicationName="Application10" />
5: </providers>
6: <properties>
7: <add name="FullName" />
8: </properties>
9: </profile>
The snippet above shows how LightSwitch configures the profile provider. It follows the same structure as the other two snippets except for the properties child element. The profile element contains a properties child element that allows a developer to define the names of properties that should be associated with each user. In the case of LightSwitch, it defines a FullName property here. Just as with the other providers, LightSwitch only makes use of the profile provider that is defined as the default provider.
Permission Mapping
ASP.NET has the concepts of users (membership), roles, and profiles of which LightSwitch makes use. But ASP.NET does not have the concept of permissions, while LightSwitch does. So LightSwitch defines its own table, named RolePermissions, within the intrinsic database to map roles and permissions as that is how permissions are assigned in LightSwitch apps. There does not exist an ASP.NET provider that accesses this table; LightSwitch uses technology outside of ASP.NET to access this table. For this reason, the storage of role-permission mappings is not customizable. You can customize how and where roles are stored but when LightSwitch needs to map a permission to that role, it always stores that mapping information in the RolePermissions table.
ASP.NET Provider Customization
Now that you understand how LightSwitch configures the ASP.NET providers it uses, let’s get into how you can go about customizing them. There are a few ways you can go about customizing the providers:
- Configure provider properties via the web.config
- Replace with an existing provider class implementation
- Define a custom provider
Configuring Provider Properties
The easiest but least flexible way to customize an ASP.NET provider is to configure its properties within the web.config file. For example, if you want to change the maximum number of password attempts allowed for a user from the default of 5 to 10, you would set the maxInvalidPasswordAttempts attribute in the add element of the default membership provider configured by LightSwitch (see line #9):
1: <membership defaultProvider="AspNetMembershipProvider">
2: <providers>
3: <clear />
4: <add
5: name="AspNetMembershipProvider"
6: type="System.Web.Security.SqlMembershipProvider"
7: connectionStringName="_IntrinsicData"
8: applicationName="Application10"
9: maxInvalidPasswordAttempts="10"
10: requiresUniqueEmail="false"
11: requiresQuestionAndAnswer="false" />
12: </providers>
13: </membership>
If you had a separate database location where your ASP.NET users and roles were stored, you could configure your providers to use that database by simply adding a connection string to your web.config file and setting the connectionStringName attributes of your providers to reference that connection string.
Consult the documentation of the provider type in order to determine which properties it defines that are configurable through the web.config file.
Replacing a Provider
If there already exists an implementation of an ASP.NET provider different than the default one used by LightSwitch that you want to use, you can simply configure the web.config file to reference that provider type and LightSwitch will make use of it. To do this, follow these steps:
- Add a reference to the assembly that contains the provider type you want to use from your LightSwitch app’s Server project, being sure to set the “Copy Local” property to true for the assembly reference. This will ensure that the assembly will be copied to the runtime environment.
- Set the add element’s type attribute for that provider to be the assembly-qualified type name of the provider type you want to use. For example, see line #6:
1: <membership defaultProvider="AspNetMembershipProvider">
2: <providers>
3: <clear />
4: <add
5: name="AspNetMembershipProvider"
6: type="SomeLibrary.SomeMembershipProvider,SomeLibrary"
7: connectionStringName="_IntrinsicData"
8: applicationName="Application10"
9: requiresUniqueEmail="false"
10: requiresQuestionAndAnswer="false" />
11: </providers>
12: </membership>
Defining a Custom Provider
In order to write your own custom provider, you define a class that inherits from the appropriate provider base type (System.Web.Security.MembershipProvider, System.Web.Security.RoleProvider, or System.Web.Profile.ProfileProvider) and update the web.config just as was explained in the previous “Replacing a Provider” section.
MSDN provides good documentation on how to go about implementing a custom provider:
These providers can contain a large number of members that must be implemented in the derived class. In the case of LightSwitch’s usage of these providers, not all the members need to be fully implemented. Here’s a list of provider members which LightSwitch uses and, thus, must be implemented (as opposed to just throwing a NotImplementedException):
- MembershipProvider
- Properties
- ApplicationName
- MaxInvalidPasswordAttempts [Forms authentication only]
- MinRequiredNonAlphanumericCharacters [Forms authentication only]
- MinRequiredPasswordLength [Forms authentication only]
- PasswordFormat
- PasswordStrengthRegularExpression [Forms authentication only]
- Methods
- ChangePassword [Forms authentication only]
- CreateUser
- DeleteUser
- GetAllUsers
- GetUser(string, bool)
- ResetPassword [Forms authentication only, “answer” argument will always be null]
- UnlockUser
- ValidateUser [Forms authentication only]
- Properties
- RoleProvider
- Properties
- ApplicationName
- Methods
- AddUsersToRoles
- CreateRole
- DeleteRole
- GetAllRoles
- GetRolesForUser
- GetUsersInRole
- RemoveUsersFromRoles
- RoleExists
- Properties
- ProfileProvider [Forms authentication only]
- Properties
- ApplicationName
- Methods
- DeleteProfiles
- GetPropertyValues
- SetPropertyValues
- Properties
Wrap-up
It was important within the design of LightSwitch that existing technologies were used where appropriate. In the case of user and role management, ASP.NET already provided a mechanism for this, so we didn’t reinvent the wheel and instead built LightSwitch on top of those APIs. This allows LightSwitch to be customized in a way that is not unique to LightSwitch but is a familiar experience for ASP.NET developers.
I encourage you to read the MSDN articles that are linked from this blog post. You can learn a lot of extra information on the APIs and provider customization from those articles.
Please let me know if you have any questions or run into any issues.
Comments
Anonymous
July 02, 2013
Thank you. This is very helpful.Anonymous
July 04, 2013
Thanks Matt. I built a custom provider and was able to get it to work while debugging, but when I go to publish the application, I get an error: Could not load file assembly "CustomProvider, Version=1.0.0.0, Culture=neutral,PublicKeyToken=90ae26a077372089" or one of its dependencies. A post has been created about this here: social.msdn.microsoft.com/.../can-not-start-publishing-wizard-when-using-custom-membership-provider-with-html-client Thanks for your help