Parse Database Authorization - Security For User Objects - c#

I have an ASP.NET MVC 4 web application where i use Parse as database in the back-end (https://www.parse.com/) and C# as programming language.
I use ParseUser class to log in registered users (https://www.parse.com/docs/dotnet_guide#users-login) like this:
ParseUser.LogInAsync("my_username", "my_password");
Then i have created a custom authorization attribute and i apply it in some controllers and action methods of my project.
public class AuthorizeParseUserAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (ParseUser.CurrentUser != null && ParseUser.CurrentUser.IsAuthenticated)
{
return true;
}
return false;
}
}
This is Parse's documentation for CurrentUser property https://www.parse.com/docs/dotnet_guide#users-current
So, I have the following problem: I successfully log in using my credentials. After log in, i enter the main page of my application (the AuthorizeParseUserAttribute has been applied to the corresponding action method). Then i send the url of this main page to another person, in another computer and the user (which is not even a registered user) can see the main page of my application and is logged in with my credentials!!! Parse's documentation for security for user objects is the following https://www.parse.com/docs/dotnet_guide#users-security
Can you please propose any solution to solve this very serious problem? Thank you.

The Parse SDK for .NET assumes you are building an app that is running on one device per user - it's not designed to integrate with ASP.NET.
From the docs:
Whenever you use any signup or login methods, the user is cached on disk.
ParseUser.CurrentUser is a static method that returns the cached user from the latest call to a signup or login method. This is why in your code, after one user logs in, everybody else that makes a request is also logged in as that user!
I am attempting to integrate Parse with an ASP.NET MVC site I'm currently developing. My plan to work around this limitation is to set the authentication cookie after logging in with Parse, then log out the user (their authentication cookie will still be set though).
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginModel model, string returnUrl)
{
ParseUser user;
try
{
user = await ParseUser.LogInAsync(model.UserName, model.Password);
}
catch (ParseException e)
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
FormsAuthentication.SetAuthCookie(user.Username, model.RememberMe);
ParseUser.LogOut();
return RedirectToLocal(returnUrl);
}
The Register method looks like this:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
var user = new ParseUser
{
Username = model.UserName,
Password = model.Password,
};
await user.SignUpAsync();
FormsAuthentication.SetAuthCookie(model.UserName, false);
ParseUser.LogOut();
return RedirectToAction("Index", "Home");
}
catch (ParseException e)
{
ModelState.AddModelError("", e.Message);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}

Related

How to Authorize and sign in at the same time

To use my app, a user must go through the sign in process to be granted access to the various parts of the app. So regardless of the entrance to the app (whether they start at /home, or /somethingElse), I'd like the user to be authorized and given their permissions.
Their permissions come from the SignUpSignIn() function in the Account controller.
When a user tries to access /somethingElse, I have an [Authorize] attribute that sends them to the Azure B2C sign in. But after being "Authorized" the SignUpSignIn() function is never hit and their permissions aren't assigned to their session. So whatever location they end up at, doesn't work properly (
and likely errors).
How can I get a user to run through the SignUpSignIn() after being authorized?
This is how I'm Authorizing
namespace TaskWebApp.Controllers
{
[Authorize] //<---
public class SomethingElseController : Controller
{
// GET: SomethingElse
public ActionResult Index()
{
return View();
}
}
}
This is how a user gets their permissions.
public class AccountController : Controller
{
public async Task SignUpSignIn()
{
if (!Request.IsAuthenticated)
{ ///}
if (Request.IsAuthenticated)
{ //apply permissions to user's session }
}
}
I expect a user to go to /somethingelse, and be redirected to the Azure B2C SignIn page. Once they click "sign in" to run SignUpSignIn() from the Account controller and have their permissions assigned to their session.
After some research I found that this isn't a good method of reapplying the permissions via session.
Instead I have created something that looks like this:
namespace TaskWebApp.Controllers
{
[Authorize]
public class SomethingElseController : Controller
{
public async Task<ActionResult> Index(){
if(Session["foo"] == null){ //there is no session data
await SignUpSignIn("/somethingelse") //This goes to the account controller and applies permissions on the session
}
else
{
return View();
}
}
private async Task SignUpSignIn(string redirectLink = "")
{
var controller = DependencyResolver.Current.GetService<AccountController>();
controller.ControllerContext = new ControllerContext(Request.RequestContext, controller);
await controller.SignUpSignIn(redirectLink);
}
}
}

ASP.Net Core MVC Identity - Add temporary (session) claim

For starters, this question was also asked here (Temporary Session Based Claims in ASP.NET Core Identity) and here (How to add claim to user dynamically?), but neither question received an answer so I am trying to revive it...
I am creating a multi-tenant web app. A user logs in, and they can see data related to their own company (but not the data of other companies who use the app). Because of franchise groups with multiple storefronts, etc, it is not uncommon for a single user to require access to several different companies, but in this case, they must choose a single company when logging in.
Nearly all data queries require a company ID as a parameter, so I need a convenient way of checking which company the user is currently logged into. If they log out and log into a different company, I need to see a different company ID. I would like to store it as an identity claim that I can see for the duration of the session, but I don't necessarily want to store it to the database since it may change with every login.
Here is my Login action (based on the standard ASP.Net Core MVC template):
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
// BEGIN CUSTOM CODE - Check which companies a user can access
IEnumerable<int> allCompanies = _myDbService.GetUserCompanies(model.Email);
if (allCompanies.Count() == 1)
{
// then they can only access one company - log them into it automatically.
// I need easy access to its ID with the User since it is used with almost every db query.
int companyID = allCompanies[0];
((ClaimsIdentity)User.Identity).AddClaim(new Claim("CompanyID", companyID.ToString())); // this shows as null on future controller actions.
Debug.WriteLine("Set breakpoint here to examine User"); // I see 1 claim (my custom claim) in the debugger, but it is not in the database yet.
// future controller actions show me 3 claims (nameidentifier, name, and security stamp) but User.Claims.First(c => c.Type == "CompanyID").Value throws error.
return RedirectToLocal(returnUrl);
}
else
{
// the user has access to several companies - make them choose one to complete login process
RedirectToAction("ChooseCompany", new { companyList = allCompanies });
}
// END CUSTOM CODE
}
// REMAINING CODE OMITTED FOR BREVITY
}
// If we got this far, something failed, redisplay form
return View(model);
}
So my questions are: why doesn't the custom claim "stick" so that it can be seen in future controller actions? Why isn't it saved to the database if that is the standard behavior? Is there a way to keep this value around for the duration of the session without using AddSession()? If I implement this as a claim, am I making a round trip to the database every time I access the value?
For anyone else with this issue, here's where I am so far... To permanently store the claim in the database and automatically load it at login, you must use the following and the change will not take effect until the next login:
await userManager.AddClaimAsync(user, new Claim("your-claim", "your-value"));
Using the following will only store the claim for the current session, so I was on the right track with that:
((ClaimsIdentity)User.Identity).AddClaim(new Claim("your-claim", "your-value"));
The only way I have found to get the claim to "stick" between controller actions is to override UserClaimsPrincipalFactory so that ASP.Net is using your custom code as part of the login process. The problem here is that you only have access to the ApplicationUser in the relevant method, and you can't really pass in any custom parameters AFAIK. So I first modified by ApplicationUser class like this:
public class ApplicationUser : IdentityUser
{
public long ActiveDealershipID { get; set; }
}
(You must apply the EF migration). Now I create a class that derives from UserClaimsPrincipalFactory like this:
public class MyClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
public MyClaimsPrincipalFactory(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> options) : base(userManager, roleManager, options)
{
}
public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
long dealershipID = user.ActiveDealershipID;
((ClaimsIdentity)principal.Identity).AddClaim(new Claim("DealershipID", dealershipID.ToString()));
return principal;
}
}
(You must register the service in startup.cs ConfigureServices):
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, MyClaimsPrincipalFactory>();
And then in my Login action, I set the new user property from the model data BEFORE the sign-in so that it will be available to the UserClaimsPrincipalFactory when it is setting the claims:
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
ApplicationUser user = await _userManager.FindByEmailAsync(model.Email);
user.ActiveDealershipID = model.ChosenDealership
await _userManager.UpdateAsync(user);
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
...
I am still open to better suggestions or other ideas, but this seems to be working for me.

One Login page that allows three Roles in but denies Unauthorized Accounts

Having a problem with my C# application and some help would be greatly appreciated.
Creating a project with C# and I've been stuck on trying to get Users who are not authorized by the Administrator to not be able to login.
This is is my Account Controller. I want to start off by just having Admins going into the application and anyone else going to a View or have an Error Message displayed.
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
WebSecurity.Login(model.UserName, model.Password);
if (User.IsInRole("Administrator"))
{
return RedirectToLocal(returnUrl);
}
else
{
WebSecurity.Logout();
return View("RegisterAuthorisation");
}
}
else
{
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
}
It just isn't working as everyone just gets through to the page located in the Else statement. My database is fine and I've implemented code in the HTML which prevents Users other than Admins access to everything.
As I said I would greatly appreciate some help!
You might not have initialized the roles manager. Add an attribute [InitializeSimpleMembership] to the accounts controller. This ensures that the user's data is retrieved from the database when authenticated. You might want to initialize this globally via RegisterGlobalFilters if your users could log into your application from other controllers. Take a look at this MSDN article.

How to programmatically login to ASP.NET Identity 2?

How do you programmatically login to asp.net identity 2? This is straight forward against Web API, but MVC only project requires __RequestVerificationToken. This is a login method:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindAsync(model.Email, model.Password);
if (user != null)
{
await SignInAsync(user, model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
you must create Login View and in that ,submit your data to this method via ajax call. In this case I prefer using jquery.ajax method instead of ajax form .
Also create LoginViewModel that contains your user info like username , password
so you use strongly typed view .

How to load the profile of a newly-logged-in user before the redirect

I started an ASP.NET MVC 2 project and I am building off of the code that was generated automatically.
The problem that I am experiencing is that after a user is logged in, it appears that the profile of the newly-logged-in user is not loaded into the HttpContext, so I get a ProviderException with the message "This property cannot be set for anonymous users" when attempting to set a property value in the current user's profile.
For the POST-only LogOn action, Visual Web Developer 2010 Express basically generated:
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
//...
where FormsService is a property of the controller of type FormsAuthenticationService (also generated):
public class FormsAuthenticationService : IFormsAuthenticationService
{
public void SignIn(string userName, bool createPersistentCookie)
{
if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
}
public void SignOut()
{
FormsAuthentication.SignOut();
}
}
After the FormsService.SignIn(model.UserName, model.RememberMe) line I was assuming that information for the newly-logged-in account would be immediately available to the controller, but this does not appear to be the case. If, for example, I add profile.SetPropertyValue("MyProfileProperty", "test") below the call to FormsService#SignIn, then I get the ProviderException "This property cannot be set for anonymous users".
How do I load the newly-logged-in user's profile into the HttpContext so that I can set a property value in his or her profile before the next request?
The naming of the function is counter intuitive, but ProfileBase.Create(username) will load the existing profile for an existing user or create a new profile if none exists.
var profile = ProfileBase.Create(userName);
This will not load the profile into ControllerContext.HttpContext.Profile but at least you can access or alter the user profile.
The "raw" Forms authentication built into the MVC template does not automatically load the profile. All it does is validate the user against the Membership tables and set the login cookie. If you want profile data persisted in Session state on login, you'll have to do that explicitly:
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
LoadProfile(model.UserName);
}
}
private void LoadProfile(string UserName)
{
MyModelContext ctx = new MyModelContext(); //EF or LINQ2SQL context
var user = ctx.Users.Where(u => u.UserName == UserName).FirstOrDefault();
Session.Add("CurrentUser", user);
}
UPDATE:
I misunderstood the original question. You have to create and save a profile of type ProfileCommon for anonymous users. See this post:
http://forums.asp.net/p/1150958/2619264.aspx

Categories