Ajax Login from MVC - ajax

I have an MVC project that uses the inbuilt forms authentication (which talks to the MDF database stored in App_data). I want to change the login form to be the Ajax login form so that I can take advantage of the "onSuccess" and "onFailure" options.
Does anyone have a working example of how I would achive this as I've tried previuosly but I can't get the form to authenticate it just does nothing. I think I may have missed a step so any help is appreciated.
Example code would also be benificial. Please find my current code below.
The login view
#model MyProject.Models.LoginViewModel
#using (Ajax.BeginForm("Login", "Account", null, new AjaxOptions
{
OnSuccess = "OnSuccess",
OnBegin = "OnBegin",
OnFailure = "OnFailure",
OnComplete = "OnComplete",
HttpMethod = "POST",
UpdateTargetId = "target"
}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Login Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.UserName)
#Html.TextBoxFor(m => m.UserName)
#Html.ValidationMessageFor(m => m.UserName)
</li>
<li>
#Html.LabelFor(m => m.Password)
#Html.PasswordFor(m => m.Password)
#Html.ValidationMessageFor(m => m.Password)
</li>
<li>
#Html.CheckBoxFor(m => m.RememberMe)
#Html.LabelFor(m => m.RememberMe, new { #class = "checkbox" })
</li>
</ol>
<input type="submit" value="Login" />
</fieldset>
}
Here is the login controller
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public JsonResult ValidateUser(string userid, string password,
bool rememberme)
{
LoginStatus status = new LoginStatus();
if (Membership.ValidateUser(userid, password))
{
FormsAuthentication.SetAuthCookie(userid, rememberme);
status.Success = true;
status.TargetURL = FormsAuthentication.
GetRedirectUrl(userid, rememberme);
if (string.IsNullOrEmpty(status.TargetURL))
{
status.TargetURL = FormsAuthentication.DefaultUrl;
}
status.Message = "Login attempt successful!";
}
else
{
status.Success = false;
status.Message = "Invalid UserID or Password!";
status.TargetURL = FormsAuthentication.LoginUrl;
}
return Json(status);
}
Here is the login view model
public class LoginStatus
{
public bool Success { get; set; }
public string Message { get; set; }
public string TargetURL { get; set; }
}
Script on the page for handling the form
$(document).ready(function () {
$("#login").click(function () {
$("#message").html("Logging in...");
var data = {
"UserName": $("#userid").val(),
"Password": $("#password").val(),
"RememberMe": $("#rememberme").prop("checked")
};
$.ajax({
url: "/Home/Index",
type: "POST",
data: JSON.stringify(data),
dataType: "json",
contentType: "application/json",
success: function (status) {
$("#message").html(status.Message);
if (status.Success)
{
window.location.href = status.TargetURL;
}
},
error: function () {
$("#message").html("Error while authenticating user credentials!");
}
});
});
});

I've an extensions (MvcNotification) that put into ViewData or TempData messages to display.
To complement this, my post actions return "ERROR" or "OK" and i use those messages inside the ajax form OnSuccess.
MessageType
public enum MessageType
{
Success,
Warning,
Error,
Info
}
AjaxMessagesFilter
/// <summary>
/// If we're dealing with ajax requests, any message that is in the view data goes to the http header.
/// </summary>
public class AjaxMessagesFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var viewData = filterContext.Controller.ViewData;
var response = filterContext.HttpContext.Response;
foreach (var messageType in Enum.GetNames(typeof(MessageType)))
{
var message = viewData.ContainsKey(messageType)
? viewData[messageType]
: null;
if (message != null) // We store only one message in the http header. First message that comes wins.
{
response.AddHeader("X-Message-Type", messageType.ToLower());
response.AddHeader("X-Message", HttpUtility.HtmlEncode(message.ToString()));
return;
}
}
}
}
}
ControllerExtensions
public static class ControllerExtensions
{
public static ActionResult ShowMessage(this Controller controller, MessageType messageType, string message, bool showAfterRedirect = false, bool UseJson = false)
{
var messageTypeKey = messageType.ToString();
if (showAfterRedirect)
{
controller.TempData[messageTypeKey] = message;
}
else
{
controller.ViewData[messageTypeKey] = message;
}
if (UseJson)
return new JsonResult() { Data = "ERROR", JsonRequestBehavior = JsonRequestBehavior.AllowGet };
else
return new ContentResult() { Content = "ERROR" };
}
public static ActionResult ShowMessage(this ControllerBase controller, MessageType messageType, string message, bool showAfterRedirect = false, bool UseJson = false)
{
var messageTypeKey = messageType.ToString();
if (showAfterRedirect)
{
controller.TempData[messageTypeKey] = message;
}
else
{
controller.ViewData[messageTypeKey] = message;
}
if (UseJson)
return new JsonResult() { Data = "ERROR", JsonRequestBehavior = JsonRequestBehavior.AllowGet };
else
return new ContentResult() { Content = "ERROR" };
}
public static ActionResult EmptyField(this Controller controller, string FieldName, bool IsJson = false)
{
controller.ShowMessage(MessageType.Info, String.Format("O campo \"{0}\" é de carácter obrigatório.", FieldName));
return IsJson == false ? (ActionResult)new ContentResult() { Content = "ERROR" } : (ActionResult)new JsonResult() { Data = "ERROR", JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
}
To call the extension inside the controller:
this.ShowMessage(MessageType.Error, "An error has occurred");
if you want to redirect after the message is thrown, you need to add true in the last parameter.
this.ShowMessage(MessageType.Error, "An error has occurred", true);
Note: I created the EmptyField method to give a standart message when some field is empty.
Action Example (LoginPost)
[HttpPost]
[AllowAnonymous]
public ActionResult LoginPost(LoginViewModel model, string returnUrl, bool Relogin = false)
{
returnUrl = string.IsNullOrEmpty(returnUrl) || string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl;
if (string.IsNullOrEmpty(model.UserName))
return this.EmptyField(Resource_General.UserName);
if (string.IsNullOrEmpty(model.Password))
return this.EmptyField(Resource_General.Password);
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = SignInManager.PasswordSignIn(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
var user = db.Users.FirstOrDefault(x => x.UserName == model.UserName);
if (!user.IsActive)
{
AuthenticationManager.SignOut();
this.ShowMessage(MessageType.Error, Messages.LockedOutUser);
return Content("ERROR");
}
if (Url.IsLocalUrl(returnUrl))
return Content(returnUrl);
else
return Content("/Home");
case SignInStatus.LockedOut:
this.ShowMessage(MessageType.Error, Messages.LockedOutUser);
return Content("ERROR");
case SignInStatus.RequiresVerification:
case SignInStatus.Failure:
default:
this.ShowMessage(MessageType.Error, Messages.WrongPassword);
return Content("ERROR");
}
}
Ajax Form
#using (Ajax.BeginForm("LoginPost", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, new AjaxOptions { OnSuccess = "OnSuccess" }, new { #id = "login-form" }))
{
#Html.AntiForgeryToken()
<div class="network-login-fields">
<div class="form-group">
<div class="input-group col-xs-12">
#Html.TextBoxFor(m => m.UserName, new { #class = "form-control", placeholder = Resource_General.UserNamePlaceHolder, name = "loginname", autofocus = "true" })
</div>
</div>
<div class="form-group">
<div class="input-group col-xs-12">
#Html.PasswordFor(m => m.Password, new { #class = "form-control", placeholder = Resource_General.PasswordPlaceHolder, name = "password" })
</div>
</div>
<div class="network-login-links">
<button class="btn btn-default"><i class="fa fa-sign-in"></i> #Resource_General.Login</button>
</div>
</div>
}
Javascript
function OnSuccess(data) {
if (data != "ERROR") {
window.location.href = data;
}
}
Here in the javascript, you need to handle the ajax form OnSuccess and do something if the response is "OK" or "ERROR".
In your main javascript file you need to include this:
Handle Messages
// START Messages and Notifications
function handleAjaxMessages() {
$(document).ajaxStart(function () {
Openloading();
}).ajaxComplete(function (e, xhr, settings) {
CloseLoading();
}).ajaxSuccess(function (event, request) {
checkAndHandleMessageFromHeader(request);
}).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) {
if (thrownError !== "abort") {
CloseLoading();
NotifyError();
}
OnInit();
});
}
function checkAndHandleMessageFromHeader(request) {
var msg = request.getResponseHeader('X-Message');
if (msg) {
var title = NotifyHeader(request.getResponseHeader('X-Message-Type'));
Notify(msg, title, request.getResponseHeader('X-Message-Type'));
}
}
function NotifyHeader(type) {
console.log(type);
var title = "";
if (type == "error")
title = CustomScriptsLocales.ErrorTitle;
if (type == "success")
title = CustomScriptsLocales.SuccessTitle;
if (type == "warning")
title = CustomScriptsLocales.WarningTitle;
if (type == "info")
title = CustomScriptsLocales.InfoTitle;
console.log(title);
return title;
}
function Notify(message, title, type) {
if (title == null || title == "" || title == undefined) {
title = NotifyHeader(type);
}
PNotify.desktop.permission();
var notice = new PNotify({
title: title,
text: decodeHtml(message),
nonblock: {
nonblock: true,
nonblock_opacity: .55
},
buttons: {
closer: true,
},
desktop: {
desktop: false,
},
hide: true,
type: type,
delay: 2000,
insert_brs: true,
remove: true,
});
}
function NotifyError() {
Notify(CustomScriptsLocales.ErrorMessage, CustomScriptsLocales.ErrorTitle, "error");
}
// END Messages and Notifications
And call it inside a ready function:
$(document).ready(function () {
handleAjaxMessages();
}
Note: I use the PNotify plugin to show notifications. If you don't want notifications just exclude all this javascript "Handle Messages".

Related

How to get selected Drop down list value in view part of MVC?

I want to pass selected Drop down list value to Ajax Action Link which is I am using in Controller. Every time When I will change drop down list value. I want that respective value pass to the action link.
What I need to write here in Ajax Action Link ????
Drop Down List
<div class="form-group">
#Html.DropDownListFor(model => model.ComponentId, ((List<string>)ViewBag.Cdll).Select(model => new SelectListItem { Text = model, Value = model }), " -----Select Id----- ", new { onchange = "Action(this.value);", #class = "form-control" })
</div>
Ajax Action Link
<div data-toggle="collapse">
#Ajax.ActionLink("Accessory List", "_AccessoryList", new { ComponentId = ???? }, new AjaxOptions()
{
HttpMethod = "GET",
UpdateTargetId = "divacc",
InsertionMode = InsertionMode.Replace
})
</div>
Controller
public PartialViewResult _AccessoryList(string ComponentId)
{
List<ComponentModule> li = new List<ComponentModule>();
// Code
return PartialView("_AccessoryList", li);
}
Here is a new post. I do dropdowns a little different than you, so I am showing you how I do it. When you ask what to pass, I am showing you how to pass the dropdown for 'component' being passed. I also show how to pass from ajax back to the page.
Controller/Model:
//You can put this in a model folder
public class ViewModel
{
public ViewModel()
{
ComponentList = new List<SelectListItem>();
SelectListItem sli = new SelectListItem { Text = "component1", Value = "1" };
SelectListItem sli2 = new SelectListItem { Text = "component2", Value = "2" };
ComponentList.Add(sli);
ComponentList.Add(sli2);
}
public List<SelectListItem> ComponentList { get; set; }
public int ComponentId { get; set; }
}
public class PassDDLView
{
public string ddlValue { get; set; }
}
public class HomeController : Controller
{
[HttpPost]
public ActionResult PostDDL(PassDDLView passDDLView)
{
//put a breakpoint here to see the ddl value in passDDLView
ViewModel vm = new ViewModel();
return Json(new
{
Component = "AComponent"
}
, #"application/json");
}
public ActionResult IndexValid8()
{
ViewModel vm = new ViewModel();
return View(vm);
}
View:
#model Testy20161006.Controllers.ViewModel
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>IndexValid8</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$("#btnClick").click(function () {
var PassDDLView = { ddlValue: $("#passThis").val() };
$.ajax({
url: '#Url.Action("PostDDL")',
type: 'POST',
data: PassDDLView,
success: function (result) {
alert(result.Component);
},
error: function (result) {
alert('Error');
}
});
})
})
</script>
</head>
<body>
<div class="form-group">
#Html.DropDownListFor(m => m.ComponentId,
new SelectList(Model.ComponentList, "Value", "Text"), new { id = "passThis" })
<input type="button" id="btnClick" value="submitToAjax" />
</div>
</body>
</html>

Leverage MVC Client-Side Validation when using Ajax Post

It's my first asp.net mvc project and I am trying to build a view that displays a list of countries. on double click it changes the input from readonly to false to enable editing and on keypress or blur it saves the changes.
Now, server-side validation is working but client-side is not. I am not sure is it because I don't have a form tag or it can't be done or needs manual hook-ups.
CountryModel:
public class Country
{
[Key]
public int CountryId { get; set; }
[Required]
[MinLength(4)]
[MaxLength(60)]
[RegularExpression(#"^[a-zA-Z]+$", ErrorMessage = "Use letters only please")]
public string Name
{ get; set; }
}
Country Controller Action:
[HttpPost]
public ActionResult Save(Country country)
{
if (ModelState.IsValid)
{
db.Entry(country).State = EntityState.Modified;
db.SaveChanges();
//return
return Json("Success");
}
else
{
//return
return Json("Failed");
}
}
Country View:
#model IEnumerable<Models.Country>
<h2>Manage Countries</h2>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
Options
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.EditorFor(modelItem => item.Name, new { htmlAttributes = new { #class = "grid-input", data_input_mode = "read", data_model_id = item.CountryId } })
#Html.ValidationMessageFor(modelItem => item.Name, "", new { #class = "text-danger" })
</td>
</tr>
}
</table>
Scripts.js
$(document).ready(function () {
$(".grid-input").attr("readonly", true);
$(".grid-input").dblclick(function () {
var elem = $(this);
if (elem.attr("data-input-mode") == "read") {
elem.attr("data-input-mode", "edit");
elem.removeAttr("readonly");
//Remove selection when mode changes from read to edit only
var sel = window.getSelection();
sel.removeAllRanges();
//bind blur and keypress to attempt saving data on focusout or enter press
elem.on("blur keypress", function (e) {
if (e.keyCode == 13 || e.type == "blur") {
//Stop Bubbling
e.stopImmediatePropagation();
//Enable Readonly To Avoid Edits While Saving
$(this).attr("data-input-mode", "read");
$(this).attr("readonly", "true");
//Get Country ID & Name
var _countryId = $(this).attr("data-model-id");
var _countryName = $(this).val();
//Validate
$.ajax({
url: '/Country/Save',
data: JSON.stringify({
country: {
CountryId: _countryId,
Name: _countryName
}
}),
type: 'POST',
contentType: 'application/json; charset=utf-8',
success: function (data) {
if (data == "Success") {
console.log("Saved!");
} else {
elem.attr("data-input-mode", "edit");
elem.removeAttr("readonly");
console.log("Error occurs on the Database level!");
}
},
error: function () {
console.log("An error has occured!!!");
}
});
}
});
}
});
});

Typed Model in Controller action is always null when doing $.post

I am trying to post back some data to one of my action methods in an MVC project. I already have an Ajax form that does something different so I can't use another Ajax form. So I resorted to using $.post function. Then problem is, when my action method gets called, my model is null.
This is my view:
#model ActivityViewModel
#using (Ajax.BeginForm("Create", "Activities", new AjaxOptions() { UpdateTargetId = "panelContent", InsertionMode = InsertionMode.Replace }, new { id = "createactivity", autocomplete = "off" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.EmployeeId)
#Html.HiddenFor(m => m.IsAbscence)
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.ConflictIds)
#Html.HiddenFor(m => m.IsAbscence)
#Html.TextBoxFor(model => model.Date, "{0:dd.MM.yyyy}", new { #type = "date", #class = "ms-TextField-field", #autocomplete = "off" })
#if (!Model.IsAbscence)
{
#Html.HiddenFor(m => Model.Favorites, new { #id = "favoritehidden" })
#Html.HiddenFor(m => Model.Archive, new { #id = "archivehidden" })
<script type="text/javascript">
attachFabricCheckBoxHandler('#favoritehidden', '#favoritelabel');
attachFabricCheckBoxHandler('#archivehidden', '#archivelabel');
$(document).ready(function () {
$('#favoritelabel').click(function () {
var frm = $('#createactivity').serialize();
var token = $('[name=__RequestVerificationToken]').val();
$.post({
type: 'POST',
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
url: '/Activities/FilterProjects/',
data: { __RequestVerificationToken: token, model: frm.substring(frm.indexOf("&") + 1) },
statusCode: {
404: function (content) { showErrorDialog(content); },
500: function (content) { showErrorDialog(content); }
},
success: function (data) {
alert(data);
},
error: function (req, status, errorObj) {
showErrorDialog(status);
}
});
});
});
</script>
#Html.DropDownListFor(m => m.ProjectId, new SelectList(ViewBag.Projects, "Id", "ProjectDescription"), new { #class = "ms-Dropdown-select" })
}
#Html.TextBoxFor(model => model.StartTime, "{0:HH:mm}", new { #readonly = "readonly", #class = "ms-TextField-field", #placeholder = "Click to choose time" })
#Html.TextBoxFor(model => model.EndTime, "{0:HH:mm}", new { #readonly = "readonly", #class = "ms-TextField-field", #placeholder = "Click to choose time" })
#Html.TextAreaFor(model => model.Description, new { #class = "ms-TextField-field", #style = "height:100px; resize:vertical;" })
#if (!Model.IsAbscence)
{
#Html.TextAreaFor(model => model.Comment, new { #class = "ms-TextField-field", #style = "height:100px; resize:vertical;" })
}
}
Notice I removed all the unnecessary HTML, the structure is basically the same. Here is my ViewModel:
public class ActivityViewModel
{
public int Id { get; set; }
public DateTime Date { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public string Description { get; set; }
public int EmployeeId { get; set; }
public string ConflictIds { get; set; }
public string Comment { get; set; }
public int ProjectId { get; set; }
public bool IsAbscence { get; set; }
public bool Archive { get; set; }
public bool Favorites { get; set; }
}
When I use this, I always get null in my action method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult FilterProjects(ActivityViewModel model)
{
//When 'object model' is being passed, the serialized string and all the properties are there...
return PartialView("Create");
}
The weird thing is, when I pass an object instead of my typed ViewModel into my action method, I get the serialized string with all my properties:
EmployeeId=1&
IsAbscence=False&
Id=0&
ConflictIds=&
IsAbscence=False&
Date=27.04.2016&
Favorites=True&
Archive=False&
ProjectId=1&
StartTime=10%3A25& //Maybe these two values are screwing up the model?
EndTime=11%3A25&
Description=&
Comment=&
I could re-instantiate my viewmodel from this string but it would be much nicer if I had my typed model passed into my action. How do you properly serialize the form so that it's not null when your action is called? I tried leaving out the "StartTime" and "EndTime" properties and I cut out the validation token string because I thought they were interfering here, but obviously that didn't work.
When you serialize form, AntiForgeryToken will be included. try to send form without specifying __RequestVerificationToken:
$('#favoritelabel').click(function () {
var frm = $('#createactivity').serialize();
$.post('/Activities/FilterProjects/', frm, function (data) {
}).fail(function (xhr, status, errorThrown) {
showErrorDialog(status);
});
});

login popup loading full page in partial view on success in mvc?

I have an issue whereby the ajax form in my login popup loads the homepage into a partial view on success. When there is a failure or validation issue the partial view is reloaded but it should not when login is successful.
Ajax form in Navbar:
<li class="login">
<span>Login</span>
#using (Ajax.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, new AjaxOptions
{
HttpMethod = "POST",
UpdateTargetId = "login_box"
}, new { id = "login_box" }))
{
#Html.Action("loginform", "Home")
}
</li>
This then loads the content of the form from a shared partial view name _loginpartial (shortened):
<div class="login_box_lower">
<p class="login_box_or">or</p>
<p class="login_sign_in">Sign in</p>
<div style="position:relative;">
#Html.EditorFor(m => m.Email, new { htmlAttributes = new { placeholder = "Email", maxlength = "30", #class = "login_username clearable" } })
<span class="login_username_icon"></span>
</div>
<div style="position:relative;">
#Html.EditorFor(m => m.Password, new { htmlAttributes = new { placeholder = "Password", maxlength = "18", #class = "login_pw clearable" } })
<span class="login_pw_icon"></span>
</div>
<div class="rememberbox" >
#Html.CheckBoxFor(m => m.RememberMe, new { #class = "remembercheck" })
#Html.LabelFor(m => m.RememberMe)
</div>
<input type="submit" class="login_button" value="Log in" />
The controller action:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return PartialView("_LoginPartial", model);
}
// Require the user to have a confirmed email before they can log on.
var user = await UserManager.FindByNameAsync(model.Email);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account-Resend");
ViewBag.errorMessage = "You must have a confirmed email to log on. "
+ "The confirmation token has been resent to your email account.";
return View("Error");
}
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return PartialView("_LoginPartial", model);
}
}
The RedirectToLocal action is as per the default:
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
}
So as I see it if the login is successful and the returnUrl string is null then the home index view should be returned? Why is it only being returned in the partial view of the login popup?
Additionally my returnURL string generated at the start of the Ajax form doesn't seem to work, so if anyone could suggest to me where I am going wrong with that it would be very helpful. Thanks.

How can I Authenticate with ServiceStack using jQuery Ajax

I'm trying to do something like the following:
jQuery Part:
function ajaxLogin() {
$.ajax({
url: "auth/credentials",
type: "POST",
data: { UserName: $("#form_username").val(), Password: $("#form_pwd").val() },
success: function (data) {
$("#login_div").hide();
},
error: function (jqXHR,textStatus,errorThrown) {
$("$login_msg").text(errorThrown);
}
});
}
However, for some reason it's always coming back to the success function and data contains the html contents of the current html document.
My ServiceStack AuthProvider contains the following TryAuthenticate:
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var session = authService.GetSession();
string error = null;
try
{
var dataSource = authService.TryResolve<RiskViewDataSource>();
var diModelInstance = dataSource.diModelRootObject;
string authResult = UserFactory.authenticate(session.Id, userName, password, false);
if ("OK".Equals(authResult))
{
session.IsAuthenticated = true;
session.UserName = session.DisplayName = userName;
session.UserAuthId = password;
UsersManager.generateUsersPolicies();
UsersManager.loadUserPolicies();
return true;
}
else
{
session.IsAuthenticated = false;
session.UserName = session.DisplayName = null;
session.UserAuthId = null;
authService.RemoveSession();
return false;
}
}
catch (Exception e)
{
Log.Error(e.ToString());
session.IsAuthenticated = false;
session.UserName = session.DisplayName = null;
session.UserAuthId = null;
error = "Could not connect to RiskView database";
}
if (error != null)
{
throw HttpError.Unauthorized(error);
}
else
{
return false;
}
}
Ok, after a day of messing about I've come up with this solution which works for me. I had to create a new service request for logging in. I called it RenewSession.
Service Stack Part:
[Route("/RenewSession", "POST")]
public class RenewSessionRequest : IReturn<RenewSessionResponse>
{
}
public class RenewSessionResponse : IHasResponseStatus
{
public RiskViewJsonObject Result { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}
public class RenewSessionService : Service, IPost<RenewSessionRequest>
{
public object Post(RenewSessionRequest request)
{
string username = this.Request.GetParam("UserName");
string password = this.Request.GetParam("Password");
string message = "";
IAuthProvider authService = AuthService.GetAuthProvider("credentials");
Boolean success = false;
try
{
var response = authService.Authenticate(this, this.GetSession(), new Auth { UserName = username, Continue = null, Password = password });
success = true;
}
catch (Exception e)
{
message = e.ToResponseStatus().Message;
}
return new RenewSessionResponse { Result = new Mapping.RiskViewJsonObject("{ \"success\" : " + (success ? "true" : "false") + ", \"message\" : \"" + RiskViewJsonObject.cleanForJSON(message)+ "\" }") };
}
}
Html and Ajax Part:
1) Add a div to the page for the login details (Hide it to start with)
<div id="login-div" style="position:absolute;display:hidden;left:100;top:100;background-image:url('images/login_bk.png');">
<p id="login_error_msg"></p>
<form id="login_form" onsubmit="loginSubmit(); return false;">
<table>
<tr>
<td>Username:<input id="in_un" type="text" name="UserName" autocomplete="off" autocorrect="off" autocapitalize="off"/></td>
</tr>
<tr>
<td>Password:<input id="in_pw" type="password" name="Password" autocomplete="off" autocorrect="off" autocapitalize="off"/></td>
</tr>
<tr>
<td style="text-align: center;">
<input id="login_submit" type="submit" class="hand_cursor" value="Login">
</td>
</tr>
</table>
</form>
</div>
2) I add 401 checks to every ajax query on my page (401 tells us that the session has expired)
$.getJSON('/Menus?format=json', function(data) {
// Do some stuff
}).fail(function (jqxhr,textStatus,error) {
if (jqxhr.status == 401) {
loginAgain();
}
});
3) Show the div to re-login
function loginAgain(reloadMenu) {
$("#login-div").show("slow");
}
4) The onclick for login button or onsubmit event for the login form
function loginSubmit() {
if ($("#in_un").val().trim() == "" || $("#in_pw").val().trim() == "") {
$("#login_error_msg").text("Username or Password is still empty.");
return false; // Prevent form from submitting
} else {
$("#login_submit_btn").attr("disabled","disabled");
$("#login_error_msg").text("");
$.ajax({
url: "/RenewSession?format=json",
type: "POST",
data: { UserName: $("#in_un").val(), Password: $("#in_pw").val() },
success: function (data, textStatus, jqXHR) {
$("#login_submit_btn").removeAttr("disabled");
if (data.Result.success) {
$("#login-div").hide();
} else {
if (data.Result.message) {
$("#login_error_msg").text(data.Result.message);
} else {
$("#login_error_msg").text(textStatus);
}
$("#in_pw").focus();
}
},
error: function (jqXHR, textStatus, errorThrown) {
$("#login_submit_btn").removeAttr("disabled");
$("#login_error_msg").text("ERROR: "+errorThrown);
$("#in_pw").focus();
}
});
}
return false; // Stop the form submiting, we're just gonna hide the div
}

Resources