Most of my ASP.NET website is accessible to an anonymous web users. However there are a few pages that I require authentication before I allow access. I control this via the web.config:
<authorization>
<allow users="*"/>
</authorization>
Currently my logon.aspx file is generic, but I would really like to include instructions telling the user why he was redirected to the logon page. Something like:
Before you can volunteer for a task, please logon so the system can identify you.
OR
You've attempted to edit an event. The system only allows Administrators to do this. Please logon so we can verify you are an administrator of this event.
The instructions on the logon page would depend on what the user was attempting prior to the forms authentication redirect.
My problem is how can the logon page determine what action was taken? Is there anyway to pass a custom querystring to the logon page? I guess I could decode the ReturnUrl and use that to try an determine what instructions to display. However that approach just feels....dirty. I don't like the logon page having a dependency on the URL names of other pages in the system.
Any suggestions?
You could use the HttpRequest.UrlReferrer Property to find out what was the user intention.
Or you could set a QueryString / Session variable before redirecting and use it to display some message.
Why dont you use custom exceptions to do this.
public class ClassName: Exception
{
protected ClassName() : base() {}
public ClassName(String message) : base(message) { }
}
Then you do
public class First: ClassName
{
public First() : base("message") {}
}
public class Second: ClassName
{
public Second() : base("message") { }
}
Now you just catch exceptions of type ClassName and get the message value and pass it into an asp label or textbox or however you wish to do it
You can also capture the redirect in global.asax's Application.EndRequest
void Application_EndRequest(object sender, EventArgs e)
{
if (!HttpContext.Current.Request.IsAuthenticated && HttpContext.Current.Response.StatusCode == 302 && HttpContext.Current.Response.RedirectLocation != null && HttpContext.Current.Response.RedirectLocation.ToLower().Contains("login.aspx"))
{
// do some switching or determining here, or have items preset in your HttpContext.Current.Items[]
HttpContext.Current.Response.RedirectLocation += "&Action=blah";
}
}
Related
I am facing an issue when trying to log in on Sitecore (back office) I am redirected on the same login page even if credentials are correct.
While debugging, I could find that when on the login process, the user is redirected to http://hostname/sitecore/shell/Applications/Login/Users/Users.aspx?su=%2Fsitecore%2Fshell%2Fdefault.aspx%3Fsc_lang%3Den
But once it is there the below issue occurs:
Sitecore.Context.User.IsAuthenticated is set to false
Sitecore.Context.User is set to Sitecore/anonymous
However, the user have been registered and ticket have been created successfully. I could verify that by debugging and checking the cookies.
After more investigation, I found out that the issue was solved by adding below line on the global.asax.cs
public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)
{
string frameworkVersion = this.GetFrameworkVersion();
if (!string.IsNullOrEmpty(frameworkVersion) && frameworkVersion.StartsWith("v4.", StringComparison.InvariantCultureIgnoreCase))
{
args.User = SC.Context.User;
}
}
It set the user to the correct user instead of sitecore\anonymous.
Trying to use form authentication to only allow access to a page once they have logged in via the login page. When I login and attempt the redirect it just redirects me back to the login page.
Web Login Control
protected void WebGenLogin_Authenticate(object sender, AuthenticateEventArgs e)
{
//Verify user against active directory
if (new AD().validate(WebGenLogin.UserName, WebGenLogin.Password))
{
Session["UserAuthentication"] = WebGenLogin.UserName;
Session.Timeout = 30;
FormsAuthentication.RedirectFromLoginPage(WebGenLogin.UserName, WebGenLogin.RememberMeSet);
Response.Redirect("~/WebGen/Gen/Create.aspx");
}
else
{
Session["UserAuthentication"] = "";
Response.Redirect("http://thekickback.com/rickroll/rickroll.php");
}
}
Create.aspx Web.config
<authentication mode="Forms">
<forms defaultUrl="~/WebGen/Gen/Create.aspx" loginUrl="../Login.aspx" slidingExpiration="true" timeout="30" />
</authentication>
Can you try this:
if (new AD().validate(WebGenLogin.UserName, WebGenLogin.Password))
{
Session["UserAuthentication"] = WebGenLogin.UserName;
Session.Timeout = 30;
FormsAuthentication.SetAuthCookie(WebGenLogin.UserName, false);
FormsAuthentication.RedirectFromLoginPage(WebGenLogin.UserName, WebGenLogin.RememberMeSet);
***SNIP***
I don't know what type of object AD() calls into, but you may not be using the default ASP.NET membership functionality. As I recall, the ValidateUser method on the membership class has the side-effect of actually logging the user in if it returns true.
After authenticating the user, you may need to set HttpContext.User to a new IPrincipal representing the user, and then call FormsAuthentication.SetAuthCookie() before redirecting them.
Ok I figured it out. It had nothing to do with my code. I did however remove storing the username in the session.
What I have to do was change the root site on IIS to an application.
Authentication mode line was placed in the root with Login.aspx
Create.aspx was in another folder. I removed the authentication mode from it's Web.config and just put in the deny section and all is working correctly.
The code actually worked. Found it to be an issue with IIS. Needed to turn the entire folder structure into an application rather than other parts of it.
In ASP.NET the FormsAuthenticationModule intercepts any HTTP 401, and returns an HTTP 302 redirection to the login page. This is a pain for AJAX, since you ask for json and get the login page in html, but the status code is HTTP 200.
What is the way of avoid this interception in ASP.NET Web API ?
In ASP.NET MVC4 it is very easy to prevent this interception by ending explicitly the connection:
public class MyMvcAuthFilter:AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest() && !filterContext.IsChildAction)
{
filterContext.Result = new HttpStatusCodeResult(401);
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.SuppressContent = true;
filterContext.HttpContext.Response.End();
}
else
base.HandleUnauthorizedRequest(filterContext);
}
}
But in ASP.NET Web API I cannot end the connection explicitly, so even when I use this code the FormsAuthenticationModule intercepts the response and sends a redirection to the login page:
public class MyWebApiAuth: AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if(actionContext.Request.Headers.Any(h=>h.Key.Equals("X-Requested-With",StringComparison.OrdinalIgnoreCase)))
{
var xhr = actionContext.Request.Headers.Single(h => h.Key.Equals("X-Requested-With", StringComparison.OrdinalIgnoreCase)).Value.First();
if (xhr.Equals("XMLHttpRequest", StringComparison.OrdinalIgnoreCase))
{
// this does not work either
//throw new HttpResponseException(HttpStatusCode.Unauthorized);
actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
return;
}
}
base.HandleUnauthorizedRequest(actionContext);
}
}
What is the way of avoiding this behaviour in ASP.NET Web API? I have been taking a look, and I could not find a way of do it.
Regards.
PS: I cannot believe that this is 2012 and this issue is still on.
In case someone's interested in dealing with the same issue in ASP.NET MVC app using the Authorize attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class Authorize2Attribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new HttpStatusCodeResult((int) HttpStatusCode.Forbidden);
}
else
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
}
base.HandleUnauthorizedRequest(filterContext);
}
}
}
This way browser properly distinguishes between Forbidden and Unauthorized requests..
The release notes for MVC 4 RC imply this has been resolved since the Beta - which are you using?
http://www.asp.net/whitepapers/mvc4-release-notes
Unauthorized requests handled by ASP.NET Web API return 401 Unauthroized: Unauthorized requests handled by ASP.NET Web API now return a standard 401 Unauthorized response instead of redirecting the user agent to a login form so that the response can be handled by an Ajax client.
Looking into the source code for MVC there appears to be an functionality added via SuppressFormsAuthRedirectModule.cs
http://aspnetwebstack.codeplex.com/SourceControl/network/forks/BradWilson/AspNetWebStack/changeset/changes/ae1164a2e339#src%2fSystem.Web.Http.WebHost%2fHttpControllerHandler.cs.
internal static bool GetEnabled(NameValueCollection appSettings)
{
// anything but "false" will return true, which is the default behavior
So it looks this this is enabled by default and RC should fix your issue without any heroics... as a side point it looks like you can disable this new module using AppSettings http://d.hatena.ne.jp/shiba-yan/20120430/1335787815:
<appSettings>
<Add Key = "webapi:EnableSuppressRedirect" value = "false" />
</appSettings>
Edit (example and clarification)
I have now created an example for this approach on GitHub. The new redirection suppression requires that you use the two correct "Authorise" attribute's; MVC Web [System.Web.Mvc.Authorize] and Web API [System.Web.Http.Authorize] in the controllers AND/OR in the global filters Link.
This example does however draw out a limitation of the approach. It appears that the "authorisation" nodes in the web.config will always take priority over MVC routes e.g. config like this will override your rules and still redirect to login:
<system.web>
<authentication mode="Forms">
</authentication>
<authorization>
<deny users="?"/> //will deny anonymous users to all routes including WebApi
</authorization>
</system.web>
Sadly opening this up for some url routes using the Location element doesn't appear to work and the WebApi calls will continue to be intercepted and redirected to login.
Solutions
For MVC applications I am simply suggest removing the config from Web.Config and sticking with Global filters and Attributes in the code.
If you must use the authorisation nodes in Web.Config for MVC or have a Hybrid ASP.NET and WebApi application then #PilotBob - in the comments below - has found that sub folders and multiple Web.Config's can be used to have your cake and eat it.
I was able to get around the deny anonymous setting in web.config by setting the following property:
Request.RequestContext.HttpContext.SkipAuthorization = true;
I do this after some checks against the Request object in the Application_BeginRequest method in Global.asax.cs, like the RawURL property and other header information to make sure the request is accessing an area that I want to allow anonymous access to. I still perform authentication/authorization once the API action is called.
This is supposed to just work. I've read all the articles I could find via google on the topic, tried to copy as much as I could from other articles on both StackOverflow and CodeProject and others, but regardless of what I try - it doesn't work.
I have a silverlight application that runs fine using Windows Authentication.
To get it running under Forms Authentication I've:
Edited the web.config file to enable Forms Authentication (and delete the Windows Authentication configuration):
<authentication mode="Forms">
<forms name=".ASPXAUTH" loginUrl="logon.aspx" defaultUrl="index.aspx" protection="All" path="/" timeout="30" />
</authentication>
Created a standard logon.aspx and logon.aspx.cs code behind page to take a user input name and password, and create a authentication cookie when the logon was successful, and then redirected the user to the root page of the web site, which is a silverlight application:
private void cmdLogin_ServerClick( object sender, System.EventArgs e )
{
if ( ValidateUser( txtUserName.Value, txtUserPass.Value ) )
{
FormsAuthentication.SetAuthCookie(txtUserName.Value, true);
var cookie = FormsAuthentication.GetAuthCookie(txtUserName.Value, true);
cookie.Domain = "mymachine.mydomain.com";
this.Response.AppendCookie(cookie);
string strRedirect;
strRedirect = Request["ReturnUrl"];
if ( strRedirect == null )
strRedirect = "index.aspx";
Response.Redirect( strRedirect, true );
}
}
So the redirect after successfully logging in launches my silverlight application.
However the user is not authenticated when executing the Silverlight startup code:
public App()
{
InitializeComponent();
var webContext = new WebContext();
webContext.Authentication = new FormsAuthentication();
ApplicationLifetimeObjects.Add( webContext );
}
private void ApplicationStartup( object sender, StartupEventArgs e )
{
Resources.Add( "WebContext", WebContext.Current );
// This will automatically authenticate a user when using windows authentication
// or when the user chose "Keep me signed in" on a previous login attempt
WebContext.Current.Authentication.LoadUser(ApplicationUserLoaded, null);
// Show some UI to the user while LoadUser is in progress
InitializeRootVisual();
}
The error occurs in the ApplicationUserLoaded method, which always has its HasError property set to true on entry to the method.
private void ApplicationUserLoaded( LoadUserOperation operation )
{
if((operation != null) && operation.HasError)
{
operation.MarkErrorAsHandled();
HandlerShowWebServiceCallBackError(operation.Error, "Error loading user context.");
return;
}
...
}
The error reported is as follows - from what it appears to me is that the user isn't considered authenticated on entry to the silverlight app, so it is directing the code to try to return the logon page, which is returning data unexpected by the silverlight app:
An exception occurred while attempting to contact the web service.
Please try again, and if the error persists, contact your administrator.
Error details:
Error loading user context.
Exception details:
Load operation failed for query 'GetUser'. The remote server returned an error: NotFound.
Any ideas?
Based on everything I read, this is supposed to be pretty simple and just work - so I'm obviously making a very basic error.
I'm wondering if after I authenticate the user on my logon.aspx web page, I need to somehow pass an authenticated WebContext instance over from the logon page to my silverlight application instead of creating a new instance in the silverlight app startup code - but have no idea how to do that.
Appreciate any or all suggestions.
I suspect the Response.Redirect("...", true);
According to this article you should pass false to keep the session.
how to check whether users is authenticated and session is valid on pages after say 30 mins.
Assuming you are either hooking into the standard ASP.NET membership providers or using Basic/Digest authentication in IIS then you can easily tell if a user is authenticated using:
if (Request.IsAuthenticated)
{
// User is authenticated, allow them to do things
}
If their authentication token has expired (defaults to 20 minutes with Forms Auth, Windows auth should re-authenticate correctly with each request), then the next time you check that, IsAuthenticated will return false.
You could store a token in the users session that maps back to their user account (either a hash of their user name, or their user id or similar), and then check the two on the request - if they don't match, then the session has become invalid:
// Get the current user, and store their ID in session
MembershipUser user = Membership.GetUser();
Session["UserId"] = user.ProviderUserKey;
To check this:
if (null != Session["UserId"]) {
MembershipUser user = Membership.GetUser();
if (Session["UserId"] == user.ProviderUserKey) {
// User and session are valid
}
}
But to be honest, it depends on what you are trying to do.
If you want to restrict access to certain areas of your website if the user isn't logged in, then there are mechanisms in the configuration that allow for that:
In your web.config you can add lines like the following:
<location path="SecureDirectory">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
This will deny all anonymous users access to the directory /SecureDirectory/ and all content below it, and direct them instead to your configured login page - for more information on the Authorization element, see "How to: Configure Directories Using Location Settings".
You have to make the session expire after certain time.
So, there is a section in your web.config or you have to add the section in <system.web />
Put this section inside:
<sessionState mode="InProc" stateConnectionString="tcpip=127.0.0.1:42424"
stateNetworkTimeout="10" cookieless="false" timeout="30" />
If you notice we are using InProc mode and timeout to be 30
Now its all up to you.
Add a key in Session object when you can find in any WebForm page.
public void btnLogin(object sender, EventArgs e) {
if (validUser) {
Session["authenticated"] = true;
}
}
and check Session["authenticated"] when required.
Session object will be expired in 30 minutes of session instantiation.
Hope this help. Please feel free to leave me a comment if you face trouble.
In the start of a session, you can store some key value in session state via the Global.asax:
void Session_Start(object sender, EventArgs e)
{
Session["userId"] = userId; // obtained from a data source or some other unique value, etc.
}
Whenever a user makes a page request or postback, on page load of any or all your pages, check if session value is null:
protected void Page_Load(object sender, EventArgs e)
{
if(Session["userId"] == null)
{
Response.Redirect("logout.aspx");
}
// do other stuff
}
If it is, then the session has expired and you can redirect then to logout page or whatever. The timeout interval is defined in your web.config file.