I'm trying to decouple my authentication from my controllers, so I cooked up an AuthenticationService
which I inject into my authentication controller (DefaultController
) using Ninject. Here's the implementation of AuthenticationService
:
public sealed class AuthenticationService {
private IAuthenticationManager AuthenticationManager { get; set; }
private UserManager<Employee, int> EmployeeManager { get; set; }
public AuthenticationService(
IAuthenticationManager authenticationManager,
UserManager<Employee, int> employeeManager) {
this.AuthenticationManager = authenticationManager;
this.EmployeeManager = employeeManager;
}
public bool SignIn(
CredentialsInput credentials) {
Employee employee = this.EmployeeManager.Find(credentials.Email, credentials.Password);
if (employee != null) {
ClaimsIdentity identityClaim = this.EmployeeManager.CreateIdentity(employee, DefaultAuthenticationTypes.ApplicationCookie);
if (identityClaim != null) {
this.AuthenticationManager.SignIn(new AuthenticationProperties(), identityClaim);
return true;
}
}
return false;
}
public void SignOut() {
this.AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
}
}
And here's how I've got Ninject configured to do the injections:
private static void RegisterServices(
IKernel kernel) {
kernel.Bind<IUserStore<Employee, int>>().To<EmployeeStore>().InRequestScope();
kernel.Bind<IRoleStore<Role, int>>().To<RoleStore>().InRequestScope();
kernel.Bind<IAuthenticationManager>().ToMethod(
c =>
HttpContext.Current.GetOwinContext().Authentication).InRequestScope();
}
And here's how I'm configuring OWIN:
public sealed class StartupConfig {
public void Configuration(
IAppBuilder app) {
this.ConfigureAuthentication(app);
}
public void ConfigureAuthentication(
IAppBuilder app) {
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/"),
ExpireTimeSpan = new TimeSpan(0, 60, 0)
});
}
}
Oh, and for good measure here's the DefaultController
:
public sealed class DefaultController : Controller {
private AuthenticationService AuthenticationService { get; set; }
public DefaultController(
AuthenticationService authenticationService) {
this.AuthenticationService = authenticationService;
}
[HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
public RedirectToRouteResult Default(
[Bind(Prefix = "Credentials", Include = "Email,Password")] CredentialsInput credentials) {
if (base.ModelState.IsValid
&& this.AuthenticationService.SignIn(credentials)) {
// Place of error. The IsInRole() method doesn't return
// the correct answer because the
// IUserStore<>.FindByIdAsync() method is not setting
// the correct data.
if (this.User.IsInRole("Technician")) {
return this.RedirectToAction<TechniciansController>(
c =>
c.Default());
}
return this.RedirectToAction(
c =>
c.Dashboard());
}
return this.RedirectToAction(
c =>
c.Default());
}
[HttpGet]
public RedirectToRouteResult SignOut() {
this.AuthenticationService.SignOut();
return this.RedirectToAction(
c =>
c.Default());
}
}
Well, it all kind of works. The problem I'm having is that the sessions being set by the UserManager
is inconsistent. For example, if I build the application and then run it (debugging or not) this is what happens:
- Build and run
- Sign in as user1@email.com
- Sign out
- Sign in as user2@email.com
- On the first post request, user1 is still showing up as the identity
- On the second post request (refresh), user2 is correctly shown as the identity
Can someone point into why that is happening? Granted I did pull the code in the SignIn
and SignOut
methods out of the controller where it was and into this AuthenticationService
, but I'm not fully convinced it is the cause of the inconsistency. Since all of the assemblies are processed by the MVC app, it should all work the same, right? Help would be greatly appreciated.
UPDATE
Found this while Googling and although it's marked as resolved, I'm a bit confused on what the resolution was. https://katanaproject.codeplex.com/workitem/201 I don't think this has anything to do with my problem anymore.
UPDATE 2
I believe the root of the issue is asynchrony somewhere in the calling pipeline, specifically in the EmployeeStore
I'm injecting into UserManager<Employee, int>
. I have a custom implementation of the UserStore
called EmployeeStore
. It implements IQueryableUserStore<Employee, int>
, IUserStore<Employee, int>
, IUserPasswordStore<Employee, int>
, and IUserRoleStore<Employee, int>
.
When I'm debugging the FindByIdAsync(int employeeId)
method I see that it fires twice for some reason. The first time it fires I see the correct employeeId
passed into it. The second time it fires the employeeId
is set to 0. This is where it screws up, I think because it doesn't subsequently call the IsInRoleAsync(Employee employee, string roleName)
method.
When I refresh the page after it throws an exception that the UserId is not found, the FindByIdAsync(...)
method is again called twice, but this time both times the employeeId
is correct, and it then proceeds to call the IsInRoleAsync(...)
method.
Here's the code for both methods:
public Task<Employee> FindByIdAsync(
int employeeId) {
this.ThrowIfDisposed();
return this.Repository.FindSingleOrDefaultAsync<Employee, int>(employeeId);
}
public Task<bool> IsInRoleAsync(
Employee employee,
string roleName) {
this.ThrowIfDisposed();
if (employee == null) {
throw new ArgumentNullException("employee");
}
if (String.IsNullOrEmpty(roleName)) {
throw new ArgumentNullException("roleName");
}
return Task.FromResult<bool>(employee.Roles.Any(
r =>
(r.Name == roleName)));
}