HTTP Module and Cookies in Sharepoint 2007 - c#

I have some proof concept code for a HTTP module. The code checks to see if a cookie exists, if so it retrieves a value, if the cookie does not exist it creates it and sets the value.
Once this is done I write to the screen to see what action has been taken (all nice and simple). So on the first request the cookie is created; subsequent requests retrieve the value from the cookie.
When I test this in a normal asp.net web site everything works correctly – yay! However as soon as I transfer it to SharePoint something weird happens, the cookie is never saved - that is the code always branches into creating the cookie and never takes the branch to retrieve the value - regardless of page refreshes or secondary requests.
Heres the code...
public class SwithcMasterPage : IHttpModule
{
public void Dispose()
{
throw new NotImplementedException();
}
public void Init(HttpApplication context)
{
// register handler
context.PreRequestHandlerExecute += new EventHandler(PreRequestHandlerExecute);
}
void PreRequestHandlerExecute(object sender, EventArgs e)
{
string outputText = string.Empty;
HttpCookie cookie = null;
string cookieName = "MPSetting";
cookie = HttpContext.Current.Request.Cookies[cookieName];
if (cookie == null)
{
// cookie doesn't exist, create
HttpCookie ck = new HttpCookie(cookieName);
ck.Value = GetCorrectMasterPage();
ck.Expires = DateTime.Now.AddMinutes(5);
HttpContext.Current.Response.Cookies.Add(ck);
outputText = "storing master page setting in cookie.";
}
else
{
// get the master page from cookie
outputText = "retrieving master page setting from cookie.";
}
HttpContext.Current.Response.Write(outputText + "<br/>");
}
private string GetCorrectMasterPage()
{
// logic goes here to get the correct master page
return "/_catalogs/masterpage/BlackBand.master";
}

This turned out to be the authentication of the web app. To work correctly you must use a FQDM that has been configured for Forms Authentication.

You can use Fiddler or FireBug (on FireFox) to inspect response to see if your cookie is being sent. If not then perhaps you can try your logic in PostRequestHandlerExecute. This is assuming that Sharepoint or some other piece of code is tinkering with response cookies. This way, you can be the last one adding the cookie.

Related

System.ArgumentException: Invalid value for 'encryptedTicket' parameter happens on any login after the first successful login

I am currently trying to replace our company wide user authentication that we use for all our internal web apps and what not as our current one was made in 2006 and fails on the regular. I was told to make it as simple as possible to implement on all existing projects. It is a .NET class library. It's .dll will be added as a reference to existing projects.
I am having an issue where I can log in exactly one time after all cookies have been cleared. Once I logout and log back in I get System.ArgumentException: Invalid value for 'encryptedTicket' parameter. I found some posts suggesting the cookie may be null, or I'm not trying to decrypt the name and not the value, but that wasn't the case. This happens on chrome and edge.
The user is authenticated every time though, assuming the correct username and password is used as I get redirected to the success page.
After authentication I add a cookie and then redirect.
private void AddCookie(int compID, bool persist, HttpContext httpContext)
{
httpContext.Request.Cookies.Add(SetUpSession(compID, persist));
FormsAuthentication.RedirectFromLoginPage(compID.ToString(), persist);
}
My method for creating the cookie
private HttpCookie SetUpSession(int companyID, bool persist)
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // ticket version
companyID.ToString(), // authenticated username
DateTime.Now, // issueDate
DateTime.Now.AddMinutes(30), // expiryDate
persist, // true to persist across browser sessions
FormsAuthentication.FormsCookiePath); // the path for the cookie
String encTick = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie("Cookie", encTick);
cookie.HttpOnly = true;
return cookie;
}
After I redirect to the success page there is a snipped of code that checks to see if the user is logged in. This is where the error happens
public dynamic isLoggedIn(HttpContext httpContext)
{
AuthenticationUtilities authUtil = new AuthenticationUtilities();
if (httpContext.Response.Cookies["Cookie"] != null)
{
companyID = authUtil.Authenticate(httpContext.Request.Cookies["Cookie"]);//the error occurs here
authUtil = new AuthenticationUtilities(companyID);
return authUtil;
}
else
{
httpContext.Response.Redirect("~/login.aspx");
return null;
}
}
The method that decrypts the cookie
public int Authenticate(HttpCookie cookie)
{
FormsAuthenticationTicket authTick = FormsAuthentication.Decrypt(cookie.Value);
return int.Parse(authTick.Name);
}
this method is called on any page that requires the user to be logged in, like this.
LMFJAuth.AuthenticationUtilities auth = _LMFJAuth.isLoggedIn(HttpContext.Current);//if the cookie is null it redirects to login.
This is the logout method
public void LogOut(HttpContext httpContext)
{
FormsAuthentication.SignOut();
HttpCookie cookie = new HttpCookie("Cookie");
cookie.Expires = DateTime.Now.AddMinutes(-1);
httpContext.Session.Clear();
httpContext.Response.Cookies.Add(cookie);
httpContext.Response.Redirect(FormsAuthentication.LoginUrl);
}
Can somone help explain what may be going on in which the value for the encrypted ticked is coming up as invalid after the first successful login/logout?
For me it was that the encrypted value of cookie.Value was coming up as greater than the maximum value of 4096, being 4200 in my case. I had just added some role strings to the user data.
I found it help to look up the source code of Microsoft classes when I'm stuck, in this case I used:
http://www.dotnetframework.org/default.aspx/DotNET/DotNET/8#0/untmp/whidbey/REDBITS/ndp/fx/src/xsp/System/Web/Security/FormsAuthentication#cs/1/FormsAuthentication#cs.

Make ASP.NET_SessionId cookie not httpOnly

The cookie used for session in ASP.NET MVC is httpOnly (property set to true).
Is there a way to make it not httpOnly?
I want to be able to access this cookie from javascript.
Even if it is less secure than the "What if all the universe stands against me?!" default setting.
If you REALLY need it you could try to add this to your Global.asax:
void Application_EndRequest(Object sender, EventArgs e)
{
if (Response.Cookies.Count > 0)
{
foreach (string s in Response.Cookies.AllKeys)
{
if (s == "ASP.NET_SessionId")
{
Response.Cookies["ASP.NET_SessionId"].HttpOnly = false;
}
}
}
}
Solution was taken from here.
I built a system that uses cookies to store search params across the site.
On the home page there are links and I wanted to use jQuery to save a cookie with the item id in it.
But on click the user is then sent to an advanced search page where they can use .net controls to modify the search. The cookies are saved again but they needed to be writable by the js on the home page when the user browsed back.
So I set HttpOnly like this:
var cookie = new HttpCookie(name)
{
Value = val,
HttpOnly = false // #DEV search cookies can be modified by JS
};
HttpContext.Current.Response.Cookies.Add(cookie);

Http module Authentication

I'm using the code below from this post to try and create a custom http module :
public class BasicAuthenticationModule: IHttpModule
{
public void Init(HttpApplication application)
{
application.AuthenticateRequest += new EventHandler(Do_Authentication);
}
private void Do_Authentication(object sender, EventArgs e)
{
var request = HttpContext.Current.Request;
string header = request.Headers["HTTP_AUTHORIZATION"];
if(header != null && header.StartsWith("Basic "))
{
// Header is good, let's check username and password
string username = DecodeFromHeader(header, "username");
string password = DecodeFromHeader(header, password);
if(Validate(username, password)
{
// Create a custom IPrincipal object to carry the user's identity
HttpContext.Current.User = new BasicPrincipal(username);
}
else
{
Protect();
}
}
else
{
Protect();
}
}
private void Protect()
{
response.StatusCode = 401;
response.Headers.Add("WWW-Authenticate", "Basic realm=\"Test\"");
response.Write("You must authenticate");
response.End();
}
private void DecodeFromHeader()
{
// Figure this out based on spec
// It's basically base 64 decode and split on the :
throw new NotImplementedException();
}
private bool Validate(string username, string password)
{
return (username == "foo" && pasword == "bar");
}
public void Dispose() {}
public class BasicPrincipal : IPrincipal
{
// Implement simple class to hold the user's identity
}
}
The code works ok at making the 401 error be returned by the server and the login dialog pop up but when the correct login details are entered the login dialog does not go away.
When debugging the code nothing happens when the Ok button on the dialog is clicked, the event isn't triggered and the user details aren't validated, I can't figure out why this isn't working.
Any help or ideas would be great, thanks.
On Microsoft's asp.net website, there is a good example on how to do custom authentication. Ignore the fact that it says it's about WebAPI. The code uses a IHttpModule, so it works with WebForms, IHttpHandler, asmx, WCF, and anything else that runs in IIS. Copying and pasting the code at the end of that page into a new project works for me. Although, I don't recommend setting the thread's CurrentPrincipal to the authenticated user, like the sample does. I prefer to just use the current context's User property.
If your breakpoint in the module isn't getting hit, then it's almost certainly because the http module wasn't registered correctly. The asp.net page I linked above shows how to register the module in your web.config file, so you should start there. You should be able to use Visual Studio's intellisense auto-complete to complete your class name, which helps make sure you typed it right (although there is a chance that Resharper is doing it on my machine, but I think it's just plain Visual Studio).

Duplicate cookies?

I have an application that leverages a cookie to support a quasi-wizard (i.e. it's a set of pages that are navigated to by each other, and they must occur in a specific order, for registration).
When the Logon.aspx page is loaded - the default page - the browsers cookies look right.
There's one cookie and it has the right value. This ensures that the next page, which is an enrollment agreement, knows that it was loaded from the Logon.aspx page. However, when I get to that page the browsers cookies look much different:
Now we have two of the same cookie.
This doesn't appear to be causing any real issues - but I can't be sure it won't. So, let me show you the code I'm using to set the cookie (because maybe there's something wrong with it):
if (!this.IsPostBack)
{
Utility.HandleReferrer(Request, Response, "Logon.aspx");
Response.Cookies["lastpage"].Value = "Enroll.aspx";
}
and the HandleReferrer method looks like this:
static public void HandleReferrer(HttpRequest request, HttpResponse response, string expectedReferrer)
{
var cookie = request.Cookies["lastpage"];
if (cookie != null && cookie.Value.ToLower().Contains(expectedReferrer.ToLower()))
{
return;
}
response.Redirect("Logon.aspx");
}
So, why in the world does it duplicate this cookie? It doesn't ever seem to create more than two.
I suggest you do one of the following.
First, get the latest glimpse and try again.
If it is still showing 2 cookies with that name then get firebug and/or fiddler and look at it that way. If I had to take a guess I'd say there's either something wrong in glimpse or something wrong in how you are interpreting the results. Perhaps glimpse is showing what cookies existed before and then after the request was processed?
A third option is to simply emit the cookies collection from your own .net code. Something like:
foreach(HttpCookie cookie in request.Cookies) {
Response.Write(String.Format("{0} = {1}", cookie.Name, cookie.Value));
}
and see what happens.
I tried another approach, by creating a method that will return the latest cookie occurrence, this way I'll always get the right data.
This method expect the collection of cookies from Request, and the name of the searched cookie, and returns the latest ticket (the information that is normally encrypted)
private static FormsAuthenticationTicket GetLatestCookie(HttpCookieCollection cookies, string cookieName) {
var cookieOccurrences = new List<FormsAuthenticationTicket>();
for (int index = 0; index < cookies.Count; index++) {
if (cookies.GetKey(index) == cookieName) {
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookies[index].Value);
cookieOccurrences.Add(ticket);
}
}
DateTime oldestTime = DateTime.MinValue;
FormsAuthenticationTicket oldestTicket = null;
foreach (var formsAuthenticationTicket in cookieOccurrences) {
if (formsAuthenticationTicket.Expiration > oldestTime) {
oldestTime = formsAuthenticationTicket.Expiration;
oldestTicket = formsAuthenticationTicket;
}
}
return oldestTicket;
}

When to use Request.Cookies over Response.Cookies?

Do I use response when at a page event (e.g. load) as this is a response from ASP.NET, and request when pressing a button as this is a response going to ASP.NET for processing? Or is there more to it?
They are 2 different things, one SAVES [Response], the other READS [Request]
in a Cookie (informatics speaking) :)
you save a small file for a period of time that contains an object of the type string
in the .NET framework you save a cookie doing:
HttpCookie myCookie = new HttpCookie("MyTestCookie");
DateTime now = DateTime.Now;
// Set the cookie value.
myCookie.Value = now.ToString();
// Set the cookie expiration date.
myCookie.Expires = now.AddMinutes(1);
// Add the cookie.
Response.Cookies.Add(myCookie);
Response.Write("<p> The cookie has been written.");
You wrote a cookie that will be available for one minute... normally we do now.AddMonth(1) so you can save a cookie for one entire month.
To retrieve a cookie, you use the Request (you are Requesting), like:
HttpCookie myCookie = Request.Cookies["MyTestCookie"];
// Read the cookie information and display it.
if (myCookie != null)
Response.Write("<p>"+ myCookie.Name + "<p>"+ myCookie.Value);
else
Response.Write("not found");
Remember:
To Delete a Cookie, there is no direct code, the trick is to Save the same Cookie Name with an Expiration date that already passed, for example, now.AddMinutes(-1)
this will delete the cookie.
As you can see, every time that the time of life of the cookie expires, that file is deleted from the system automatically.
In a web application the request is what comes from the browser and the response is what the server sends back. When validating cookies or cookie data from the browser you should use the Request.Cookies. When you are constructing cookies to be sent to the browser you need to add them to Response.Cookies.
When writing a cookie, use Response but reading may depend on your situation. Normally, you read from Request but if your application is attempting to get a cookie that has just been written or updated and the round trip to the browser has not occured, you may need to read it form Response.
I have been using this pattern for a while and it works well for me.
public void WriteCookie(string name, string value)
{
var cookie = new HttpCookie(name, value);
HttpContext.Current.Response.Cookies.Set(cookie);
}
public string ReadCookie(string name)
{
if (HttpContext.Current.Response.Cookies.AllKeys.Contains(name))
{
var cookie = HttpContext.Current.Response.Cookies[name];
return cookie.Value;
}
if (HttpContext.Current.Request.Cookies.AllKeys.Contains(name))
{
var cookie = HttpContext.Current.Request.Cookies[name];
return cookie.Value;
}
return null;
}
The cookies comes from the browser in the Request.Cookies collection. That is where you read the cookies that was sent.
To send cookies back to the browser you put them in the Response.Cookies collection.
If you want to delete a cookie, you have to tell the browser to remove it by sending the cookie with an expiration date that has passed. The browser is using the local time of the client computer so if you are using the server time to create a date, be sure to subtract at least one day to be sure that it has actually passed in the clients local time.
When i create or update a cookie in .NET i normally do it to both the request and response cookie collection. That way you can be sure if you try to read the cookie further down the page request sequence it will have the correct information.
Andrew's Code gave an error in "AllKeys.Contains" Method. So I corrected a little..
public void WriteCookie(string strCookieName, string strCookieValue)
{
var hcCookie = new HttpCookie(strCookieName, strCookieValue);
HttpContext.Current.Response.Cookies.Set(hcCookie);
}
public string ReadCookie(string strCookieName)
{
foreach (string strCookie in HttpContext.Current.Response.Cookies.AllKeys)
{
if (strCookie == strCookieName)
{
return HttpContext.Current.Response.Cookies[strCookie].Value;
}
}
foreach (string strCookie in HttpContext.Current.Request.Cookies.AllKeys)
{
if (strCookie == strCookieName)
{
return HttpContext.Current.Request.Cookies[strCookie].Value;
}
}
return null;
}

Categories

Resources