I'm playing around with authentication and authorization to prepare for some task. I've created two pages: Login.aspx and Default.aspx. In config file i've set authentication to forms and denied unauthenticated users access:
<authentication mode="Forms">
<forms name="aaa" defaultUrl="~/Login.aspx" />
</authentication>
<authorization>
<deny users="?"/>
</authorization>
Then I've written some simple code to authenticate my user in Login.aspx:
protected void Page_Load(object sender, EventArgs e)
{
GenericIdentity identity = new GenericIdentity("aga", "bbb");
Context.User = new GenericPrincipal(identity, new String[] { "User" }); ;
Response.Redirect("~/Default.aspx");
}
When i run it, the redirection doesn't take place. Instead Login.aspx is called over and over because the user is not authenticated (Context.User.Identity.IsAuthenticated is false at every load). What am i doing wrong?
Context.User only sets the principal for the current request. Once the redirect takes place, the current request ends and a new one begins with the non-overridden principal again (which is apparently not authenticated). So, setting Context.User doesn't actually authenticate anything.
Using FormsAuthentication.SetAuthCookie() will set the user's cookie to a valid value accepted by the FormsAuthentication provider, or put the token in the URL. You can redirect to your heart's content because the cookie obviously sticks with the user for future requests.
From MSDN (em added):
With forms authentication, you can use the SetAuthCookie method when you want to authenticate a user but still retain control of the navigation with redirects.
As stated, this does not necessarily require cookies - the name is a little misleading, because it will still work via the URL if FormsAuthentication is in cookieless mode:
The SetAuthCookie method adds a forms-authentication ticket to either the cookies collection, or to the URL if CookiesSupported is false.
Use FormsAuthentication.SetAuthCookie(..). Or FormsAuthentication.RedirectFromLoginPage(..).
You need to actually set the user as authenticated. All of the following methods will work and let you actually get away from your login screen.
FormsAuthentication.Authenticate()
FormsAuthentication.RedirectFromLoginPage()
FormsAuthentication.SetAuthCookie()
Lots of ways to get to the same result.
You need to actually make a call to the formsAuthentication provider to set the login.
FormsAuthentication.RedirectFromLoginPage(txtUser.Text, chkPersistLogin.Checked)
is a simple example
After creating the dummy Context.User, you need to perform a FormsAuthentication.SetAuthCookie or RedirectFromLoginPage method.
Related
I have some code where we create an authentication ticket. After creating the ticket, we call SetAuthCookie to set the cookie such as:
FormsAuthentication.SetAuthCookie(username, true);
Response.Redirect("/", true);
If I check on the root page to see if the user is authenticated, it returns false. However, if I hard code the username in and do:
FormsAuthentication.GetAuthCookie("jason", true).value);
I get the appropriate cookie value. So, the cookie exists. But the name and the flag are not modified. Any ideas as to what my issue could be? I'm using ASP.NET 4 and MVC.
Solved
I was missing the forms section in the web.config. It was removed for local testing as the login form resides on another server. So, adding the following to web.config solved my issue:
<authentication mode="Forms" />
I have made an asp.net application. This application has session variables which shows that a user is logged in or logged out.
If there any way to capture the expiration of session without going to single pages and once this is logged out user can be redirected to login( Re-login) page?
Can my asp.net classes inherit some base class which can have some abstract method to check if session is existing or not.
Please suggest me some architecture to do this.
Thanks in advance.
This application has session variables which shows that a user is logged in or logged out.
This can be achieved by checking the HttpContext.Current.Request.IsAuthenticated property.
If there any way to capture the expiration of session without going to single pages and once > this is logged out user can be redirected to login( Re-login) page?
Configure the following elements in your applications web.config to restrict\allow access to resources\pages\directories on your site:
<authorization>
<allow .../>
<deny .../>
</authorization>
http://msdn.microsoft.com/en-us/library/8d82143t
And, configure the FormsAuthentication defaulturl and loginurl attributes to redirect users away from restricted resources.
You can use global.asax's session end event:
void Session_End(Object sender, EventArgs E) {
// your code....
}
When the session expire this event called.
I created STS that does the authentication part. It uses Custom Membership provider.
After successful login I get redirected to my RP website. All works fine in terms of authentication.
I have defined a CustomRolesProvider defined in web.config of my RP website. It uses the username returned by STS to fetch the roles for that user from RP's database.
When I use Roles.GetRolesForUser I do get the right roles.
I have the following in the web.config of my RP to allow only admin to give access to admin folder.
And the sitemap provider has securityTrimmingEnabled="true"
<location path="admin">
<system.web>
<authorization>
<allow roles="admin" />
<deny users="*" />
</authorization>
</system.web>
</location>
<add name="default" type="System.Web.XmlSiteMapProvider" siteMapFile="Web.sitemap" securityTrimmingEnabled="true" />
Problem:
When the user is in the admin role, the menu tabs for admin pages won't showup. I did check that Roles.IsUserInRole("admin") returns true. So the role is recognized by roles provider but not by authorization rules and sitemap provider in the web.config.
If I comment out the "location" from the web.config i.e. allowing every logged-in user to admin folder, my menu items show up fine.
From my understanding of WIF, RP can have it's own implementation of Roles and does not have to rely on Roles Claim from STS.
Does anyone has any ideas?
Update 2(01/20/2012): I found that the STS returns role claims as below:
http://schemas.microsoft.com/ws/2008/06/identity/claims/role = Manager
So if I change <allow roles="admin" /> to <allow roles="Manager" /> the role is picked up and menu tabs are shown appropriately.
So I am sure I am missing a link on how to make use of my roles and not the one returned via claims.
Update 2(01/20/2012):
If I add the role to the claimsIdentity like below it works:
void Application_AuthenticateRequest(object sender, EventArgs e) {
if (Request.IsAuthenticated) {
IClaimsPrincipal claimsPrincipal = HttpContext.Current.User as IClaimsPrincipal;
IClaimsIdentity claimsIdentity = (IClaimsIdentity)claimsPrincipal.Identity;
if (!claimsIdentity.Claims.Exists(c => c.ClaimType == ClaimTypes.Role))
{
claimsIdentity.Claims.Add(new Claim(ClaimTypes.Role, "admin"));
}
}
}
But then what would be the best place to add that code? If I add it in Application_AuthenticateRequest it's added upon each request and it keeps adding.(I fixed this by adding if statement)
*Update 3(01/24/2012):*Version 2 of my code that uses my CustomRoleProvider to get the Roles and then add it to the ClaimsCollection:
void Application_AuthenticateRequest(object sender, EventArgs e) {
if (Request.IsAuthenticated) {
string[] roleListArray = Roles.GetRolesForUser(User.Identity.Name);
IClaimsPrincipal claimsPrincipal = HttpContext.Current.User as IClaimsPrincipal;
IClaimsIdentity claimsIdentity = (IClaimsIdentity)claimsPrincipal.Identity;
var roleclaims = claimsIdentity.Claims.FindAll(c => c.ClaimType == ClaimTypes.Role);
foreach (Claim item in roleclaims)
{
claimsIdentity.Claims.Remove(item);
}
foreach(string role in roleListArray)
{
claimsIdentity.Claims.Add(new Claim(ClaimTypes.Role, role));
}
HttpContext.Current.User = claimsPrincipal;
}
But I am not sure if that's the right way.
Is there anyone who has done something like this??
Update 4 (01/26/2012): Found that I can use Custom ClaimsAuthencationManager(Step 4) to transform my claims.
I moved the code in AuthenticateRequest method in Global.asax to Authenticate method in ClaimsAuthenticationManager class.
I doubt it can get any better than this. I will post my solution as answer. But still if anyone has any other better solution feel free to comment.
You could use a custom ClaimsAuthencationManager, however, it will be called on every request. My recommendation would be to use WSFederationAuthenticationModule.SecurityTokenValidated. Use the ClaimsPrincipal property of SecurityTokenValidatedEventArgs class and add the roles using your provider. Also, instead of hard coding the role claim type, you may wish to consider using ClaimsIdentity.RoleClaimType.
The looked up roles will be saved in the encrypted cookie (assuming you are using the default).
The best solution would be to have an IdP (your current STS) and an RP-STS (or Federation Provider). As you say, if in the future you rely on more than one IdP (e.g. you use Live or Google, etc), it is very unlikely that they will provide the claims you need.
The purpose of the RP-STS is precisely to normalize the claimset to whatever your app requires, without polluting your app with identity concerns.
It would look like this:
An RP-STS is especially useful when you have:
Many IdP (yours and external ones)
Many Aplications
Claims transformations that can apply to many RPs. This, the RP-STS being an "authority" on the knowledge of userX being in role Y. And that knowledge not being exclusive of one app.
Protocol transition functions
The transformation (T) would add/remove claims as needed by each app, independently of the IdP.
The reason your app works when you add a "role" claim, but not with Roles.IsUserInRole, is because in general apps check User.IsInRole, which is resolved against the claimset in the principal, and is completed disconnected from Roles provider. This is arguably, a problem in the way Roles provider is designed.
The drawback of an RP-STS is the extra component you need to manage. There are however, rather simpler options today: ACS (Access Control Service) is one. If you are building a custom STS, you could do any of this of course.
The proper place to transform claims in the RP itself is by writing a Custom ClaimsAuthenticationManager (already identitfied by you). At least, that's the "official" extensibility point for doing it. Other soutions might work too though.
The default document feature is turned off in IIS and here's the situation...
My start page for my project say is A.aspx. I run the project and sure enough, A.aspx appears in the url of the browser. Like it should though, A.aspx finds no user logged in and redirects to Login.aspx like it should.
A.aspx:
if (Session["UserStuff"] == null)
Response.Redirect("~/Account/Login.aspx");
The login.aspx shows up BUT when the user Logs in, the code:
FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, true);
always redirects to "Default.aspx" and not "A.aspx"
I've examined FormsAuthentication.GetRedirectUrl and sure enough it returns "Default.aspx"
I'm stumped????
In web.config you could set the default page using the defaultUrl attribute:
<authentication mode="Forms">
<forms
loginUrl="login.aspx"
defaultUrl="a.aspx"
protection="All"
timeout="30"
/>
</authentication>
http://www.codeproject.com/KB/aspnet/custom_authentication.aspx Follow this
If you're using FormsAuthentication, your settings should be defined in the web.config. It sounds like you have a default setting in the web.config for DefaultUrl. You shouldn't need the session redirect though. FormsAuthentication should perform this for you. It doesn't hurt to check the session and force a SignOut() if you don't find it, but FormsAuthentication should perform this redirect.
From my understanding, when the user is redirectoed to your login screen, the Forms Authentication mechanism will add the url of the page that the user was originally tring to access, to the login url that that they user tried to access. For example, if you had a login page: http;//bob/login.aspx, and a user tried to access http;//bob/showmethemoney.aspx, then they would get redirected to http;//bob/login.aspx?ReturnUrl=showmethemoney.aspx. So, if you use the ReturnUrl to redirect the user after the user logs in, the user will always be returned to the resource that they were originally trying to get to.
I used the Single Sign-on demo from: http://www.codeproject.com/KB/aspnet/SingleSignon.aspx
And I add a SignOut function for this demo, but found a problem:
when I set the cookie.Domain, FormsAuthentication.SignOut() is not working and the cookie can not be cleared.
If the cookie has not been set the cookie.Domain, FormsAuthentication.SignOut() works.
I used C# asp.net.
And could anybody tell me some simple and practical Single Sign-On and Single Sign-Off solutions using asp.net ?
In case you are using the authentication for the same domain and subdomain, try adding the domain name in the web.config instead of adding the domain through code. you will no have to code anything if you use this web.config entry
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout="2880" domain="abc.com"/>
</authentication>
This entry tells the asp.net engine that the authentication cookie will be used for all subdomains of abc.com . Try using this and see if it works.
This worked for me:
In the Logout event/ method of each site, use Cookies collection in Request object & delete the relevant cookies as below:
enter code hereHttpCookie cookie = Request.Cookies.Get(".CommonCookieName");
cookie.Expires = DateTime.Now.AddDays(-1);
HttpContext.Current.Response.Cookies.Add(cookie);
If all the sites in SSO use same cookie, then this is simple as described above.
If few or each site participating in SSO use their own cookie/ user name for same subject (person), then u need to remove all the cookies. (perhaps establish a central location with just the mapping of the usernames & cookie names in each site in SSO collection of sites.
This works for me
public virtual ActionResult LogOff()
{
FormsAuthentication.SignOut();
foreach (var cookie in Request.Cookies.AllKeys)
{
Request.Cookies.Remove(cookie);
}
foreach (var cookie in Response.Cookies.AllKeys)
{
Response.Cookies.Remove(cookie);
}
return RedirectToAction(MVC.Home.Index());
}