How to get logged in users data (in a secure way) - c#

I'm using ASP.NET MVC Entity Framework.
In my application I'm currently able to login and retrieve some data, but I'm not sure if the way I do it is a secure way.
Here is how my application works:
Currently when a user logs in, I make use of the following to store the username in a cookie:
FormsAuthentication.SetAuthCookie(user.Username, true);
Then I have a RoleProvider class that contains the following method, which returns a string array with the users role:
public override string[] GetRolesForUser(string username)
{
List<string> rolesList = new List<string>();
string role = CurrentlyLoggedInUser.User.Role.Name;
rolesList.Add(role);
string[] rolesArray = rolesList.ToArray();
return rolesArray;
}
Then I have the following class, that stores data of the currently logged in user:
public class CurrentlyLoggedInUser
{
private const string UserKey = "MyWebApp.Infrastructure.UserKey";
public static User User
{
get
{
MyDBEntities db = new MyDBEntities();
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
return null;
}
var user = HttpContext.Current.Items[UserKey] as User; //we set the
if (user == null)
{
user = db.Users.FirstOrDefault(x => x.Username == HttpContext.Current.User.Identity.Name);
if (user == null)
{
return null;
}
HttpContext.Current.Items[UserKey] = user;
}
return user; //Then we return the user object
}
}
}
Then whenever I need to query the database to find any related data of the user, I retrieve the users Id as follow:
int id = CurrentlyLoggedInUser.User.UserId;
Now I can make queries using this Id.
When I'm done, I logout and call the following:
FormsAuthentication.SignOut();
Is this recommended and secure enough?

Consider requiring https to secure you login form, don't forget about Authorize attribute on your MVC controllers and/or actions (or, preferably set Authorize attribute as a global filter) and on the basic level that's enough. #Chad-Nedzlek is not absolutely correct, FormsAuthentication, you involve, takes care about your authentication cookie encryption, so it's not so easy for anyone to impersonate any other. But you have to take care about preventing bruteforce attacks when implementing your custom login: consider password lifetime limitation, required complexity etc.
And if you are thinking about future extensibility, SSO for web, mobile apps and API, or possibility of easy use of external (Facebook, Google etc) login, consider switching to OpenId Connect.

Related

How can I retrieve a user from a password reset token in ASP.NET Core?

I have an ASP.NET Core 2.1 web application and am adding forgot password functionality. I have looked at several examples, and they seem to take one of two approaches. The first approach is to include either the user id or the user's email in the password reset url along with the password reset token. The second approach is to include only the password reset token in the password reset url and then require the user to enter identifying information (such as email) when attempting to change the password (Binary Intellect example). Is there a way to look up the user given just the password reset token?
My team lead has asked me to pass just the token in the password reset url and then look up the user. My initial research makes me believe that I would have to manually keep record of the user id and token relationship, but am hoping that there's something built in. I have reviewed the ASP.NET Core UserManager documentation, but did not find any methods for retrieving a user for a given token.
Here's some of the example code embedding the user id in the password reset URL (Microsoft Password Recovery Doc):
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
There is a way to get the UserId from the password reset token, but in my opinion it's tricky and a lot of work.
What are the defaults
If you have some codes like the following,
services.AddIdentity<AppUser, AppRole>(options =>
{
...
}
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
the last line .AddDefaultTokenProviders() adds 4 default token providers, which are used to generate tokens for reset passwords, change email and change phone number options, and for two factor authentication token generation, into the pipeline:
DataProtectorTokenProvider
PhoneNumberTokenProvider
EmailTokenProvider
AuthenticatorTokenProvider
The first one, DataProtectorTokenProvider, is what we're looking for. It uses data protection to serialize/encrypt those tokens.
And within the DataProtectorTokenProvider, its protector is default to the name of "DataProtectorTokenProvider".
How tokens are generated
If you look at GenerateAsync() method inside DataProtectorTokenProvider, you can kind of tell the token consists of:
Utc timestamp of the token creation (DateTimeOffset.UtcNow)
userId
Purpose string
Security stamp, if supported
The generate method concatenates all those, transform them to a byte array, and calls the protector inside to protect/encrypt the payload. Finally the payload is converted to a base 64 string.
How to get User Id
To get the userId from a token, you need to do the reverse engineering:
Convert the token from base 64 string back to the byte array
Call the protector inside to unprotect/decrypt the byte array
Read off the Utc timestamp
Read userId
The tricky part here is how to get the same DataProtector used to generate those token!
How to get the default Data Protector
Since the default DataProtectorTokenProvider is DIed into the pipeline, the only way I can think of to get the same DataProtector is to use the default DataProtectorTokenProvider to create a protector with the same default name, "DataProtectorTokenProvider", used to generate tokens!
public class GetResetPasswordViewModelHandler : IRequestHandler<...>
{
...
private readonly IDataProtector _dataProtector;
public GetResetPasswordViewModelHandler(...,
IDataProtectionProvider dataProtectionProvider)
{
...
_dataProtector = dataProtectionProvider.CreateProtector("DataProtectorTokenProvider");
// OR
// dataProtectionProvider.CreateProtector(new DataProtectionTokenProviderOptions().Name);
}
public async Task<ResetPasswordViewModel> Handle(GetResetPasswordViewModel query, ...)
{
// The password reset token comes from query.ResetToken
var resetTokenArray = Convert.FromBase64String(query.ResetToken);
var unprotectedResetTokenArray = _dataProtector.Unprotect(resetTokenArray);
using (var ms = new MemoryStream(unprotectedResetTokenArray))
{
using (var reader = new BinaryReader(ms))
{
// Read off the creation UTC timestamp
reader.ReadInt64();
// Then you can read the userId!
var userId = reader.ReadString();
...
}
}
...
}
}
Screenshot:
My 2 cents
It seems like it's a lot of work just try to read the userId off a password reset token. I understand your team lead probably doesn't want to expose the user id on the password reset link, or (s)he thinks it's redundant since the reset token has the userId.
If you're using integer to represent the userId and don't want to expose that to public, I would change it to GUID.
If you have to use integer as your userId, I would just create a column of the type unique_identifier off the user profile (I would call it PublicToken) and use that to identifier a user for all public matters.
var callbackUrl = Url.Action("resetPassword", "account", new
{
area = "",
rt = passwordResetToken, // reset token
ut = appUser.Id // user token, use GUID user id or appUser.PublicToken
}, protocol: Request.Scheme);
I believe there is no way you can do that you can pass user email then find it look for user in your code
public async Task<IActionResult> ResetPassword([FromBody]ResetPasswordViewModel model)
{
if (string.IsNullOrEmpty(model.Token) || string.IsNullOrEmpty(model.Email))
{
return RedirectToAction("Index", "Error", new { statusCode = AppStatusCode.NotFound });
}
var isResetTokenValid = await _userManager.CheckValidResetPasswordToken(model.Token, model.Email);
if (!isResetTokenValid || string.IsNullOrEmpty(model.Email))
{
return StatusCode(AppStatusCode.ResetPassTokenExpire);
}
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null)
{
return Ok();
}
await _userManager.ResetPasswordAsync(user, model.Token, model.Password);
return Ok();
}
You can view the implementaion detail here
What I do in this case is I keep that new token in a cache or sql table with user id in it. That way you first query that table containing reset token, validate it if you need it and get user.

Using OWIN Identity v2 Claims to track custom properties in WebAPI2 app

getting my head wrapped around the new Identity framework and am trying to figure out how best to handle custom user properties. I have tried extending the IdentityUser, which works to store the information, but so far is requiring an additional db call to get the property back out. I am looking at switching to using claims to store/retrieve this information.
First, the specific prop I want to store/retrieve is not unique to an individual user (many to one). Consider grouping users together in a custom Group structure. I want to store the GroupId for use in other related entities.
I am able to store the GroupId (currently using the ClaimTypes.NameIdentifier which I don't think it the correct usage for that type, but...). But, when I go to retrieve that value, the claim type isn't found in the claims collection. It's in the db, so I know it's there. I'm missing something.
FWIW: Since it's WebAPI, I'm not using a traditional sign-in. I'm using token auth.
When I create the user, I have something like:
public async Task<IdentityResult> CreateUserAsync(string email, string password, string groupId)
{
var userId = ObjectId.GenerateNewId(DateTime.UtcNow).ToString(); // yes, it's a NoSQL store
var user = new ApplicationUser
{
Id = userId,
UserName = email
};
var claim = new IdentityUserClaim { ClaimType = ClaimTypes.NameIdentifier, ClaimValue = groupId, UserId = userId, Id = ObjectId.GenerateNewId(DateTime.UtcNow).ToString() };
user.Claims.Add(claim);
var result = await _UserManager.CreateAsync(user, password);
return result;
}
That creates what looks to be an appropriate db entry.
When I retrieve the value, I get null reference errors. Here's that code via an extension method:
public static string GetGroupId(this IIdentity identity)
{
var claimsIdentity = identity as ClaimsIdentity;
return claimsIdentity == null ? "" : claimsIdentity.FindFirst(ClaimTypes.NameIdentifier).Value;
}
The error hits when trying to get Value as the FindFirst is returning a null value.
Any hints or better/best practices here would be appreciated! Honestly, I'd prefer to just store this on the ApplicationUser : IdentityUser object, but I can't find a simple way of retrieving that of User.Identity in my api controller context without an additional call to the db.
Your gut feeling about storing extra data as a claim is correct, but implementation is a bit broken.
I recommend to have your own claim types created for your domain information. Do not reuse claim types provided from framework. Reason for that is ClaimTypes.NameIdentifier represents User.Id.
The framework itself adds standard list of claims to all users:
User.Id => represented as ClaimTypes.NameIdentifier
Username => represented as 'ClaimTypes.Name'
ProviderName => represented as ClaimTypes.ProviderName (not 100% sure about this one); Usually value is "ASP.NET Identity"
SecurityStamp value (not sure what the claim type name for it)
All the roles assigned to the user are stored as ClaimTypes.Role
So in your case you have tried to overwrite claim with value of User.Id which is quite important, I would think -)
Now, let's try to fix your coding problems. When you create a user, you add claims after you have created a user object:
public async Task<IdentityResult> CreateUserAsync(string email, string password, string groupId)
{
var user = new ApplicationUser
{
Id = userId,
UserName = email
};
var userCreateResult = await _UserManager.CreateAsync(user, password);
if(!userCreateResult.IsSuccess)
{
// user creation have failed - need to stop the transaction
return userCreateResult;
}
// better to have a class with constants representing your claim types
var groupIdClaim = new Claim("MyApplication:GroupClaim", ObjectId.GenerateNewId(DateTime.UtcNow).ToString());
// this will save the claim into the database. Next time user logs in, it will be added to Principal.Identity
var claimAddingResult = await _UserManager.AddClaimAsync(userId, groupIdClaim);
return claimAddingResult;
}
As for extension methods I usually work with IPrincipal or ClaimsPrincipal. But IIdentity is also workable. Don't forget you can access ClaimsPrincipal anywhere by calling ClaimsPrincipal.Current.
This is how I usually work with extension methods:
public static string GetGroupId(this ClaimsPrincipal principal)
{
var groupIdClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApplication:GroupClaim");
if (personIdClaim != null)
{
return groupIdClaim.Value;
}
return String.Empty;
}
So in your methods you'd retrieve assigned groupId for the currently logged in user like this:
var groupId = ClaimsPrincipal.Current.GetGroupId();
Hope this clarifies your confusion!

How to use two external OAuth 2 OWIN providers (for separate login and fetching of LinkedIn profile data)

We wish to allow login to an MVC Razor website, with the usual assortment of OAuth providers:
e.g.
Google/Google+
Microsoft Account
Facebook
LinkedIn
These all work perfectly fine by themselves, but we need to have the additional option to import the user's profile data, specifically from LinkedIn regardless of how they login.
As a test of fetching a LinkedIn profile, we successfully stored the access token from LinkedIn in the ApplicationUser object like this:
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Manage");
}
if (ModelState.IsValid)
{
// Get the information about the user from the external login provider
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info == null)
{
return View("ExternalLoginFailure");
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
//check claims here
if (info.Login.LoginProvider.ToLower()=="linkedin")
{
var claim = info.ExternalIdentity.Claims.First(p => p.Type == "urn:linkedin:accesstoken");
if (claim != null)
{
user.OAuthAccessToken = claim.Value;
}
}
IdentityResult result = await UserManager.CreateAsync(user);
And fetch that token back later with:
var usertoken = UserManager.FindById(User.Identity.GetUserId()).OAuthAccessToken;
But of course this only works if they signed in with LinkedIn.
What we really want is to login via OAuth, with say Google, and then optionally have the user login to their the LinkedIn account to get the access token and fetch the profile data.
Does OWIN support making an additional OAuth login that does not change the current login? There is so much framework code provided by the latest Razor templates, I am not sure of the best way to do this.

ASP.NET MVC Forms Authentication multiple simultaneous logins

My scenario is probably the opposite of most, I want to ALLOW multiple simultaneous logins but only for different types of users.
User — Has their own area
Admin — Has their own area
The problem occurs as an Admin can be a User as well (they have two accounts, this is mainly so they can check how the system is working from a user PoV) and want to be logged into both at the same time.
With Forms authentication, this doesn't seem possible. So I've had to "hack" around it slightly and am worried I might have overlooked something.
Plan:
Two action filters for each type of user: UserAuthorise &
AdminAuthorise
Have two session cookies for each type of user
Decorate controllers which the correct action filter based on what
user can access it.
Code might need some tidying up.
I'll make the cookie names more unique as well.
Excluded stuff like Views/Routes as they don't seem relevant.
Left password salting/hashing out of samples and stuck with test values.
UserAuthorise:
public class UserAuthorize : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var authCookie = filterContext.RequestContext.HttpContext.Request.Cookies["User"];
if (authCookie == null || authCookie.Value == "")
{
filterContext.HttpContext.Response.Redirect("/login");
base.OnActionExecuting(filterContext);
return;
}
FormsAuthenticationTicket authTicket;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
filterContext.HttpContext.Response.Redirect("/login");
base.OnActionExecuting(filterContext);
return;
}
if (authTicket.Expired || authTicket.Expiration <= DateTime.Now)
{
filterContext.HttpContext.Response.Redirect("/login");
}
base.OnActionExecuting(filterContext);
}
}
AdminAuthorise:
public class AdminAuthorise : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var authCookie = filterContext.RequestContext.HttpContext.Request.Cookies["Admin"];
if (authCookie == null || authCookie.Value == "")
{
filterContext.HttpContext.Response.Redirect("/admin/login");
base.OnActionExecuting(filterContext);
return;
}
FormsAuthenticationTicket authTicket;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
filterContext.HttpContext.Response.Redirect("/admin/login");
base.OnActionExecuting(filterContext);
return;
}
if (authTicket.Expired || authTicket.Expiration <= DateTime.Now)
{
filterContext.HttpContext.Response.Redirect("/admin/login");
}
base.OnActionExecuting(filterContext);
}
}
User Login controller action:
[HttpPost]
public virtual ActionResult Login(FormCollection form)
{
if (form["username"] == "admin" && form["password"] == "pass")
{
var authTicket = new FormsAuthenticationTicket(
1, // version
form["username"], // user name
DateTime.Now, // created
DateTime.Now.AddMinutes(20), // expires
false, // persistent?
"" // can be used to store roles
);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie("User", encryptedTicket);
Response.Cookies.Add(authCookie);
// Redirect back to the page you were trying to access
return RedirectToAction(MVC.Home.Index());
}
else
{
ModelState.AddModelError("", "Bad info mate");
}
return View();
}
Admin Login controller action:
[HttpPost]
public virtual ActionResult Login(FormCollection form)
{
if (form["username"] == "admin" && form["password"] == "pass")
{
var authTicket = new FormsAuthenticationTicket(
1, // version
form["username"], // user name
DateTime.Now, // created
DateTime.Now.AddMinutes(20), // expires
false, // persistent?
"" // can be used to store roles
);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie("Admin", encryptedTicket);
Response.Cookies.Add(authCookie);
// Redirect back to the page you were trying to access
return RedirectToAction(MVC.Admin.Home.Index());
}
else
{
ModelState.AddModelError("", "Bad info mate");
}
return View();
}
Does this all seem sensible and secure?
Looking in FireFox's Page Info window at cookies I see each user type has its own cookie and you can't access a user type area without logging in.
First, you should probably be deriving from AuthorizeAttribute rather than ActionFilterAttribute. AutorizationFilters exectue before ActionFilters, and allow short-circuiting (that is, if an authorization filter fails, action filters will never execute). Also, ActionFilters are chained together, and might execute in any order.
Second, it's not a good idea to have the admin username and password hard coded into the attribute. Passwords should really be one-way hashed.
What you need for this scenario is called impersonation, basically all you have to do is set a fake authentication cookie with the impersonated user data (so the admin can see what the customer see).
Probably you would also want to keep track of this, so you can place on the user interface of the admin impersonating the user infos about the state of the application, and also give it a link to end the impersonation session (you would at this point restore the previous cookie), instead of letting him "log in twice".
You can check this out, as it may contain some useful infos for you (a bit old but always valid).
Regarding your database model, I'd assign several roles (simple user, admin, supervisor, etc) to a user. This way, you would login just once using a default role (admin), and have an option to switch to another role (simple user PoV) and store permissions on session.

WCF Custom Validator: How to initialize a "User" object from custom validator

I have a working custom UserNamePasswordValidator that calls into my Oracle DB.
This class derives from System.IdentityModel.Selectors.UserNamePasswordValidator and the Validate() method returns void.
I load my User object from the database, and once the password is validated, I want to stash my "User" object so the service can access it when going about its business. In ASP.NET / Java land I would stash it into a session, or perhaps my overall Controller class. How do I do this from the Validator in WCF?
Or, in other words, what is the best practice in WCF land to set a custom User domain object for the service.
Update: This is how I've worked around it. I cache the User object during the validator, then access it later in the AuthorizatinPolicy step.
// this gets called after the custom authentication step where we loaded the User
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
// get the authenticated client identity
IIdentity client = GetClientIdentity(evaluationContext);
User user;
OraclePasswordValidator.users.TryGetValue(client.Name, out user);
if(user != null) {
// set the custom principal
evaluationContext.Properties["Principal"] = user;
return true;
}
return false;
}
I'm not a WCF expert, but from what I've read and implemented so far, the 'correct' way to do this would be to use the Validator to authenticate the user, and then implement an IAuthorizationPolicy to do the actual authorization. So it would be in the authorization policy that you'll set your custom principal on the current thread.
To be able to forward information from the username/password validation, you can implement a security token authenticator that inherits from UserNameSecurityTokenAuthenticator. The SecurityTokenAuthenticator will first call the validator and if validation succeeds, it can add your custom authorization policy and send userinfo to the policy through the constructor. Something a long the lines of this:
public class CustomUsernameSecurityTokenAuthenticator : UserNameSecurityTokenAuthenticator
{
protected override bool CanValidateTokenCore(System.IdentityModel.Tokens.SecurityToken token)
{
return (token is UserNameSecurityToken);
}
protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateTokenCore(SecurityToken token)
{
var authorizationPolicies = new List<IAuthorizationPolicy>();
try
{
var userNameToken = token as UserNameSecurityToken;
new CustomUserNameValidator().Validate(userNameToken.UserName, userNameToken.Password);
var claims = new DefaultClaimSet(ClaimSet.System, new Claim(ClaimTypes.Name, userNameToken.UserName, Rights.PossessProperty));
authorizationPolicies.Add(new CustomAuthorizationPolicy(claims));
}
catch (Exception)
{
authorizationPolicies.Add(new InvalidAuthorizationPolicy());
throw;
}
return authorizationPolicies.AsReadOnly();
}
}
There's an article here that describes a bit more around the involved classes; http://blogs.msdn.com/card/archive/2007/10/04/how-identity-providers-can-show-custom-error-messages-in-cardspace.aspx
I have exactly the same issue.
I am using an API to connect to my underlying Oracle Database, and I "validate" logon details by opening a connection.
I then want to store this connection somewhere (easy enough, I will create a connection pool for all the different users), but also create a custom Identity and Principal representing this user, so that once it gets to my custom IAuthorizationPolicy, it doesn't need to reload this information.
I have done a lot of searching and not found anything so my plan is to do this:
Validate login details in custom UserNamePasswordValidator by opening API connection.
Store opened connection in connection pool under the user name.
When my custom IAuthorizationPolicy.Evaluate() is called, I will look at the generic identity provided:
IIdentity GetClientIdentity(EvaluationContext evaluationContext)
{
object obj;
if (!evaluationContext.Properties.TryGetValue("Identities", out obj))
throw new Exception("No Identity found");
IList<IIdentity> identities = obj as IList<IIdentity>;
if (identities == null || identities.Count <= 0)
throw new Exception("No Identity found");
return identities[0];
}
(sorry I can't get rid of this poor HTML escaping)
I then grab a connection from the pool based on the IIdentity.Name, use this connection to load up user-specific data from the database and store this in a custom Identity and Principal which I set in the EvaluationContext:
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
IIdentity identity = GetClientIdentity(evaluationContext);
if (identity == null)
throw new Exception();
// These are my custom Identity and Principal classes
Identity customIdentity = new Identity();
Principal customPrincipal = new Principal(customIdentity);
// populate identity and principal as required
evaluationContext.Properties["Principal"] = customPrincipal;
return true;
}
Then I should have access to my custom identity and principal whenever I need it by using System.Threading.Thread.CurrentPrincipal or CurrentIdentity.
Hope this helps in some way; I'm not sure it's the best way to go about it, but it's the best I've come up with so far...
Steve

Categories