We have an old classic asp app that is used to manage and launch our other web applications.
The way it launches apps is as follows:
<form name="frmMain" action="http://xxxx/mvc3app/Index" target=_self method=post>
<script language="javascript">
frmMain.submit();
</script>
User login and password is passed through as part of the request.
To Authenticate the user in the ASP.NET app I call the following AuthenticateUser function:
public bool AuthenticateUser()
{
var userName = Context.Request["txtName"];
var password = Context.Request["txtPassword"];
if (Membership.ValidateUser(userName, password))
{
FormsAuthentication.SetAuthCookie(userName, true);
}
}
I assumed that the correct place to call AuthenticateUser would be in the Session_Start() method in global.asax but it doesn't seem that this method is called when submitting "frmMain". It seems to work intermittently - if I close IE completely, try again and then enter the URL manually.
void Session_Start(object sender, EventArgs e)
{
Log("In Session Start");
AthenticateUser();
}
Where would be the correct place to in my ASP.NET app to authenticate users?
Here is a screeny from dev tools of the forms auth failing - Session_Start() isn't called.
EDIT
Looks like this wasn't working because the IsAuthenticated property is only set on subsequent requests which was causing auth to fail on the index action.
I'll test this now but see Who sets the IsAuthenticated property of the HttpContext.User.Identity
Solution:
First Error was not redirecting after calling SetAuthCookie which was causing the Index view to fail auth.
I also realised there is no need to place this in global.asax but I could rather redirect to a LogOn action instead of going directly to the index action:
public ActionResult LogOn()
{
var userName = Context.Request["txtName"];
var password = Context.Request["txtPassword"];
if (Membership.ValidateUser(userName, password))
{
FormsAuthentication.SetAuthCookie(userName, false);
return RedirectToAction("Index", "Index");
}
else
{
return RedirectToAction("IncorrectLogin", "Index");
}
}
I think a controller action would be the best place, its a good idea to keep controller actions to a minimum so it doesn't get bloated. But if the action isn't touching too many layers it seems like a good fit.
If you were doing any "weird" stuff with session manipulation you can still use Session_Start though. But avoid it if you can, nothing like magic happening all over the place to confuse you on your own application exec path :D
Related
Im using Webforms throught C#
When the users does a certain action it might be redirected to another link, which is not working atm (it was before and i cant rollback the project since idh tfs or github)
The user is on this site:
localhost:53145/VistaUsuario.aspx
When it users fills the form and click accept the code behind is:
Session["user"] = txtDominio.Text;
Response.Clear();
Response.Redirect("FormOrden.aspx");
When it does the action it's redirected to the same link adding this parameters :
localhost:53145/VistaUsuario.aspx?ReturnUrl=%2fFormOrden.aspx
This is the code behind "FormOrden.aspx"
protected void Page_Load(object sender, EventArgs e)
{
string usuario = (string)Session["user"];
if (usuario != null)
{
this.MostrarDatos();
}
else
{
this.DoSomethingElse();
}
}
How to fix this?
It looks like you have Authentication enabled in your web.config. Your session is not authenticated so any attempt to go to another page goes back to the authentication page, which happens to be your current page (likely default.aspx).
The reason you see ReturnUrl="%2fFormOrden.aspx is because that's how Forms Authentication handles redirects after logging in.
Basically, you try to hit a page that requires authentication and are not authenticated. Forms Authentication redirects the request to the login page. Upon Logging in you make a call to FormsAuthentication to RedirectFromLogin page and it goes to the page specified in the ReturnUrl to go back to where the user was trying to go.
Either finish your authentication workflow, or turn off forms authentication in your web.config.
When my AuthorizeAttribute is called, it sends the page to login like it should. But when login is completed, it goes out to home/index. This is super annoying. How can I keep that from happening?
Since the code path never goes into my controller, I don't have any control of what it's doing.
Using forms authentication, when you access a page that requires authentication, ASP.NET will redirect you to the login page, passing in the ReturnUrl as a parameter.
So, all you need to do is use that parameter to return the user to the place it was before.
public ActionResult Login(string username, string pass, string returnUrl)
{
// code to authenticate user here...
if (!string.IsNullOrEmpty(returnUrl))
return Redirect(returnUrl);
return RedirectToAction("Index", "Home");
}
You might also want to consider using FormsAuthentication.RedirectFromLoginPage
Redirects an authenticated user back to the originally requested URL or the default URL.
https://msdn.microsoft.com/en-us/library/ka5ffkce(v=vs.110).aspx
Notice that calling this method will also issue the authentication cookie, that you might be already doing. So you have to check you are not doing this twice.
Edit: See https://stackoverflow.com/a/1206728/722778
Perhaps you are generating your form specifying a controller an action (e.g. Html.BeginForm("Logon", "Account")?
In that case, try with only this in your view:
Html.BeginForm()
Or passing the ReturnUrl parameter yourselve:
Html.BeginForm("LogOn", "Account", new {ReturnUrl = Request.QueryString["ReturnUrl"] })
I do have an Asp.Net MVC Application (version 6.0.0-rc1-final) with custom role and user stores. After some struggling I finally could create a working login mechanism. However I do have now troubles to create a clean logout. What my logout code in the controller currently looks like:
public async Task<ActionResult> Logout()
{
if (User.Identity.IsAuthenticated)
{
await SignInManager.SignOutAsync();
}
return RedirectToAction("Index", "App");
}
The problem with this code is, that one cookie is not deleted: .AspNet.Microsoft.AspNet.Identity.Application
As long as I don't delete the cookie manually the application is in a dirty state and throws null pointer exceptions because User.Identity is null.
I have found a question on stackoverflow describing a similar scenario. But the solution there is not appropriate for me because I am using MVC 6 which does not have System.Web any more.
I do also have a sample solution which just works fine. In this solution the mentioned cookie is never created. Perhaps the right solution is not to delete the cookie after logout, but rather to prevent somehow the creation of the cookie.
The problem is that your RedirectToAction overwrites the redirect to the Identity Server endsession URL that SignOutAsync issues.
(The same explanation for the same problem is given here by Microsoft's HaoK.)
Edit: The solution is to send a redirect URL in an AuthenticationProperties object with the final SignOutAsync:
// in some controller/handler, notice the "bare" Task return value
public async Task LogoutAction()
{
// SomeOtherPage is where we redirect to after signout
await MyCustomSignOut("/SomeOtherPage");
}
// probably in some utility service
public async Task MyCustomSignOut(string redirectUri)
{
// inject IHttpContextAccessor to get "context"
await context.SignOutAsync("Cookies");
var prop = new AuthenticationProperties()
{
RedirectUri = redirectUri
});
// after signout this will redirect to your provided target
await context.SignOutAsync("oidc", prop);
}
I could fix the dirty state of my application after the logout by manually delete the cookie after the logout action:
public async Task<ActionResult> Logout()
{
if (User.Identity.IsAuthenticated)
{
await SignInManager.SignOutAsync();
}
foreach (var key in HttpContext.Request.Cookies.Keys)
{
HttpContext.Response.Cookies.Append(key, "", new CookieOptions() { Expires = DateTime.Now.AddDays(-1) });
}
return RedirectToAction("Index", "App");
}
As cookies cannot deleted from the server directly I just overwrite the existing cookies with an already passed expiry date.
In addition to everything already mentioned, also make sure you are not omitting the scheme argument in the calls to SignInAsync and SignOutAsync, and that you are passing the same value to both. For example:
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
and
HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
So in this example the scheme is CookieAuthenticationDefaults.AuthenticationScheme. In my case I was forgetting to pass this to SignOutAsync, and while obvious after the fact, it took longer than I'd like to admit for me to track down.
Another gotcha that could leave the identity server cookies on the client is a logout failure. One typical cause of logout failures is a misconfiguration of the client’s PostLogoutRedirectUris.
The logout failures are not visible from the client side, the endsession call returns 200 OK, as well as the logout call.
There will however be traces on your identity server logs that the logout failed.
Currently, my application can track whenever user has logged in or out. The logging out activity is saved to database because User simply click a button. However, I would be interested in how to implement something similar but when user's ticket is no longer valid, either because it's been removed or expired.
I would like to handle any event (if possible) that is fired whenever user is no longer valid.
Is it possible to implement anything like this?
Cheers!
EDIT
I found two solutions. One uses BaseController--the code below from Handle asp.net MVC session expiration. This is simply what the author claims the easiest and working solution. First, is it a good idea to inherit all conrollers from a custom controllers? Second, I have already customized my own [Authorize] attribute. I don't know how all this together is going to work, and three, can i still have access to the cookie of a logged user where I hold UserID and some other data such as UserRole? And also it is not triggered automatically but whenver user trying to access and cookie is expired. It could be 6 hours later.
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Check if session exists
if (filterContext.HttpContext.Session != null)
{
//if new session
if (filterContext.HttpContext.Session.IsNewSession)
{
string cookie = filterContext.HttpContext.Request.Headers["Cookie"];
//if cookie exists and session id index is greater than zero
if ((cookie != null) && (cookie.IndexOf("ASP.NET_SessionId") >= 0))
{
//redirect to desired session expiration action and controller
filterContext.Result = RedirectToAction("SessionExpired", "Home");
return;
}
}
}
//otherwise continue with action
base.OnActionExecuting(filterContext);
}
}
The second solution I found is based on Global.aspx's function
void Session_End(object sender, EventArgs e)
{
Response.Redirect("~/Account/Timeout");
}
but I read it only works when I use InProc. I don't know what that is, though.
If you go with the filter [Authorize] you can add it as a global filter.
filters.Add(new AuthorizeAttribute());
Which is what your controller does too.
To answer your questions:
Yes, if you are building a framework that impacts all controllers. There is no problem with inheriting from a base controller.
Looks like the global filter is right up your alley.
You can't access the cookie once the session expires since there is no client!
When a session expires, the screen gets frozen to information they've already been authorized for until any subsequent request is made.
Hope that helps.
I'm having problem with getting ServiceStack [Authentication] attribute to work in ASP.Net MVC4 controller, pages / action methods with the attribute keep redirecting Users to the login page even after the login details are submitted correctly.
I've followed the SocialBootstrapApi example, with the difference being that all the authentication web service calls are made from the controllers:
this.CreateRestClient().Post<RegistrationResponse>("/register", model);
Other things that I've done so far:
Use my own user session implementation subclassing AuthUserSession (not too different from the example, but using my own implementation of User table)
Inherit ServiceStackController on my BaseController, overriding the default login URL
Enable Auth feature in AppHost with my user session implementation
Registration does work, user auth logic works (even though the session does not persist), and I can see the ss-id and ss-pid cookies in the request.
So my complete list of questions:
How do I make the [Authenticate] attribute work (or, what did I do wrong)?
How do I save and reuse the user session in an MVC controller? At the moment this.UserSession is always null.
How do I logout a user? this.CreateRestClient().Get<AuthResponse>("/auth/logout"); does not seem to work.
Update 1:
The session cookies (ss-id and ss-pid) gets created when I attempt to load the secured page (ones with [Authenticate] attribute), before any credentials get submitted. Is this the expected behaviour?
Update 2:
I can see that the session is saved in MemoryCacheClient, however trying to retrieve it in the base controller via this.Cache.Get<CustomUserSession>(SessionKey) returns null (where SessionKey is like: urn:iauthsession:1)
After much fiddling around, apparently the way to hook ServiceStack authentication is to call the AuthService via:
try {
authResponse = AuthService.Authenticate(new Auth{ UserName = model.UserName, Continue = returnUrl, Password = model.Password });
} catch (Exception ex) {
// Cut for brevity...
}
and NOT authResponse = this.CreateRestClient().Post<AuthResponse>("/auth/credentials", model);!
Where AuthService is defined in the base controller as:
public AuthService AuthService
{
get
{
var authService = ServiceStack.WebHost.Endpoints.AppHostBase.Instance.Container.Resolve<AuthService>();
authService.RequestContext = new HttpRequestContext(
System.Web.HttpContext.Current.Request.ToRequest(),
System.Web.HttpContext.Current.Response.ToResponse(),
null);
return authService;
}
}
Everything else (incl. session) works correctly now.
You can find how it could be done in the ServiceStack Use Cases repository. The following example is based on MVC4 but works perfectly for MVC3 either: CustomAuthenticationMvc.