Authorization denied message with FormsAuthentication - asp.net

So, I've implemented my IPrincipal.IsInRole(...) and I'm using FormsAuthentication like so:
<authentication mode="Forms">
<forms loginUrl="Login.aspx" name="someName" timeout="600"/>
</authentication>
Then I have a page that requires you to be authenticated and that you have "roleA". This is configured like so:
<location path="SomePage.aspx">
<system.web>
<authorization>
<allow roles="roleA" />
<deny users="*"/>
</authorization>
</system.web>
</location>
Now, I login to my web application, but with a user that does NOT have roleA. When I visit SomePage.aspx I get redirected to Login.aspx, the url specified in loginUrl of the forms element. So, my question is shouldn't I be able be specify an authorization denied message or url? If the user is authenticated, but not authorized why would I want to redirect to the login page. It's confusing as hell to the user. Please tell me I am missing something simple.
Thanks for reading!

Yeah, this is a little annoying. Maybe someone has a simpler idea, but the solution (hack?) that we came up with was to look for the originally-requested URL that ASP.NET appends to the query string when the user is redirected to the login page.
We created a new web.config section that stores a set of keys/values that match a fragment of the redirect URL to an authorization message:
<configSections>
<section name="authorizationFailureMessages" type="System.Configuration.NameValueSectionHandler, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
...etc...
</configSections>
<authorizationFailureMessages>
<add key="MemberResources" value="MembershipRequired" />
<add key="Staff" value="StaffOnly" />
<add key="Departments/Administration/BoardOfDirectors" value="BoardOfDirectorsOnly" />
...etc...
</authorizationFailureMessages>
In the Page_Load() event of the Login.aspx page, we call a method that uses this URL to determine which (un)authorization event occurred then redirect them to a message page that displays the appropriate text:
private void DisplayAppropriateAuthorizationMessage ()
{
if ( !Page.User.Identity.IsAuthenticated )
return;
string redirectUrl = FormsAuthentication.GetRedirectUrl( Page.User.Identity.Name, false );
if ( string.IsNullOrEmpty( redirectUrl ) )
return;
NameValueCollection authorizationFailureMessages = ConfigurationManager.GetSection( "authorizationFailureMessages" ) as NameValueCollection;
if ( authorizationFailureMessages == null )
return;
foreach ( string key in authorizationFailureMessages.AllKeys )
{
if ( redirectUrl.Contains( key ) )
{
Response.Redirect( String.Format( "Message.aspx?{0}={1}", Constants.QueryStringKeys.ERRORMESSAGENAME, authorizationFailureMessages[ key ] ), true );
}
}
}

Roles.IsUserInRole - If you're just using it for this page, throw this in the code behind. If you have a lot of pages, you could consider putting this in a base class and reading either from the web.config or the database per page. I believe this will give you the most control.

I basically agree to #MattPeterson 's solution. But I suggest two improvements.
In my view, you just tell that "according to the roles that you are, you are not allowed to visit that page", that is enough. You do not need to tell which extra roles are needed, which will expose the details of authorization management of your website.
You can get access control list from web.config (in each folder), and no need to write <add key="MemberResources" value="MembershipRequired" /> again.
I believe you should have something similar to
<authorization>
<deny users="?" />
</authorization
in your web.config.

Related

SSO FormsAuthentication - 2 Applications - 1 x WebForms and 1 x MVC

I have read many posts and while I have gleaned all the information I need I cannot get this to work. So I am hopeful someone can point me in the right direction. Or if you have a working dummy project(s) even better. All the examples I have see do not specifically deal with my requirement 2.
I have an existing WebForms (W1) application that uses FormsAuthentication. As part of migrating this to MVC we want to create an MVC (M1) application "alongside" it and implement new functionality there. (It is currently out of scope to port the entire W1 application to MVC.) Existing FormsAuthentication is to be maintained and used for both sites.
Both applications will run on the same IIS but under different subdomains as:
w1.mydomain.com
m1.mydomain.com
Expectations
Users will log in to W1 and once authenticated access M1 urls as well as W1 urls
M1 application needs to "know" which user is logged in
Logging out either via W1 or M1 will log the user out for the other application
Implemented Solution
Login:
Share the authentication cookie at domain level viz mydomain.com
On successful login in W1 redirect to M1 to set the user auth cookie
Redirect back from M1 to W1
Logout:
W1/M1 Call FormsAuthentication.SignOut(); and then redirect to the the other side to do the same
Configurations
W1
web.config
<machineKey decryption="AES" validation="HMACSHA256" decryptionKey="AutoGenerate" validationKey="AutoGenerate" />
<authentication mode="Forms">
<forms loginUrl="~/account/login" timeout="120" defaultUrl="~/" domain=".mydomain.com" />
</authentication>
<compilation targetFramework="4.6.1"></compilation>
<!-- requestValidationMode needs to remain -->
<httpRuntime targetFramework="4.6.1" requestValidationMode="2.0"
maxRequestLength="20480" executionTimeout="300" />
login control on /account/login
<asp:Login ID="idLogin" runat="server" ViewStateMode="Disabled" DestinationPageUrl="~/sso/BounceLogin.aspx" >
Code behind for BounceLogin.aspx:
public partial class BounceLogin : Page
{
protected void Page_Load(object aSender, EventArgs aArgs)
{
Response.Redirect("https://m1.mydomain.com/sso/login");
}
}
M1
web.config
<machineKey decryption="AES" validation="HMACSHA256" decryptionKey="AutoGenerate" validationKey="AutoGenerate" />
<compilation debug="true" targetFramework="4.6.1" />
<httpRuntime targetFramework="4.6.1" />
<authentication mode="Forms">
<forms loginUrl="https://w1.mydomain.com/account/login" timeout="120" domain=".mydomain.com" />
</authentication>
SSO Controller:
public class SsoController : Controller
{
[Authorize]
// GET: Secure
public ActionResult Index()
{
return View();
}
public ActionResult Status()
{
return View(new ViewModel
{
TheUser = User
});
}
public ActionResult Logout()
{
FormsAuthentication.SignOut();
return Redirect("https://w1.mydomain.com/");
}
public ActionResult Login()
{
FormsAuthentication.SetAuthCookie("test#mydomain.com", false);
return Redirect("https://w1.mydomain.com/sso/BounceLoginReturn");
}
}
Interactions
So here is what happens when I log in with my test user: test#mydomain.com. (I have omitted the username passing to M1 to keep the code simpler).
A. w1.mydomain.com/account/login - Perform Successful Login and redirect to m1.mydomain.com/sso/login
B. m1.mydomain.com/sso/login - Sets cookie for user test#mydomain.com and redirects to w1.mydomain.com/BounceLoginReturn
Problem
When I return to w1.mydomain.com/BounceLoginReturn W1 still thinks I am not logged in and redirects me to w1.mydomain.com/account/login. (If I open M1 in another browser tab it tells me I am logged in as user#test.com)
I have checked and both w1.mydomain.com and m1.mydomain.com have the same cookie value set for the domain .mydomain.com.
So what am I doing wrong here to make W1 think I am not logged in bearing in mind I originally logged in via the asp:Login control it contains?
If anyone else has this issue it turns out the solution was simple. My code above was functionally correct. I did however need to use hard coded keys:
<machineKey decryption="AES" validation="HMACSHA256" decryptionKey="{Hard Coded Key Here}" validationKey="{Hard Coded Key Here}" />
There are heaps of sites out there to generate these, but the easiest way is using IIS itself:
and then use "Generate Keys" on the right hand side.

Session variable get empty after paypal and recieve value after press ctrl+f5

I faced weird situation where my session variable set it to null once after coming back from PayPal.
In my scenario before redirect page to PayPal i assign value to session.
public string sessionToken
{
get
{
if (Session["token"] != null)
{
return Session["token"].ToString();
}
else
return string.Empty;
}
set
{
Session["token"] = value;
}
}
calling paypal:
bool ret = payPalCaller.ShortcutExpressCheckout(amt, ref token, ref retMsg, ref status);
if (ret)
{
sessionToken = token;
Response.Redirect(retMsg,false);
}
after user complete paypal (if user takes some time to complete txn) and return back to sucess page and from there i'm trying to access above session variable, then that value is empty. but if i press ctrl+f5 few times then it get value.
what is the problem in here?
in my development pc, this is working fine and problem occurs when i hosted in server. (IIS 6)
My web config configuration as follows:
<configuration>
<location path="RegisterUser.aspx">
<system.web>
<authorization>
<allow users="?" />
</authorization>
<httpRuntime executionTimeout="43200" maxRequestLength="104856" />
<sessionState timeout="3600" mode="InProc" cookieless="false"></sessionState>
<customErrors mode="ON" />
</system.web>
</location>
</configuration>
EDIT:
i have used similar code in Checkout and Payment with PayPal. i found this weird question mentioned in the comment section, but no reply for that question as well.
If pressing ctrl+F5 fixes the issue for you then your success page may just be cached. Try disabling cache on your success page, that might just do the trick. You can refer to this link for reference: Disabling browser caching for all browsers from ASP.NET

Current user with ASP.NET Forms authentication app

I am trying to retrieve the current user in my web application that uses ASP.NET Forms authentication.
However, System.Security.Principal.WindowsIdentity.GetCurrent().Name returns domain\windowsUser, NOT the username that was used in the FormsAuthentication.RedirectFromLoginPage method.
I am using Forms authentication in my config file:
<authentication mode="Forms">
<forms loginUrl="Views/Login.aspx" name=".ASPXFORMSAUTH" timeout="1" cookieless="UseUri">
</forms>
</authentication>
<authorization>
<deny users="?" />
</authorization>
I am also trying to follow Microsoft's walk through and retrieve the Authentication ticket using the following snippet:
if (Request.IsAuthenticated)
{
var ident = User.Identity as FormsIdentity;
if (ident != null)
{
FormsAuthenticationTicket ticket = ident.Ticket;
var name = ticket.Name;
}
}
However, ident is always null because it's WindowsIdentity not FormsIdentity. What's wrong here?
Thank you!
Use User.Identity.Name to get the user name.
Windows authentication does not use the FormsAuthenticationTicket.

Informing ASP.NET website about the currently logged-in user

I'm new to ASP.NET and have been trying to solve this for awhile now.
I've came across this blog, and everything looks to be good, except one thing: the below code always evaluates to false in HttpContext.Current.User.Identity is FormsIdentity in this code snippit:
protected void Application_AuthenticateRequest(Object sender,
EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id =
(FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
// Get the stored user-data, in this case, our roles
string userData = ticket.UserData;
string[] roles = userData.Split(',');
HttpContext.Current.User = new GenericPrincipal(id, roles);
}
}
}
}
When I used breakpoints, it turned that my PC name is the current user, which I think is not a FormsIdentity.
Web.config:
<?xml version="1.0"?>
<!--
For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0"/>
</system.web>
</configuration>
What am I missing here? and how to tell HttpContext of the current user after a sucessful login?
If you want users to have a FormsIdentity, then you need to turn on FormsAuthentication in your web.config file. In your element, you need to populate the authentication and authorization nodes. (For example, see the section of the blog post you mentioned titled "Securing Directories with Role-based Forms Authentication") Right now these elements are not present in your web.config file, and so Forms Authentication is not happening. A configuration like this is typical:
<system.web>
<authentication mode="Forms">
<forms name=".ASPXFORMSDEMO" loginUrl="logon.aspx" protection="All" path="/" timeout="30" />
</authentication>
<authorization>
<deny users ="?" />
<allow users = "*" />
</authorization>
</system.web>
The node instructs ASP.Net to use forms authentication, while the node denies access to the site for any user who is not authentication.
More information can be found here and here

Authenticate against AD LDS

I've just installed AD LDS on my developer PC and everything works find, I've even created the user "abc" via ADSI Edit.
My goal is to test my ASP.NET Mvc 3 web application with my test AD LDS instance.
How can I get the app to authenticate the user against the instance? Do I have to write a custom membership provider? (overriding some stuff in the default AD membership provider?)
Thank you for any help!
You don't have to do any authentication since it is handled by iis.
All you have to do is change authentication mode to windows.
<system.web>
<authentication mode="Windows" />
</system.web>
Remember to either install iis after you installed AD, or register it manually.
Because you are using AD LDS I don't think authentication mode "Windows" will be so helpful. I believe you need to create a Login View(here /Account/Logon) and use authentication mode "Forms".
Enter the followwing in web.config
<authentication mode="Forms">
<forms name=".ADAuthCookie" loginUrl="~/Account/Logon" timeout="30" slidingExpiration="false" protection="All"/>
</authentication>
<authorization>
<deny users="?"/>
</authorization>
Authenticating the user can be accomplished by using System.DirectoryServices.AccountManagement. The controller code should look something like this:
public ActionResult Logon(LogonModel model)
{
if (model.Username != null && model.Password != null)
{
string container = "CN=...,DC=....,DC=...."; //Your container in LDS
string ldapserver = "server:port"; //LDS server
PrincipalContext context = new PrincipalContext(
ContextType.ApplicationDirectory,
ldapserver,
container,
ContextOptions.SimpleBind);
bool authenticate = context.ValidateCredentials(string.Format("CN={0},{1}", model.Username, container), model.Password, ContextOptions.SimpleBind);
if (authenticate)
{
FormsAuthentication.RedirectFromLoginPage(model.Username, false);
}
else
{
System.Threading.Thread.Sleep(5000);
this.ModelState.AddModelError("Password", "Wrong username or password");
}
}
return View("Logon", new LogonModel { Username = model.Username });
}
Note that this ONLY solves authentication and not authorization.
You can also use membership providers, but if you are looking for an easy solution I think this should do the trick.

Resources