Implementing UserManager to use a custom class and Stored Procedures

20k views Asked by At

All of the authentication and authorization process of my app is done using stored procedures. I've written a class with all of functionalities that I need, e.g. GetUsers, Login, AddRole, AddMember, etc. Also the admin page for managing users and roles and permissions is done by using this class.

I just need to add authentication (I mean that authorize attribute), cookies for Login and Logout and storing some server-side data for each Login. I think I need to implement Identity for that?

In that case, can you please guide me with its implementation? It seems the very basic thing you need to do is to implement a create method that passes an instance of IUserStore to the constructor. But I don't need to have any tables for users or roles, how can I implement this method?

This is the current class, and please let me know if you need to see my custom authentication class that uses stored procedures.

public class AppUserManager : UserManager<AppUser>
{
    public AppUserManager(IUserStore<AppUser> store) : base(store) { }
    public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context)
    {
        //AppUserManager manager = new AppUserManager();
        //return manager;
        return null;
    }
}
3

There are 3 answers

9
LeftyX On BEST ANSWER

As alisabzevari suggested you have to implement your IUserStore.
You do not even depend on the storage and table structure defined. You can customize every single bit of your storage layer.

I did some experiments and tried to implement my own UserManager and RoleManager using a different storage, such as Biggy:

A File-based Document Store for .NET.

You can find the code here on GitHub.

First thing to do is to implement your UserManager where you can configure the requirements for your password validation:

public class AppUserManager : UserManager<AppUser, int>
{
    public AppUserManager (IUserStore<AppUser, int> store): base(store)
    {
        this.UserLockoutEnabledByDefault = false;
        // this.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(10);
        // this.MaxFailedAccessAttemptsBeforeLockout = 10;
        this.UserValidator = new UserValidator<User, int>(this)
        {
        AllowOnlyAlphanumericUserNames = false,
        RequireUniqueEmail = false
        };

        // Configure validation logic for passwords
        this.PasswordValidator = new PasswordValidator
        {
        RequiredLength = 4,
        RequireNonLetterOrDigit = false,
        RequireDigit = false,
        RequireLowercase = false,
        RequireUppercase = false,
        };
    }
}

and then define your IUserStore implementation. The main method you must implement is CreateAsync:

public System.Threading.Tasks.Task CreateAsync(User user)
{
    // Saves the user in your storage.
    return Task.FromResult(user);
}

it will receive an IUser which you have to persist in your custom storage and return it.

If you have a look at the code I've implemented you can see I've used a few interfaces IUserRoleStore, IUserPasswordStore, IUserClaimStore etc etc as I needed to use roles and claims.

I've also implemented my own SignInManager.

Once you've defined all your implementation you can bootstrap everything at startup:

app.CreatePerOwinContext<Custom.Identity.UserManager>(() => new Custom.Identity.UserManager(new Custom.Identity.UserStore(folderStorage)));
app.CreatePerOwinContext<Custom.Identity.RoleManager>(() => new Custom.Identity.RoleManager(new Custom.Identity.RoleStore(folderStorage)));
app.CreatePerOwinContext<Custom.Identity.SignInService>((options, context) => new Custom.Identity.SignInService(context.GetUserManager<Custom.Identity.UserManager>(), context.Authentication));

You can check my AccountController where I try to validate the user:

var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
    return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
    return View("Lockout");
case SignInStatus.RequiresVerification:
    return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
    ModelState.AddModelError("", "Invalid login attempt.");
    return View(model);
}

Once PasswordSignInAsync is called you will notice a few of the methods of your UserManager will be called. The first one will be FindByNameAsync:

public System.Threading.Tasks.Task<User> FindByNameAsync(string userName)
{
    //Fetch your user using the username.
    return Task.FromResult(user);
}

You will have to implement your stored procedure, I guess, where you'll fetch your user from the DB.

Then another method FindByIdAsync will be called:

public System.Threading.Tasks.Task<User> FindByIdAsync(int userId)
{
    // Fetch - again - your user from the DB with the Id.
    return Task.FromResult(user);
}

Again you'll have to use your stored procedure to find your user by his/her id.

If you download my project from github and play around with it you'll notice that most of those methods will be called multiple times. Don't get scared. That's the way it is.

I would suggest you to insert breakpoints in every single method of the UserStore and see how everything fits together.

0
alisabzevari On

You have to implement IUserStore interface. See this article to learn how to implement Custom Storage Providers for ASP.NET Identity.

0
Hans Vonn On

You can also override methods in your UserManager class (e.g. ApplicationUserManager) to manage authorization. Here is an example that uses custom UserManager.FindAsync logic. The UserManager class is used by the ApplicationOAuthProvider class during authentication.

public class ApplicationUserManager : UserManager<ApplicationUser>
{
    public ApplicationUserManager() : base(new EmptyUserStore()) { }

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
    {
        return new ApplicationUserManager();
    }

    public override Task<ApplicationUser> FindAsync(string userName, string password)
    {
        // Authentication logic here.
        throw new NotImplementedException("Authenticate userName and password");

        var result = new ApplicationUser { UserName = userName };
        return Task.FromResult(result);
    }
}

/// <summary>
/// User Store with no implementation. Required for UserManager.
/// </summary>
internal class EmptyUserStore : IUserStore<ApplicationUser>
{
    public Task CreateAsync(ApplicationUser user)
    {
        throw new NotImplementedException();
    }

    public Task DeleteAsync(ApplicationUser user)
    {
        throw new NotImplementedException();
    }

    public Task<ApplicationUser> FindByIdAsync(string userId)
    {
        throw new NotImplementedException();
    }

    public Task<ApplicationUser> FindByNameAsync(string userName)
    {
        throw new NotImplementedException();
    }

    public Task UpdateAsync(ApplicationUser user)
    {
        throw new NotImplementedException();
    }

    public void Dispose()
    {
        // throw new NotImplementedException();
    }
}

Note that this implementation does not use the benefits of the IUserStore interface.