401 code not handled in mvc project properly - asp.net-mvc-4

I have ASP.NET MVC4 project with custom AuthorizeAttribute to control the authorization. In order to explain my situation easier I created a dummy project to illustrate the issue.
I have one single controller
using System.Web.Mvc;
using MvcApplication2.Helper;
using MvcApplication2.Models;
namespace MvcApplication2.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new ViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(ViewModel model)
{
Session["ModelValue"] = model.InputValue;
return RedirectToAction("Step2");
}
[MyAuthorize]
public ActionResult Step2()
{
return View();
}
}
}
The purpose is very simple, From Index I accept some input, store the value in a session and redirect to Step2. I have my authorize attribute on step 2. The code for my attribute is
using System;
using System.Web;
using System.Web.Mvc;
namespace MvcApplication2.Helper
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Session["ModelValue"] == null)
{
return false;
}
else
{
string inputValue = httpContext.Session["ModelValue"] as string;
if (inputValue != "1")
{
return false;
}
else
{
return true;
}
}
}
}
}
The purpose is very simple, check if the session value is 1 or not.
Run the application, you input 1 in textbox and you see step2 view, if you input anything else you get the default 401.0 page.
Now I opened the web.config file to include
<system.web>
<customErrors mode="On" defaultRedirect="~/Error">
<error statusCode="401" redirect="~/401.htm" />
</customErrors>
<compilation debug="true" targetFramework="4.0" />
</system.web>
I hope when the application captures 401 code, I should see the content of 401.htm. But the reality is that I still get the default 401 error display from server.
Any idea why?

In addition use this:
<system.webServer>
<httpErrors>
<error statusCode="401" path="~/Home/Login"/>
<error statusCode="404" path="~/Error/NotFound"/>
</httpErrors>
</system.webServer>

Related

ASP.NET MVC Custom configuration GetSection returns null

I'm working on an inherited ASP.NET MVC 4 project using .net framework 4.5.
We've added a new configuration section files and relevant class files and from what we can tell (docs.Microsoft and other online guides) it's set up correctly.
The Problem
ConfigurationManager.GetSection() returns null.
According to the docs this returns null if the section doesn't exist. Troubleshooting this has been troublesome.
The Code
The website is an ASP.NET Web Application. Properties window sets assembly name to Client.Project.UI.Base (which is the DLL in the published bin). This is the assembly name used for the config types FQN and assembly in web.config.
NB: the config section SupportCaseConfiguration was originally in a separate file and the SupportTickets section just specified the configSource. This has been moved into the web.config to reduce the number of potential issues while troubleshooting.
web.config:
<configSections>
<!-- define type for new section -->
<section name="SupportTickets" type="Client.Project.UI.Base.Infrastructure.Services.SupportCaseConfigurationSection, Client.Project.UI.Base"/>
</configSections>
<!-- new config section -->
<SupportTickets>
<SupportCaseConfiguration>
<caseTypes>
<add name="tenant.TestCase" label="Test Case" recipient="email_here" ccList="" bccList="" />
</caseTypes>
</SupportCaseConfiguration>
</SupportTickets>
SupportCaseConfiguration.cs:
namespace Client.Project.UI.Base.Infrastructure.Services
{
using System.Configuration;
//Extend the ConfigurationSection class.
public class SupportCaseConfigurationSection : ConfigurationSection
{
[ConfigurationProperty("caseTypes", IsDefaultCollection = true)]
public CaseTypeElementCollection CaseTypes
{
get { return (CaseTypeElementCollection)this["caseTypes"]; }
}
}
//Extend the ConfigurationElementCollection class.
[ConfigurationCollection(typeof(CaseTypeElement))]
public class CaseTypeElementCollection : ConfigurationElementCollection
{
public CaseTypeElement this[int index]
{
get { return (CaseTypeElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
BaseRemoveAt(index);
BaseAdd(index, value);
}
}
protected override ConfigurationElement CreateNewElement()
{
return new CaseTypeElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((CaseTypeElement)element).Name;
}
}
//Extend the ConfigurationElement class. This class represents a single element in the collection.
public class CaseTypeElement : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true)]
public string Name
{
get { return (string)this["name"]; }
set { this["name"] = value; }
}
[ConfigurationProperty("label", IsRequired = true)]
public string Label
{
get { return (string)this["label"]; }
set { this["label"] = value; }
}
[ConfigurationProperty("recipient", IsRequired = true)]
public string Recipient
{
get { return (string)this["recipient"]; }
set { this["recipient"] = value; }
}
[ConfigurationProperty("ccList", IsRequired = true)]
public string CcList
{
get { return (string)this["ccList"]; }
set { this["ccList"] = value; }
}
[ConfigurationProperty("bccList", IsRequired = true)]
public string BccList
{
get { return (string)this["bccList"]; }
set { this["bccList"] = value; }
}
}
}
Elsewhere, getting new config data:
SupportCaseConfigurationSection supportTicketsConfigurationSection = ConfigurationManager.GetSection("SupportCaseConfiguration") as SupportCaseConfigurationSection;
The site is being published locally, I can attach a debugger to ensure the latest versions of files are being used. I can see the config section in the published web.config.
I've been looking at this I can no longer see if anything is amiss. It all looks fine for me...
Any ideas, troubleshooting tips or even pointing out I'm being a muppet would be useful.
Cheers.
I can only assume the issue was something to do with the config classes in the site assembly.
Even after trying online examples, copypasting into the project, not even they worked.
As soon as I put the configuration section/element classes in a separate project (moved out of the website project) it started working.

MVC4 simplemembership not working after publish

I have the following problem with using MVC4 SimpleMembership: Register and Login methods work fine in local debugg mode, but after publish they crash the site. The cause from what i saw is that for example when i register an user the information is stored in my UserProfile table but nothing in webpages_Membership table. So it would appear that the WebSecurity can't access for some reason the webpages_Membership table.
I configured a basic MVC4 project to use SimpleMembership by enabling it in my WebConfig file like this:
<membership defaultProvider="DefaultMembershipProvider">
<providers>
<add name="DefaultMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
</providers>
</membership>
<roleManager defaultProvider="DefaultRoleProvider">
<providers>
<add name="DefaultRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData" />
</providers>
</roleManager>
Added my connectionstring:
<add name="RMBase" providerName="System.Data.SqlClient" connectionString="Data Source=SQL5003.Smarterasp.net;Initial Catalog=DB_9C0558_test;User Id=DB_9C0558_test_admin;Password=*****" />
Initialised the connection in Global.asax:
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
WebSecurityConfig.InitializeDatabase();
And in my App_Start folder i added the class for WebSecurityConfig
public static void InitializeDatabase()
{
WebSecurity.InitializeDatabaseConnection("RMBase", "UserProfile", "UserID", "UserName", true);
}
Added my DBContext class
public class RMDatabase : DbContext
{
public RMDatabase() : base("RMBase") { }
}
And my Login/Register methods look like this:
[HttpGet]
public ActionResult Login()
{
return View(new Authentication());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(Authentication model)
{
if (!ModelState.IsValid)
{
return View(model);
}
if (WebSecurity.Login(model.Username, model.Password))
{
return RedirectToAction("Index","Home");
}
else
{
ViewBag.ErrorMessage = "incorect";
return View(model);
}
}
[HttpGet]
public ActionResult Register()
{
return View(new Registration());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Register(Registration model)
{
if (ModelState.IsValid)
{
if (!WebSecurity.UserExists(model.Username))
{
WebSecurity.CreateUserAndAccount(model.Username, model.Password);
WebSecurity.Login(model.Username, model.Password);
return RedirectToAction("Index","Home");
}
ViewBag.ErrorMessage = "incorect";
}
return View(model);
}
Anyone see something that i did wrong that may be the cause of this?
Install Elmah.MVC, then in the Application_Error event in Global.asax, add
var exception = Server.GetLastError();
Elmah.ErrorSignal.FromCurrentContext().Raise(exception);
Then, on the site, navigate to /elmah, which should now have the exception logged for you.
After following Tieson T. advice i found that i had System.Web.Helpers, Version=2.0.0.0 missing from my web project referencess. Added it and all is good. Still don't understand how it was working in local debugg but i am glad that it was fixed.

Custom Membership Provider with Windows authetication

I've created my own Membership Provider where I have below method:
public override bool ValidateUser(string username, string password)
{
if (username == "John")
return true;
else
return false;
}
I've also added below lines to web.config file:
<authentication mode="Windows" />
<authorization>
<deny users="?" />
</authorization>
<membership defaultProvider="MembershipProviter">
<providers>
<clear />
<add name="cls_MembershipProvider" type="App.cls_MembershipProvider"
enablePasswordRetrieval="false"
enablePasswordReset="false"
requiresQuestionAndAnswer="false"
requiresUniqueEmail="false"
maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="5"
minRequiredNonalphanumericCharacters="0"
passwordAttemptWindow="10"
applicationName="App"
/>
</providers>
</membership>
As you may notice I am using Windows authentication and I don't have Log In page. By default all users from Active Directory has access to the page. My goal is to check if user exist in my database.
Everywhere I searched, there is Log In page, where ValidateUser is launched. My question is where should I implement ValidateUser method as I don't have Log In page. I just want to have control on each Controler method so I could add [Authorize] so only users from my database can actually access the page.
You can define your own CustomAuthorizeAttribute deriving from AuthorizeAttribute. Override OnAuthorization method to perform validation using details in context. Apply your custom filter on top of each controller or define a BaseController and derive your controllers from BaseController. For example you can define a class like:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class RdbiAuthorizationAttribute : AuthorizeAttribute
{
/// <summary>
/// Verifies that the logged in user is a valid organization user.
/// </summary>
/// <param name="filterContext"></param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
Guard.ArgumentNotNull(filterContext, "filterContext");
Guard.ArgumentNotNull(filterContext.Controller, "filterContext.Controller");
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(
typeof(AllowAnonymousAttribute), inherit: true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(
typeof(AllowAnonymousAttribute), inherit: true);
if (skipAuthorization)
{
return;
}
if (string.IsNullOrEmpty(filterContext.HttpContext.User.Identity.Name))
throw new AuthenticationException("User must be logged in to access this page.");
var controller = filterContext.Controller as BaseController;
if (controller != null)
{
var user = controller.GetUser();
if (user == null)
{
throw new InvalidOperationException(string.Format("Logged in user {0} is not a valid user", filterContext.HttpContext.User.Identity.Name));
}
}
base.OnAuthorization(filterContext);
}
}
Then you can define controller like:
[RdbiAuthorization]
public class BaseController : Controller
{
}
public class MyTestController : BaseController
{
}

How can I redirect users, who or not logged in to login page, in C# MVC 4.5 if they try to access other site pages via URL

I have one website, where I want users to be redirected to "Login" page if they are not signed in. The users may try to access the webpages by posting url. I want to do it in C# MVC 4.5
Here I dont want the action "[Authorize]" to be available unless signed in.
It is index action to view index page.
//Login Controller
[AllowAnonymous]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(FormCollection frm)
{
....//other code
}
[Authorize]
public ActionResult Index()
{
List<DTO> obj = objConfigurator.GetDataList();
return View(obj);
}
public ActionResult Edit(int Id)
{
DTO obj = objC.GetListById(Id);
return View(obj);
}
Use the [Authorize] attribute on your controller.
[Authorize]
public class YourController: Controller
{
. . .
[AllowAnonymous]
public ActionResult Register()
{
}
[AllowAnonymous]
public ActionResult LogIn()
{
}
. . .
}
Also, you have to add your login page in the web.config -
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Login" timeout="2880" />
</authentication>
</system.web>
You have another, even better option, to register AuthorizeAttribute as global filter in the global.asax file.
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
....
filters.Add(new System.Web.Mvc.AuthorizeAttribute());
}
This way, you only have to apply the [AllowAnonymous] to actions tha you want to be visited by anonimous users.

MVC 4 Intranet Authentication with Custom Roles

I have spent some time searching and found a lot of confusing answers, so I will post here for clarification.
I am using MVC4 VS2012 created an Intranet site using domain authentication. Everything works. However, to manage the users that have access to different areas of this webapp I prefer not to use AD groups that I cannot manage and nor can the users of the webapp.
Is there an alternative? I assume this would involve associating/storing domain names belonging to custom roles and using the Authorize attribute to control access.
[Authorize(Roles = "Managers")]
Can anyone suggest the best pattern for this or point me in the right direction?
I see a similar solution link, but I am still not sure how to use this against a stored list of roles and validate the user against those roles. Can anyone elaborate if this solution would work?
protected void Application_AuthenticateRequest(object sender, EventArgs args)
{
if (HttpContext.Current != null)
{
String[] roles = GetRolesFromSomeDataTable(HttpContext.Current.User.Identity.Name);
GenericPrincipal principal = new GenericPrincipal(HttpContext.Current.User.Identity, roles);
Thread.CurrentPrincipal = HttpContext.Current.User = principal;
}
}
I'm using this configuration with SQL Server and MVC3.
Web.config:
<system.web>
<roleManager enabled="true" defaultProvider="SqlRoleManager">
<providers>
<clear />
<add name="SqlRoleManager" type="System.Web.Security.SqlRoleProvider" connectionStringName="SqlRoleManagerConnection" applicationName="YourAppName" />
</providers>
</roleManager>
....
<authentication mode="Windows" />
....
<connectionStrings>
<add name="SqlRoleManagerConnection" connectionString="Data Source=YourDBServer;Initial Catalog=AppServices;Integrated Security=True;" providerName=".NET Framework Data Provider for OLE DB" />
</connectionStrings>
To inicialize roles:
Global.asax.cs
using System.Web.Security;
////
protected void Application_Start()
{
//You could run this code one time and then manage the rest in your application.
// For example:
// Roles.CreateRole("Administrator");
// Roles.AddUserToRole("YourDomain\\AdminUser", "Administrator");
Roles.CreateRole("CustomRole");
Roles.AddUserToRole("YourDomain\\DomainUser", "CustomRole");
}
In your Controller
[Authorize(Roles ="CustomRole")]
public class HomeController : Controller
{
To manage users
public class Usuario
{
public string UserName { get; set; }
public string RoleName { get; set; }
public string Name { get; set; }
public const string Domain = "YourDomain\\";
public void Delete()
{
Roles.RemoveUserFromRole(this.UserName, this.RoleName);
}
public void Save()
{
if (Roles.IsUserInRole(Usuario.Domain + this.UserName, this.RoleName) == false)
{
Roles.AddUserToRole(Usuario.Domain + this.UserName, this.RoleName);
}
}
}
Users Class
public class Usuarios : List<Usuario>
{
public void GetUsuarios() //Get application's users
{
if (Roles.RoleExists("CustomRole"))
{
foreach (string _usuario in Roles.GetUsersInRole("CustomRole"))
{
var usuario = new Usuario();
usuario.UserName = _usuario;
usuario.RoleName = "CustomRole";
this.Add(usuario);
}
}
//
public void GetUsuariosRed() //Get Network Users (AD)
{
var domainContext = new PrincipalContext(ContextType.Domain);
var groupPrincipal = GroupPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, "Domain Users");
foreach (var item in groupPrincipal.Members)
{
var usuario = new Usuario();
usuario.UserName = item.SamAccountName;
usuario.Name = item.Name;
this.Add(usuario);
}
}
You can create an "Admin" controller like this, to manage the users:
[Authorize(Roles = "AdminCustomRole")]
public class AdminController : Controller
{
//
public ActionResult Index()
{
var Usuarios = new Usuarios();
Usuarios.GetUsuarios();
return View(Usuarios);
}
[HttpGet]
public ActionResult CreateUser()
{
var Usuarios = new Usuarios();
Usuarios.GetUsuariosRed();
return View(Usuarios);
}
//
In my application, custom roles are fixed.