Created: Sunday, August 6, 2017 3:57:48 PM views(655)
In some reason, you might want to avoid using standard Identity package to work with users, roles, permissions etc. I had 2 reasons: Identity works only with Entity Framework (unless you write your own implementation of the IUserStore interface), but in my project (content management system) I didn’t want to limit future users in this way; it is too huge and complicated, I don’t want to have all that features I will never use. That’s why I decided to write my own. But I had to replace only user/role/permission management, while the standard ASP.NET Core sign in/sign out features had to be still in use. We will write simple web application that will have its own user manager to validate, sign in (using the standard ASP.NET Core implementation), and sign out users. It will use SQLite database to store related information and Entity Framework as the ORM, but it is easy to replace it with any other storage and ORM you want: Architecture I want our user manager to support different login types (email and password, Microsoft, Google, Facebook etc.) and I want it to be easy to add new ones. Also, I would like User object to have information only about the user, not about the ways he logs in. I don’t like the way it is done in Identity: Why we have so huge class? What if we use only Facebook login? In this case most of these properties (PhoneNumber, PhoneNumberConfirmed, Email, EmailConfirmed, PasswordHash etc.) are useless. Of course, that is not so big deal, but it is not elegant from my point of view. I decided to have 3 main classes: User, Credential, and CredentialType. Each supported by the application login type is described by the correspondingCredentialType class object: Login information of the particular user is described by the set of the Credential class objects (one per credential type): As you can see, there are the Identifier and Secret properties in this class. In context of email and password authentication, they contain email and password hash respectively. In case of Facebook authentication, Identifier property will contain Facebook ID, while Secret property value will be null. User is represented by the User class and contains nothing except user-related information: I think this separation is useful, because user is user and the way how he logs in into the application is something different. Also, we have 4 more classes: Role, UserRole, Permission and UserPermission. I will not pay too much attention to these 4 to keep post simple, but you can look at them in the sample web application (see link at the bottom). The main idea is that roles can be assigned to the user, and permissions can be assigned to the role. We could also make it possible to assign permissions to the user directly too, but it is not so important now. User Manager Implementation First of all, we should turn on cookies authentication in our web application. This is done my registering corresponding middleware inside the Configuremethod of the Startup class: Now let’s create the UserManager class. The most important method of this class is the Validate one: It tries to find user with given identifier and password within given credential type. MD5 transformation is applied to the secret automatically. If matching credential is found, corresponding user is returned. Next method logs in the user: The key thing here is calling of the GetUserClaims method. It gets all of the user roles and permissions and creates corresponding claims for them. Also, it creates claims for user ID and name: This allows developer to get all the user claims when user is logged in and check them and to restrict access to different resources based on this information. Using in the Web Application We will test how our custom user manager works using extremely simple web application. First of all, register our UserManager class inside the DI (using scoped lifetime is important, because UserManager uses Storage service, which is registered as scoped; we will not pay attention on the Storage service, but it’s just the Entity Framework database context): Now, create the HomeController class with the few methods: Our view will display the information about logged in user (if user is authenticated), and Login/Logout user buttons. These buttons (inside the corresponding forms) will make simple POST requests to our controller’s Login and Logout methods, which will then redirect you to the index page again. To make everything as simple as possible, user’s login and password are hardcoded inside the Login method, but you can replace this with login page instead. Here is our view: You can see that we use standard ASP.NET Core User.Identity.IsAuthenticated property to check whether the user is authenticated or not. Also, we use same User property to check claims. While our UserManager is registered inside the DI, we can inject it into the view and use to display current user’s name as well. Conclusions I have created the demo project for this post. Feel free to ask if you have any questions!.