Problem. In a registration scenario, I'm trying to insert a user in my User table and then call WebSercurity.CreateAccount for that user (in a transaction). This causes the error that MS DTC is not available on the server.
Description. The reason I'm doing this is because I have a Customer Entity which inherits from User, so WebSercurity.CreateUserAndAccount cannot be used because it doesn't know about Customer and just inserts a User record.
I'm using Asp.net MVC 4 with EntityFramework 5, CodeFirst, and SQL Server 2008 R2.
any suggestions for not using DTC would be appreciated!
EDIT.
It is obvious why this error occurs, because websecurity uses its own connection to the database, and my repositories use another connection, although I've configured simplemembership to use the same DbContext class as my repositories, but the problem is it creates a new instance of the DbContext ...
I was hoping if there is a way to pass an existing context object, or connection to the WebSecurity to use with its methods.
here's the code:
if (ModelState.IsValid)
{
//using (TransactionScope tx = new TransactionScope())
//{
UnitOfWork.UserRepository.Insert(new Customer
{
FirstName = model.FirstName,
LastName = model.LastName,
Email = model.Email,
Tel = model.Tel,
Mobile = model.Mobile,
BirthDate = model.BirthDate,
InsertDate = DateTime.Now,
UserType = UserType.Customer,
MaritalStatus = model.MaritalStatus,
ZipCode = model.ZipCode,
StreetAddress = model.StreetAddress,
City = model.City,
State = model.State
});
UnitOfWork.Commit();
string token = WebSecurity.CreateAccount(model.Email, model.Password, true);
Roles.AddUserToRole(model.Email, "Customer");
//WebSecurity.Login(model.Email, model.Password, true);
await Task.Run(() => EmailHelper.SendConfrimationEmail(token, model.Email));
// tx.Complete();
//}
The DTC error occurs because you are trying to span a transaction over two different database connections. You have several options.
SimpleMembership is designed for simple scenarios. You are doing an advanced scenario, so you should probably use a different membership provider.
I found a possible solution but I'm not sure if it's the best solution. The idea came from this blog post that says how we can include simplemembership tables in our POCO entities and create the tables ourselves (not using WebSecurity).
So as a result I think I can implement the CreateAccount method in my repositories by simply inserting a record in the webpages_Membership table, and AddUserToRole by inserting a record in webpages_UsersInRoles table.
For other queries like GetUser and ... we can use WebSecurity and Roles class like before.
It seems to work OK (otherwise I'm missing something), but has some extra work to do which I wish not to!
So if anyone can give me a better solution I would be glad.
As Mystere Man pointed out the error is occurring because the transaction is over two different databases. You have several options. Here are a few that come to mind.
You can customize the existing User entity used by the SimpleMembership provider to capture the custom information you want for each user. This is fairly straightforward and is discussed in this blog post. It looks like you are trying to do an email confirmation which the SimpleMembership provider also supports and is discussed here.
You can also tie the information in your other database with the unique user ID (int) that SimpleMembership uses. Once you create the user and log them in you can get that id by calling WebSecurity.CurrentUserId.
You could bypass the whole SimpleMempership provider and create your own custom membership provider, which is discussed here.
Related
I'm currently failing at wrapping my head around claims. I have a ASP.Net Core 3 project with the angular template and users stored in app.
I want to add claims to my users, reading up on I thought it would be easy, just add something along the lines of
await _UserManager.AddClaimAsync(user, new Claim(AccountStatic.ClaimTypes._Claim_Id, user.Id));
When you create the user, and then get it back using the below line once they are logged in again:
User.FindFirst(AccountStatic.ClaimTypes._Claim_Id)?.Value;
This does however not work. I can see the claims being written to AspNetUserClaims table in my database but it's not there in the users claims when they log in. There are a few other claims there, but not the ones I have added.
Do I need to define somewhere which of the users claims get included when they log in?
Edit.
I found a post stating that I need to add claims using a DI AddClaimsPrincipalFactory. So I added this class.
public class UserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser>
{
public UserClaimsPrincipalFactory(UserManager<ApplicationUser> userManager,IOptions<IdentityOptions> optionsAccessor): base(userManager, optionsAccessor)
{}
//https://levelup.gitconnected.com/add-extra-user-claims-in-asp-net-core-web-applications-1f28c98c9ec6
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
{
var identity = await base.GenerateClaimsAsync(user);
identity.AddClaim(new Claim(AccountStatic.ClaimTypes.Claim_Id, user.Id ?? "[no id]"));
return identity;
}
}
And if I step through the code I can see the claims being added here. But in the Controller my custom claims are not present.
internal string GetUserId()
{
if (User.Identity.IsAuthenticated == false)
return null;
return User.FindFirst(AccountStatic.ClaimTypes.Claim_Id)?.Value;
}
Update. Ok I find this very strange. I have been trying to do what others claim work but for me nothing gets me the users name or id. inspecting the User I get the following. Nothing here contains any reference to the logged in user.
Update 2:
Just noticed that there is actually an Id in there: {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: ed107a11-6c62-496b-901e-ed9e6497662a} Seems to be the users id from the database. Not sure how to access it yet though.
These return null.
User.FindFirst(JwtRegisteredClaimNames.NameId)?.Value;
User.FindFirst("nameidentifier")?.Value;
User.FindFirst("NameIdentifier")?.Value;
Another update
I'm using a UserClaimsPrincipalFactory and breakingpointing it and looking at the Claims I can see that all of the ones I want are there. But again, these are not available in my API controllers as seen in the first picture.
I finally understood the problem, in large parts thanks to Ruard van Elburgs comments, and the answer he made in the linked question IdentityServer4 Role Based Authorization.
The problem is that the claims are not added to the access token.
There are two tokens, the access token and the identity token.
- Ruard van Elburg
They key to understanding what was going on was finding out that there are two tokens, and that they contain different claims and have different purposes.
You can force claims from one token to also be included in the other if you deem it necessary.
The solution to my problem was to add this in Startup.ConfigureServices
services
.AddIdentityServer(options => {})
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
foreach (var c in options.ApiResources)
{
// the string name of the token I want to include
c.UserClaims.Add(AccountStatic.ClaimTypes.Claim_Id);
}
});
I still have not figured out how to get the Identity token, but as I'm now including the user Id in the access token my problems are solved for the moment.
I am debugging confirmation email flow when signing up a new User in Asp.Net Core web application with Identity Server 4.
Since I had already signed up with my actual email, to reuse it, I modified the UserName and Email in AspNetUsers table using SQL Update to some random value.
Now when I am signing up with the original email again. I am getting a duplicate user error
result = await _userManager.CreateAsync(user, model.Password);
I have already:
Cleared browser cache.
Closed local IIS Express
Restarted Visual Studio.
Used_userManager.DeleteAsync() after updating the UserName and Email back to original values but this gives an Microsoft.AspNetCore.Identity.IdentityError with description Optimistic concurrency failure, object has been modified.
On running this query on Sql Server
select * from INFORMATION_SCHEMA.COLUMNS where COLUMN_NAME in ( 'UserName' , 'Email')
I get the following:
I know that this is not a good practice to mess with backend, but this is development environment and I could continue my work with another email.
I would request readers to help in understanding how the User could be safely scorched to be able to reuse the email.
Appreciate your time
I agree with Kyle's comment and to further speed up your debug process you should note that if you use gmail to do this you can debug this process using one email.
from google/gmails perspective myaccount#gmail.com == my.acount#gmail.com == m.y.a.c.c.ount#gmail.com etc etc just try it out, google disregards all period characters in the email. you can enumerate/exhaust ~2^8 emails (in this example) if you just enumerate through the local-part of the e-mail address. but from your applications side, myaccount#gmail.com is not the same as my.account#gmail.com, ie they are different user accounts. Basically you can use one email to test out this feature of yours without having to delete the user.
Here is how I did it and finally got passed the pesky "concurrency failure" error message... This works in ASP.NET CORE 2.2
Obtain the user object through the FindByName method first.
Remove the user from their assigned Role (in this case I hard coded "Admin" because that is the role I'm interested in but fill in your own), then delete the user.
//Delete user.
//Obtain the user object through the FindByName method first.
//Remove the user from their assigned Role, then delete the user.
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
ApplicationUser delAppUser = new ApplicationUser
{
Email = "SomeEmailForindividualAdminUser",
UserName = "SomeUsernameindividualAdminUser"
};
Task <ApplicationUser> taskGetUserAppUser = userManager.FindByNameAsync(delAppUser.UserName);
taskGetUserAppUser.Wait();
Task<IdentityResult> taskRemoveFromRoleAppUser = userManager.RemoveFromRoleAsync(taskGetUserAppUser.Result, "Admin");
taskRemoveFromRoleAppUser.Wait();
Task<IdentityResult> taskDeleteAppUser = userManager.DeleteAsync(taskGetUserAppUser.Result);
taskDeleteAppUser.Wait();
We use Apache Shiro to authenticate and authorize users using our active directory.
Authenticating the user and mapping groups works just fine using the following config:
adRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm
adRealm.searchBase = "OU=MYORGANIZATION,DC=MYDOMAIN,DC=COM"
adRealm.groupRolesMap = "CN=SOMEREADGROUP":"read","CN=SOMEMODIFYGROUP":"modify","CN=SOMEADMINGROUP":"admin"
adRealm.url = ldaps://my.ad.url:636
adRealm.systemUsername= systemuser
adRealm.systemPassword= secret
adRealm.principalSuffix= #myorganization.mydomain.com
I can authenticate in Shiro using the following lines:
String user = "someuser";
String password = "somepassword";
Subject currentUser = SecurityUtils.getSubject ();
if (!currentUser.isAuthenticated ()){
UsernamePasswordToken token = new UsernamePasswordToken (user,
password);
token.setRememberMe (true);
currentUser.login (token);
}
We now want to get more user information from our ActiveDirectory. How can I do that using Apache Shiro? I was not able to find anything about it in the documentation.
In the source code of ActiveDirectoryRealm I found this line:
NamingEnumeration answer = ldapContext.search(searchBase, searchFilter, searchArguments, searchCtls);
So the first part of the answer is clear: use the ldapContext to search something in it. But how can I retrieve the LdapContext?
It depends on what you are trying to do. Are you just trying to reuse the context to run a query for something other then authentication or authorization? Or are you trying to change the behavior of the query in the AD realm?
If the latter, you would need to extend the ActiveDirectoryRealm and override the queryForAuthorizationInfo() method.
Are you implementing something that is custom for your environment?
(updated)
A couple things:
The realm has access to the LdapContext in the two touch points: queryForAuthenticationInfo() and queryForAuthorizationInfo(), so if you extend the AD realm or AbstractLdapRealm you should already have it. You could change the query to return other info and add the extra info to your Principal. Then you have access to that info directly from your Subject object.
Your realms, are not required to be singletons.
If you want to do some other sort of user management (email all users with a given role, create a user, etc). Then you could create a LdapContextFactory in your shiro.ini, and use the same instance for multiple objects.
[main]
...
ldapContextFactory = org.apache.shiro.realm.ldap.JndiLdapContextFactory
ldapContextFactory.systemUsername = foobar
ldapContextFactory.systemPassword = barfoo
adRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm
adRealm.ldapContextFactory = $ldapContextFactory
...
myObject = com.biz.myco.MyObject
myObject.ldapContextFactory = $ldapContextFactory
This would work well if myObject is interacting with other Shiro components, (responding to events, etc), but less so if you need access to it from another framework. You could work around this by some sort of static initialization that builds creates the ldapContextFactory, but in my opinion, this is where the sweet spot of using the shiro.ini ends, and where using Guice or Spring shines.
I have a Web API 2 project using MVC. It uses entity framework. This entity framework uses a database first approach with a .edmx file.
The project is based on VS 2013 Express Web API 2 template. I just used my own database. I didn't modify any account related code. But when I try to register a new user, the following statement in AccountController.cs throw exception:
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
...
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
...
}
The exception says:
Cannot insert the value NULL into column 'Discriminator', table 'xxx.dbo.AspNetUsers'; column does not allow nulls. INSERT fails.
The statement has been terminated.
Can anyone help me? Thank you!
The answer is in your question's body.
table 'xxx.dbo.AspNetUsers'; column does not allow nulls.
Seems to be, that you're trying to insert a NULL value from your model instance RegisterBindingModel model, where the structure of your table in database server doesn't allow to accept NULL value.
Try to debug and check, is your model instance is creating correctly when the request is coming.
Also I can see, that you use:
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
Why user isn't a member of your model, like the Password property?
Maybe try to use:
IdentityResult result = await UserManager.CreateAsync(model.User, model.Password);
Anyway... I don't have more info and don't imagine, what is inside your model instance, how you're binding data in it from the request. You should to look in the debugger and debug very well to repair your project or provide more info.
But one thing is clear as a crystal, you're trying to insert a NULL value into the column, which doesn't accept NULL, so you must check how is your model binding. Maybe your client-side application doesn't send correctly some arguments, maybe you're binding you model incorrectly, maybe something else... There is a need to get more information from you to help you, otherwise you should debug carefully.
I upgraded all NuGet Packages yesterday. Among them Asp.net Identity is upgraded to 2.0.0.0.
It magically worked.
So I suspect that was because of some bug in Identity 1.0
We have audit columns set by triggers.
For obscure security reasons predating my tenure and out of my control, we log in with a generic user, and do a 'set session authorization' to change the user to the db user of the user who is logged in.
When we converted to NHibernate, it creates a whole new session and jacks everything up
when we try to do a set session auth, so we turned the set session auth off...
Now we are trying to find out a way to get NHibernate to let us do 'set session authorization' without recycling the session on us, so we can use our existing trigger based audit column stuff with both legacy apps, and our new NHibernate apps.
It's not a ideal soloution, or the best way to do it even, but is it possible?
I was hoping there was a alternate interface that allowed this kind of access.
Does anyone know how to do it, or can you point me towards and good hints?
Thanks,
Eric-
You can inherit DriverConnectionProvider and do whatever you need when creating a connection.
Example:
public class MyConnectionProvider : DriverConnectionProvider
{
public override IDbConnection GetConnection()
{
var connection = base.GetConnection();
var sessionAuthCommand = connection.CreateCommand();
sessionAuthCommand.CommandText = "set session authorization " + GetUser();
sessionAuthCommand.ExecuteNonQuery();
return connection;
}
}
And then configure NHiberate to use that as the connection provider.
(GetUser is the method where you'll provide the correct user)