Seed Roles (RoleManager vs RoleStore) - asp.net-mvc

Through looking at the posts here, I've seen two different ways of creating ASP.NET Identity roles through Entity Framework seeding. One way uses RoleManager and the other uses RoleStore. I was wondering if there is a difference between the two. As using the latter will avoid one less initialization
string[] roles = { "Admin", "Moderator", "User" };
// Create Role through RoleManager
var roleStore = new RoleStore<IdentityRole>(context);
var manager = new RoleManager<IdentityRole>(roleStore);
foreach (string role in roles)
{
if (!context.Roles.Any(r => r.Name == role))
{
manager.Create(new IdentityRole(role));
}
// Create Role through RoleStore
var roleStore = new RoleStore<IdentityRole>(context);
foreach (string role in roles)
{
if (!context.Roles.Any(r => r.Name == role))
{
roleStore.CreateAsync(new IdentityRole(role));
}
}

In your specific case, using both methods, you achieve the same results.
But, the correct usage would be:
var context = new ApplicationIdentityDbContext();
var roleStore = new RoleStore<IdentityRole>(context);
var roleManager = new RoleManager<IdentityRole>(roleStore);
string[] roles = { "Admin", "Moderator", "User" };
foreach (string role in roles)
{
if (!roleManager.RoleExists(role))
{
roleManager.Create(new IdentityRole(role));
}
}
The RoleManager is a wrapper over a RoleStore, so when you are adding roles to the manager, you are actually inserting them in the store, but the difference here is that the RoleManager can implement a custom IIdentityValidator<TRole> role validator.
So, implementing the validator, each time you add a role through the manager, it will first be validated before being added to the store.

Related

How can we give grant and revoke permissions by Entity Framework code-first?

All I want is to give the permissions to the user from Entity Framework (using the code first approach) that is like DDL, DML commands to a certain user.
The steps below will help you create user, create roles, assign user to roles, and limit their access depending on their role.
Open your Solution in Visual Studio
Tools > Nuget Package Manager Console > Enter Enable-Migrations
Open Migrations > Configuration.cs
Use this as your seed method;
protected override void Seed(ISPRC.Models.ApplicationDbContext context)
{
var userStore = new UserStore<ApplicationUser>(context);
var userManager = new UserManager<ApplicationUser>(userStore);
userManager.UserValidator = new UserValidator<ApplicationUser>(userManager)
{
AllowOnlyAlphanumericUserNames = false,
};
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
// Create a User Role
if (!roleManager.RoleExists("Admin"))
{
var role = new IdentityRole();
role.Name = "Admin";
roleManager.Create(role);
}
if (!context.Users.Any(u => u.UserName == "admin#mail.com"))
{
var user = new ApplicationUser
{
UserName = "admin#mail.com",
Email = "admin#mail.com",
EmailConfirmed = true,
};
// Create User
userManager.Create(user, "Password#777");
// Add User to Admin Role
userManager.AddToRole(user.Id, "Admin");
}
}
To limit certain roles in accessing your controllers or actions, add [Authorize] or [Authorize(Roles="Admin,User")]
The controller below will only allow Logged-in users that are of Admin role. If they're role does not match the requirement, they will be automatically redirected to login page.
[Authorize(Roles = "Admin")]
public class AdminController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
public ActionResult Accounts()
{
return View();
}
}
Tools > Nuget Package Manager Console > Enter Update-Database
You can use Custom Migration Operations, or you can switch to a database-first workflow, and manage the database schema with another tool, and periodically reverse engineer the schema into a code-based model.

MVC Razor view to detect if authenticated user has social login provider?

Is there any way in an MVC Razor view to do something like:
if (user has associated/logged in with a Facebook account)
I think in code behind I can retrieve it like so:
var logins = await UserManager.GetLoginsAsync(loggedInUserId);
string loginProvider = "Facebook"
string providerKey = logins.Where(c => c.LoginProvider == loginProvider)
.Select(c => c.ProviderKey)
.FirstOrDefault();
However a helper class doesn't have the context to get 'UserManager' (I don't think). I'm not sure if I'm reinventing the wheel here or if there's a simple way to do this....
Thanks.
UPDATE
Sorry....I probably should have mentioned I want to add this logic to a shared partial view (my main menu) that I want to use across all pages. Hence i think a ViewModel is probably out?
I think I have a working solution. In a helper class I've created:
public static string GetLoginType(this IPrincipal user)
{
if (!(user.Identity is ClaimsIdentity)) return "";
string loginProvider = ((ClaimsIdentity)user.Identity).Claims
.Where(c => c.Type.Equals("ExternalLoginProvider"))
.Select(c => c.Value)
.FirstOrDefault();
return loginProvider;
}
and in my razor view I have:
#using Namespace.Helpers
if (User.GetLoginType() == "Facebook")
{
//do something
}
and in my external login sign-in method i currently have:
private async Task SignInAsync(ApplicationUser user, bool isPersistent, string loginProvider)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
//create new claim called ExternalLoginProvider with a value of for example, "Facebook"
Claim clm = new Claim("ExternalLoginProvider", loginProvider);
identity.AddClaim(clm);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

How to add custom data to claim during login process?

I was reading article from here
This way we can add claim during login
var user = userManager.Find(userName, password);
identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = persistCookie }, identity);
This way reading back the value stored in clam
var prinicpal = (ClaimsPrincipal)Thread.CurrentPrincipal;
var email = prinicpal.Claims.Where(c => c.Type == ClaimTypes.Email).Select(c => c.Value).SingleOrDefault();
Now I have few questions
How could I add my custom data to claim. Suppose user role names.
Suppose the things I want to add that is not available in ClaimTypes then how could I add my custom data to claim?
How to read back my custom data stored in claim?
My action is decorated with authorized attribute where role name is specified like below one:
..
public class HomeController : Controller
{
[Authorize(Roles = "Admin, HrAdmin")]
public ActionResult PayRoll()
{
return View();
}
}
Do I need to go for custom authentication to extract roles from claim to set in GenericPrincipal?
Last question: When we go for role based authorization then roles are stored in authorization cookie? Do I need to write code to store roles in authorization cookie or ASP.net engine does it for us?
Same way claims are store in authorization cookie generated by owin cookie?
If you are using Identity than identity have its own method which can handle roles and everything you just have to login with this line.
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
you will have to add role manager in Identity Config File
public class ApplicationRoleManager : RoleManager<IdentityRole>
{
public ApplicationRoleManager(IRoleStore<IdentityRole, string> roleStore)
: base(roleStore)
{ }
public static ApplicationRoleManager Create(
IdentityFactoryOptions<ApplicationRoleManager> options,
IOwinContext context)
{
var manager = new ApplicationRoleManager(
new RoleStore<IdentityRole>(context.Get<ApplicationDbContext>()));
return manager;
}
}
and register in Startup.Auth.cs
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
and you won't have to give roles to the authentication manually. you just have to write
[Authorize(Roles = "Admin, HrAdmin")]
if you want to add that manually without identity given method than use this below
private void IdentityLogin(UserInfo UserInfo)
{
// Waleed: added the role in the claim
var identity = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.Name, UserInfo.Email),
new Claim(ClaimTypes.Sid, UserInfo.UserID),
new Claim(ClaimTypes.Role, UserInfo.Roles)
}, DefaultAuthenticationTypes.ApplicationCookie);
var claimsPrincipal = new ClaimsPrincipal(identity);
// Set current principal
Thread.CurrentPrincipal = claimsPrincipal;
var ctx = Request.GetOwinContext();
var authManager = ctx.Authentication;
authManager.SignIn(identity);
}
Claims are of two types one are in your session and other are stored in db. Session Claims are above in IdentityLogin method and db claims can be written as
UserManager.AddClaim(userId,new Claim())

Updating claims using Federated Authentication in asp.net mvc

I'm using the FederatedAuthentication class in my MVC project.
To create the cookie I use FederatedAuthentication.SessionAuthenticationModule.CreateSessionCookie(...);, but I can't seem to find a way to update them if the user wants to change for example their first name.
How can I access and update the claims without logging out?
The process is rather quite easy
Get the list of current claims of the ClaimsPrincipal
Remove the claims you want to update
Add new claims
Authenticate like normal, passing the list of claims.
var claims = FederatedAuthentication.SessionAuthenticationModule
.ContextSessionSecurityToken.ClaimsPrincipal.Claims.ToList();
var givenNameClaim = claims.Single(x => x.Type == "given_name");
var familyNameClaim = claims.Single(x => x.Type == "family_name");
var timeZoneClaim = claims.Single(x => x.Type == "time_zone_string");
var newName = new Claim(givenNameClaim.Type.ToString(), model.FirstName, givenNameClaim.ValueType, givenNameClaim.Issuer);
var familyName = new Claim(familyNameClaim.Type.ToString(), model.LastName, familyNameClaim.ValueType, familyNameClaim.Issuer);
var timeZone = new Claim(timeZoneClaim.Type.ToString(), model.SelectedTimeZone, timeZoneClaim.ValueType, timeZoneClaim.Issuer);
UpdateClaims(newName, familyName, timeZone);
public void UpdateClaims(params Claim[] updatedClaims)
{
var currentClaims = FederatedAuthentication.SessionAuthenticationModule.ContextSessionSecurityToken.ClaimsPrincipal.Claims.ToList();
foreach (var claim in updatedClaims)
{
currentClaims.Remove(currentClaims.Single(x => x.Type == claim.Type));
currentClaims.Add(claim);
}
var principal = new ClaimsPrincipal(new ClaimsIdentity[] { new ClaimsIdentity(currentClaims, "Auth0") });
var session = FederatedAuthentication.SessionAuthenticationModule.CreateSessionSecurityToken(
principal,
null,
DateTime.UtcNow,
DateTime.MaxValue.ToUniversalTime(),
true);
FederatedAuthentication.SessionAuthenticationModule.AuthenticateSessionSecurityToken(session, true);
}

MVC 5 seeding users only works for email

I am working on a porject built on MVC5 and EF Code First.
I have multiple contexts, but the one I'm concered about here is the ApplicationDbContext which has the following configuration code:
namespace DX.DAL.Migrations.ApplicationDbMigrations
{
public class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
MigrationsDirectory = #"Migrations\ApplicationDbMigrations";
ContextKey = "DX.DAL.Context.ApplicationDbContext";
}
protected override void Seed(ApplicationDbContext context)
{
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
if (!roleManager.RoleExists("Admin"))
{
roleManager.Create(new IdentityRole("Admin"));
}
var user = new ApplicationUser { UserName = "John", Email = "j.doe#world.com" };
if (userManager.FindByName("John") != null) return;
var result = userManager.Create(user, "Password123#");
if (result.Succeeded)
{
userManager.AddToRole(user.Id, "Admin");
}
}
}
}
When I try and login with the email and password seeded above, I get the error:
Invalid login attempt
I wrote the following SQL Query:
SELECT * FROM AspNetUsers
And I see the following:
So the seed has been created. But why can't I login?
Also, I know that if I change the Username to be the same as the email, then it works and I can login. Must the username and email be the same for ASP.NET Membership in MVC 5 to work?
After trying so many different things, I went with LukeP's solution here.
I left Identity as it is and just added a new property called DisplayUsername and allowed the user to set that up on registration.

Resources