I am taking the concept of building a OWIN login from a empty MVC and I am just starting to add the part with using my database to login the user after creating a Identity claim to put in the URL.
This is my code to create the claim to login the user
public class AuthenticationController : Controller
{
IAuthenticationManager Authentication
{
get { return HttpContext.GetOwinContext().Authentication; }
}
[GET("Login")]
public ActionResult Show()
{
return View();
}
[POST("Login")]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel input)
{
if (ModelState.IsValid)
{
if (input.HasValidUsernameAndPassword())
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, input.Username),
},
DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name, ClaimTypes.Role);
// if you want roles, just add as many as you want here (for loop maybe?)
if (input.isAdministrator)
{
identity.AddClaim(new Claim(ClaimTypes.Role, "Admins"));
}
else
{
identity.AddClaim(new Claim(ClaimTypes.Role, "User"));
}
// tell OWIN the identity provider, optional
// identity.AddClaim(new Claim(IdentityProvider, "Simplest Auth"));
Authentication.SignIn(new AuthenticationProperties
{
IsPersistent = input.RememberMe
}, identity);
return RedirectToAction("show", "authentication");
}
}
return View("show", input);
}
[GET("logout")]
public ActionResult Logout()
{
Authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return RedirectToAction("Login");
}
}
Here is my code for the Show.cshtml
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Details</h4>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(model => model.Username, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Username)
@Html.ValidationMessageFor(model => model.Username)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Password, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Password)
@Html.ValidationMessageFor(model => model.Password)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.RememberMe, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.RememberMe)
@Html.ValidationMessageFor(model => model.RememberMe)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Login" class="btn btn-default" />
</div>
</div>
</div>
}
This is my code where I call my database and store my data for the logged in user
public class LoginModel
{
[Required]
public string Username { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
public bool RememberMe { get; set; }
public bool isAdministrator { get; set; }
public bool HasValidUsernameAndPassword()
{
bool result = false;
int countChecker = 0;
using (SqlConnection connection = new SqlConnection("server=server; database=db; user id=user; password=user"))
{
connection.Open();
using (SqlCommand command = new SqlCommand("SELECT count(*) FROM ACCOUNTS WHERE active = 1 AND UserName = @param1 AND PasswordField = @param2", connection))
{
command.Parameters.Clear();
command.Parameters.AddWithValue("@param1", Username);
command.Parameters.AddWithValue("@param2", Password);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
countChecker = Convert.ToInt32(reader[i].ToString());
}
}
if (countChecker > 0)
{
result = true;
using (SqlConnection connect = new SqlConnection("server=server; database=db; user id=user; password=user"))
{
connect.Open();
using (SqlCommand com = new SqlCommand("SELECT Administrator FROM ACCOUNTS WHERE active = 1 AND UserName = @param1 AND PasswordField = @param2", connect))
{
com.Parameters.Clear();
com.Parameters.AddWithValue("@param1", Username);
com.Parameters.AddWithValue("@param2", Password);
SqlDataReader read = com.ExecuteReader();
while (read.Read())
{
for (int i = 0; i < read.FieldCount; i++)
{
isAdministrator = Convert.ToBoolean(read[i].ToString());
}
}
}
connect.Dispose();
connect.Close();
}
}
else
{
result = false;
}
}
connection.Dispose();
connection.Close();
}
return result;
}
}
My Login(LoginModel input) completes and returns to Show.cshtml and then has a exception right here @Html.AntiForgeryToken(). The exception I get is:
A claim of type 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' or 'http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider' was not present on the provided ClaimsIdentity. To enable anti-forgery token support with claims-based authentication, please verify that the configured claims provider is providing both of these claims on the ClaimsIdentity instances it generates. If the configured claims provider instead uses a different claim type as a unique identifier, it can be configured by setting the static property AntiForgeryConfig.UniqueClaimTypeIdentifier.
I am new to working with MVCs and I can't tell which one of the two is missing from ActionResult Login(LoginModel input). Can someone please explain to me which one I am missing.
I added the suggestion from Corey to my Global.asax:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
AttributeRoutingConfig.RegisterRoutes(RouteTable.Routes);
}
}
Now I get this:
Additional information: A claim of type 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' was not present on the provided ClaimsIdentity.
I changed the AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier; to AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name; and I don't get any more exceptions. Would this be the correct syntax?
This article might help: