I am trying to setup an intranet MVC app that authenticates against our company's AD with forms authentication; we do want the user to have to login. I am getting the following exception when posting to the Login action: "To call this method, the "Membership.Provider" property must be an instance of "ExtendedMembershipProvider"." Anyone else had this problem?
Web.config:
<connectionStrings>
<add name="ADConnectionString" connectionString="LDAP://example.domain.com/DC=example,DC=domain,DC=com"/>
</connectionStrings>
<appSettings>
<add key="enableSimpleMembership" value="false" />
</appSettings>
<system.web>
<authentication mode="Forms">
<forms name=".ADAuthCookie" loginUrl="~/Account/Login" timeout="45" slidingExpiration="false" protection="All"/>
</authentication>
<membership defaultProvider="ADMembershipProvider">
<providers>
<clear/>
<add name="ADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider" connectionStringName="ADConnectionString" attributeMapUsername="sAMAccountName"/>
</providers>
</membership>
AccountController:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
//The call to WebSecurity.Login is what throws
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);
}
Created an MVC3 site in VS2010 and got it working with the ActiveDirectoryMembershipProvider. Then changed the MVC4 AccountController to use the old System.Web.Security instead of WebMatrix.WebData.WebSecurity.
from:
[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);
}
to:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, 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);
}
My login was working. It was my logout that wasn't working. In any case, I changed WebSecurity.Logout() to FormsAuthentication.SignOut() and that worked.
Related
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.
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
{
}
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.
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>
I have some simple DbContext:
public class AuthContext : DbContext
{
public AuthContext() : base("AuthContext")
{
}
public DbSet<User> Users { get; set; }
}
And simple User model:
[Table("User")]
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string Login { get; set; }
}
What I need is to seed data to WebSecurity always or after model creating. I have tried:
Database.SetInitializer(new AuthDbSeeder());
//--------------------------------------------------------
<add name="AuthContext" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=ChatAuth; Integrated Security=SSPI; MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
and in <system.web> I added:
<roleManager enabled="true" defaultProvider="SimpleRoleProvider">
<providers>
<clear/>
<add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/>
</providers>
</roleManager>
<membership defaultProvider="SimpleMembershipProvider">
<providers>
<clear/>
<add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
</providers>
</membership>
//--------------------------------------------------------
public class AuthDbSeeder : DropCreateDatabaseAlways<AuthContext>
{
protected override void Seed(AuthContext context)
{
WebSecurity.InitializeDatabaseConnection("AuthContext", "User", "UserId", "Login",
autoCreateTables: true);
WebSecurity.CreateUserAndAccount("Sergey", "1234");
But after all I have error that Database can't be droped because it is already in use. I need some working method to seed data to WebSecurity.
Also very important for me is: how I can add my custom models in the same DbContext and seed data to this context properly.
Also any ideas how I can Unit test WebSecurity?
There is an example on how to seed and customize WebSecurity here.
I have found the solution for the seed question
I reworked SimpleMembershipInitializer:
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
try
{
WebSecurity.InitializeDatabaseConnection("AuthContext", "User", "UserId", "Login",
autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
And in my seed method call
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
LazyInitializer.EnsureInitialized(ref initializer, ref isInitialized, ref initializerLock);
}
with null as the parameter.
Unit testing is still a problem