9

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;
    }
}
Akbari
  • 2,369
  • 7
  • 45
  • 85

3 Answers3

10

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.

Community
  • 1
  • 1
LeftyX
  • 35,328
  • 21
  • 132
  • 193
  • `The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.` For login and in the `UserManager.FindAsync` I get the above error. Any idea? I think it's relative to `GetPasswordHashAsync`.What can I do for that? – Akbari Jun 16 '15 at 04:16
  • 1
    It's hard to tell. Did you try my sample project? FindAsync uses UserLoginInfo. It goes through that pipe if you're using an external provider (google, facebook). If you don't use that you should go through there. In case you're using an external provider you can implement the UserLoginInfo manager. You can check Raven's [implementation](https://github.com/ILMServices/RavenDB.AspNet.Identity/blob/master/RavenDB.AspNet.Identity/UserStore.cs#L134). – LeftyX Jun 16 '15 at 09:40
  • Thanks Lefty, I've checked your code. I'm not using external providers. And instead of using User manager's Find, I've used my own custom function to save some time! :) – Akbari Jun 17 '15 at 02:37
  • @Akbari: Glad I've helped. If you think my answer is good enough, could you, please, accept it? Cheers. – LeftyX Jun 17 '15 at 17:11
  • I have downloaded the project from the link you have given in your answer but i am stuck here :app.CreatePerOwinContext(() => new Custom.Identity.UserManager(new Custom.Identity.UserStore(folderStorage))); app.CreatePerOwinContext(() => new Custom.Identity.RoleManager(new Custom.Identity.RoleStore(folderStorage))); – I Love Stackoverflow Sep 03 '16 at 08:45
  • I want to store directly in my table.can you please guide me a little.please – I Love Stackoverflow Sep 03 '16 at 08:46
  • @Learning: Maybe you should provide us with some more details? The implementation here is for a custom IUserStore. Maybe you don't need one. – LeftyX Sep 03 '16 at 09:26
  • Can you please help me with this question:http://stackoverflow.com/questions/39304464/how-to-save-new-record-with-hashed-password-in-my-custom-table-instead-of-aspnet – I Love Stackoverflow Sep 03 '16 at 09:38
  • Thank you a lot! This was very clarifying! You're awesome!!!! – benjamingranados Oct 15 '20 at 17:44
5

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

alisabzevari
  • 8,008
  • 6
  • 43
  • 67
3

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.

Hans Vonn
  • 3,949
  • 3
  • 21
  • 15