Custom Authentication .Net MVC - asp.net-mvc

I have a web app that allows a user to pay for a holiday booking.
The user will log in using a combination of a unique booking ID and a booking password.
Is there a simple way I can authenticate the user once they have successfully logged in. Is it a good idea to use the .net session object for this purpose?
The app wont be load balanced so I don't need to worry about storing session across different machines.
I would like to not have to use .Net membership as I don't want to create any extra tables in the database.

If you are able to retrieve the user, I guess this is possible. Below is the way I would try:
public ActionResult UrlAuth(string bookingId, string bookingPass)
{
var userManager=HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
// Retrieve the user by the give booking values (Custom part in your app
string userId= _bookingRepository.GetUserIdByBooking(bookingId, bookinggPass);
var user = userManager.FindById(userId);
if (user != null)
{
var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = false }, userIdentity );
// authentication succeed do what you want
return Redirect("Wherever");
}
ModelState.AddModelError("", "Invalid booking values");
return View();
}

Here's a good general article with custom authentication for MVC
http://www.ryadel.com/en/http-basic-authentication-asp-net-mvc-using-custom-actionfilter/
http://www.codeproject.com/Articles/1005485/RESTful-Day-sharp-Security-in-Web-APIs-Basic

Related

MVC give authorization on login to access actions

I've been running in circles trying to find an answer to this, and I can't seem to make any progress.
All I want to do, is check for a correct username and password combo, then GIVE the user authorization to access MVC actions decorated with the [Authorize] tag.
public ActionResult DoLogin(PageInitModel model)
{
EF_db db = new EF_db();
string saltPassword = getSaltedPasswordEncryption(model.UserName, model.Password);
var user = (from s in db.Users
where s.Username == model.UserName
&& s.Password == saltPassword
select s).FirstOrDefault();
if(user == null)
{
model.LoginFail = true;
return View("Login", model);
}
else
{
//
// give the user some magical token to access [Authorize] actions here
//
return RedirectToAction("Index", "Menu");
}
}
Above is the login action (called from a basic form), and below would be one of the actions I would like to restrict access to:
public class MenuController : Controller
{
[Authorize]
public ActionResult Index()
{
var pageInitModel = new PageInitModel();
return View("Menu",pageInitModel);
}
}
I would like to keep track of the users myself (in my own tables), because there are many additional attributes I would like to track. I'm not sure if I need to write a custom AuthorizeAttribute, or what, but I can't seem to make any headway.
Any help pointing me in the right direction is greatly appreciated.
Thanks!
You should look into ASP.NET Identity. Note this was introduced in ASP.NET 5 (?) and replaces some older frameworks Microsoft had like Basic Membership etc.
Honestly you really don't want to roll your own. ASP.NET Identity does exactly what you describe right out of the box. Keep in mind there are two distinct concepts Authentication and Authorization.
Authentication is verifying the user is who he says he is.
Authorization is restricting access to only users who are "allowed".
There are many ways to structure Authorization but I assume Role based Authorization will meet your need. You will define multiple roles, say User, Admin, Moderator, Admin, etc.
You then restrict access to actions based on the role. You can even make roles overlap and allow a single user to have multiple roles. The end result is that once a user logs in their role determines what they can do via the authorize tags.
[Authorize(Roles="Admin")]
If the user is not logged in, they will be redirected to the login form to AUTHENTICATE ("Um I don't know who you are". Once authenticated if they are not authorized they will still be restricted from this action ("Oh I know who you are but you are not allowed to do this".
To round it out you can also have anonymous actions which mean no authentication is required and Authorize actions which are not limited to a specific role (any authenticated user is allowed but not unauthenticated users).
The fact that even with a basic [Authorize] you are having issues leads be to believe there is some configuration problems in even the Authentication. I recommend going through a tutorial building an example app like this one:
http://blogs.msdn.com/b/webdev/archive/2013/10/20/building-a-simple-todo-application-with-asp-net-identity-and-associating-users-with-todoes.aspx
As I understood you have your users information and you may want to use the authentication by custom code. But to actually login the user you must create a session of the user (Cookie session) for further authentication of requests.
You have already verified the username and password. now you've to create a session cookie using form authentication. Here's answer to your first problem:
// give the user some magical token to access [Authorize] actions here
// User name and password matches
if (userValid)
{
FormsAuthentication.SetAuthCookie(username, false);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
The Authorize attribute will then check if the request coming for the action is authenticated. This attribute internally checks if a session cookie is valid and authenticated.
Also this attribute not only checks for authentication but also can verify authorization. So you can use custom roles as well with Authorize attribute to restrict users to accessing specific views/actions.
It was pointed out to me that I needed to look into cookies or existing membership providers, so I looked around in FormsAuthenticationTickets and rolled my own solution that I would love feedback on.
I ended up creating a new ticket when the login was successful with the FormsAuthenticationTicket class (see below).
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
user.Username,
DateTime.Now,
DateTime.Now.AddHours(2),
false,
userData,
FormsAuthentication.FormsCookiePath);
// Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(ticket);
// Create the cookie.
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
Then I wrote a custom ActionFilter, so I could make my own method decorators (or whatever they're called).
namespace MyApp.Filters
{
public class CustomLoginFilter : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
// Retrieves the cookie that contains your custom FormsAuthenticationTicket.
HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
// There's actually a cookie
// Decrypts the FormsAuthenticationTicket that is held in the cookie's .Value property.
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null && user != null && data == authTicket.UserData)
{
// Everything looks good
this.OnActionExecuting(filterContext);
}
else
{
//log bounceback - someone screwed with the cookie
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Login" },
{ "action", "Index" }
});
}
}
else
{
//log bounceback - not logged in
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Login" },
{ "action", "Index" }
});
}
}
}
}
I'll likely play with it more, but it seems to work fine for now.
Thanks for the help.

problems with DotNetOpenAuth OAuth2 Client for Google

I have an ASP.NET MVC 4 application.
Yesterday my users started to complain they cannot login using their Google accounts. After lots of googling I found this: DotNetOpenAuth.GoogleOAuth2. I followed the instructions.
I created Client ID for web applications in Google console.
In AuthConfig.RegisterAuth() I have:
var client = new DotNetOpenAuth.GoogleOAuth2.GoogleOAuth2Client(googleClientID, googleClientSecret);
var extraData = new Dictionary<string, object>();
OAuthWebSecurity.RegisterClient(client, "Google", extraData);
In AccountController, I have something like this:
public ActionResult ExternalLoginCallback(string returnUrl)
{
DotNetOpenAuth.GoogleOAuth2.GoogleOAuth2Client.RewriteRequest();
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
if (!result.IsSuccessful)
{
return RedirectToAction("ExternalLoginFailure");
}
if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false))
{
// here I have some logic where is user sent when login was successfull
return RedirectToLocal(returnUrl);
}
if (User.Identity.IsAuthenticated)
{
// If the current user is logged in add the new account
OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name);
return RedirectToLocal(returnUrl);
}
else
{
// User is new, ask for their desired membership name
string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId);
// some logic
return View("ExternalLoginConfirmation", new RegisterExternalLoginModel { UserName = username, ExternalLoginData = loginData, EncryptedEmail = encryptedEmail });
}
}
I have two problems:
Before the change, the result.UserName contained the users email. Now it contains name. But I need email. Except for this, registration works fine.
My biggest problem - existing users cannot log in using their google account. The code goes to "// User is new, ask for their desired membership name" for them. The ProviderUserId I get now is different for the same email address.
Thanks a lot for any advice.
Can you configure this library to pass additional parameters to the Google authorization service? If so, you should pass 'openid.realm=$your_app_openid2_realm' (if your app was configured for OpenID2 before, it most likely asserted a 'realm' value in its requests, you should use the same value).
In this case, you'll receive two identifiers from Google. The new one (which is compatible with profile URLs and overall more Google APIs) and the old one (returned as openid_id).

Change e-mail or password in AspNetUsers tables in ASP.NET Identity 2.0

I have implemented the ForgotPassword (with token reset) into my MVC 5 application. We are in production. Although this works in majority of the cases, many of our end-users are of older age and get confused when they cannot login and need a reset. So in those situations, I am considering giving one of our admin staff the ability to reset a user's password and giving them the new password on the phone. The data is not that sensitive.
I tried this:
public ActionResult ResetPassword()
{ UserManager<IdentityUser> userManager =
new UserManager<IdentityUser>(new UserStore<IdentityUser>());
var user = userManager.FindByEmail("useremail.samplecom");
userManager.RemovePassword(user.Id);
userManager.AddPassword(user.Id, "newpassword");
}
I get a cryptic error stating Invalid Column EMail, Invalid Column Email Confirmed ......
I also tried the userManager.ResetPassword(), but abandoned that idea because it needs a token reset. I want to bypass it.
What am I not seeing?
Thanks in advance.
I also tried the userManager.ResetPassword(), but abandoned that idea because it needs a token reset. I want to bypass it.
How about you just generate the token and pass it to the Reset routine ?
var userManager = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
var code = await userManager.GeneratePasswordResetTokenAsync("username");
var result = await userManager.ResetPasswordAsync("username", code, "your new password");
if (!result.Succeeded)
{
//password does not meet standards
}
The idea here is you are just emulating/bypassing the usual routine of sending the token to the client (via email) and having the link that they click on call ResetPasswordAsync
I'm not completely sure if this will work in your implementation but I use the following code with success in a use case which has basically the same requirements as yours. The difference is that I'm not letting any user reset it's own password. This is always the task of an admin.
I'm bypassing the ApplicationUserManager and edit the information directly in the table, using just Entity Framework.
// I created an extension method to load the user from the context
// you will load it differently, but just for completeness
var user = db.LoadUser(id);
// some implementation of random password generator
var password = General.Hashing.GenerateRandomPassword();
var passwordHasher = new Microsoft.AspNet.Identity.PasswordHasher();
user.PasswordHash = passwordHasher.HashPassword(password);
db.SaveChanges();
You have to get the user from the database and generate the code not by username :
public async Task<Unit> ResetPassword(string userName, string password)
{
if (!string.IsNullOrWhiteSpace(userName))
{
var returnUser = await _userManager.Users.Where(x => x.UserName == userName).FirstOrDefaultAsync();
var code = await _userManager.GeneratePasswordResetTokenAsync(returnUser);
if (returnUser != null)
await _userManager.ResetPasswordAsync(returnUser, code, password);
}
return Unit.Value;
}

Setup a route {tenant}/{controller}/{action}/{id} with ASP.NET MVC?

I would like to setup a multi-tenant ASP.NET MVC app. Ideally, this app would have a route with {tenant}/{controller}/{action}/{id}, each tenant representing an logical instance of the app (simply independent multi-user accounts)
The fine grained details how do that are still quite unclear to me. Any guide available to setup such multi-tenant scheme with ASP.NET MVC?
I am currently working on a similar project using ASP.Net MVC, Forms Authentication and the SQL providers for Membership/Roles/Profile. Here is the approach I am taking:
Register the default route as `{tenant}/{controller}/{action}/{id}
Change the default behavior of the FormsAuthenticationService that comes with the standard MVC template. It should set the UserData of the authentication ticket to include the tenant name (from your route).
public void SignIn(string userName, bool createPersistentCookie, string tenantName)
{
var ticket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddMinutes(30),
createPersistentCookie, tenantName);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
HttpContext.Current.Response.AppendCookie(cookie);
}
In your global.asax file to do some tenant security checking and allow partioning of users between tenants in one membership database
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
//Since this method is called on every request
//we want to fail as early as possible
if (!Request.IsAuthenticated) return;
var route = RouteTable.Routes.GetRouteData(new HttpContextWrapper(Context));
if (route == null || route.Route.GetType().Name == "IgnoreRouteInternal") return;
if (!(Context.User.Identity is FormsIdentity)) return;
//Get the current tenant specified in URL
var currentTenant = route.GetRequiredString("tenant");
//Get the tenant that that the user is logged into
//from the Forms Authentication Ticket
var id = (FormsIdentity)Context.User.Identity;
var userTenant = id.Ticket.UserData;
if (userTenant.Trim().ToLower() != currentTenant.Trim().ToLower())
{
//The user is attempting to access a different tenant
//than the one they logged into so sign them out
//an and redirect to the home page of the new tenant
//where they can sign back in (if they are authorized!)
FormsAuthentication.SignOut();
Response.Redirect("/" + currentTenant);
return;
}
//Set the application of the Sql Providers
//to the current tenant to support partitioning
//of users between tenants.
Membership.ApplicationName = currentTenant;
Roles.ApplicationName = currentTenant;
ProfileManager.ApplicationName = currentTenant;
}
Partition each tenants data. Here are two options:
4a. Use a separate database for each tenant. This provides the best data security for your tenants. In the shared membership database, add a table that is keyed on unique appid for each tenant and use this table to store and retrieve the connection string based on the current tenant.
4b. Store all data in one database and key each table on the unique tenant id. This provides slightly less data security for your tenants but uses only one SQL Server license.
You will prob find these links useful.

Where to store logged user information on ASP.NET MVC using Forms Authentication?

I'm using ASP.NET MVC and Forms Authentication on my application. Basically I use FormsAuthentication.SetAuthCookie to login and FormsAuthentication.SignOut to logout.
In the HttpContext.Current.User.Identity I have stored the user name but I need more info about the logged user. I don't want to store my entire User obj in the Session because it might be big and with much more infomation than I need.
Do you think it's a good idea to create like a class called LoggedUserInfo with only the attributes I need and then add it to the Session variable? Is this a good approach?
Or do you have better ideas?
I use this solution:
ASP.NET 2.0 Forms authentication - Keeping it customized yet simple
To summarize: I created my own IPrincipal implementation. It is stored in HttpContext.Current.Cache. If it is somehow lost, I have username from client side authorization cookie and can rebuild it. This solution doesn't rely on Session, which can be easily lost.
EDIT
If you want to use your principal in your controller and make it testable, you can do this:
private MyPrincipal _myPrincipal;
MyPrincipal MyPrincipal
{
get
{
if (_myPrincipal == null)
return (MyPrincipal)User;
return _myPrincipal;
}
set
{
_myPrincipal = value;
}
}
In your test, you will set object prepared for testing. Otherwise it will be taken from HttpContext. And now I started thinking, why do I use Ninject to do it?
Store it server side in the session.
Eg.
// Make this as light as possible and store only what you need
public class UserCedentials
{
public string Username { get; set; }
public string SomeOtherInfo { get; set; }
// etc...
}
Then when they sign in just do the following to save the users info:
// Should make typesafe accessors for your session objects but you will
// get the point from this example
Session["UserCredentials"] = new UserCredentials()
{ Username = "SomeUserName", SomeOtherInfo = "SomeMoreData" };
Then whenever you need it fetch it:
UserCredentials user = (UserCredentials)(Session["UserCredentials"]);
I have written a couple of question/answers regarding doing custom authorization in MVC:
How to implement authorization checks in ASP.NET MVC based on Session data?
How does the Authorize tag work? - Asp.net Mvc
I actually like to use a CustomPrincipal and CustomIdentity which I set in the logon action method like
if (!String.IsNullOrEmpty(username) && !String.IsNullOrEmpty(password) && _authService.IsValidLogin(username, password))
{
User objUser = _userService.GetUserByName(username);
if (objUser != null)
{
//** Construct the userdata string
string userData = objUser.RoleName + "|" + objUser.DistrictID + "|" + objUser.DistrictName + "|" + objUser.ID + "|" + objUser.DisplayName;
HttpCookie authCookie = FormsAuthentication.GetAuthCookie(username, rememberMe.GetValueOrDefault());
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, userData);
authCookie.Value = FormsAuthentication.Encrypt(newTicket);
Response.Cookies.Add(authCookie);
return RedirectToAction("Index", "Absence");
}
else
{
return RedirectToAction("LogOn", "Account");
}
}
else
{
return RedirectToAction("LogOn", "Account");
}
Then in the custom principal you can have methods that access specific information you passed in to the constructor like
((CustomIdentity)((CustomPrincipal)HttpContext.Current.User).Identity).DisplayName;
where the DisplayName property is declared in the CustomIdentity class.
Well you will have to store these somewhere. Two main possible places though:
The server
You can either put them into Session. I suggest you do create a separate class that will hold only data that you actually need to avoid of wasting too much memory. Or you can also store into Cache that can end up in having many DB calls when there are huge amounts of concurrent users.
The client
In this case if you can limit the amount of data with a separate class, to that and use whatever way to serialize it and send it to the client. Either in a cookie or in URI (if length permits and cookies are disabled)...
Outcome of these thoughts:
the main thing here would be to create a separate class if you gain much memory resources this way. So that's the first thing you should do.

Resources