Mvc, Authorize bounces authorized users - c#

I'm trying to make a section of a MVC 5 webpage restricted to users of a certain Active directory group, however the [Authorize] attribute (on controller) blocks logged in users aswell.
My Login page code behind looks as follows:
public class AccountController: Controller
{
[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)
{
ActiveDirectoryHelper ad = new ActiveDirectoryHelper();
if (Membership.ValidateUser(model.UserName, model.Password))
{
if (ad.CheckGroupMembership(model.UserName))
{
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("", "Credentials are correct but you are no authorised \n You Need membership in group: HKF-HIT-FortigateAPI-GS");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect");
}
}
// if we got this far, something failed, redisplay form
return View(model);
}
// POST: /Account/LogOff
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
}
public class ActiveDirectoryHelper
{
string group = "HKF-HIT-FortigateAPI-GS";
public bool CheckGroupMembership(string name)
{
var context = new PrincipalContext(
ContextType.Domain,
"AD-Domain", #"Username", "Password");
var userPrincipal = UserPrincipal.FindByIdentity(
context,
IdentityType.SamAccountName,
name);
var test = userPrincipal;
if (userPrincipal.IsMemberOf(context,
IdentityType.Name,
group))
{
return true;
}
return false;
}
}
The user passes and is redirected to Index in Home controller.
This controller however has the [Authorized] value set as follows:
[Authorize]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
And here the user in bounced back to the loginpage, as if he was not Authorized.
Also this is web.config:
In the browser i can see the ADAuthCookie.
Edit: Ading pictures of Request data:
Account Post:
Fiddler:
Index Get:
Fiddler:
EDIT: Question has been solved, after going trough the amazing guide linked by in the comments i realised i was never handling my cooke in the Global.asaz.cs Class.
Adding an overide to Application_PostAuthenticateRequest solved my problem.
The code i added ended up using:
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JavaScriptSerializer serializer = new JavaScriptSerializer();
CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
newUser.Name = serializeModel.Name;
HttpContext.Current.User = newUser;
}
}
In global.asax and i also added:
CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
serializeModel.Name = model.UserName;
JavaScriptSerializer serializer = new JavaScriptSerializer();
string userData = serializer.Serialize(serializeModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
model.UserName,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
To my login page.

AuthorizeAttribute checks the HttpContext.User value (an IPrincipal implementation) and the HttpContext.User.Identity value (an IIdentity implementation).
All of the security frameworks (Identity, Membership, etc.) from Microsoft use these interfaces to communicate with MVC/ASP.NET. If you are using a custom security framework, you also need to implement these interfaces and set them in the AcquireRequestState (if using session state) or PostAuthorizeRequest event.
See ASP.NET MVC - Set custom IIdentity or IPrincipal for an example of the latter along with custom IPrincipal and IIdentity implementations.

Related

Username auto fill in Login Page after Register success (Redirect to Login) in asp.net mvc

I redirected to my login page after register for success. In fact, how can I carry my registered username when I redirect to the login page and show it in the Login page username input.
My controller
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Register(Account account)
{
if (!db.Accounts.Any(x => x.Username.Equals(account.Username)))
{
Account acc = new Account()
{
Username = account.Username,
Password = account.Password,
CreatedDate = DateTime.UtcNow,
IsDeleted = false,
};
db.Accounts.Add(acc);
db.SaveChanges();
FormsAuthentication.SetAuthCookie(acc.AccountID.ToString(), false);
ViewBag.Success = "User registered. You may login now!";
return RedirectToAction("Login", "Account");
}
else
{
ViewBag.Error = "User already exist!";
return View();
}
}
Login Username Input
<input type="text" name="Username" class="form-control" placeholder="Username">
You may create an overload for Login.
public ActionResult Login(string userName)
{
return View();
}
[HttpPost]
public ActionResult Register(Account account)
{
...
return RedirectToAction("Login", "Account", new {userName=account.Username});
}
EDIT: To hide username value you could apply basic base64 encoding.
public string Encode(string strToEncode)
{
byte[] encodedVal = System.Text.Encoding.UTF8.GetBytes(strToEncode);
return Convert.ToBase64String(encodedVal);
}
public string Decode(string strToDecode)
{
byte[] decodedVal = Convert.FromBase64String(strToDecode);
return System.Text.Encoding.UTF8.GetString(decodedVal);
}
public ActionResult Login(string userName)
{
var userNameDecoded = Decode(userName);
return View();
}
[HttpPost]
public ActionResult Register(Account account)
{
...
return RedirectToAction("Login", "Account", new {userName=Encode(account.Username)});
}
There are two way to get the username in Login action result.
To pass the anonymous object in RedirectToAction.
return RedirectToAction("Login", "Account", new {username=account.username});
To fetch the username from FormsAuthentication ticket
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
// Get the forms authentication ticket.
FormsAuthenticationTicket authTicket =
FormsAuthentication.Decrypt(authCookie.Value);
var identity = new GenericIdentity(authTicket.Name, "Forms");
var principal = new MyPrincipal(identity);
// Get the custom user data encrypted in the ticket.
string userData = ((FormsIdentity)(Context.User.Identity)).Ticket.UserData;
// Deserialize the json data and set it on the custom principal.
var serializer = new JavaScriptSerializer();
principal.User = (User)serializer.Deserialize(userData, typeof(User));
// Set the context user.
Context.User = principal;
}
Here two ways commonly used for send data form one controller to another controller
You can pass data return RedirectToAction("Login", "Account", new {userName=account.Username}); and get it
public ActionResult Login(string userName)
{
return View();
}
Or You can simply use tempdata
Set ==>
TempData["UserName"] = account.Username;
Get ==> var userName = TempData["UserName"].ToString();
First when you redirect to login action pass username to it and receive username in login action then store the username in viewBag. On view set text value using viewBag
public ActionResult Login(string UserName)
{
ViewBag.UserName=username;
return View();
}
[HttpPost]
public ActionResult Register(Account account)
{
if (!db.Accounts.Any(x => x.Username.Equals(account.Username)))
{
Account acc = new Account()
{
Username = account.Username,
Password = account.Password,
CreatedDate = DateTime.UtcNow,
IsDeleted = false,
};
db.Accounts.Add(acc);
db.SaveChanges();
FormsAuthentication.SetAuthCookie(acc.AccountID.ToString(), false);
ViewBag.Success = "User registered. You may login now!";
return RedirectToAction("Login", "Account",account.Username);
}
else
{
ViewBag.Error = "User already exist!";
return View();
}
}
On View
<input type="text" name="Username" class="form-control" value="#ViewBag.UserName" placeholder="Username">

Role-Based Authentication with aspnet identity in ASP.NET MVC 4

I'm creating ASP.NET MVC 4 Internet Application.
In that Application I created Login Page that any user can log in to, then I allowed to redirect user to different pages based on their role.
ASP.NET Identity is the membership system here.
This is my Login Controller method:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindAsync(model.UserName, model.Password);
if (user != null)
{
if (user.ConfirmedEmail == true)
{
await SignInAsync(user, model.RememberMe);
if (String.IsNullOrEmpty(returnUrl))
{
if (UserManager.IsInRole(user.Id, "HEC_Admin"))
{
return RedirectToAction("Index", "HEC");
}
//role Admin go to Admin page
if (UserManager.IsInRole(user.Id, "HEI_User"))
{
return RedirectToAction("Index", "HEI");
}
}
else
{
return RedirectToLocal(returnUrl);
}
}
else
{
ModelState.AddModelError("", "Confirm Email Address.");
}
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
This is HEI Controller Class:
public class HEIController : Controller
{
//
// GET: /HEI/
[Authorize(Roles = "HEI_User")]
public ActionResult Index()
{
return View();
}
}
This is my HEC Controller Class:
public class HECController : Controller
{
//
// GET: /HEC/
[Authorize(Roles = "HEC_Admin")]
public ActionResult Index()
{
return View();
}
}
when I remove [Authorize(Roles = "HEC_Admin")] above the index action in HECController class and when I remove [Authorize(Roles = "HEC_User")] above the index action in HEIController class this is working fine,
but then How restrict unauthorized access to these pages?
I had the same problem as you and I still don't know the reason why it happens. What I did was to create my own custom Authorization Attribute and check the Roles myself.
public class CustomAuthorizationAttribute : AuthorizeAttribute
{
public string IdentityRoles
{
get { return _identityRoles ?? String.Empty; }
set
{
_identityRoles = value;
_identityRolesSplit = SplitString(value);
}
}
private string _identityRoles;
private string[] _identityRolesSplit = new string[0];
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//do the base class AuthorizeCore first
var isAuthorized = base.AuthorizeCore(httpContext);
if (!isAuthorized)
{
return false;
}
if (_identityRolesSplit.Length > 0)
{
//get the UserManager
using(var um = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
var id = HttpContext.Current.User.Identity.GetUserId();
//get the Roles for this user
var roles = um.GetRoles(id);
//if the at least one of the Roles of the User is in the IdentityRoles list return true
if (_identityRolesSplit.Any(roles.Contains))
{
return true;
}
}
return false;
}
else
{
return true;
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//if the user is not logged in use the deafult HandleUnauthorizedRequest and redirect to the login page
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(filterContext);
}
else
//if the user is logged in but is trying to access a page he/she doesn't have the right for show the access denied page
{
filterContext.Result = new RedirectResult("/AccessDenied");
}
}
protected static string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return new string[0];
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}
I also added the HandleUnauthorizedRequest method to redirect to a appropriated page if the user has is logged in but has no access to this action or controller
To use it just do this:
[CustomAuthorization(IdentityRoles = "HEI_User")]
public ActionResult Index()
{
return View();
}
Hope it helps.

MVC 5 Identity 2.0 display profile completion at login

Using MVC 5 and Identity 2.0, I add custom properties to ApplicationUserClass like FirstName, LastName, Address. Those will be new fields in the database. When the user register to the application he/she will enter the email address and password only. After they register, and they log in I want to force them to complete their profile, or at least each time they login they should be redirected to the profile completion page where they can mention the FirstName, Lastname and Address. Afther they complete the profile they will not be redirected to complete profile page each time they login.
Something like:
if UserProfile != Completed
go to CompleteProfilePage
else
go to MainPage
You can try a global filter. This will not allow your users to bypass the check by modifying the URL manually.
public class ProfileCompletionCheckAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//this will prevent redirect for still unauthenticated users
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
return;
//replace these to actual values of your profile completion action and controller
string actionOfProfilePage = "Index";
string controlerOfProfilePage = "Home";
bool areWeAlreadyInProfilePage = filterContext.ActionDescriptor.ActionName == actionOfProfilePage
&& filterContext.ActionDescriptor.ControllerDescriptor.ControllerName == controlerOfProfilePage;
if (areWeAlreadyInProfilePage) //this will prevent redirect loop
return;
bool isProfileComplete = false; //replace this with your custom logic
if (!isProfileComplete)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", controlerOfProfilePage },
{ "action", actionOfProfilePage }
});
}
}
}
To enable it, just add this to FilterConfig.cs
filters.Add(new ProfileCompletionCheckAttribute());
In your AccountController something like this :
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
Upgrade like this
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
UsersContext dbu = new UsersContext();
UserProfile usr = dbu.UserProfiles.Where(u => u.UserName == model.UserName).First();
if (usr.FirstName == null) return RedirectToAction("Profile", "Account");
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}

ASP.NET MVC 5 - create own login

Hy,
I'm very new to MVC 5 (or any other MVC). I want to create my own custom login with registration. Can somebody point me to this?
The login should have a simple email and password textbox. The registration should have additional data like first/lastname, age, etc. which stored in a table (user) and a dropdownbox with roles to select (stored in table "roles"). After successful login/registration should the user be redirected to the dashboard.
Or is there a good tutorial about this for MVC 5 .. I just found one for MVC 4.
Thanks for help :)
Try this
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = MyViewModels.checkUser(model.UserName, model.Password);
if (user!=null)
{
SignInAsync();
return RedirectToAction("Welcome");
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
return View(model);
}
private void SignInAsync()
{
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, "UserName"));
claims.Add(new Claim(ClaimTypes.Email, "User#mail.com"));
var id = new ClaimsIdentity(claims,
DefaultAuthenticationTypes.ApplicationCookie);
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
}
[Authorize]
public ActionResult Welcome()
{
return View();
}
If you add [Authorize] attribute in the action, then it will redirect only the user name and password is authorize
Function to get user name and password from database
public static UserTable checkUser(string userName, string password)
{
DemoEntities db = new DemoEntities();
var query = (from u in db.UserTables
where u.UserName == userName && u.Password == password
select u).FirstOrDefault();
if(query!=null)
return query;
else
return null;
}

MVC 4.0 FormsAuthentication and AuthorizeAttribute in custom authorization

I am using MVC4 and I'm trying to modify the allocation procedure for authenticating a user and assign roles to users. Everything works fine for the attribute [Authorize (Users = "adminadmin")], but the [Authorize (Roles = "Admin")] every time there is a login page and lack of access.
Global.asax.cs:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// look if any security information exists for this request
if (HttpContext.Current.User != null)
{
// see if this user is authenticated, any authenticated cookie (ticket) exists for this user
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
// see if the authentication is done using FormsAuthentication
if (HttpContext.Current.User.Identity is FormsIdentity)
{
// Get the roles stored for this request from the ticket
// get the identity of the user
FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
//Get the form authentication ticket of the user
FormsAuthenticationTicket ticket = identity.Ticket;
//Get the roles stored as UserData into ticket
List<string> roles = new List<string>();
if (identity.Name == "adminadmin")
roles.Add("Admin");
//Create general prrincipal and assign it to current request
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(identity, roles.ToArray());
}
}
}
}
AccountController:
[InitializeSimpleMembership]
public class AccountController : Controller
{
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(LoginModel model, string returnUrl)
{
// Lets first check if the Model is valid or not
if (ModelState.IsValid)
{
string username = model.UserName;
string password = model.Password;
bool userValid = username == password ? true : false;
// User is valid
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");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
}
HomeController.cs:
public class HomeController : Controller
{
[AllowAnonymous]
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
[Authorize]
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
return View();
}
[Authorize(Roles = "Admin")]
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
Web.config:
(...)
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880"/>
</authentication>
(...)
You're almost there. Right now what's happening is that you set the principal to a custom principal, and the SimpleMembership provider comes in after you and blows away your principal by setting it to a System.Web.Security.RolePrincipal. Move your current Application_AuthenticateRequest code into a new Application_PostAuthenticateRequest handler and your custom principal will remain in place.
This is what you want (although it uses a custom membership)
http://mycodepad.wordpress.com/2014/05/17/mvc-custom-authorizeattribute-for-custom-authentication/

Categories