I have taken what used to be an ad authenticated web site on iis7 and migrated it to iis 8.5. In the process I have tried to switch to forms authentication. Not every page on the site is an aspx page - there is static content and files on there as it is an intranet site - but basic forms authentication has worked because I set the app pool to be in integrated mode and set the web.config to use the forms authentication. I've been pretty pleased that if you try to do so much as load a pdf it will make sure you're logged into a cas server first.
My problem is with roles - I've set up the cookie in the login page to have roles in it (that I extract from CAS attributes) and when I do a User.IsInRole() on that page it says that yes, the person has the role. But then if I move onto a new page, aspx or not, the person is still authenticated but does not have the role. I am trying to avoid using the role manager because I do not want to deal with the provider (the cas server is handling the authentication piece) but I will use the role manager if I must. I've had bad luck with it so far. I believe that the roles aren't loading from the cookies because I put the same (User.IsInRole("staff"))?"True":"False"; line in the main page as below, but on the login page it returns true and on the main page it returns false.
I'll append my web config and the relevant section of my login page code.
Web config (only announce folder uses roles atm):
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<modules>
<!-- Re-add auth modules (in their original order) to run for all static and dynamic requests -->
<remove name="FormsAuthentication" />
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
<remove name="DefaultAuthentication" />
<add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" />
<remove name="RoleManager" />
<add name="RoleManager" type="System.Web.Security.RoleManagerModule" />
<remove name="UrlAuthorization" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
</modules>
</system.webServer>
<system.web>
<authentication mode="Forms" />
<authorization>
<deny users="?" />
<allow users="*" />
</authorization>
</system.web>
<location path="announce">
<system.web>
<authorization>
<allow roles="staff, faculty" />
<deny users="*" />
</authorization>
</system.web>
</location>
</configuration>
Relevant section of login page code behind (the redirect is commented out so I can see their roles):
// Second time (back from CAS) there is a ticket= to validate
// CAS 3 doesn't list attributes through /serviceValidate, so lets build a SAML request and use that instead.
string SAMLstr = "<?xml version='1.0'?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
"<SOAP-ENV:Header/><SOAP-ENV:Body><samlp:Request xmlns:samlp=\"urn:oasis:names:tc:SAML:1.0:protocol\" " +
"MajorVersion=\"1\" MinorVersion=\"1\" RequestID=\"_" + Request.ServerVariables["REMOTE_HOST"] + "." + DateTime.Now.Ticks + "\"" +
"IssueInstant=\"" + DateTime.Now + "\"><samlp:AssertionArtifact>" + tkt + "</samlp:AssertionArtifact>" +
"</samlp:Request></SOAP-ENV:Body></SOAP-ENV:Envelope>";
string validateurl = CASHOST + "samlValidate?TARGET=" + service + "&ticket=" + tkt;
// Set up an xml document to catch the SAML responce and then parse it.
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(new WebClient().UploadString(validateurl, SAMLstr));
// The response uses multiple namespaces, whee!
var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
nsmgr.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/");
nsmgr.AddNamespace("nsR", "urn:oasis:names:tc:SAML:1.0:protocol");
nsmgr.AddNamespace("nsA", "urn:oasis:names:tc:SAML:1.0:assertion");
// Let's see if they authed successfully:
if (xmlDoc.SelectSingleNode("/s:Envelope/s:Body/nsR:Response/nsR:Status/nsR:StatusCode", nsmgr).Attributes["Value"].Value == ("samlp:Success")) {
// They authenticated successfully; populate their id and roles.
string netid = xmlDoc.SelectSingleNode("/s:Envelope/s:Body/nsR:Response/nsA:Assertion/nsA:AuthenticationStatement/nsA:Subject/nsA:NameIdentifier", nsmgr).InnerText;
// Grab the "memberOf" attriubute - it contains a child node for each group distinguished name.
XmlNode memberOf = xmlDoc.SelectSingleNode("/s:Envelope/s:Body/nsR:Response/nsA:Assertion/nsA:AttributeStatement/nsA:Attribute[@AttributeName='memberOf']", nsmgr);
// I don't want to iterate through that, so I'll just grab all the text and split it with a regex to split up the OUs and DCs.
string[] groups = Regex.Split(memberOf.InnerText.ToLower().Substring(3), "cn=(.*?),ou=");
HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(netid),new string[] {"staff", "union", "UTech"});
// Splicing together the groups names but droping the OUs (every other entry)
string roles=groups[1];
for (int x=3;x<groups.Length;x+=2) roles+="|"+groups[x];
// Let's bake cookies with the netid, roles, and persistence. 1: Make the ticket.
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, netid, DateTime.Now, DateTime.Now.AddMinutes(2880), isPersistent, roles, FormsAuthentication.FormsCookiePath);
// 2. Encrypt the ticket.
string hash = FormsAuthentication.Encrypt(ticket);
// 3. Turn the encrypted ticket into a cookie!
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
// 4. Set expiration on the cookie to match the ticket.
if (ticket.IsPersistent) cookie.Expires = ticket.Expiration;
// 5. Put the cookie in the jar.
Response.Cookies.Add(cookie);
// We're done! Send them home.
// [TEMP, next 3 lines are debug] Response.Redirect(service + Session["backto"], true);
Label1.Text = "Welcome, "+netid+"!";
Label2.Text = roles;
Label3.Text = (User.IsInRole("staff"))?"True":"False";
I hate to answer my own question, but after a week of searching and reading tutorials, I got it worked out.
The cookies and forms authentication in the above question were fine; the reason the roles were not being loaded was because I did not have a PostAuthenticateRequest in my global.asax (and in fact did not have a global.asax at all). This question was most helpful, not from the accepted answer but by an appended item by P.Hoxha. Which had 0 voted but I upvoted. A Microsoft tutorial here was also helpful with corrections for what did not work in the response I upvoted.
Adding the global.asax solved the problem by catching the PostAuthenticateRequest and running the below code to load the roles: