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;
}
}
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.