How can I store a users preferences in MVC3 - asp.net-mvc

I have an MVC3 application and I would like to give the users the ability to set preferences that would be enabled when the user logs in.
I really don't have any idea where to start with this and would really appreciate being pointed in the right direction. I did try some changes to the membership class but now I am thinking that's probably not the best way to go about things.

You could do it in a database (sounds like you might be using one at least with the out-of-the-box membership provider) once uniquely identifying a user. In that case, you may want to implement your own membership provider.
You have to do a little work to start implementing your own provider. If this is your only requirement, you might be able to avoid it by writing your own class that returns settings in a format of your choosing
public static class UserSettings
{
public static string GetSettings(IPrincipal user)
{
if(user.Identity.IsAuthenticated)
{
// dip into database using user.Identity.Name property
return "string with user settings";
// this also assumes user.Identity.Name is uniquely able
// to identify a user in your database!
}
return string.Empty;
}
}
Or, if the information is completely trivial, maybe you could implement a cookie representation of the user settings. This, of course, comes with all the caveats of using cookies, but you could avoid storing the information in a database
Anywhere you have an HttpContext you could grab the settings value like so:
if(HttpContext.Current != null)
{
string userSettings = HttpRequest.Current.Request.Cookies["NameOfCookie"];
}

You can use the FormsAuthentication cookie to store your user information and avoid accessing the database all the time. That cookie is encrypted and whatever information you're storing as safe as the user session itself. The only problem with the cookies is that they have a maximum size of 4K so, if your user info is massive then you might run into a problem. When I use the cookie approach I store my user data as a JSON and then deserialize that JSON on each page request. Here is my login controller logic (I'm using SimpleMembership but the approach is the same:
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, model.RememberMe))
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null)
{
var user = _userLogic.GetItem(model.UserName);
if (user != null && user.IsActive)
{
var newAuthTicket = new FormsAuthenticationTicket(authTicket.Version, authTicket.Name, authTicket.IssueDate, authTicket.Expiration, authTicket.IsPersistent, JsonConvert.SerializeObject(user));
var newCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(newAuthTicket))
{
Expires = authCookie.Expires
};
Response.Cookies.Add(newCookie);
return RedirectToLocal(returnUrl);
}
WebSecurity.Logout();
ModelState.AddModelError("UserName", "This account has been deactivated.");
return View(model);
}
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
Notice the newAuthTicket creation and how user instance is passed to it as a JSON. After that all I have to do is desirialize this user object in my base controller's OnAuthorization method:
protected override void OnAuthorization(AuthorizationContext filterContext)
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null)
{
var principal = new CustomPrincipal(HttpContext.User.Identity)
{
CurrentUserInfo = JsonConvert.DeserializeObject<User>(authTicket.UserData)
};
HttpContext.User = principal;
AppUser = principal.CurrentUserInfo;
ViewBag.AppUser = AppUser;
}
}
base.OnAuthorization(filterContext);
}

Create a new table in your database.

Related

Retrieving user info from db while having identity claims

I must admit, I'm newbie to MVC.. so everything is kind of like a black magic right now.
This is the code
public ActionResult LogIn(Models.profile profile)
{
if (ModelState.IsValid)
{
if(IsValid(profile.profile_email, profile.profile_password))
{
var db = new DatabaseContext();
var _id = db.profile.Where(u => u.profile_email == profile.profile_email).Select(n => n.profile_id).FirstOrDefault();
var identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Email, profile.profile_email),
new Claim(ClaimTypes.Name, _id.ToString())},
"ApplicationCookie");
var ctx = Request.GetOwinContext();
var authmngr = ctx.Authentication;
authmngr.SignIn(identity);
return RedirectToAction("LogIn", "Main");
} else
{
ModelState.AddModelError("", "Something goes wrong");
}
}
return View(profile);
}
I've managed to login with the identity thing, which was quite hard #_ #
The thing was that those claimtype are not too flexible from what I've seen.
I mean I have to choose something from the list and I kind just pick my own properties or something.
So, I decided to store current user ID in the ClaimTypes.Name. Not sure if it is any of the good idea. I'm not even sure if the whole login pattern is any viable ;o
But... to the point. How do I retrieve user infos with just this claim if there would be no user id in there? Or how could I retrieve users info from database at all with using claims identity?
If there is anything I should add, please let me know ;3

Using OpenID/OpenAuth in MVC3 app with overridden authentication method

We override the basic authentication in an MVC3 application by calling a webservice with the user's credentials and returning a WCF structure that contains the user's ID, a "LogonTicket". This LogonTicket is used to "authenticate the user for each call made to the webservice.
Now, we override by replacing the defaultProvider in the Web.config. All we do in this overridden provider is
to override the ValidateUser() function. That is where we call the web service with their credentials and return
the "LogonTicket".
This is the LogOn() function from our AccountController, essentially the base code from the template:
public ActionResult LogOn(LogOnModel model)
{
string ReturnUrl = "";
if (HttpContext.Request.UrlReferrer.Query.Length > 11)
{
ReturnUrl = Uri.UnescapeDataString(HttpContext.Request.UrlReferrer.Query.Substring(11));
}
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(ReturnUrl) && ReturnUrl.Length > 1 && ReturnUrl.StartsWith("/")
&& !ReturnUrl.StartsWith("//") && !ReturnUrl.StartsWith("/\\"))
{
return Redirect(ReturnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
ViewBag.MainWebsite = MainWebsite;
return View(model);
}
This is the overridden ValidateUser() function from our new default provider:
public override bool ValidateUser(string username, string password)
{
MyServiceClient mps = new MyServiceClient();
string sha1password = HashCode(password);
LogonInfo logonInfo = mps.GetLogonTicket(username, sha1password);
if (logonInfo.LogonTicket != "" && logonInfo.LogonTicket != "0")
{
// Authenticated so set session variables
HttpContext.Current.Session["LogonTicket"] = logonInfo.LogonTicket;
HttpContext.Current.Session["ParticipantID"] = logonInfo.ParticipantID;
return true;
}
else
{
return false;
}
}
I'm not really sure how to combine the use of the two, so my questions are:
How can I implement OpenID and Facebook logins and keep my current authentication method?
How can we "map" the OpenID user with our current user DB values? We MUST know so we can retrieve their info.
I know we can retrieve their email address but what if their OpenID email is different than the one they use for their record on our site?
Are there any examples of how to do this, anywhere?
Thanks for looking at my question.
I have done a project which required multiple log-on possibilities (custom account, Google and Facebook)
In the end your authentication with ASP.NET is entirely dependant on your configuration. (In your case it is FormsAuthentication) this means that FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); basicly determines everything in regard to your user and where you set this isn't restricted.
You have now basicly the same implementation as we started out with, using a MembershipProvider to handle your own custom account. You only need to expand now to facilitate the openIds. You would have to expand your Controller with various actions for each login type (Now you have ActionResult LogOn() you can add to that for example: ActionResult LogOnOpenId()). Inside that method you basicly call the same code but instead of Membership.ValidateUser(model.UserName, model.Password) you call the OpenId services.
I have provided below an example of our google implementation using dotnetopenauth. The service method uses formsService.SignIn(userId.Value.ToString(), false); which basicly calls FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); (we only do some custom behaviour there in regard to the SecurityPrincipal but this doesn't affect your Authentication process). You can also see that we make a new account when we receive a new user. To solve your question part 2 we have implemented a profile which can be merged if you can provide another login. This allows our users to keep their account consolidated and use whatever login method they like.
For examples in regard to multiple signons I will refer to the answer of Tomas whom referenced StackExchange as a good example. Also I'd advise you to install MVC4 and VS2012 and just do a File > New Project. The newest default template of MVC includes openid implementation alongside a custom login!
Example google openid implementation:
The controller method:
public virtual ActionResult LoginGoogle(string returnUrl, string runAction)
{
using (var openId = new OpenIdRelyingParty())
{
IAuthenticationResponse response = openId.GetResponse();
// If we have no response, start
if (response == null)
{
// Create a request and redirect the user
IAuthenticationRequest req = openId.CreateRequest(WellKnownProviders.Google);
var fetch = new FetchRequest();
fetch.Attributes.AddRequired(WellKnownAttributes.Name.First);
fetch.Attributes.AddRequired(WellKnownAttributes.Name.Last);
fetch.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
fetch.Attributes.AddRequired(WellKnownAttributes.Preferences.Language);
req.AddExtension(fetch);
req.RedirectToProvider();
return null;
}
_service.ConnectViaGoogle(response, TempData);
}
The service method:
public void ConnectViaGoogle(IAuthenticationResponse response, TempDataDictionary tempData)
{
// We got a response - check it's valid and that it's me
if (response.Status == AuthenticationStatus.Authenticated)
{
var claim = response.GetExtension<FetchResponse>();
Identifier googleUserId = response.ClaimedIdentifier;
string email = string.Empty;
string firstName = string.Empty;
string lastName = string.Empty;
string language = string.Empty;
if (claim != null)
{
email = claim.GetAttributeValue(WellKnownAttributes.Contact.Email);
firstName = claim.GetAttributeValue(WellKnownAttributes.Name.First);
lastName = claim.GetAttributeValue(WellKnownAttributes.Name.Last);
language = claim.GetAttributeValue(WellKnownAttributes.Preferences.Language);
}
//Search User with google UserId
int? userId = _userBL.GetUserIdByGoogleSingleSignOnId(googleUserId);
//if not exists -> Create
if (!userId.HasValue)
{
_userBL.CreateGoogleUser(
googleUserId,
firstName,
lastName,
email,
language,
DBConstants.UserStatus.DefaultStatusId,
out userId);
}
if (userId.HasValue)
{
_userBL.UpdateLastLogon(userId.Value);
var formsService = new FormsAuthenticationService();
formsService.SignIn(userId.Value.ToString(), false);
AfterLoginActions(tempData);
}
}
}
Any questions or comments? I'll gladly hear them.
it should be perfectly possible to have multiple authentications methods. All IIS / ASP.net cares about is the FormsAuthentication cookies. So you would have one set of actions for your standard username/password auth, and another for OpenId. This is at least what I have done on one project.
You can't even trust the openId provider to give you an email address! A common solution to this problem is to allow a user to attach multiple OpenId identifiers (URI's) to the his account after logging in. This is e.g. how StackOverflow works. If this is the first time the user visits the system then you can auto create a new account, or force the user through a signup process.
When I added the OpenId support in the system mentioned, it had an existing table used to store username and password(users table). I added a new table with a many to one relationship with the users table, and used this to store the URI's.
As mentioned above StackOverflow it self is a good place to start, also there are a lot of good examples in the http://www.dotnetopenauth.net/ project.
As far as I know the source of SO is not public, and they are using the dotnetopenauth project.
This may be to abstract, but this library is a openId (among other things) for the open source orchard CMS: http://orchardopenauth.codeplex.com/
I hope this helps, but if you have any questions then please expand your question with more details.

FormsAuthenticationTicket Userdata still readable after encryption (asp.net MVC3 with forms Auth.)

Im building a website with the new ASP.NET MVC3 framework and using FormsAuth. for securing the website. I'm storing the role of a user in the UserData property of the FormsAuthenticationTicket, (setting the cookie manually), I then call the encrypt method on the ticket before adding it to the cookie(see below a Standard ticket sniplet).
if (Validate(model.UserName, model.Password))
{
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1,
model.UserName,
DateTime.Now,
DateTime.Now.AddMinutes(30),
false,
UserType.Administrator.ToString());
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
Response.Cookies.Add(faCookie);
return RedirectToAction("startpage", "mycontroller");
}
}
Now I've made a custom AuthorizeAttribute thats able to check if the user is 1. authenticated and 2. has the admin role (from the ticket). (below)
The AuthorizeCore method of this derived class will be called when an action takes places in a class that has the attribute annotion.
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
}
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = httpContext.Request.Cookies[cookieName];
if (authCookie == null)
return false;
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket.UserData != UserType.Administrator.ToString())
return false;
return true;
So here's where im getting confused.
When I follow the code being executed (with valid credentials, in debug), and check the values of the variables made on each line, the encryptedTicket encrypts just fine before adding it to the reponsecookie.
But when I then check the AuthorizeCore method when the controller (of the index page) is being called, the parameter its getting, the HttpContext, contains the ticket with everything unencrypted, so there is no need to decrypt the ticket anymore when reading the cookie.
Why do I see the ticket succesfully being encrypted in the logon controller where I send it back to the client, but then when I receive the httpcontext in the AuthorizeAdministrator class its all unencrypted again.
Sorry for the long question/story, there's probably a simple and short answer for it.
Hope my story is clear.
Thanks.
Forms auth needs to decrypt the cookie early in the page processing pipeline, to determine if the user is authorized -- that's when it fills in the details for User.Identity, etc.

How to integrate OpenId with ASP.Net Membership in MVC

I am using the following code from MVC Storefront to test OpenId in MVC. How do I integrate it with my ASP.Net Membership so I can use roles and save a user name for the user in my tables? I believe that SO is also using something similar.
public ActionResult OpenIdLogin()
{
string returnUrl = VirtualPathUtility.ToAbsolute("~/");
var openid = new OpenIdRelyingParty();
var response = openid.GetResponse();
if (response == null)
{
// Stage 2: user submitting Identifier
Identifier id;
if (Identifier.TryParse(Request["openid_identifier"], out id))
{
try
{
IAuthenticationRequest req = openid.CreateRequest(Request["openid_identifier"]);
var fetch = new FetchRequest();
//ask for more info - the email address
var item = new AttributeRequest(WellKnownAttributes.Contact.Email);
item.IsRequired = true;
fetch.Attributes.Add(item);
req.AddExtension(fetch);
return req.RedirectingResponse.AsActionResult();
}
catch (ProtocolException ex)
{
ViewData["Message"] = ex.Message;
return View("Logon");
}
}
else
{
ViewData["Message"] = "Invalid identifier";
return View("Logon");
}
}
else
{
// Stage 3: OpenID Provider sending assertion response
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
var fetch = response.GetExtension<FetchResponse>();
string name = response.FriendlyIdentifierForDisplay;
if (fetch != null)
{
IList<string> emailAddresses = fetch.Attributes[WellKnownAttributes.Contact.Email].Values;
string email = emailAddresses.Count > 0 ? emailAddresses[0] : null;
//don't show the email - it's creepy. Just use the name of the email
name = email.Substring(0, email.IndexOf('#'));
}
else
{
name = name.Substring(0, name.IndexOf('.'));
}
//FormsAuthentication.SetAuthCookie(name, false);
SetCookies(name, name);
AuthAndRedirect(name, name);
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
case AuthenticationStatus.Canceled:
ViewData["Message"] = "Canceled at provider";
return View("Logon");
case AuthenticationStatus.Failed:
ViewData["Message"] = response.Exception.Message;
return View("Logon");
}
}
return new EmptyResult();
}
ActionResult AuthAndRedirect(string userName, string friendlyName)
{
string returnUrl = Request["ReturnUrl"];
SetCookies(userName, friendlyName);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
There are several questions like yours already on StackOverflow. This one seems particularly similar.
If you're already using the Membership provider for your site and are just adding OpenID to it, then I guess you're stuck with Membership for now and can use one of the answers to the question I linked to to get a semi-decent membership provider that MAY work for you.
But if you're writing a new site and just want "use roles and save a user name for the user in my tables" as you said, then DON'T use ASP.NET Membership at all. It's SO not worth it! It doesn't fit OpenID's password-less paradigm and just causes more grief than anything else. If you're not afraid of a little bit of database access yourself, do it that way. And you can get Roles behavior very easily by just issuing your own FormsAuthentication.RedirectFromLoginPage or FormsAuthentication.SetAuthCookie call and passing in the roles the user fills.
The open id provider will return data about the user. If you don't request/require specific tokens of information, then all you'll be given is the user's display name and identity URL.
Depending on what open id library you're using, you can request tokens like FirstName LastName, DOB (if you really cared) and if the user provided that information on their chosen identity, then you'd get it returned to you.
You can then use this to create a new user in the membership system. You'll probably have to give them a dummy password to get around the requirements of the Membership API.
To validate a login, provide 1 form that takes username & password and the other that takes an identity URL. After you've validated the user via open id, try to find the user by username (identity url) in the Membership API. If it doesn't exist, create it.

ASP.NET MVC - Alternative to Using Session

I have an ASP.NET MVC view that uses jquery.Uploadify to post files to one of my controllers for use in my application and one of the side effects I noticed with Uploadify is that when the Flash file Uploadify uses to submit files to the server posts to my controller it gets its own SessionID from ASP.NET. This would be fine, of course, if my Upload controller didn't use the Session to store a list of files that have been uploaded by the current user for future manipulation by my application... So given this issue, after I upload files with my View, the current user's session does not contain any of the files that were just posted.
Any suggestions on how to achieve what I want without relying on Sessions (and, preferably, without a database)?
Since Uploadify is purely a front end script, I don't understand why it would be getting a session from ASP.NET. I also don't fully understand what your particular problem is.
If your problem is that once the files are uploaded, the user can't see them on the screen, then I would suggest figuring out a method displaying the list of files that is independent of Uploadify. If it can, have it send an ID token along with the files and then grab the data needed to show the list from a database.
Maybe a static hashmap which key is the user:ip of the client?
The value will be whatever object you want to stored across the different Sessions.
One thing to cross check--did you make the session "live" by adding some data to it before going to uploadify? ASP.NET regenerates sessions until it has data in the session.
This is the solution I came up with. I haven't done much testing, but it seems to be an acceptable alternative to Session in my current scenario. I will use the Global.asax's Session_End/Session_Start to ensure rows are created and removed as needed.
public class UserTable : Dictionary<string, Dictionary<string, object>>
{
public new object this[string key]
{
get
{
object value = null;
if (HttpContext.Current != null)
{
var sessionId = HttpContext.Current.Session.SessionID;
if (ContainsKey(sessionId) && base[sessionId].ContainsKey(key))
value = base[sessionId][key];
}
else
throw new Exception("No HttpContext present.");
return value;
}
set
{
if (HttpContext.Current != null)
{
var sessionId = HttpContext.Current.Session.SessionID;
if (!ContainsKey(sessionId))
Add(sessionId, new Dictionary<string, object>());
if (!base[sessionId].ContainsKey(key))
base[sessionId].Add(key, value);
else
base[sessionId][key] = value;
}
else
throw new Exception("No HttpContext present.");
}
}
public object this[string sessionId, string key]
{
get
{
object value = null;
if (ContainsKey(sessionId) && base[sessionId].ContainsKey(key))
value = base[sessionId][key];
return value;
}
set
{
if (!ContainsKey(sessionId))
Add(sessionId, new Dictionary<string, object>());
if (!base[sessionId].ContainsKey(key))
base[sessionId].Add(key, value);
else
base[sessionId][key] = value;
}
}
public void Add(string sessionId)
{
Add(sessionId, new Dictionary<string, object>());
}
public void Add()
{
if (HttpContext.Current != null)
Add(HttpContext.Current.Session.SessionID);
else
throw new Exception("No HttpContext present.");
}
public new void Remove(string sessionId)
{
base.Remove(sessionId);
}
public void Remove()
{
if (HttpContext.Current != null)
Remove(HttpContext.Current.Session.SessionID);
else
throw new Exception("No HttpContext present.");
}
}

Resources