I'm trying to customize my own implementation of ExtendedMembershipProvider. I have no idea what the GetUserIDFromOauth method is supposed to do? I see it is throwing an exception by default, and that it is supposed to return the user ID from the open auth provider.
I fail to see how this is supposed to be done, unless this means find if that user exists in the system? Is that it's purpose? I find the lack of documentation confusing...
Thanks.
GetUserIdFromOAuth is a method used by ExtendedMembershipProvider class to find User.Id in your table of users in your web application database based on Provider and ProviderUserId that you get from OAuth or OpenId Provider. After getting Provider and ProviderUserId data for a specified user, you need to save it in your database.
It returns throw new NotImplementedException(); by default. You need to implement this method to return an integer of your User.Id from your application database.
This is a sample implementation:
public override int GetUserIdFromOAuth(string provider, string providerUserId)
{
using (var context = new YourApplicationEntities())
{
// Try to find user with certain Provider and ProviderUserId
var user = context.Users.SingleOrDefault(
q => q.Provider == provider &&
q.ProviderUserId == providerUserId
);
if (user != null)
{
return user.Id;
}
}
return -1;
}
This implementation assumed that you have Provider and ProviderUserId field in your User table. If this information saved in a different table, you just need to modify the LINQ to return the desired result.
Related
I was going through the identity server 4 doc and I came across this piece of code.
private (TestUser user, string provider, string providerUserId, IEnumerable<Claim> claims) FindUserFromExternalProvider(AuthenticateResult result)
{
var externalUser = result.Principal;
// try to determine the unique id of the external user (issued by the provider)
// the most common claim type for that are the sub claim and the NameIdentifier
// depending on the external provider, some other claim type might be used
var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ??
externalUser.FindFirst(ClaimTypes.NameIdentifier) ??
throw new Exception("Unknown userid");
// remove the user id claim so we don't include it as an extra claim if/when we provision the user
var claims = externalUser.Claims.ToList();
claims.Remove(userIdClaim);
var provider = result.Properties.Items["scheme"];
var providerUserId = userIdClaim.Value;
// find external user
var user = _users.FindByExternalProvider(provider, providerUserId);
return (user, provider, providerUserId, claims);
}
and it is called like this.
var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result);
I dont quiet understand what is happening here. what sort of function definition usage is this?
You mean the returned data? It's a value tuple that is returned, see this article for a guide to Value Tuples. Value Tuples is a way to return multiple parameters without creating a custom class.
The method tries to lookup the user in the local database after the user has externally authenticated.
Background:
I'm building more and more web applications where the designers / template makers decide that adding a "profile picture" and some other user-related data, of course only when someone is logged in.
As most ASP.NET MVC developers I use viewmodels to provide razor layouts with the information that I need shown, sourced from repositories et al.
It is easy to show a user name through using
HttpContext.Current.User.Identity.Name
What if I want to show information that's saved in my backing datastore on these pages? Custom fields in the ApplicationUser class like a business unit name or a profile picture CDN url.
(for sake of simplicity let's assume I use the Identity Framework with a Entity Framework (SQL database) containing my ApplicationUsers)
Question
How do you solve this:
Without poluting the viewmodel/controller tree (e.g. building a BaseViewModel or BaseController populating / providing this information?
Without having to roundtrip the database every page request for these details?
Without querying the database if a user is not logged in?
When you cannot use SESSION data (as my applications are often scaled on multiple Azure instances - read why this isn't possible here- I'm not interested in SQL caching or Redis caching.
I've thought about using partials that new their own viewmodel - but that would still roundtrip the SQL database every pageload. Session data would be safe for now, but when scaled up in azure this isn't a way either. Any idea what would be my best bet?
TLDR;
I want to show user profile information (ApplicationUser) on every page of my application if users are logged in (anon access = allowed). How do I show this info without querying the database every page request? How do I do this without the Session class? How do I do this without building base classes?
The best way with Identity is to use claims to store custom data about the user. Sam's answer pretty close to what I'm saying here. I'll elaborate a bit more.
On ApplicationUser class you have GenerateUserIdentityAsync method which used to create ClaimsIdentity of the user:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaims(new[]
{
new Claim("MyApp:FirstName",this.FirstName), //presuming FirstName is part of ApplicationUser class
new Claim("MyApp:LastName",this.LastName),
});
return userIdentity;
}
This adds key-value pairs on the user identity that is eventually serialised and encrypted in the authentication cookie - this is important to remember.
After user is logged in, this Identity are available to you through HttpContext.Current.User.Identity - that object is actually ClaimsIdentity with claims taken from the cookie. So whatever you have put into claims on login time are there for you, without having to dip into your database.
To get the data out of claims I usually do extension methods on IPrincipal
public static String GetFirstName(this IPrincipal principal)
{
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
{
throw new DomainException("User is not authenticated");
}
var personNameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "MyApp:FirstName");
if (personNameClaim != null)
{
return personNameClaim.Value;
}
return String.Empty;
}
This way you can access your claims data from your Razor views: User.GetFirstName()
And this operation is really fast because it does not require any object resolutions from your DI container and does not query your database.
The only snag is when the values in the storage actually updated, values in claims in the auth cookie are not refreshed until user signs-out and signs-in. But you can force that yourself via IAuehtenticationManager.Signout() and immediately sign them back in with the updated claims values.
You could store your extra information as claims. In your log in method fill your data to generated identity. For example if you are using Identity's default configuration you could add your claims in ApplicationUser.GenerateUserIdentityAsync() method:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaims(new[]
{
new Claim("MyValueName1","value1"),
new Claim("MyValueName2","value2"),
new Claim("MyValueName2","value3"),
// and so on
});
return userIdentity;
}
And in your entire application you have access those information by reading current user claims. Actually HttpContext.Current.User.Identity.Name uses same approach.
public ActionResult MyAction()
{
// you have access the authenticated user's claims
// simply by casting User.Identity to ClaimsIdentity
var claims = ((ClaimsIdentity)User.Identity).Claims;
// or
var claims2 = ((ClaimsIdentity)HttpContext.Current.User.Identity).Claims;
}
I think the "how to" is a little subjective as there are probably many possible ways to go about this but I solved this exact problem by using the same pattern as HttpContext. I created a class called ApplicationContext with a static instance property that returns an instance using DI. (You could alter the property to generate a singleton itself as well if you aren't, for some reason, using DI.)
public interface IApplicationContext
{
//Interface
string GetUsername();
}
public class ApplicationContext : IApplicationContext
{
public static IApplicationContext Current
{
get
{
return DependencyResolver.Current.GetService<IApplicationContext>();
}
}
//appropriate functions to get required data
public string GetUsername() {
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
return HttpContext.Current.User.Identity.Name;
}
return null;
}
}
Then you just reference the "Current" property in your view directly.
#ApplicationContext.Current.GetUsername()
This would solve all of you requirements except #2. The database call may not add a significant enough overhead to warrant avoiding altogether but if you require it then your only option would be to implement some form of caching of the user data once it is queried the first time.
Simply implement ChildAction with caching and vary by loggedin user
in ASP.NET web API in the log in algorithm i have a action filter that generates a token for each user and the front end sends that token back to authenticate the user by using that token in web server i can get current user information till now every thing is working fine however i have new requirements that every user has relation many to many with account which means the same user can exists in more than one account with different roles for example in account one he is an admin in account two he is normal user so i have to regenerate the token which requires the user to re log in again i do not want him to be redirected to the log in page again. what i think of is to store user name and password in html 5 local storage but i think that is a bad practices any ideas.
Her is how i generate token.
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.Request.Headers
.Any(header => header.Key == "AuthorizationHeader"))
{
if (this.IsAnonymousAllowed(actionContext) == false)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, "Un Autherized");
}
}
else
{
string token = actionContext.Request.Headers
.Where(header => header.Key == "AuthorizationHeader")
.First().Value.First();
if (this.IsAnonymousAllowed(actionContext) == true)
{
return;
}
string passPhrase = System.Configuration.ConfigurationSettings.AppSettings["PassPhrase"];
string ticket_string = Crypto.Decrypt(token, passPhrase);
TicketData ticket = JsonConvert.DeserializeObject<TicketData>(ticket_string);
if (ticket == null || ticket.Expiration < DateTime.Now)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, "UnAuthorized");
}
else
{
OurIdentity identity = (OurIdentity)ticket.TokenData.OurIdentity;
System.Threading.Thread.CurrentPrincipal = new OurPrincipal
{
OurIdentity = identity,
};
}
}
}
You are right saving username and password in the local storage is bad. It is bad to save it anywhere on the client.
Usually a token is generated and put in a cookie. That token corresponds with a record on the server, in either a session log or a database.
I strongly suggest to use existing methods for this, like OAUTH Bearer tokens in this tutorial.
As far as I understand, if you are storing a hash (perhaps with a salt for extra protection) it is not nessecescarily bad to store the credentials. These would have to be stored somewhere at the end of the day anyway.
I have created a class to handle membership user creation with custom fields.
I have done it based on this solutions:
How to assign Profile values?
Using ASP .NET Membership and Profile with MVC, how can I create a user and set it to HttpContext.Current.User?
namespace CCL
{
public static MemberProfile CurrentUser
{
get
{
if (Membership.GetUser() != null)
return ProfileBase.Create(Membership.GetUser().UserName) as MemberProfile;
else
return null;
}
}
}
And now I'm trying to use create the user and get the profile data:
if (Membership.GetUserNameByEmail(email) == null)
{
MembershipUser member = Membership.CreateUser(username, password, email);
Roles.AddUserToRole(username, "WebsiteUsers");
CCL.MemberProfile currentProfile = CCL.MemberProfile.CurrentUser;
bool exists = currentProfile != null;
Response.Write(exists.ToString());
}
but currentProfile is returning null.
So I'm unable to assign values from the form to my member custom properties which are handled by the properties set in the class :(
I don't get how I can make it working :(
Does anyone have some thoughts? Thanks
Suggestion 1:
Make sure that ProfileBase.Create returns something that can be cast to a "MemberProfile", otherwise if it can't then casting it will just return NULL.
Suggestion 2:
Make sure the context you are running in has a logged in user, so your call to Membership.GetUser() can find the current user object.
Other thoughts:
The ProfileBase.Create method assumes that the username you pass in is an authenticated user, I'm not sure on it's behavior when the user isn't authenticated..maybe it returns NULL?
I created one database and tables to store the user login values and credentials.
asp.net is providing aspnet_regsql tool to create a database for the membership related activities. But I dont want to use it. Thats why I created another database. Now I want to connect this database to my project. I changed in web.config file for the connectionstring parameter to my newly created database. But I am unable to login. It is giving following error message.
Could not find stored procedure 'dbo.aspnet_CheckSchemaVersion'
How to work with this. Is there any step by step procedures are there!! If so please provide.
Is there any thing to change rather than the connection string in the web.config file?
You need to create a membership provider to connect to your custom tables for authentication. MSDN has some documentation on the subject. You can also view a video on the subject at ASP.NET. Here are the links.
http://msdn.microsoft.com/en-us/library/f1kyba5e(v=vs.100).aspx
http://www.asp.net/web-forms/videos/how-do-i/how-do-i-create-a-custom-membership-provider
The main method for validation is going to be the ValidateUser method, you will override this method to provide authentication.
public sealed class CustomMembershipProvider : MembershipProvider
{
// implement other methods
public override bool ValidateUser(string username, string password)
{
try
{
var user = // GET USER OBJECT HERE
if (user != null)
{
string name = // set username
// Set your forms authentication ticket
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, user.ID.ToString(), DateTime.Now, DateTime.Now.AddMinutes(30), false, name, FormsAuthentication.FormsCookiePath);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
HttpContext.Current.Response.Cookies.Add(authCookie);
return true;
}
}
catch
{
}
return false;
}
// Other implementations
}
If you have roles in your application you may also want to implement a custom role provider:
http://msdn.microsoft.com/en-us/library/8fw7xh74(v=vs.100).aspx