How to do session management in aspnet identity? - c#

I am using Asp.net identity for Login,Register,Forgot Password etc and source code is taken from this below link:
http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-web-app-with-email-confirmation-and-password-reset
http://www.asp.net/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity.
Now i have 1 table that is UserMaster and during registration i am asking for this following fields:
FullName,EmailId,Password,ContactNumber,Gender.
My UserMaster Contains this following fields:Id,FullName,EmailId,ContactNumber,Gender
Now when user will submit registration form this FullName,EmailId,ContactNumber,Gender will be saved in UserMaster along with the Email,Password will be saved in AspnetUser.
My Register Method is same as provided in above 2 links.
Here you might notice that there is no relationship between my UserMaster and AspnetUser so during login when user will enter his email id to login i will use this method await SignInManager.PasswordSignInAsync to verify user and if this method returns success then what i will do is use this email id and check this email in my UserMaster and where match will be found i will fetch that UserId from UserMaster and store it in session and use thorugh out my application in my login method like below:
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
using (var context = new MyEntities())
{
var fetchUSerId = context.UserMaster.Where(t => t.Email == model.Email).Select(t=>t.UserId).SingleOrDefault();
Session["UserId"] = fetchUSerId;
}
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
I am talking about this in my login method:
case SignInStatus.Success:
using (var context = new MyEntities())
{
var fetchUSerId = context.UserMaster.Where(t => t.Email == model.Email).Select(t=>t.UserId).SingleOrDefault();
Session["UserId"] = fetchUSerId;
}
Is this an appropriate way or still a better way and i want to store entire user object instead of just storing User Id.
So can anybody tell me how to do this with aspnet identity??

Since you are using Asp.Net Identity, you want to store session related stuff as claims. This is very easy to extend with customised claims.
As an aside, I think you'd be better off simple extending ApplicationUser to hold the additional data, as detailed here.
That said, here is a complete example of how to add custom claim types to your application.
Step 1 - Define one or more custom claim types to hold your additional information
public static class CustomClaimTypes
{
public const string MasterFullName = "http://schemas.xmlsoap.org/ws/2014/03/mystuff/claims/masterfullname";
public const string MasterUserId = "http://schemas.xmlsoap.org/ws/2014/03/mystuff/claims/masteruserid";
}
A claim type is just a unique string that identifies the specific claim. Here we are just using a similar format as the built in claim types.
Step 2 - During the sign in process, set values for the custom claim types
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
//Fetch data from the UserMaster table
var userdata = GetdatafromUserMaster();
//Using the UserMaster data, set our custom claim types
identity.AddClaim(new Claim(CustomClaimTypes.MasterUserId, userdata.UserId));
identity.AddClaim(new Claim(CustomClaimTypes.MasterFullName, userdata.FullName));
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
Note: we are using custom claim types so that we preserve the existing NameIdentifier and Name claims, and can therefore easily access identity information from both Asp.Net Identity and our custom UserMaster table.
Step 3 - Add extension method(s) to IIdentity so we can easily access our custom claim data
public static class IdentityExtensions
{
public static string GetMasterUserId(this IIdentity identity)
{
if (identity == null)
return null;
return (identity as ClaimsIdentity).FirstOrNull(CustomClaimTypes.MasterUserId);
}
public static string GetMasterFullName(this IIdentity identity)
{
if (identity == null)
return null;
return (identity as ClaimsIdentity).FirstOrNull(CustomClaimTypes.MasterFullName);
}
internal static string FirstOrNull(this ClaimsIdentity identity, string claimType)
{
var val = identity.FindFirst(claimType);
return val == null ? null : val.Value;
}
}
Nothing fancy here. We just cast the IIdentity as a ClaimsIdentity and then return the value of either the first claim of the given CustomClaimType that we find, or we return null if a claim doesn't exist.
Step 4 - Now we can access our custom claim data in views and/or controllers really easily. Say you wanted to use the full name from your UserMaster table instead of the ApplicationUser? You can now do this:
<ul class="nav navbar-nav navbar-right">
<li>
#Html.ActionLink("Hello " + User.Identity.GetMasterFullName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
</li>
<li>Log off</li>
</ul>
You can also do the same thing from within a Controller.

You can add as:
var listClaims=new[] { new Claims(ClaimsType.SerialNumber,Id), new Claims(ClaimsType.Name,FullName), new Claims(ClaimsType.HomePhone,ContactNumber), new Claims(ClaimsType.Gender,Gender)};
var oAuthIdentity=new ClaimsIdentity(listClaims, otherparameter ...);
For more details you can check System.Secutity.Claims.ClaimTypes

you may do this:
var fetchUser = context.UserMaster.Where(t => t.Email == model.Email).SingleOrDefault();
if (null == fetchUser)
throw new Exception("Not found");
Session["User"] = fetchUser;

Related

Identity Server 4 Register Users from External Providers

I'm trying to store new users data from the claims return from an external login.
lets just say I have a model
public class User
{
Guid UniqueIdentifier;
string Username;
string Firstname;
string LastName;
string Email;
Date DateOfBirth;
}
and a method to add a user to the Database:
_userService.Add(new User());
This is the standard IdentityServer implementation for their eternal login call back.
[HttpGet]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl)
{
// read external identity from the temporary cookie
var info = await HttpContext.Authentication.GetAuthenticateInfoAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
var tempUser = info?.Principal;
if (tempUser == null)
{
throw new Exception("External authentication error");
}
// retrieve claims of the external user
var claims = tempUser.Claims.ToList();
// try to determine the unique id of the external user - 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 = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject);
if (userIdClaim == null)
{
userIdClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
}
if (userIdClaim == null)
{
throw new Exception("Unknown userid");
}
// remove the user id claim from the claims collection and move to the userId property
// also set the name of the external authentication provider
claims.Remove(userIdClaim);
var provider = info.Properties.Items["scheme"];
var userId = userIdClaim.Value;
// check if the external user is already provisioned
var user = await _userManager.FindByLoginAsync(provider, userId);
if (user == null)
{
user = new IdentityUser { UserName = Guid.NewGuid().ToString()
};
await _userManager.CreateAsync(user);
await _userManager.AddLoginAsync(user, new UserLoginInfo(provider, userId, provider));
}
var additionalClaims = new List<Claim>();
// if the external system sent a session id claim, copy it over
var sid = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
if (sid != null)
{
additionalClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
}
// issue authentication cookie for user
await HttpContext.Authentication.SignInAsync(user.Id, user.UserName, provider, additionalClaims.ToArray());
// delete temporary cookie used during external authentication
await HttpContext.Authentication.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// validate return URL and redirect back to authorization endpoint
if (_interaction.IsValidReturnUrl(returnUrl))
{
return Redirect(returnUrl);
}
return Redirect("~/");
}
My question is how can I retrieve a unique Identifier for each user that logs in? Say a user logs in via google, I Cannot use their email address as a unique id because they potentially could already have registered using that email from another provider?
You might need to construct some form of identifier that comes from the token issued from the external provider. In OpenID Connect the subject claim is assumed to be locally unique to the provider. One way to construct a globally unique identifier is to make some sort of composite key (or constructed key) that uses both the issuer (iss) claim and the subject (sub) claim of the token, this identifier is generally guaranteed to be globally unique.

How to insert a dependent record when using UserManager Identity to create users?

I'm using the Identity with UserManager Pattern.
When I need to create a new user, my Register action of Account controller has this code
var user = new WebUser() { UserName = model.Email, Email = model.Email, CreatedOn = DateTime.Now, FullName = model.Email, EmailConfirmed = true };
IdentityResult result = await RegisterService.CreateAsync(user, model.Password);
if (!result.Succeeded && ((string[])result.Errors).Length > 0)
{
foreach (var erro in result.Errors)
{
ModelState.AddModelError("", erro);
}
return BadRequest(ModelState);
}
else if (!result.Succeeded)
return InternalServerError();
For now it works, but my rule needs something specific. Every user in my database need to have a record in other table. How can I implement it?
I feel like wrong putting into the controller the rule like "if succeeded create a record into another table" because I'll need a transaction scope in the controller. It's sounds dirty to me.
Can someone help me?

Using UserManager.FindAsync with a custom UserStore

I have implemented a custom UserStore, it implements IUserStore<DatabaseLogin, int> and IUserPasswordStore<DatabaseLogin, int>.
My Login action method is as below:
if (ModelState.IsValid)
{
if (Authentication.Login(user.Username, user.Password))
{
DatabaseLogin x = await UserManager.FindAsync(user.Username, user.Password);
DatabaseLogin Login = Authentication.FindByName(user.Username);
if (Login != null)
{
ClaimsIdentity ident = await UserManager.CreateIdentityAsync(Login,
DefaultAuthenticationTypes.ApplicationCookie);
AuthManager.SignOut();
AuthManager.SignIn(new AuthenticationProperties
{
IsPersistent = false
}, ident);
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "Invalid Login");
}
}
return View();
In the custom authentication class that I wrote, Authentication, I have a Login method that works fine, also FindByName method returns an app user. But if I try to SignIn with that login, the user isn't recognized as authenticated and HttpContext.User.Identity is always null, so I imagine that I have to try UserManager.FindAsync.
This method calls FindByNameAsync and GetPasswordHashAsync, and it always return null.
public Task<DatabaseLogin> FindByNameAsync(string userName)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentNullException("userName");
return Task.FromResult<DatabaseLogin>(Authentication.FindByName(userName));
}
public Task<string> GetPasswordHashAsync(DatabaseLogin user)
{
if (user == null)
throw new ArgumentNullException("user");
return Task.FromResult<string>(user.Password);
}
And the Authentication.FindByName
public static DatabaseLogin FindByName(string name)
{
string GetUserQuery = string.Format(
"USE db;SELECT principal_id AS id, name as userName, create_date AS CreateDate, modify_date AS modifyDate FROM sys.database_principals WHERE type='S' AND authentication_type = 1 AND name = '{0}'"
, name);
DatabaseLogin user;
using (var db = new EFDbContext())
{
user = db.Database.SqlQuery<DatabaseLogin>(GetUserQuery).FirstOrDefault();
}
user.Password = Convert.ToBase64String(Encoding.ASCII.GetBytes("pass"));
return user;
}
As you can see I'm using database users, I'm not sure how I can retrieve a hashed password for them. For now, I'm just storing the Base65 of the correct password!
I have no idea where I'm going wrong, any guidance is welcome.
Short answer: nothing's wrong. User is authenticated in other action methods, but apparently not in the current action method.
This is the process that I followed, maybe it will help you debug your app.
After reading the source code, FindAsync first calls FindByNameAsync, followed by CheckPasswordAsync which references VerifyPasswordAsync. So it should be fine If I could override VerifyPasswordAsync.
I created a custom password hasher that implements IPasswordHasher, and registered it in the create method of my UserManager like this:
manager.PasswordHasher = new DbPasswordHasher();
So by now, I can get my user from UserManager.FindAsync, but it turned out that it doesn't matter where you get the user since HttpContext.User.Identity is still null! My mistake was that I didn't notice the user isn't authenticated in the current action, in other action methods it works as expected!

How can I get ExternalIdentity after all the login process?

I am using MVC 5 and I can successfully login using Google.
I want to have access to the user external identity claims after the login process. I want in a view to access, for example, the claim "picture" from the user. However if I try to run this code it always return null. (except in the login process - auto generated code for mvc template)
Is there a way for me to have access to the external identity claims? (after the login process)
I found how the identity is created. Basically the ExternalSignInAsync makes an internal call to SignInAsync which makes a call to CreateUserIdentityAsync.
I found a class ApplicationSignInManager in the IdentityConfig file and then I changed the CreateUserIdentityAsync method to:
public override async Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var localIdentity = await user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
foreach (var item in externalIdentity.Claims)
{
if (!localIdentity.HasClaim(o => o.Type == item.Type))
localIdentity.AddClaim(item);
}
return localIdentity;
}
So every time I sign in I am going to have my claims + external claims in the loggedin user. From a view I can call:
#HttpContext.Current.GetOwinContext()
.Authentication.User.FindFirst("urn:google:picture").Value
You need to store the auth token and then use that to query the login provider's API for the information you need. Storing it is easy enough:
Startup.Auth.cs
const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
...
var googlePlusOptions = new GoogleOAuth2AuthenticationOptions
{
ClientId = "yourclientid",
ClientSecret = "yourclientsecret",
Provider = new GoogleOAuth2AuthenticationProvider
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:googleplus:access_token", context.AccessToken, XmlSchemaString, "Google"));
return Task.FromResult(0);
}
}
};
app.UseGoogleAuthentication(googlePlusOptions);
Then, after you create the new user in ExternalLoginCallback or ExternalLoginConfirm:
await SaveAccessToken(user, identity);
With the following definition for SaveAccessToken (just put it with the other helper methods in the controller):
private async Task SaveAccessToken(User user, ClaimsIdentity identity)
{
var userclaims = await UserManager.GetClaimsAsync(user.Id);
foreach (var at in (
from claims in identity.Claims
where claims.Type.EndsWith("access_token")
select new Claim(claims.Type, claims.Value, claims.ValueType, claims.Issuer)))
{
if (!userclaims.Contains(at))
{
await UserManager.AddClaimAsync(user.Id, at);
}
}
}
Now, you'll have the access token to use later whenever you need it. So, for Google, to get the profile photo, you'd just send a request to https://www.googleapis.com/oauth2/v3/userinfo?access_token=[token], where [token] is the value of the claim you saved.

Correct way of setting the role for user when he is registered with Identity

I have a question, I'm new to identity, but still i would like to know what would be the correct way of assigning role to a user when he is registering?
I have here a code:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName };
RoleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
IdentityRole role = new IdentityRole("Admin");
await RoleManager.CreateAsync(role);
// Store Gender as Claim
user.Claims.Add(new IdentityUserClaim() { ClaimType = ClaimTypes.Gender, ClaimValue = "Male" });
//user.Roles.Add(new IdentityUserRole() { RoleId=role.Id, UserId=user.Id });
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
//await UserManager.AddToRoleAsync(user.Id, "Admin");
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
This is just a test code, but basically if i use method UserManager.AddToROleAsync( ...) it works, BUT, it only happens after the user is added, so basically i do twice the roundtrip to database.
I tried doing it with user.Roles.Add(...) but i get an error when running it.
So my question would be what is the most efficient and correct way of doing it?
I don't know if there's a better way. I normally to it the same way as you do, first creating the role (if it doesn't exist), then creating the user, and as a last step adding the user to the role.
To use user.Roles.Add(...) the role must be present. The reason is the database (in this case Entity Framework and SQL Server). When looking closer at the Identity database you'll see that there is a relationship between the AspNetRoles and AspNetUsers table through the AspNetUserRoles table which has the UserId and the RoleId as a key. That means you can't add a user to a role when the user does not exist yet (and vice versa). So in my opinion you have to do twice the roundtrip (if you don't directly work on the context).
This works fine (Asp.Net Core Identity):
var role = await this.roleManager.FindByNameAsync( "Admin" );
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var userRole = new IdentityUserRole<int> {
RoleId = role.Id,
};
user.Roles.Add(userRole );
var result = await this.userManager.CreateAsync( user, model.Password);

Categories

Resources