I've been trying to get my feet wet with ASP.net MVC 5 for 2013, but so far I've failed to get even the most basic authentication working correctly.
I've been reading around for the last few days and I finally stumbled upon (http://brockallen.com/2013/10/24/a-primer-on-owin-cookie-authentication-middleware-for-the-asp-net-developer/) which seemed to give the most basic simple example I could find. So I tried that, but it still fails to seem to actually create a session for the user.
Here is my cookie config
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/FooBar")
});
}
Here is my basic custom login.
public class LoginController : ApiController
{
private IAuthenticationManager Authentication
{
get { return Request.GetOwinContext().Authentication; }
}
// POST api/login
public void Post([FromBody]LoginInfo email)
{
var fooBar = Authentication.User;
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "name")
,new Claim(ClaimTypes.Email, "email@email.com")
,new Claim(ClaimTypes.Role, "Foo")
};
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
Authentication.SignIn(new AuthenticationProperties() { IsPersistent = true }, identity);
}
}
If I hit the the login api twice I would have expected the second time that the fooBar variable to be set to a user who is marked as authenticated, but when I check it, it just says it's not authenticated, and it doesn't have any of the claims I would have expected.
I also tried creating a basic service to just check to see if it was authenticated in case I misunderstood how it worked, but this also fails. If I try to go to it, it says I'm not authenticated, it doesn't redirect me as I thought it would.
public class TestController : ApiController
{
[Authorize(Roles = "Foo")]
public int Get()
{
return 1;
}
}
I'm sure I must just be missing some basic, but so far no matter what I fiddled with and regardless of the various guides and advice I've seen online, nothing has been able to get even this simple scenario working. Any ideas on what I'm doing wrong?
In the following post http://www.khalidabuhakmeh.com/asp-net-mvc-5-authentication-breakdown there is an helpful OWIN examples.
I did a mistake, the correct link is: http://www.khalidabuhakmeh.com/asp-net-mvc-5-authentication-breakdown-part-deux So, here we go with vb approach for basic cookie login:
a) Cookie Config.
Imports Microsoft.AspNet.Identity
Imports Microsoft.Owin
Imports Microsoft.Owin.Security.Cookies
Imports Owin
Partial Public Class Startup
Public Sub ConfigureAuth(app As IAppBuilder)
app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
.LoginPath = New PathString("/Account/Login")})
End Sub
End Class
b) Home controller (Home index available for auth users)
<Authorize>
Public Class HomeController
Inherits System.Web.Mvc.Controller
<HttpGet>
Function Index() As ActionResult
Return View()
End Function
End Class
c) Account controller (Login)
Imports System.Security.Claims
Imports System.Threading.Tasks
Imports Microsoft.AspNet.Identity
Imports Microsoft.AspNet.Identity.Owin
Imports Microsoft.Owin.Security
<Authorize>
Public Class AccountController
Inherits Controller
Private Function AuthenticationManager() As IAuthenticationManager
Return HttpContext.GetOwinContext().Authentication
End Function
<AllowAnonymous>
Public Function Login(returnUrl As String) As ActionResult
ViewBag.ReturnUrl = returnUrl
Return View()
End Function
<HttpPost>
<AllowAnonymous>
<ValidateAntiForgeryToken>
Public Function Login(model As LoginViewModel, returnUrl As String) As ActionResult
If ModelState.IsValid Then
If model.UsuarioValido Then 'Local authentication, this must be on Repository class
Dim Identidad = New ClaimsIdentity({New Claim(ClaimTypes.Name, model.UserName)},
DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name,
ClaimTypes.Role)
Identidad.AddClaim(New Claim(ClaimTypes.Role, "Invitado"))
AuthenticationManager.SignIn(New AuthenticationProperties() With {.IsPersistent = model.RememberMe}, Identidad)
Return RedirectToAction("index", "home")
End If
End If
Return RedirectToAction("login", model)
End Function
<HttpGet>
Public Function LogOff() As ActionResult
AuthenticationManager.SignOut()
Return RedirectToAction("login")
End Function
End Class
d) Account Model
Imports System.ComponentModel.DataAnnotations
Public Class LoginViewModel
<Required>
<Display(Name:="Nombre de usuario")>
Public Property UserName As String
<Required>
<DataType(DataType.Password)>
<Display(Name:="Contraseña")>
Public Property Password As String
<Display(Name:="¿Recordar cuenta?")>
Public Property RememberMe As Boolean
Public ReadOnly Property UsuarioValido As Boolean
Get
Return Password = "secreto" 'Password Here!
End Get
End Property
End Class
e) Index view
@Imports Microsoft.AspNet.Identity
@Code
ViewData("Title") = "Página Inicial"
End Code
<h2>Bienvenido @User.Identity.GetUserName()</h2>
<a href="@Url.Action("LogOff", "Account")">
Click para salir! (Cerrar Sesión)
</a>
f) Login View
@ModelType LoginViewModel
@Code
ViewBag.Title = "Iniciar sesión"
End Code
<h2>@ViewBag.Title.</h2>
<div class="row">
<div class="col-md-8">
<section id="loginForm">
@Using Html.BeginForm("Login", "Account", New With { .ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, New With {.class = "form-horizontal", .role = "form"})
@Html.AntiForgeryToken()
@<text>
<h4>Utilice una cuenta local para iniciar sesión.</h4>
<hr />
@Html.ValidationSummary(True)
<div class="form-group">
@Html.LabelFor(Function(m) m.UserName, New With {.class = "col-md-2 control-label"})
<div class="col-md-10">
@Html.TextBoxFor(Function(m) m.UserName, New With {.class = "form-control"})
@Html.ValidationMessageFor(Function(m) m.UserName)
</div>
</div>
<div class="form-group">
@Html.LabelFor(Function(m) m.Password, New With {.class = "col-md-2 control-label"})
<div class="col-md-10">
@Html.PasswordFor(Function(m) m.Password, New With {.class = "form-control"})
@Html.ValidationMessageFor(Function(m) m.Password)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
@Html.CheckBoxFor(Function(m) m.RememberMe)
@Html.LabelFor(Function(m) m.RememberMe)
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Iniciar sesión" class="btn btn-default" />
</div>
</div>
</text>
End Using
</section>
</div>
</div>
@Section Scripts
@Scripts.Render("~/bundles/jqueryval")
End Section
I have had similar problems
I could not figure out what was different between my app (I inherited from someone else) and the default sample code.
I discovered that my [Authorize]
attribute was not being applied by the framework even when the rest of the user management stack was working.
I realised eventually that AuthorizeAttribute
is an example of a Filter
and that by adding it explicitly in the FilterConfig
it started to be used as expected (even though it is not added by the default sample code):
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new AuthorizeAttribute());
}
}
and everything as usual in the Application_Start method:
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
Edit:
Although this lets the basic Authorize attribute work, it leads to a problem where the framework isn't instantiating the attribute per-method call with the Roles property set. So I had to figure out what caused the problem. It was due to some Unity setup code:
var oldProvider = FilterProviders.Providers.Single(f => f is FilterAttributeFilterProvider);
FilterProviders.Providers.Remove(oldProvider);
Removing this (it was actually unused) fixed the issue, so I no longer needed FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
either