Seeding admin database Role not adding - c#

I am having some issues with getting my admin user have an admin role. Once I create a user and it succeeds I tried adding the admin role with AddToRoleAsync. But I am not sure why it's not being added. I then tried to have a check so that if the admin has no admin role it would add it. It looks like the command executes but I don't see it added to the database.
This is my code:
public async Task CreateAdmin()
{
// Add roles
string[] roles = new string[] {"Administrator", "User"};
foreach(string role in roles)
{
bool result = await _roleManager.RoleExistsAsync(role);
if(!result)
{
await _roleManager.CreateAsync(new IdentityRole(role));
}
}
// Add admin user
if (!_context.Users.Any(u => u.UserName == "Admin"))
{
var user = new Users
{
Email="admin#admin.com",
NormalizedEmail="ADMIN#ADMIN.COM",
UserName="admin#admin.com",
NormalizedUserName="ADMIN",
EmailConfirmed = true,
};
var result = await _userManager.CreateAsync(user, "Password123");
}
var adminuser = await _userManager.FindByEmailAsync("admin#admin.com");
bool flag = await _userManager.IsInRoleAsync(adminuser, "Administrator");
if(!flag)
{
var role = await _roleManager.FindByNameAsync("Administrator");
await _userManager.AddToRoleAsync(adminuser, "Administrator");
}
}
If you want the full DbIntilizer or more code let me know.
Anyone know what I am doing wrong?
Edit
Reworked it to follow this
http://www.locktar.nl/programming/net-core/seed-database-users-roles-dotnet-core-2-0-ef/
and now it works.

This is how i do it.
public async Task InitializeDatabase(IApplicationBuilder app)
{
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
// Create DB
serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
// Add roles
var roleManager = serviceScope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole<long>>>();
if (!roleManager.Roles.Any())
{
foreach (var role in Config.GetTestRolls())
{
await roleManager.CreateAsync(role);
}
}
// Add users
var userManager = serviceScope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
if (userManager.Users.Any()) return;
foreach (var user in Config.GetTestUsers())
{
await userManager.CreateAsync(user, "Password123!");
}
// find first user add to first role (hack)
var adminUser = await userManager.FindByEmailAsync(Config.GetTestUsers().FirstOrDefault()?.Email);
if (adminUser != null)
{
await userManager.AddToRoleAsync(adminUser, Config.GetTestRolls().FirstOrDefault()?.Name);
}
}
Code ripped from my GitHub Project found here

Related

Blazor Server Add Claims after authentication

Hi I want user to select branch to enter after login and user may have different role for each branch. Then this info will be added to user claims. Below is how I implemented IClaimsTransformation TransformAsync method.
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
// Clone current identity
var clone = principal.Clone();
var newIdentity = (ClaimsIdentity)clone.Identity;
// Support AD and local accounts
var nameId = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier ||
c.Type == ClaimTypes.Name);
if (nameId == null)
{
return principal;
}
// Get userInfo from database
UserBranchRoleModel userInfo = await Task.Run(() => _userService.GetCurrentBranchRole(nameId.Value));
if (userInfo?.User == null)
{
return principal;
}
List<Claim> claims = new()
{
new Claim(newIdentity.NameClaimType, userInfo.User.UserID),
new Claim("BranchID", userInfo.Branch.BranchID.ToString()),
new Claim("BranchName", userInfo.Branch.BranchName),
new Claim(newIdentity.RoleClaimType, userInfo.Role.RoleID)
};
newIdentity.AddClaims(claims);
return clone;
}
The problem is claims don't get updated if I do not refresh the page. Is there any way refresh claims without refreshing page altogether ?
Here is How I display claims in Index page
#foreach (var c in #user.Claims)
{
<div>#c.Type: #c.Value</div>
}
And this is how I Update DB
async Task changeBranch(string UserID)
{
DialogOptions options = new DialogOptions() { CloseButton = false };
DialogParameters parameters = new DialogParameters();
parameters.Add("UserID", UserID);
var dialog = DialogService.Show<ChooseBranch>("Select A Branch", parameters, options);
var result = await dialog.Result;
if (!result.Cancelled)
{
UserBranchRoleModel BranchRole = (UserBranchRoleModel)result.Data;
_user.SetCurrentBranchRole(BranchRole);
StateHasChanged();
}
}
}

c# - Adding a role to idenitity user on signup fails with foreign key constraint

I don't know if this is a duplicate, but I couldn't find any similar problems with solutions that helped in my case.
So I have an identity user that I want to add a role to based on which dropdown value the user chooses on signup page. I tested this part and it goes through just fine. But adding a role doesn't seem to work no matter what I try.
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
bool ownerRoleExists = await _roleManager.RoleExistsAsync("Owner");
if (!ownerRoleExists)
{
var roleResult = await _roleManager.CreateAsync(new IdentityRole("Owner"));
}
bool patientRoleExists = await _roleManager.RoleExistsAsync("Patient");
if (!patientRoleExists)
{
var roleResult = await _roleManager.CreateAsync(new IdentityRole("Patient"));
}
bool workerRoleExists = await _roleManager.RoleExistsAsync("Worker");
if (!workerRoleExists)
{
var roleResult = await _roleManager.CreateAsync(new IdentityRole("Worker"));
}
if (ModelState.IsValid)
{
var user = new Person { UserName = Input.Email, Email = Input.Email, Name = Input.Name, LastName = Input.LastName, Address = Input.Address };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
if (Input.PersonType.Equals("patient"))
{
var patient = new Patient { PersonId = user.PersonId, VisitCount = 0, Number = Input.Number };
_context.Add(patient);
var patient_result = await _context.SaveChangesAsync();
if(patient_result > 0)
{
var result_role = await _userManager.AddToRoleAsync(user, "Patient");
}
}
else if(Input.PersonType.Equals("worker"))
{
var worker = new Worker { PersonId = user.PersonId, WorkCode = Input.WorkCode };
_context.Add(worker);
var worker_result = await _context.SaveChangesAsync();
if(worker_result > 0)
{
var result_role = await _userManager.AddToRoleAsync(user, "Worker");
}
}
_logger.LogInformation("User created a new account with password.");
}
}
}
The error I get is:
SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_AspNetUserRoles_person_UserId". The conflict occurred in database "hospital-prod", table "dbo.person", column 'person_id'.
The statement has been terminated.
The values I get from user are correct, when I checked in the debug right before role is added so variable user should be correct.
Also user and patient/worker are added to the database successfully, so only the role is a problem.
Okay, so I figured out what the real problem was; and I'm going to briefly explain it, if someone else is in the same situation at some point.
The problem with adding a role in my case was that method AddToRoleAsync(User user, String role) accepts a User type variable not an ID, so it selects the ID by itself.
Well, I have a custom database where i already have a person_id column and the rules of IdentityUser don't allow you to remove any of the default columns and that includes the Id. So the problem was that that method automaticay selected a wrong id and I had to change the behaviour of ids' to get it to work/select the right one.

ASP.NET MVC 5 get claims

I use third-party auth nuget instagram package for login and set new claim:
app.UseInstagramAuthentication(new InstagramAuthenticationOptions
{
ClientId = "XXXXXXXXXXXXXXXXXXXXXXXXX",
ClientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXX",
Provider = new InstagramAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim("urn::instagram::accesstoken", context.AccessToken));
return Task.FromResult(0);
}
}
but when I try to get this claim
var ctx = HttpContext.GetOwinContext();
ClaimsPrincipal user = ctx.Authentication.User;
IEnumerable<Claim> claims = user.Claims;
This claim does not exist in the list. Why?
You need to retrieve and store those claims on external login, maybe something like:
private async Task StoreAuthTokenClaims(ApplicationUser user)
{
// Get the claims identity
ClaimsIdentity claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (claimsIdentity != null)
{
// Retrieve the existing claims
var currentClaims = await UserManager.GetClaimsAsync(user.Id);
// Get the list of access token related claims from the identity
var tokenClaims = claimsIdentity.Claims
.Where(c => c.Type.StartsWith("urn:tokens:"));
// Save the access token related claims
foreach (var tokenClaim in tokenClaims)
{
if (!currentClaims.Contains(tokenClaim))
{
await UserManager.AddClaimAsync(user.Id, tokenClaim);
}
}
}
}
And on the ExternalLoginConfirmation method:
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (result.Succeeded)
{
await StoreAuthTokenClaims(user);
// Sign in and redirect the user
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
After that, you can retrieve claims like:
var claimsIdentity = HttpContext.User.Identity as ClaimsIdentity;
if (claimsIdentity != null)
{
var claims = claimsIdentity.Claims;
}

SSO with AD FS and OWIN how to create an account and handle permissions

I configure a Web App that use AD FS, for this I use OWIN.
For the login, all is ok. If i'm an user of a domain and go to the website, he is automatically connected.
But what I want to have is to handle users and roles by myself after login.
So I want to check that an user exists in my database with this AD account (this process will be make before the login in another application)
I want to use Identity from Microsoft to handle claims (roles and permissions). But I don't understand how to put my code to handle the successfull connection from AD FS (with Ws-Federation) and add verification and fill in the right roles.
My code in ConfigureAuth:
public partial class Startup
{
private static string realm = ConfigurationManager.AppSettings["ida:Wtrealm"];
private static string adfsMetadata = ConfigurationManager.AppSettings["ida:ADFSMetadata"];
private NLogLoggingService _loggingService;
public void ConfigureAuth(IAppBuilder app)
{
_loggingService = new NLogLoggingService("Startup");
_loggingService.Debug("ConfigureAuth");
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata,
//CallbackPath = PathString.FromUriComponent("/Account/TestCallback"),
// https://msdn.microsoft.com/en-us/library/microsoft.owin.security.authenticationmode(v=vs.113).aspx
AuthenticationMode = AuthenticationMode.Passive,
//Notifications = new WsFederationAuthenticationNotifications
//{
//}
});
}
In Web.config, realm is the link to my Web App (https://ssoadfs.test) and adfsMetadata is the link to metadata.xml from AD FS.
What is the way to go to set my role and login logic after AD FS connection ?
Schema that what I was thinking:
EDIT:
After some tries, I cannot handle any success callback. I don't want to have to handle roles in HomeController ...
My last Auth config:
_loggingService = new NLogLoggingService("Startup");
_loggingService.Debug("ConfigureAuth");
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationUser.ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Provider = new CookieAuthenticationProvider
{
OnResponseSignIn = ctx =>
{
_loggingService.Debug("OnResponseSignIn");
ctx.Identity = TransformClaims(ctx, app);
},
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata,
Caption = "Active Directory",
CallbackPath = PathString.FromUriComponent("/Account/TestCallback"),
Notifications = new WsFederationAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
new NLogLoggingService("Startup").Debug("SecurityTokenValidated");
var incomingClaimsFromAdfs = n.AuthenticationTicket.Identity.Claims.ToList();
var incomingClaimsHasNameIdentifier =
incomingClaimsFromAdfs.Any(
c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier);
_loggingService.Debug("SecurityTokenValidated - incomingClaimsHasNameIdentifier: " +
incomingClaimsHasNameIdentifier);
if (!incomingClaimsHasNameIdentifier)
{
var emailClaim =
incomingClaimsFromAdfs.First(c => c.Type == System.Security.Claims.ClaimTypes.Name);
_loggingService.Debug(emailClaim.Value);
}
//if (!incomingClaimsHasNameIdentifier)
//{
// var emailClaim = incomingClaimsFromAdfs.First(c => c.Type == System.Security.Claims.ClaimTypes.Name);
// incomingClaimsFromAdfs.Add();
// IUser user = await this.UserStore.FindByNameOrEmailAsync(userNameOrEmailAddress);
// if ((Entity<long>)user == (Entity<long>)null)
// LoginResult = new ApplicationUserManager.LoginResult(LoginResultType.InvalidUserNameOrEmailAddress, default(IUser));
// //else if (!loggedInFromExternalSource && new PasswordHasher().VerifyHashedPassword(user.Password, plainPassword) != PasswordVerificationResult.Success)
// // LoginResult = new UserManager<TTenant, TRole, TUser>.LoginResult(LoginResultType.InvalidPassword, user);
// else
// LoginResult = await this.CreateLoginResultAsync(user, tenant);
//}
//else
//{
// throw new ApplicationException("Get ADFS to provide the NameIdentifier claim!");
//}
//var normalizedClaims = incomingClaimsFromAdfs.Distinct(new ClaimComparer());
//var claimsIdentity = new ClaimsIdentity(normalizedClaims, n.AuthenticationTicket.Identity.AuthenticationType);
//n.AuthenticationTicket = new AuthenticationTicket(claimsIdentity, n.AuthenticationTicket.Properties);
return Task.FromResult(0);
}
}
});
In this code, I tried CallbackPath (nothing appeared in my log), WsFederationAuthenticationNotifications.SecurityTokenValidated (nothing appeared in my log), CookieAuthenticationProvider.OnResponseSignIn (same nothing happened)
In HomeController i'm able to have Identity.Name:
public ActionResult Index()
{
if (HttpContext.GetOwinContext().Authentication.User.Identity.IsAuthenticated)
{
new NLogLoggingService("Home").Debug("User is authenticated");
}
return View();
}
Did I miss something to get Notifications working or Provider in CookieAuthenticationOptions ???
If you use ASP.NET Identity 2.0 or later version, you can use an approach similar to it shown below. Please note that this approach assign GroupRoles to the user instead of assigning each of roles one by one. You can change necessary parts according to your needs.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
ApplicationGroupManager groupManager = new ApplicationGroupManager();
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
//Assign Roles to the current User
ApplicationUser user = UserManager.FindByName(model.UserName);
//If the user is registered in the system (ASP.NET Identity) add record to AspNetUsers table
if (user != null)
{
//Returns Group Id and Role Id by using User Id parameter
var userGroupRoles = groupManager.GetUserGroupRoles("bfd9730e-2093-4fa0-89a2-226e301d831b");
foreach (var role in userGroupRoles)
{
string roleName = RoleManager.FindById(role.ApplicationRoleId).Name;
UserManager.AddToRole(user.Id, roleName);
}
}
else
{
//crate new user
//first retrieve user info from LDAP:
// Create an array of properties that we would like and add them to the search object
string[] requiredProperties = new string[] { "samaccountname", "givenname", "sn", "mail", "physicalDeliveryOfficeName", "title" };
var userInfo = CreateDirectoryEntry(model.UserName, requiredProperties);
var newUser = new ApplicationUser();
newUser.UserName = userInfo.GetDirectoryEntry().Properties["samaccountname"].Value.ToString();
newUser.Name = userInfo.GetDirectoryEntry().Properties["givenname"].Value.ToString();
newUser.Surname = userInfo.GetDirectoryEntry().Properties["sn"].Value.ToString();
newUser.Email = userInfo.GetDirectoryEntry().Properties["mail"].Value.ToString();
newUser.EmailConfirmed = true;
newUser.PasswordHash = null;
var result = await UserManager.CreateAsync(newUser);
if (result.Succeeded)
{
//If the user is created ...
}
//Assign user group (and roles)
var defaultGroup = "751b30d7-80be-4b3e-bfdb-3ff8c13be05e";
groupManager.SetUserGroups(newUser.Id, new string[] { defaultGroup });
}
return this.RedirectToAction("Index", "Issue");
}
this.ModelState.AddModelError(string.Empty, "Wrong username or password!");
return this.View(model);
}
static SearchResult CreateDirectoryEntry(string sAMAccountName, string[] requiredProperties)
{
DirectoryEntry ldapConnection = null;
try
{
ldapConnection = new DirectoryEntry("LDAP://OU=******, DC=******, DC=******", "acb#xyz.com", "YourPassword");
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
DirectorySearcher search = new DirectorySearcher(ldapConnection);
search.Filter = String.Format("(sAMAccountName={0})", sAMAccountName);
foreach (String property in requiredProperties)
search.PropertiesToLoad.Add(property);
SearchResult result = search.FindOne();
//SearchResultCollection searchResultCollection = search.FindAll(); //You can also retrieve all information
if (result != null)
{
return result;
}
else {
return null;
//Console.WriteLine("User not found!");
}
}
catch (Exception e)
{
Console.WriteLine("Exception caught:\n\n" + e.ToString());
}
return null;
}

UserManager<ApplicationUser>.Create() is returning null

I have the following code in the Seed method of my Configuration.cs file:
var userStore = new UserStore<ApplicationUser>();
var manager = new UserManager<ApplicationUser>(userStore);
IdentityResult result = manager.Create(new ApplicationUser() { UserName = "test#mail.com", Email = "test#mail.com", Name = "Martin Tracey" }, "password");
if (result.Succeeded) { Console.WriteLine("User created successfully"); }
else {
Console.WriteLine("Something went wrong. result is "+result.ToString());
foreach (var error in result.Errors) Console.WriteLine(error);
}
For whatever reason, the manager.Create call is returning null.
Any idea why this method would ever return null?
I figured it out! It's a very simple solution.
My userStorevariable didn't have a DbContext which would allow it to access and write to the Database. The simple solution is to use the context passed into the Seed method. Works like a charm now! See below:
protected override void Seed(MyFirstWebApplication.Models.ApplicationDbContext context)
{
if( !context.Users.Any( u => u.Email == "test#mail.com" ) )
{
var userStore = new UserStore<ApplicationUser>(context);
var manager = new UserManager<ApplicationUser>(userStore);
var user = new ApplicationUser() { UserName = "test#mail.com", Email = "test#mail.com", Name = "Martin Tracey" };
IdentityResult result = manager.Create(user, "password");}
}
}

Categories