When I login using google authentication in the startup I can get the access token.
Startup:
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = "",
ClientSecret = "",
Scope = { "" },
Provider = new GoogleOAuth2AuthenticationProvider
{
OnAuthenticated = async context =>
{
context.Identity.AddClaim(new Claim("googletoken", context.AccessToken));
context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Name, "http://www.w3.org/2001/XMLSchema#string"));
context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, "http://www.w3.org/2001/XMLSchema#string"));
}
}
});
My custom claim manager:
public class ClaimManager
{
private readonly ClaimsIdentity _user;
public ClaimManager(ClaimsIdentity user)
{
this._user = user;
}
public static string GetAccessToken(ClaimsIdentity user)
{
var claim = user.Claims.Select(c => new { Type = c.Type, Value = c.Value }).FirstOrDefault(c => c.Type == "googletoken");
return claim == null ? null : claim.Value;
}
public static string GetName(ClaimsIdentity user)
{
var claim = user.Claims.Select(c => new { Type = c.Type, Value = c.Value }).FirstOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name");
return claim == null ? null : claim.Value;
}
public static string GetEmail(ClaimsIdentity user)
{
var claim = user.Claims.Select(c => new { Type = c.Type, Value = c.Value }).FirstOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
return claim == null ? null : claim.Value;
}
}
The access token is not persisted in the user claims. How can I persist claims so that they stay in the user session?
Here is the solution I came up with. I am storing the access token just like a regular claim in the database.
In account controller:
public async Task StoreAccessToken(ExternalLoginInfo loginInfo)
{
var user = await UserManager.FindAsync(loginInfo.Login);
if (user != null)
{
var newClaim = loginInfo.ExternalIdentity.Claims.Select(c => new Claim(c.Type, c.Value)).FirstOrDefault(c => c.Type == "googletoken");
if (newClaim != null)
{
var userClaims = await UserManager.GetClaimsAsync(user.Id);
foreach (var userClaim in userClaims.Where(c => c.Type == newClaim.Type).ToList())
await UserManager.RemoveClaimAsync(user.Id, userClaim);
await UserManager.AddClaimAsync(user.Id, newClaim);
}
}
}
ExternalLoginCallback():
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
await StoreAccessToken(loginInfo);
ExternalLoginConfirmation():
if (result.Succeeded)
{
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (result.Succeeded)
{
await StoreAccessToken(info);
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
}
Related
I am getting the oddest of errors when I attempt to assign roles to user the role is their in the db fine.
But the function used to assign roles is causing an exception
public static async Task<IdentityResult>
AssignRoles(IServiceProvider services,ApplicationUser user,
string email, string[] roles)
{
IdentityResult result = new IdentityResult();
UserManager<ApplicationUser> _userManager =
services.GetService<UserManager<ApplicationUser>>();
result = await _userManager.AddToRolesAsync(user, roles);
return result;
}
The exception that I am getting is after the AddToRolesAsync is called
Invalid Operation the connection is closed
Code I use to call above
public async static void CreateParent(IServiceProvider serviceProvider)
{
var context = new DBContext();//
serviceProvider.GetService<DBContext>();
string[] roles = new string[] { "Parent" };
foreach (string role in roles)
{
var roleStore = new RoleStore<IdentityRole>(context);
if (!context.Roles.Any(r => r.Name == role))
{
await roleStore.CreateAsync(new IdentityRole(role));
}
}
var user = new ApplicationUser
{
FirstName = "Parent",
LastName = "Two",
Email = "parenttwo#apps-mobileapp.com",
NormalizedEmail ="PARENTTWO#APPS-MOBILEAPP.COM",
UserName = "parenttwo#apps-MOBILEAPP.com",
NormalizedUserName = "PARENTTWO#APPS-MOBILEAPP.COM",
PhoneNumber = "+111111111111",
EmailConfirmed = true,
PhoneNumberConfirmed = true,
SecurityStamp = Guid.NewGuid().ToString("D")
};
var db = new DBContext();
if (!db.Users.Any(u => u.UserName == user.UserName))
{
var password = new PasswordHasher<ApplicationUser>();
var hashed = password.HashPassword(user, "Test12345!");
user.PasswordHash = hashed;
var userStore = new UserStore<ApplicationUser>(context);
var result = await userStore.CreateAsync(user);
}
await AssignRoles(serviceProvider,user, user.Email, roles);
await db.SaveChangesAsync();
}
But its the AddToRolesAsync it fails with the error mentioned above, I am calling it form the startup class as such?
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env,IServiceProvider
service)
{
SampleData.CreateParent(service);
}
I am trying to return a partial view (modal) from another method but I am unsure of how to get the object from the Edit User model into RemoveUserClaim method. When I delete a claim from a user, I want it to return back to the Edit User Modal, right now - I can only get it to redirect to the home maintenance screen.
[HttpPost]
public async Task<IActionResult> RemoveUserClaim(UserClaimsViewModel model, string userid, string claimtype)
{
var user = await userManager.FindByIdAsync(model.UserId);
if (user == null)
{
ViewBag.ErrorMessage = $"User with Id = {model.UserId} cannot be found";
return View("~/Views/Administration/Users/UserMaint.cshtml");
}
var claims = await userManager.GetClaimsAsync(user);
foreach (var claimtypething in claims)
{
if (claimtypething.Type == claimtype)
{
var results = await userManager.RemoveClaimAsync(user, claimtypething);
break;
}
}
//Right here is where I want to return back to EditUser's modal -----------
return PartialView("~/Views/Modals/_EditUserModalPartial.cshtml");
}
[HttpGet]
public async Task<IActionResult> EditUser(string id)
{
var user = await userManager.FindByIdAsync(id);
if (user == null)
{
ViewBag.ErrorMessage = $"User with Id = {id} cannot be found";
return View("NotFound");
}
var userClaims = await userManager.GetClaimsAsync(user);
var userRoles = await userManager.GetRolesAsync(user);
var model = new EditUserViewModel
{
Id = user.Id,
Email = user.Email,
UserName = user.UserName,
City = user.City,
};
//GET LIST OF ROLES (RoleID, RoleName)
foreach (var RoleName in userRoles)
{
//Execute identiy method to get full information for the Role and store into an object (fullroleinfo)
var fullRoleInfo = await roleManager.FindByNameAsync(RoleName);
//Store this inforamtion into the Role list in the viewmodel
var roleinfo = new EditUserViewModel.Role
{
RoleName = fullRoleInfo.Name,
RoleID = fullRoleInfo.Id
};
model.Roles.Add(roleinfo);
};
//GET LIST OF CLAIMS
foreach (var ClaimName in userClaims)
{
var fullClaimInfo = ClaimName;
var claiminfo = new EditUserViewModel.Claim
{
ClaimType = fullClaimInfo.Type,
ClaimID = fullClaimInfo.Value
};
ViewBag.ClaimType = fullClaimInfo.Type;
model.Claims.Add(claiminfo);
};
ViewBag.UserModel = model;
return PartialView("~/Views/Modals/_EditUserModalPartial.cshtml", model);
}
Im trying to get the current user form the session UserId but get this error. 'Unable to cast object of type 'System.Int32' to type 'Trinity.Models.tblUser'.'
Where I createthe Session Id:
[HttpPost]
public ActionResult Authorise(tblUser user)
{
using (var db = new TrinityEntities())
{
var userEmail = db.tblUsers.FirstOrDefault(x => x.Email == user.Email);
var userPassword = db.tblUsers.FirstOrDefault(y => y.Password == user.Password);
//check login incorrect
if (userEmail == null || userPassword == null)
{
ViewBag.LoginIncorrect = "E-mail or Password not correct";
return View("Index", user);
}
else
{
Session["UserID"] = userEmail.Id;
return RedirectToAction("Index", "Home");
}
}
}
Where I get the error:
public ActionResult Index()
{
if (Session["UserID"] == null)
{
return Redirect("/");
}
var currentUser = (Models.tblUser) Session["UserID"];
using (var db = new Models.ChatContext())
{
ViewBag.allUsers = db.Users.Where(u => u.FirstName != currentUser.FirstName).ToList();
}
ViewBag.currentUser = currentUser;
return View();
}
You're only storing the ID (a number) but when you read it back you're expecting the whole user - that's not going to work. Instead you'll need to use the ID to reload the user:
var currentUserId = Session["UserID"];
var user = db.tblUsers.FirstOrDefault(x => x.Id == currentUserId);
I have a couple of legacy ASP.NET web apps that share a database for ASP.NET Membership. I want to move to a microservices architecture utilizing .NET Core and IdentityServer4 and have the identity server in the new microservices ecosystem to use the existing ASP.NET Membership user store, but .NET Core doesn't appear to support ASP.NET Membership at all.
I currently have a proof of concept stood up involving a web API, identity server and an MVC web app as my client. The identity server implements a subclass of IdentityUser and implements IUserStore/IUserPasswordStore/IUserEmailStore to adapt it to the ASP.NET Membership tables in my existing database. I can register new users and login via my POC MVC client app but these users cannot log into my legacy apps. Conversely, users registered in legacy apps can't log into my POC MVC client. I assume its because my implementation of IPasswordHasher isn't hashing the passwords the same as ASP.NET Membership in my legacy apps.
Below is my code. Any insight into what I might be doing wrong would be greatly appreciated. Security and cryptography are not my strong suit.
Startup.cs
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets<Startup>();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
/* Add CORS policy */
services.AddCors(options =>
{
// this defines a CORS policy called "default"
options.AddPolicy("default", policy =>
{
policy.WithOrigins("http://localhost:5003")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
/* Add MVC componenets. */
services.AddMvc();
/* Configure IdentityServer. */
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
// Cookie settings
options.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromDays(150);
options.Cookies.ApplicationCookie.LoginPath = "/Account/Login";
options.Cookies.ApplicationCookie.LogoutPath = "/Account/Logout";
// User settings
options.User.RequireUniqueEmail = true;
});
/* Add the DbContext */
services.AddDbContext<StoreContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyConnectionString")));
/* Add ASP.NET Identity to use for registration and authentication. */
services.AddIdentity<AspNetMembershipUser, IdentityRole>()
.AddEntityFrameworkStores<StoreContext>()
.AddUserStore<AspNetMembershipUserStore>()
.AddDefaultTokenProviders();
services.AddTransient<IPasswordHasher<AspNetMembershipUser>, AspNetMembershipPasswordHasher>();
/* Add IdentityServer and its components. */
services.AddIdentityServer()
.AddInMemoryCaching()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
/* Configure logging. */
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
if (env.IsDevelopment())
{
loggerFactory.AddDebug();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
/* Configure wwwroot */
app.UseStaticFiles();
/* Configure CORS */
app.UseCors("default");
/* Configure AspNet Identity */
app.UseIdentity();
/* Configure IdentityServer */
app.UseIdentityServer();
/* Configure MVC */
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
AspNetMembershipUser.cs
public class AspNetMembershipUser : IdentityUser
{
public string PasswordSalt { get; set; }
public int PasswordFormat { get; set; }
}
AspNetMembershipUserStore.cs
public class AspNetMembershipUserStore : IUserStore<AspNetMembershipUser>, IUserPasswordStore<AspNetMembershipUser>, IUserEmailStore<AspNetMembershipUser>
{
private readonly StoreContext _dbcontext;
public AspNetMembershipUserStore(StoreContext dbContext)
{
_dbcontext = dbContext;
}
public Task<IdentityResult> CreateAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
try
{
User dbUser = new User();
this.Convert(user, dbUser);
_dbcontext.Users.Add(dbUser);
_dbcontext.SaveChanges();
return IdentityResult.Success;
}
catch (Exception ex)
{
return IdentityResult.Failed(new IdentityError
{
Code = ex.GetType().Name,
Description = ex.Message
});
}
});
}
public Task<IdentityResult> DeleteAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
try
{
User dbUser = _dbcontext.Users
.Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetMembership)
.Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetApplication)
.Include(u => u.UserGroups)
.SingleOrDefault(u => u.ProviderUserName == user.NormalizedUserName);
if (dbUser != null)
{
_dbcontext.AspNetUsers.Remove(dbUser.AspNetUser);
_dbcontext.Users.Remove(dbUser);
_dbcontext.SaveChanges();
}
return IdentityResult.Success;
}
catch (Exception ex)
{
return IdentityResult.Failed(new IdentityError
{
Code = ex.GetType().Name,
Description = ex.Message
});
}
});
}
public void Dispose()
{
_dbcontext.Dispose();
}
public Task<AspNetMembershipUser> FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
User dbUser = _dbcontext.Users
.Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetMembership)
.Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetApplication)
.Include(u => u.UserGroups)
.SingleOrDefault(u => u.ProviderEmailAddress == normalizedEmail);
if (dbUser == null)
{
return null;
}
AspNetMembershipUser user = new AspNetMembershipUser();
this.Convert(dbUser, user);
return user;
});
}
public Task<AspNetMembershipUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
long lUserId = long.Parse(userId);
return Task.Factory.StartNew(() =>
{
User dbUser = _dbcontext.Users
.Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetMembership)
.Include(u => u.AspNetUsers).ThenInclude(u=> u.AspNetApplication)
.Include(u => u.UserGroups)
.SingleOrDefault(u => u.UserId == lUserId);
if (dbUser == null)
{
return null;
}
AspNetMembershipUser user = new AspNetMembershipUser();
this.Convert(dbUser, user);
return user;
});
}
public Task<AspNetMembershipUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
User dbUser = _dbcontext.Users
.Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetMembership)
.Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetApplication)
.Include(u => u.UserGroups)
.SingleOrDefault(u => u.ProviderUserName == normalizedUserName);
if (dbUser == null)
{
return null;
}
AspNetMembershipUser user = new AspNetMembershipUser();
this.Convert(dbUser, user);
return user;
});
}
public Task<string> GetEmailAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.Email);
}
public Task<bool> GetEmailConfirmedAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.EmailConfirmed);
}
public Task<string> GetNormalizedEmailAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.NormalizedEmail);
}
public Task<string> GetNormalizedUserNameAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.NormalizedUserName);
}
public Task<string> GetPasswordHashAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.PasswordHash);
}
public Task<string> GetUserIdAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.Id.ToString());
}
public Task<string> GetUserNameAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.UserName);
}
public Task<bool> HasPasswordAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => !string.IsNullOrEmpty(user.PasswordHash));
}
public Task SetEmailAsync(AspNetMembershipUser user, string email, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.Email = email);
}
public Task SetEmailConfirmedAsync(AspNetMembershipUser user, bool confirmed, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.EmailConfirmed = confirmed);
}
public Task SetNormalizedEmailAsync(AspNetMembershipUser user, string normalizedEmail, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.NormalizedEmail = normalizedEmail);
}
public Task SetNormalizedUserNameAsync(AspNetMembershipUser user, string normalizedName, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.NormalizedUserName = normalizedName);
}
public Task SetPasswordHashAsync(AspNetMembershipUser user, string passwordHash, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.PasswordHash = passwordHash);
}
public Task SetUserNameAsync(AspNetMembershipUser user, string userName, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => user.UserName = userName);
}
public Task<IdentityResult> UpdateAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
try
{
User dbUser = _dbcontext.Users
.Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetMembership)
.Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetApplication)
.Include(u => u.UserGroups)
.SingleOrDefault(u => u.UserId.ToString() == user.Id);
if (dbUser != null)
{
this.Convert(user, dbUser);
_dbcontext.Users.Update(dbUser);
_dbcontext.SaveChanges();
}
return IdentityResult.Success;
}
catch(Exception ex)
{
return IdentityResult.Failed(new IdentityError
{
Code = ex.GetType().Name,
Description = ex.Message
});
}
});
}
private void Convert(User from, AspNetMembershipUser to)
{
to.Id = from.ProviderUserKey.ToString();
to.UserName = from.ProviderUserName;
to.NormalizedUserName = from.ProviderUserName.ToLower();
to.Email = from.ProviderEmailAddress;
to.NormalizedEmail = from.ProviderEmailAddress.ToLower();
to.EmailConfirmed = true;
to.PasswordHash = from.AspNetUser.AspNetMembership.Password;
to.PasswordSalt = from.AspNetUser.AspNetMembership.PasswordSalt;
to.PasswordFormat = from.AspNetUser.AspNetMembership.PasswordFormat;
to.AccessFailedCount = from.AspNetUser.AspNetMembership.FailedPasswordAttemptCount;
to.EmailConfirmed = true;
to.Roles.Clear();
from.UserGroups.ToList().ForEach(ug =>
{
to.Roles.Add(new IdentityUserRole<string>
{
RoleId = ug.GroupId.ToString(),
UserId = ug.UserId.ToString()
});
});
to.PhoneNumber = from.Phone ?? from.ShippingPhone;
to.PhoneNumberConfirmed = !string.IsNullOrEmpty(to.PhoneNumber);
to.SecurityStamp = from.AspNetUser.AspNetMembership.PasswordSalt;
}
private void Convert(AspNetMembershipUser from , User to)
{
AspNetApplication application = _dbcontext.AspNetApplications.First();
to.ProviderUserKey = Guid.Parse(from.Id);
to.ProviderUserName = from.UserName;
to.ProviderEmailAddress = from.Email;
to.InternalEmail = $"c_{Guid.NewGuid().ToString()}#mycompany.com";
to.AccountOwner = "MYCOMPANY";
to.UserStatusId = (int)UserStatus.Normal;
AspNetUser aspNetUser = to.AspNetUser;
if (to.AspNetUser == null)
{
to.AspNetUser = new AspNetUser
{
ApplicationId = application.ApplicationId,
AspNetApplication= application,
AspNetMembership = new AspNetMembership
{
ApplicationId = application.ApplicationId,
AspNetApplication = application
}
};
}
to.AspNetUser.UserId = Guid.Parse(from.Id);
to.AspNetUser.UserName = from.UserName;
to.AspNetUser.LoweredUserName = from.UserName.ToLower();
to.AspNetUser.LastActivityDate = DateTime.UtcNow;
to.AspNetUser.IsAnonymous = false;
to.AspNetUser.ApplicationId = application.ApplicationId;
to.AspNetUser.AspNetMembership.CreateDate = DateTime.UtcNow;
to.AspNetUser.AspNetMembership.Email = from.Email;
to.AspNetUser.AspNetMembership.IsApproved = true;
to.AspNetUser.AspNetMembership.LastLoginDate = DateTime.Parse("1754-01-01 00:00:00.000");
to.AspNetUser.AspNetMembership.LastLockoutDate = DateTime.Parse("1754-01-01 00:00:00.000");
to.AspNetUser.AspNetMembership.LastPasswordChangedDate = DateTime.Parse("1754-01-01 00:00:00.000");
to.AspNetUser.AspNetMembership.LoweredEmail = from.NormalizedEmail.ToLower();
to.AspNetUser.AspNetMembership.Password = from.PasswordHash;
to.AspNetUser.AspNetMembership.PasswordSalt = from.PasswordSalt;
to.AspNetUser.AspNetMembership.PasswordFormat = from.PasswordFormat;
to.AspNetUser.AspNetMembership.IsLockedOut = false;
to.AspNetUser.AspNetMembership.FailedPasswordAnswerAttemptWindowStart = DateTime.Parse("1754-01-01 00:00:00.000");
to.AspNetUser.AspNetMembership.FailedPasswordAttemptWindowStart = DateTime.Parse("1754-01-01 00:00:00.000");
// Merge Groups/Roles
to.UserGroups
.Where(ug => !from.Roles.Any(r => ug.GroupId.ToString() == r.RoleId))
.ToList()
.ForEach(ug => to.UserGroups.Remove(ug));
to.UserGroups
.Join(from.Roles, ug => ug.GroupId.ToString(), r => r.RoleId, (ug, r) => new { To = ug, From = r })
.ToList()
.ForEach(j =>
{
j.To.UserId = long.Parse(j.From.UserId);
j.To.GroupId = int.Parse(j.From.RoleId);
});
from.Roles
.Where(r => !to.UserGroups.Any(ug => ug.GroupId.ToString() == r.RoleId))
.ToList()
.ForEach(r =>
{
to.UserGroups.Add(new UserGroup
{
UserId = long.Parse(from.Id),
GroupId = int.Parse(r.RoleId)
});
});
}
}
AspNetMembershipPasswordHasher.cs
public class AspNetMembershipPasswordHasher : IPasswordHasher<AspNetMembershipUser>
{
private readonly int _saltSize;
private readonly int _bytesRequired;
private readonly int _iterations;
public AspNetMembershipPasswordHasher()
{
this._saltSize = 128 / 8;
this._bytesRequired = 32;
this._iterations = 1000;
}
public string HashPassword(AspNetMembershipUser user, string password)
{
string passwordHash = null;
string passwordSalt = null;
this.HashPassword(password, out passwordHash, ref passwordSalt);
user.PasswordSalt = passwordSalt;
return passwordHash;
}
public PasswordVerificationResult VerifyHashedPassword(AspNetMembershipUser user, string hashedPassword, string providedPassword)
{
// Throw an error if any of our passwords are null
if (hashedPassword == null)
{
throw new ArgumentNullException("hashedPassword");
}
if (providedPassword == null)
{
throw new ArgumentNullException("providedPassword");
}
string providedPasswordHash = null;
if (user.PasswordFormat == 0)
{
providedPasswordHash = providedPassword;
}
else if (user.PasswordFormat == 1)
{
string providedPasswordSalt = user.PasswordSalt;
this.HashPassword(providedPassword, out providedPasswordHash, ref providedPasswordSalt);
}
else
{
throw new NotSupportedException("Encrypted passwords are not supported.");
}
if (providedPasswordHash == hashedPassword)
{
return PasswordVerificationResult.Success;
}
else
{
return PasswordVerificationResult.Failed;
}
}
private void HashPassword(string password, out string passwordHash, ref string passwordSalt)
{
byte[] hashBytes = null;
byte[] saltBytes = null;
byte[] totalBytes = new byte[this._saltSize + this._bytesRequired];
if (!string.IsNullOrEmpty(passwordSalt))
{
// Using existing salt.
using (var pbkdf2 = new Rfc2898DeriveBytes(password, Convert.FromBase64String(passwordSalt), this._iterations))
{
saltBytes = pbkdf2.Salt;
hashBytes = pbkdf2.GetBytes(this._bytesRequired);
}
}
else
{
// Generate a new salt.
using (var pbkdf2 = new Rfc2898DeriveBytes(password, this._saltSize, this._iterations))
{
saltBytes = pbkdf2.Salt;
hashBytes = pbkdf2.GetBytes(this._bytesRequired);
}
}
Buffer.BlockCopy(saltBytes, 0, totalBytes, 0, this._saltSize);
Buffer.BlockCopy(hashBytes, 0, totalBytes, this._saltSize, this._bytesRequired);
using (SHA256 hashAlgorithm = SHA256.Create())
{
passwordHash = Convert.ToBase64String(hashAlgorithm.ComputeHash(totalBytes));
passwordSalt = Convert.ToBase64String(saltBytes);
}
}
}
One of my coworkers was able to help me out. Below is what the hash function should look like. With this change, ASP.NET Identity is able to piggy back on an existing ASP.NET Membership database.
private void HashPassword(string password, out string passwordHash, ref string passwordSalt)
{
byte[] passwordBytes = Encoding.Unicode.GetBytes(password);
byte[] saltBytes = null;
if (!string.IsNullOrEmpty(passwordSalt))
{
saltBytes = Convert.FromBase64String(passwordSalt);
}
else
{
saltBytes = new byte[128 / 8];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(saltBytes);
}
}
byte[] totalBytes = new byte[saltBytes.Length + passwordBytes.Length];
Buffer.BlockCopy(saltBytes, 0, totalBytes, 0, saltBytes.Length);
Buffer.BlockCopy(passwordBytes, 0, totalBytes, saltBytes.Length, passwordBytes.Length);
using (SHA1 hashAlgorithm = SHA1.Create())
{
passwordHash = Convert.ToBase64String(hashAlgorithm.ComputeHash(totalBytes));
}
passwordSalt = Convert.ToBase64String(saltBytes);
}
You can find all the source code on GitHib.
One of the annoying things I have found with .net and NUGet is that I never know what version of anything is installed by default.
This can be really frustrating when trying to add things to an existing project.....
Here is my current dilemma.
I have an MVC 5 project with only MVC on it. This project is massive and the work to move it to another project would take too much time.
So, I opened up NUGet and type WebApi and installed the one that came along.
Then I created a blank WebApi project with Individual Accounts set up and copied the StartUp code into my current StartUp along with any other configuration that is needed.
Then I came to create my AccountController which is just copied straight from the clean project I created. It looks like this:
[Authorize]
[RoutePrefix("api/Account")]
public class AccountController : ApiController
{
private const string LocalLoginProvider = "Local";
public AccountController()
: this(Startup.UserManagerFactory(), Startup.OAuthOptions.AccessTokenFormat)
{
}
public AccountController(UserManager<IdentityUser> userManager,
ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
{
UserManager = userManager;
AccessTokenFormat = accessTokenFormat;
}
public UserManager<IdentityUser> UserManager { get; private set; }
public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; private set; }
// GET api/Account/UserInfo
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("UserInfo")]
public UserInfoViewModel GetUserInfo()
{
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
return new UserInfoViewModel
{
UserName = User.Identity.GetUserName(),
HasRegistered = externalLogin == null,
LoginProvider = externalLogin != null ? externalLogin.LoginProvider : null
};
}
// POST api/Account/Logout
[Route("Logout")]
public IHttpActionResult Logout()
{
Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return Ok();
}
// GET api/Account/ManageInfo?returnUrl=%2F&generateState=true
[Route("ManageInfo")]
public async Task<ManageInfoViewModel> GetManageInfo(string returnUrl, bool generateState = false)
{
IdentityUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user == null)
{
return null;
}
List<UserLoginInfoViewModel> logins = new List<UserLoginInfoViewModel>();
foreach (IdentityUserLogin linkedAccount in user.Logins)
{
logins.Add(new UserLoginInfoViewModel
{
LoginProvider = linkedAccount.LoginProvider,
ProviderKey = linkedAccount.ProviderKey
});
}
if (user.PasswordHash != null)
{
logins.Add(new UserLoginInfoViewModel
{
LoginProvider = LocalLoginProvider,
ProviderKey = user.UserName,
});
}
return new ManageInfoViewModel
{
LocalLoginProvider = LocalLoginProvider,
UserName = user.UserName,
Logins = logins,
ExternalLoginProviders = GetExternalLogins(returnUrl, generateState)
};
}
// POST api/Account/ChangePassword
[Route("ChangePassword")]
public async Task<IHttpActionResult> ChangePassword(ChangePasswordBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword,
model.NewPassword);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
// POST api/Account/SetPassword
[Route("SetPassword")]
public async Task<IHttpActionResult> SetPassword(SetPasswordBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
// POST api/Account/AddExternalLogin
[Route("AddExternalLogin")]
public async Task<IHttpActionResult> AddExternalLogin(AddExternalLoginBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
AuthenticationTicket ticket = AccessTokenFormat.Unprotect(model.ExternalAccessToken);
if (ticket == null || ticket.Identity == null || (ticket.Properties != null
&& ticket.Properties.ExpiresUtc.HasValue
&& ticket.Properties.ExpiresUtc.Value < DateTimeOffset.UtcNow))
{
return BadRequest("External login failure.");
}
ExternalLoginData externalData = ExternalLoginData.FromIdentity(ticket.Identity);
if (externalData == null)
{
return BadRequest("The external login is already associated with an account.");
}
IdentityResult result = await UserManager.AddLoginAsync(User.Identity.GetUserId(),
new UserLoginInfo(externalData.LoginProvider, externalData.ProviderKey));
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
// POST api/Account/RemoveLogin
[Route("RemoveLogin")]
public async Task<IHttpActionResult> RemoveLogin(RemoveLoginBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result;
if (model.LoginProvider == LocalLoginProvider)
{
result = await UserManager.RemovePasswordAsync(User.Identity.GetUserId());
}
else
{
result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(),
new UserLoginInfo(model.LoginProvider, model.ProviderKey));
}
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
// GET api/Account/ExternalLogin
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
{
if (error != null)
{
return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
}
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != provider)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return new ChallengeResult(provider, this);
}
IdentityUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
bool hasRegistered = user != null;
if (hasRegistered)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await UserManager.CreateIdentityAsync(user,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await UserManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
}
else
{
IEnumerable<Claim> claims = externalLogin.GetClaims();
ClaimsIdentity identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
Authentication.SignIn(identity);
}
return Ok();
}
// GET api/Account/ExternalLogins?returnUrl=%2F&generateState=true
[AllowAnonymous]
[Route("ExternalLogins")]
public IEnumerable<ExternalLoginViewModel> GetExternalLogins(string returnUrl, bool generateState = false)
{
IEnumerable<AuthenticationDescription> descriptions = Authentication.GetExternalAuthenticationTypes();
List<ExternalLoginViewModel> logins = new List<ExternalLoginViewModel>();
string state;
if (generateState)
{
const int strengthInBits = 256;
state = RandomOAuthStateGenerator.Generate(strengthInBits);
}
else
{
state = null;
}
foreach (AuthenticationDescription description in descriptions)
{
ExternalLoginViewModel login = new ExternalLoginViewModel
{
Name = description.Caption,
Url = Url.Route("ExternalLogin", new
{
provider = description.AuthenticationType,
response_type = "token",
client_id = Startup.PublicClientId,
redirect_uri = new Uri(Request.RequestUri, returnUrl).AbsoluteUri,
state = state
}),
State = state
};
logins.Add(login);
}
return logins;
}
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityUser user = new IdentityUser
{
UserName = model.UserName
};
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
// POST api/Account/RegisterExternal
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
IdentityUser user = new IdentityUser
{
UserName = model.UserName
};
user.Logins.Add(new IdentityUserLogin
{
LoginProvider = externalLogin.LoginProvider,
ProviderKey = externalLogin.ProviderKey
});
IdentityResult result = await UserManager.CreateAsync(user);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
UserManager.Dispose();
}
base.Dispose(disposing);
}
#region Helpers
private IAuthenticationManager Authentication
{
get { return Request.GetOwinContext().Authentication; }
}
private IHttpActionResult GetErrorResult(IdentityResult result)
{
if (result == null)
{
return InternalServerError();
}
if (!result.Succeeded)
{
if (result.Errors != null)
{
foreach (string error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
if (ModelState.IsValid)
{
// No ModelState errors are available to send, so just return an empty BadRequest.
return BadRequest();
}
return BadRequest(ModelState);
}
return null;
}
private class ExternalLoginData
{
public string LoginProvider { get; set; }
public string ProviderKey { get; set; }
public string UserName { get; set; }
public IList<Claim> GetClaims()
{
IList<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, ProviderKey, null, LoginProvider));
if (UserName != null)
{
claims.Add(new Claim(ClaimTypes.Name, UserName, null, LoginProvider));
}
return claims;
}
public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
{
if (identity == null)
{
return null;
}
Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer)
|| String.IsNullOrEmpty(providerKeyClaim.Value))
{
return null;
}
if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
{
return null;
}
return new ExternalLoginData
{
LoginProvider = providerKeyClaim.Issuer,
ProviderKey = providerKeyClaim.Value,
UserName = identity.FindFirstValue(ClaimTypes.Name)
};
}
}
private static class RandomOAuthStateGenerator
{
private static RandomNumberGenerator _random = new RNGCryptoServiceProvider();
public static string Generate(int strengthInBits)
{
const int bitsPerByte = 8;
if (strengthInBits % bitsPerByte != 0)
{
throw new ArgumentException("strengthInBits must be evenly divisible by 8.", "strengthInBits");
}
int strengthInBytes = strengthInBits / bitsPerByte;
byte[] data = new byte[strengthInBytes];
_random.GetBytes(data);
return HttpServerUtility.UrlTokenEncode(data);
}
}
#endregion
}
Now, we can see that because it is using RoutePrefix that his is definitely version 2+ of WebApi.
My problem is that code does not compile. It states:
The type or namespace name 'HostAuthenticationAttribute' could not be found (are you missing a using directive or an assembly reference?)
Looking at my clean project, I can see that this class resides in System.Web.Http.Owin. The problem is, I don't have that reference in my main project and I have no idea how to install it.
I have tried installing all the other different versions of the WebApi to no avail.
Surely someone has had this issue before?
I experienced the same error when trying to upgrade my WebAPI project to version 2.0. Installing the nuget package Microsoft.AspNet.WebApi.Owin resolved the missing reference.
Install-Package Microsoft.AspNet.WebApi.Owin