Modifying login MVC 5 Asp Identity - asp.net-mvc

I am using MVC 5 and Asp Identity for a new app I am building. I need to modify the login process so only information pertaining to the users community is shown. I was thinking of creating another table with Community ID's and User ID's in it similar to the AspUserRoles table. How do I tie this into the login process? Sorry I am new to MVC coming from Web Forms :(
Thanks!

Seems like a valid approach. So you'll end up with something like:
public class Community
{
...
public virtual ICollection<ApplicationUser> Members { get; set; }
}
public class ApplicationUser : IdentityUser
{
...
public virtual ICollection<Community> Communities { get; set; }
}
Entity Framework will automatically generate a join table from that. Then, in your login, somehow, you feed the community id. That could be from a special URL, a hidden input, select box, whatever. It's up to you. Then, you just need to modify the sign in process slighly. By default in a generated project template, sign in is handled via the following line:
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
You can see the code for PasswordSignInAsync here. But I'll post it here for posterity as well:
public virtual async Task<SignInResult> PasswordSignInAsync(string userName, string password,
bool isPersistent, bool shouldLockout)
{
var user = await UserManager.FindByNameAsync(userName);
if (user == null)
{
return SignInResult.Failed;
}
return await PasswordSignInAsync(user, password, isPersistent, shouldLockout);
}
So, just add your own version of this to ApplicationSignInManager in IdentiyConfig.cs:
public virtual async Task<SignInResult> PasswordSignInAsync(int communityId, string userName, string password,
bool isPersistent, bool shouldLockout)
{
var user = await UserManager.FindByNameAsync(userName);
if (user == null || !user.Communities.Any(m => m.Id == communityId))
{
return SignInResult.Failed;
}
return await PasswordSignInAsync(user, password, isPersistent, shouldLockout);
}
Notice the extra condition. Then, you just need to modify the original call in the Login action to pass in the communityId:
var result = await SignInManager.PasswordSignInAsync(communityId, model.Email, model.Password, model.RememberMe, shouldLockout: false);

Related

Can I use Asp.Net Application User in SignalR messages?

Hello, I'm starting with private messages between ASP.net users, using SignalR, everything Works fine if I'm using ConnectionId - s, In this way I can take recipient's ConnectionId and send private message to this person. Now about problem, I want store messages in database and load them on login, I'm using Standard membership of ASP.net mvc5 application, so after reconnect ConnectionId is changing. I was reading article Mapping SignalR Users to Connections, I but cannot understand how to use IUserProvider Can you explain me how to make this taks. lot of thanks.
Here is my hub code:
[HubName("chatHub")]
public class ChatHub : Hub
{
static List<ApplicationUser> Users = new List<ApplicationUser>();
private ApplicationDbContext db = new ApplicationDbContext();
public void Send(string name, string message)
{
Clients.All.addMessage(name, message);
}
public void SendPM(string name, string privatemessage, string userid)
{
//This line not Works I've commented it but filling It's correct way
//Clients.User(userid).addPM(name, privatemessage);
Clients.Client(userid).addPM(name, privatemessage);
}
public void Connect(string userName)
{
var id = Context.ConnectionId;
var appuser = db.Users.FirstOrDefault(x => x.UserName == userName);
var dbUsers = db.Users.ToList();
if (!Users.Any(x => x.ConnectionId == id))
{
Users.Add(new ApplicationUser { ConnectionId = id, UserName = userName, Id = appuser.Id });
Clients.Caller.onConnected(id, userName, Users);
Clients.AllExcept(id).onNewUserConnected(id, userName);
}
}
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
{
var item = Users.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
if (item != null)
{
Users.Remove(item);
var id = Context.ConnectionId;
Clients.All.onUserDisconnected(id, item.UserName);
}
return base.OnDisconnected(stopCalled);
}
}
The article says:
By default, there will be an implementation that uses the user's IPrincipal.Identity.Name as the user name.
This means that for the mapping to work, you need to make user that users are logged in (authorized) when calling SignalR hub. The easiest way to ensure this is to add [Authorize] attribute to ChatHub and the controller that returns chat view.
If user is authorized, you can retrieve his username from current context by calling:
var username = Context.User.Identity.Name;
Then, the following should work (without the need to provide your own IUserIdProvider):
public void SendPM(string name, string privatemessage, username)
{
Clients.Client(username).addPM(name, privatemessage);
}

Why after adding a claim in login action, it can’t be accessed in other controllers?

I'm working on a system that uses ASP.NET MVC 5 and Identity 2 with Entity Framework 6.
When a user logs in, I add some claims to that login session. I don’t want to use the claims table.
For one of my claims, I did like this:
public class User : IdentityUser<int, UserLogin, UserRole, UserClaim>
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User, int> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
//We add the display name so that the _LoginPartial can pick it up;
userIdentity.AddClaim(new Claim("DisplayName", FirstName + " " + LastName));
// Add custom user claims here
return userIdentity;
}
public virtual ICollection<UserInsurance> UserInsurances { get; set; }
public User()
{
UserInsurances = new List<UserInsurance>();
}
}
And for accessing the claim:
var claimsIdentity = User.Identity as System.Security.Claims.ClaimsIdentity;
var displayNameClaim = claimsIdentity != null
? claimsIdentity.Claims.SingleOrDefault(x => x.Type == "DisplayName")
: null;
var nameToDisplay = displayNameClaim == null ? User.Identity.Name : displayNameClaim.Value;
This works well. But the problem is when I need a field that is not in the User table. In fact, it is one record in the user’s navigation property (UserInsurances) and I need a linq query for access that.
var lastUserInsurance = UserInsurances.OrderByDescending(x => x.CompanyInsuranceId).First();
userIdentity.AddClaim(new Claim("CompanyInsuranceId", lastUserInsurance.CompanyInsuranceId.ToString()));
If I put this code in GenerateUserIdentityAsync method like “DisplayName”, UserInsurances is null. So I should add this code to login action and after the user logs in successfully. But I tried that and it doesn’t work. I don’t know why but when I want to access that claim, it does not exist.
public virtual async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
var user = _user.Include(x => x.UserInsurances).FirstOrDefault(x => x.NationalCode == model.UserName);
var identity = await SignInManager.CreateUserIdentityAsync(user);
var lastUserInsurance = user.UserInsurances.OrderByDescending(x => x.CompanyInsuranceId).FirstOrDefault();
identity.AddClaim(new Claim("CompanyInsuranceId", lastUserInsurance.CompanyInsuranceId.ToString()));
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:
return View(model);
}
}
Can anyone tell me why I can’t access this claim and it does not exist? I don’t know how I implement this scenario and access “CompanyInsuranceId” claim in all parts of my application.
You must add your claims before sign the user in. So if for any reason you can not fill your claims in GenerateUserIdentityAsync method. Simply generate Identity object in the log in action method then sign in it. Consider this example:
public async Task<ActionResult> Login(LoginViewModel model,string returnUrl)
{
var user = UserManager.Find(model.Email, model.Password);
// now you have the user object do what you to gather claims
if(user!=null)
{
var ident = UserManager.CreateIdentity(user,
DefaultAuthenticationTypes.ApplicationCookie);
ident.AddClaims(new[] {
new Claim("MyClaimName","MyClaimValue"),
new Claim("YetAnotherClaim","YetAnotherValue"),
});
AuthenticationManager.SignIn(
new AuthenticationProperties() { IsPersistent = true },
ident);
return RedirectToLocal(returnUrl);
}
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
As you can see you could do anything you want to gather claims and fill the identity then sign in the user.
But if you want use SignInManager.PasswordSignInAsync() method simply override SignInManager.CreateUserIdentityAsync() method in a way so you could generate desired claims. For example if you need DbContext to fetch extra information for feeding your claims simply you could inject DbContext in SignInManager and use it in CreateUserIdentityAsync() method like this:
public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
private readonly ApplicationDbContext _context;
public ApplicationSignInManager(
ApplicationUserManager userManager,
IAuthenticationManager authenticationManager,
ApplicationDbContext context)
: base(userManager, authenticationManager)
{
_context=context;
}
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
var companyInsuranceId=_context.Users
.Where(u=>u.NationalCode == user.UserName)
.Select(u=>u.UserInsurances
.OrderByDescending(x => x.CompanyInsuranceId)
.Select(x=>x.CompanyInsuranceId)
.FirstOrDefault())
.FirstOrDefault();
var ident=user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
ident.AddClaim(new Claim("CompanyInsuranceId",
companyInsuranceId.ToString()));
return ident;
}
public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
{
return new ApplicationSignInManager(
context.GetUserManager<ApplicationUserManager>(),
context.Authentication,
context.Get<ApplicationDbContext>());
}
}
Now simply just by writing
var result = await SignInManager.PasswordSignInAsync(
model.UserName, model.Password,
model.RememberMe, shouldLockout: false);
you could sign the user in and inject additional claims.
For MVC5, additional claims can be added easily via the ApplicationUser class.
eg.
public ClaimsIdentity GenerateUserIdentity(ApplicationUserManager manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = manager.CreateIdentity(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
This also resolve
Identity cookie loses custom claim information after a period of time

Creating varying access levels using authentication

I have created an enum with security access levels, an example:
public enum AccessLevel
{
Total,
DeletionPrivileges,
MaintainUsers,
MaintainInventory,
QueriesOnly,
None
}
I can manage the site so certain features eg delete, are not presented to someone without deletion privileges. But I am also wanting to use some kind of authorisation within the code.
Within the default framework, there is the facility to prevent access to certain areas of a project using [Authorize], how can I create differing levels of authority to tag each method?
You could use claim based authentication feature of Identity to aim this purpose easily. First you need add proper claim per user in log in action method to do this change your log in action method like this:
[HttpPost]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var userManager=HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
var user = userManager.Find(model.UserName, model.Password);
if (user != null)
{
var ident = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
// imaging you have a custom class which return user access levels
var userAccessLevels=_accessLevelManager.GetAccessLevels(user.Id);
// now we are going to add our custom claims
ident.AddClaims(new[]
{
// add each access level as a separate claim
new Claim("AccessLevel",userAccessLevels[0].ToString()),
new Claim("AccessLevel",userAccessLevels[1].ToString()),
// and so on
});
HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = false }, ident);
// authentication succeed do what you want
return Redirect(login.ReturnUrl ?? Url.Action("Index", "Home"));
}
}
ModelState.AddModelError("", "Invalid username or password");
return View(login);
}
Now we have successfully injected our claims to Identity. But you need a custom authorize attribute to check your claims like this:
public class ClaimsAccessAttribute : AuthorizeAttribute
{
public string ClaimType { get; set; }
public string Value { get; set; }
protected override bool AuthorizeCore(HttpContextBase context)
{
return context.User.Identity.IsAuthenticated
&& context.User.Identity is ClaimsIdentity
&& ((ClaimsIdentity)context.User.Identity).HasClaim(x =>
x.Type == ClaimType && x.Value == Value);
}
}
Now you could easily use your attribute in your action methods:
[ClaimsAccess(CliamType="AccessLevel",Value="DeletionPrivileges")]
public ActionResult MyAction()
{
// also you have access the authenticated user's claims
// simply by casting User.Identity to ClaimsIdentity
// ((ClaimsIdentity)User.Identity).Claims
}

how to use async await functionality with MVC / Membershipprovider

I would like to authenticate user using Parse Library.
Here are the methods which I would like to make it asyc as api call supports only async call. I am new to MVC and aysc/await functionality.
Problem now is that it goes in await method and never returns result and view cant be loaded.
I spend quite some time understanding and trying to use different options but no success yet.
Do I need to use Partialviews or something can be done in ValidateUser method.
Any sample code is really appreciated.
Thanks.
AccountController.cs
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
var loggedIn = true;
}
return View(model);
}
ParseMembershipProvider : ExtendedMembershipProvider
public override bool ValidateUser(string username, string password)
{
var pUserRepo = new PUserRepository();
bool flag = false;
var requiredUser = pUserRepo.GetUserObjByUserName(username, password );
if (requiredUser.Result != null)
flag = true;
return flag;
}
PUserRepository.cs
public async Task<ParseUser> GetUserObjByUserName(string userName, string passWord)
{
return await ParseUser.LogInAsync("test1", "test123");
}
You're seeing a deadlock situation due to Result that I explain on my blog.
Unfortunately, membership providers are not (yet) async-aware. So try using ConfigureAwait(false) everywhere in your async methods:
public async Task<ParseUser> GetUserObjByUserName(string userName, string passWord)
{
return await ParseUser.LogInAsync("test1", "test123").ConfigureAwait(false);
}
(same for any other async methods you have).
Solution is
return Task.Run(() => ParseUser.LogInAsync(userName, passWord)).Result;
So basically I have sync wrapper around async method call. I understood from article that it depends also how that function in Parse library is using await or configureawait(false).

How to tap into the automatic repeated login?

I am making an ASP.Net MVC3 application. I use for now the built in Authentication code that comes with a Visual Studio 2010 project. The problem is dat I need to retrieve the logged in user's database ID as soon as he has logged in. I do that now by adding code to the Login Action of the Account controller that retrieves the ID from the database by looking it up by username. This works for new logins, but not for "remembered" ones. On restarting the application the last user is automatically logged in again, but the Login code is not fired, so I do not get the database ID.
How can I solve this?
EDIT:
I tried to implement Daniel's solutions which looks promising and I came up with this code. It nevers gets called though! Where have I gone wrong?
Global.asax.cs:
protected void Application_Start()
{
Database.SetInitializer<StandInContext>(new StandInInitializer());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
this.AuthenticateRequest +=
new EventHandler(MvcApplication_AuthenticateRequest);
}
void MvcApplication_AuthenticateRequest(object sender, EventArgs e)
{
if(Request.IsAuthenticated)
{
using (var db = new StandInContext())
{
var authenticatedUser = db.AuthenticatedUsers.SingleOrDefault(
user => user.Username == User.Identity.Name);
if (authenticatedUser == null)
return;
var person = db.Persons.Find(authenticatedUser.PersonID);
if (person == null)
return;
Context.User = new CustomPrincipal(
User.Identity, new string[] { "user" })
{
Fullname = person.FullName,
PersonID = person.PersonID,
};
}
}
}
You can use the AuthenticateRequest event in your Global.asax.cs:
protected void Application_AuthenticateRequest()
{
if (Request.IsAuthenticated)
{
// retrieve user from repository
var user = _membershipService.GetUserByName(User.Identity.Name);
// do other stuff
}
}
Update:
Now that I see what you're trying to do a little clearer, I would recommend against using sessions in this particular case. One reason is that Session requires a reference to System.Web, which you don't have access to from some places, like a business logic layer in a separate class library. IPrincipal, on the other hand, exists for this very reason.
If you need to store more user information than what IPrincioal provides, you simply implement it and add your own properties to it. Easier yet, you can just derive from GenericPrincipal, which implements IPrincipal and adds some basic role checking functionality:
CustomPrincipal.cs
public class CustomPrincipal : GenericPrincipal
{
public CustomPrincipal(IIdentity identity, string[] roles)
: base(identity, roles) { }
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
...
}
So then you replace the default principal with your own in AuthenticateRequest, as before:
Global.asax.cs
protected void Application_AuthenticateRequest()
{
if (Request.IsAuthenticated)
Context.User = _securityService.GetCustomPrincipal(User.Identity.Name);
}
And that is it. The greatest advantage you get is that you automatically get access to your user data from literally everywhere, without having to stick a userId parameter into all your methods. All you need to do is cast the current principal back to CustomPrincipal, and access your data like so:
From your razor views:
<p>Hello, #((CustomPrincipal)User).FirstName!</p>
From your controllers:
var firstName = ((CustomPrincipal)User).FirstName;
From a business logic layer in another assembly:
var firstName = ((CustomPrincipal)Thread.CurrentPrincipal).FirstName;
To keep things DRY, you could pack this into an extension method and hang it off IPrincipal, like so:
public static class PrincipalExtensions
{
public static string GetFirstName(this IPrincipal principal)
{
var customPrincipal = principal as CustomPrincipal;
return customPrincipal != null ? customPrincipal.FirstName : "";
}
}
And then you would just do #User.GetFirstName(), var userName = User.GetFirstName(), Thread.CurrentPrincipal.GetFirstName(), etc.
Hope this helps.
I wasn´t thinking clear. I was trying to store the userinfo in the Session object, while it available through the User object. Sorry to have wasted your time.

Resources