This question already has an answer here:
Asp.net web forms, Asp Identity - how to store claims from Facebook, Twitter, etc
(1 answer)
Closed 8 years ago.
I'm using asp.net forms and have written my own UserStore class that implements the Interface IUserClaimStore<IdentityUser> (and others), because I wanted to use AspNet Identity without EntityFramework and write my own data access layer for it.
Everything seems to work pretty good, but when I try to add Claims to my database tables I get problems.
In the Startup.Auth.cs I configure the FacebookAuthentication like this:
var options = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions();
options.AppId = "xxxxxxxxxxxxxxxxxxx";
options.AppSecret = "xxxxxxxxxxxxxxxxxxxxxx";
options.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
options.Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
foreach (var x in context.User)
{
context.Identity.AddClaim(new System.Security.Claims.Claim(x.Key, x.Value.ToString()));
}
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
return System.Threading.Tasks.Task.FromResult(0);
}
};
app.UseFacebookAuthentication(options);
The OnAuthenticated method gets called and some claims should be added. But the AddClaimAsync method inside my UserStore class never gets called and so there are never UserClaim entries in my DB. At some point the GetClaimsAsync method will be called (I think during calling authenticationManager.SignIn(...)). So I think my UserStore should work properly.
The reason is that you're adding a claim to the ClaimsIdentity which is an in-memory object that represents the user from the external provider. You're not calling into ASP.NET Identity to store the claims in the database. To do so, you should be calling the AddClaim API on the UserManager.
Related
#Roles.GetRolesForUser() in razor layout view is not returning roles. #Roles.GetRolesForUser().Count() is 0.
While #Roles.IsUserInRole('name_of_logged_in_role') returns true in the same view at the same place.
Razor View:
<p>
#User.Identity.Name //Output: MyName
#Roles.GetRolesForUser().Count() //Output: 0
#Roles.IsUserInRole("Radiologist") //Output: True
</p>
Update
#Roles.GetRolesForUser(User.Identity.Name).Length //Output: 0
#Roles.GetRolesForUser(User.Identity.GetUserName()).Length //Output: 0
After extensive research, I finally found the problem. I was able to reproduce the issue in a web application. Apparently, you cannot combine ASP.NET Identity with Simple Membership, simply like you did so with the GetRolesForUser method. The Roles object is by default setup for Simple Membership using a default provider, but it seems like your using ASP.NET Identity not Simple Membership. I didn't even notice the difference until I was wondering myself why it wasnt working.
The reason why you got string[0] is because GetRolesForUser executed an sql query to a table that doesnt exist in your database.
The reason why IsUserInRole worked was more or less it did not even use the default provider to check it used the CacheRolesInCookie
If CacheRolesInCookie is true, then roleName may be checked against the roles cache rather than the specified role provider.
So, technically it went to a connectionString that was listed by the default provider and return string[0] because you have no data in the database with that connectionString. Adding your current database to the providers would not help either because Simple Membership database schema is different from ASP.NET Identity
That being said, you should get the roles by UserName like this:
Simple Solution:
public List<string> GetRoles(string UserName)
{ List<string> roles = new List<string>();
if (!string.IsNullOrWhiteSpace(UserName))
{
ApplicationUser user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var account = new AccountController();
roles = account.UserManager.GetRoles(user.Id);
}
return roles;
}
Updated
Extended Solution:
You could extend the ASP.NET Identity Roles in your context
http://www.codeproject.com/Articles/799571/ASP-NET-MVC-Extending-ASP-NET-Identity-Roles
Today I was configuring authorization provider for Oauth middleware and trying to insert some guid value into Thread.CurrentPrincipal.Identity.Claims. But when I tried to call Thread.CurrentPrincipal's FindFirst I've got nothing.
Here is the example what I was trying to do:
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var claimsIdentity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
if (claimsIdentity != null)
claimsIdentity.AddClaim(new Claim("TestClaim", Guid.NewGuid().ToString()));
var claimValue = ((ClaimsPrincipal)Thread.CurrentPrincipal)
.FindFirst(x => x.Type == "TestClaim"); //claimValue == null!
}
Checking inner properties, found that Thread.CurrentPrincipal.Identity still contains claim I've set before, but Thread.CurrentPrincipal.Identities[0] - doesn't. So there are two different identity instances with their own set of claims.
I tried to do the same steps inside Web Api controller's action and there Identity was referencing to Identities[0] which means that there is the same instance.
What is happening to OWIN middleware's Currentprincipal so it's Identity and Identities[0] refer to different instances? Can anyone explain me this, please?
Thank you!
I met the same issue. I don't know why the Identity property and the first identity of the Identities property are different instances...
But it seems that all methods relative to claims in the ClaimsPrincipal class (Claims, FindFirst...) are based on the Identities property, so updating the Identity property has no effect.
I prefer to keep the two identities consistent, so I use the following workaround to solve the problem :
principal = (ClaimsPrincipal)Thread.CurrentPrincipal
identity = (ClaimsIdentity)user.Identity;
identity1 = user.Identities.First();
identity.AddClaim(claim);
identity1.AddClaim(claim);
I am working with the SQLMemebershipProvider and using Profiles. I have a custom class called UserProfile that inherits from the ProfileBase class and I use this to set custom properties like "FullName". I am wanting to loop through all the users in the database and get access to their profile properties. On each iteration I am calling ProfileBase.Create() to get a new profile and then access the properties.
It looks to me like every time ProfileBase.Create() is called it hits my SQL database. But I am just looking for confirmation of this. So, does anyone know if this does in fact hit the DB each time?
And better yet, does anyone have a better solution of how I could make one call to the DB to get all users with their custom profile attributes?
I know I could write my own stored proc, but I am wondering if there is a way built in to the Membership Provider.
Mike, I believe what you observed is true. I am working with a ProfileProvider that uses Azure TableStorage as data store. I wanted to get a list of user profiles from database and merge them with information from membership provider.
It took some time until I realized that calling ProfileBase.Create() with a username as argument performs a lookup against TableStorage and actually retrieves the data associated with that username. As far as I'm concerned, calling this method Create() is misleading, I would expect Load() or Get().
Currently my code looks like this:
public IEnumerable<AggregatedUser> GetAllAggregatedUsers()
{
ProfileInfoCollection allProfiles = this.GetAllUsersCore(
ProfileManager.GetAllProfiles(ProfileAuthenticationOption.All)
);
//AggregatedUser is simply a custom Class that holds all the properties (Email, FirstName) that are being used
var allUsers = new List<AggregatedUser>();
AggregatedUser currentUser = null;
MembershipUser currentMember = null;
foreach (ProfileInfo profile in allProfiles)
{
currentUser = null;
// Fetch profile information from profile store
ProfileBase webProfile = ProfileBase.Create(profile.UserName);
// Fetch core information from membership store
currentMember = Membership.FindUsersByName(profile.UserName)[profile.UserName];
if (currentMember == null)
continue;
currentUser = new AggregatedUser();
currentUser.Email = currentMember.Email;
currentUser.FirstName = GetStringValue(webProfile, "FirstName");
currentUser.LastName = GetStringValue(webProfile, "LastName");
currentUser.Roles = Roles.GetRolesForUser(profile.UserName);
currentUser.Username = profile.UserName;
allUsers.Add(currentUser);
}
return allUsers;
}
private String GetStringValue(ProfileBase profile, String valueName)
{
if (profile == null)
return String.Empty;
var propValue = profile.PropertyValues[valueName];
if (propValue == null)
return String.Empty;
return propValue.PropertyValue as String;
}
Is there a better (more straightforward, more performant) way to
retrieve all the custom profile information from profile provider and
merge them with membership provider info to show them e.g. in an administrator page?
I have had a look at Web Profile Builder but IMO this only provides design-time intellisense for custom profile properties by generating a proxy class.
You don't persist to the database until you call Save:
The Save method writes modified
profile property values to the data
source. The profile provider can
reduce the amount of activity at the
data source by performing updates only
when the IsDirty property is set to
true. This is the case for the default
SqlProfileProvider.
Is there any good way of combining ASP.NET Windows Authentication with a custom IPrincipal/IIdentity object? I need to store the user's email address and have done so for Forms Authentication using a custom IIdentity/IPrincipal pair that I added to the Context.CurrentUser during the AuthenticateRequest event.
How would I best go by to accomplish this using WindowsAuthentication?
Maybe you could create your "ExtendedWindowsPrincipal" as a derived class based on WindowsPrincipal, and just add your extra data to the derived class?
That way, your ExtendedWindowsPrincipal would still be recognized anywhere where a WindowsPricinpal is needed.
OR: since you're talking about using Windows Authentication, you're probably in a Windows network - is there an Active Directory or a user database somewhere, where you could look up your e-mail address that you're interested in instead of storing it in the principal?
Marc
I ended up refactoring my initial solution into replacing the Principal instead of the Identity as I originally thought. Replacing the Identity proved troublesome, since i ran into security problems when creating an instance of a new extended WindowsPrincipal.
public class ExtendedWindowsPrincipal : WindowsPrincipal
{
private readonly string _email;
public ExtendedWindowsPrincipal(WindowsIdentity ntIdentity,
string email) : base(ntIdentity)
{
_email = email;
}
public string Email
{
get { return _email; }
}
}
In my Authentication module i replaced the principal on the HttpContext like this:
var currentUser = (WindowsIdentity)HttpContext.Current.User.Identity;
HttpContext.Current.User =
new ExtendedWindowsPrincipal(currentUser, userEmail);
how can i use asp.net dynamic data using EF in another dll and i dont want to put connection string in web.config or any config file.
I have this code in Global.asax
model.RegisterContext(() => new MyObjectContext("entityconnectionString"), new ContextConfiguration() { ScaffoldAllTables = true });
the defalut page is ok but when i click on any table to see the details, I get this error:
The specified named connection is either not found in the configuration, not intended to be used with the EntityClient provider, or not valid.
How can i solve this problem?
I was able to resolve this by forcing the load of the MetadataWorkspace and use an overload of the RegisterContext().
var context = new MyEntities(); // DataContext
context.MetadataWorkspace.LoadFromAssembly(typeof(MyEntity).Assembly); // An EF Entity
var config = new ContextConfiguration() {ScaffoldAllTables = true};
DefaultModel.RegisterContext(() => context, config);
I'm having the same problem. I have my EDMX data model file in one project called NW.DataModel. I added the code generation item for POCO objects, which I then moved off to a separate project called NW.Entities, so that they could be persistence ignorant. I have to tweak a few property settings for namespace generation in the Context object so that the Context would recognize the entities when building the solution. This was all fine, and I can use these projects in Console apps and a WCFdata service. Now I want to add a dynamic data site for some basic admin, and that's when the separate assemblies no longer play together. I'm just testing the project settings with the Northwind database.
I get this error: Could not find the CLR type for 'NWEntities.Shipper'.
This blog post seems to have some ideas, and links to forums where the issue has some recent activity, but no word from Microsoft yet.
http://thedatafarm.com/blog/data-access/wcf-data-services-and-ef-pocos-that-are-in-their-own-assembly/
Jeff's answer just about works for me, but I kept getting ObjectDisposed Exceptions every now and then.
I've changed the context factory to create a new context, which seems to work:
var config = new ContextConfiguration() { ScaffoldAllTables = true };
DefaultModel.RegisterContext(() =>
{
var context = new MyEntities(); // ObjectContext
context.MetadataWorkspace.LoadFromAssembly(typeof(AnyPOCOInOtherAssembly).Assembly);
return context;
},
config);