Forms Authentication
For .net 4.5+, see Owin
Web.config
<authentication mode="Forms">
<forms loginUrl="Login.aspx"/>
</authentication>
<authorization>
<deny users="?" />
</authorization>
In MVC, use a global action filter instead of <authorization>
ASP MVC
In ApplicationStart (App_Start/FilterConfig.cs) add a global Authorize action filter.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new AuthorizeAttribute());
}
}
Login page needs AllowAnonymous filters. For fun, this sets a custom cookie with roles.
[AllowAnonymous]
public ActionResult Login()
{
var model = new LoginModel();
return View(model);
}
[AllowAnonymous]
[HttpPost]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid) //Required, string length etc
{
var userStore = new UserRepository();
var user = userStore.FindUser(model.UserName, model.Password);
if (user != null)
{
//simple
//FormsAuthentication.SetAuthCookie(user.Name, false);
//manually set cookies
SetAuthCookie(user.Name, user.RolesList.ToArray());
//redirect to returnUrl
if (!string.IsNullOrEmpty(returnUrl) &&
Url.IsLocalUrl(returnUrl) &&
!returnUrl.Equals("/Error/NotFound", StringComparison.OrdinalIgnoreCase))
{
return Redirect(returnUrl);
}
return Redirect("~/");
}
ModelState.AddModelError("UserName", "User or password not found");
}
return View(model);
}
private void SetAuthCookie(string userName, string[] roles)
{
var userData = string.Join(",", roles); //could JsonConvert.SerializeObject(obj)
var authTicket = new FormsAuthenticationTicket(
1, //version
userName,
DateTime.Now, //issue date
DateTime.Now.AddMinutes(30), //expiration
false, //isPersistent
userData,
FormsAuthentication.FormsCookiePath); //cookie path
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName,
FormsAuthentication.Encrypt(authTicket));
Response.Cookies.Add(cookie);
}
Sign-out is simple
public ActionResult SignOut()
{
FormsAuthentication.SignOut();
return Redirect("~/");
}
To go with the custom cookie, global.asax has the following. NB: AuthenticateRequest to create the principal (we let FormsAuthentication do it by default, PostAuthenticateRequest to add roles)
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
var context = HttpContext.Current;
if (context.User == null || !context.User.Identity.IsAuthenticated)
{
return;
}
var formsIdentity = context.User.Identity as FormsIdentity;
if (formsIdentity == null)
{
return;
}
var id = formsIdentity;
var ticket = id.Ticket;
var userData = ticket.UserData; // Get the stored user-data, in this case, our roles
var roles = userData.Split(',');
var userPrincipal = new GenericPrincipal(formsIdentity, roles);
//set both thread principal and HttpContext user
Thread.CurrentPrincipal = Context.User = userPrincipal;
}
Razor view
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("Secure", "Secure", "Home")</li>
</ul>
@if (User.Identity.IsAuthenticated)
{
<ul class="nav navbar-nav navbar-right">
<li class="navbar-text">Logged in as @User.Identity.Name</li>
<li>@Html.ActionLink("Sign out","SignOut","Account")</li>
</ul>
}
</div>
WebAPI
The MVC [System.Web.Mvc.Authorize()] attribute doesn't work on WebAPI, which has [System.Web.Http.Authorize()].
using System;
using System.Configuration;
using System.Web;
using System.Web.Http.Controllers;
namespace Website
{
/// <summary>
/// Restrict access to MVC controllers to roles specified in AppSettings SecurityRole:Write
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class WriteAuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (!isAuthorized)
{
return false;
}
var writeRole = ConfigurationManager.AppSettings["SecurityRole:Write"];
if (!string.IsNullOrEmpty(writeRole))
{
return httpContext.User.IsInRole(writeRole);
}
return true;
}
}
/// <summary>
/// Restrict access to WebAPI controllers to roles specified in AppSettings SecurityRole:Write
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class WriteAuthorizeApiAttribute : System.Web.Http.AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var isAuthorized = base.IsAuthorized(actionContext);
if (!isAuthorized)
{
return false;
}
var writeRole = ConfigurationManager.AppSettings["SecurityRole:Write"];
if (!string.IsNullOrEmpty(writeRole))
{
return actionContext.RequestContext.Principal.IsInRole(writeRole);
}
return true;
}
}
}
Add global filters onto GlobalConfiguration in WebApiConfig:
config.Filters.Add(new AuthorizeAttribute { Roles = "Reader" });
ASP Forms
In your login page :
protected void btnLogin_Click(object sender, EventArgs e)
{
string userName = txtUserName.Text.Trim();
string password = txtPassword.Text.Trim();
//if (Membership.ValidateUser(userName, password)) //membership provider
if (ValidateUser(userName, password)) //local validation
{
//if using redirection
FormsAuthentication.RedirectFromLoginPage(userName, false);
//otherwise just set cookie
//FormsAuthentication.SetAuthCookie(userName, false);
}
else
{
Page.Validators.Add(new BusinessValidationError("Invalid UserID and Password"));
}
}
To implement a custom MembershipProvider see msdn sample (and here for web.config to set the membership defaultProvider)
To logout manually (you can also just use the asp:LoginStatus control):
FormsAuthentication.SignOut();
Response.Redirect(FormsAuthentication.GetLoginPage(null));
//if you did NOT specify forms/@cookieless=UseCookies (or are using cookieless) you are automatically redirected
Roles
To do role management (User.IsInRole(x), authorization/allow/@roles, sitemap roles) you have to manually create a FormsAuthenticationTicket and put it in a cookie (RoleManager creates a separate role cookie). NB: you have about 1k of the 4k cookie size maximum for the role list- otherwise use Cache (you can't use Session, it isn't available early enough).
string userRoles = "Admin, PowerUser"; //from database?
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // version
userName, // user name
DateTime.Now, // issue time
DateTime.Now.AddMinutes(30),// expires
false, // persistent
userRoles // user data
);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
cookie.Secure = FormsAuthentication.RequireSSL;
cookie.Domain = FormsAuthentication.CookieDomain;
cookie.HttpOnly = true; //a little extra security
Response.Cookies.Add(cookie);
Response.Redirect("Secure.aspx");
Now wire this in the global.asax (or use an HttpModule)
void Application_OnAuthenticateRequest(object sender, EventArgs e)
{
HttpContext c = HttpContext.Current;
if (c.Request.IsAuthenticated)
{
FormsIdentity id = (FormsIdentity)c.User.Identity;
string[] roles = id.Ticket.UserData.Split(',');
System.Security.Principal.GenericPrincipal p =
new System.Security.Principal.GenericPrincipal(c.User.Identity, roles);
Context.User = System.Threading.Thread.CurrentPrincipal = p;
}
}
Simple Authentication In Web.Config
For quick and simple/dirty security, you can put users directly into the web.config.
Nested web.config: You must only set <authentication> on the top level. You can't set it on subfolder web.config or in <location>. You should instead use different <authorization> sections.
<authentication mode="Forms">
<forms loginUrl="~/Admin/Login.aspx">
<credentials passwordFormat="Clear">
<user name="Martin" password="secret"/>
</credentials>
</forms>
</authentication>
Then just use the login control.
<asp:Login ID="Login1" runat="server" DisplayRememberMe="False" VisibleWhenLoggedIn="False"
OnAuthenticate="Login1_Authenticate" />
<script runat="server">
protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
{
e.Authenticated = FormsAuthentication.Authenticate(Login1.UserName, Login1.Password);
}
</script>
Saving new users into web.config. If you're doing this, you really should be using a database, but hey ho. Also you really should use SHA1 format.
if (!Page.IsValid) return;
string userName = Server.HtmlEncode(txtName.Text);
string pw = Server.HtmlEncode(txtPassword.Text);
Configuration config = WebConfigurationManager.OpenWebConfiguration("~/");
//also (AuthenticationSection)WebConfigurationManager.GetWebApplicationSection("system.web/authentication") but users is readonly;
AuthenticationSection auth = (AuthenticationSection)config.SectionGroups["system.web"].Sections["authentication"];
string passwordFormat = auth.Forms.Credentials.PasswordFormat.ToString();
if (passwordFormat != "Clear")
pw = FormsAuthentication.HashPasswordForStoringInConfigFile(txtPassword.Text, passwordFormat);
auth.Forms.Credentials.Users.Add(new FormsAuthenticationUser(userName, pw));
try
{
config.Save();
Response.Redirect(Request.CurrentExecutionFilePath);// Redirect to self.
}
catch (ConfigurationErrorsException ex)
{
Label1.Text = "ASP.NET process account (ASPNET or Network Service) must have write permission granted for the Web.config file<br/>";
Label1.Text += "Manually copy this xml into your web.config forms/credentials section:<br/>";
Label1.Text += string.Format("<user name=\"{0}\" password=\"{1}\" />", userName, pw);
}