Thursday, January 19, 2006

 

Sharing Authentication Across ASP.NET

Back in October, I posted a question on the microsoft.public.dotnet.framework.aspnet newsgroup. The title/subject was "Sharing Authentication Across ASP.NET". I did not receive a response. Five days later I replied to the post, stating that I had found a solution, and then made the mistake of telling people they can contact me for the solution. What I should have done is blogged the problem and the solution.

Below you will find a copy of the problem as I described it in my newsgroup posting, as well as a copy of the solution e-mail that I send out to those who request it.

Problem

Hi All,

I have two ASP.NET applications which I am trying to have share forms
authentication. But I am running into problems.

App A is an ASP.NET 2.0 Beta 2 application. App B is an ASP.NET 1.1
application (Telligent's Community Server) compiled with VS.NET 2003.

App B runs in a virtual sub-directory of App A. Both applications run
fine. Both site's ASP.NET tabs are set appropriately (A = 2.0.5X B =
1.1.X)

I have done a lot of research and I believe both applications are setup
to share the same authentication cookie.

Here are the steps I took:

1. Added identical to the root web.config of each app.
Example:



<!-- Keys shortened for brevity -->
<machineKey
validationKey="5FC1F907ADE8C5800DB3B1F195B8E...EADFF5E78070CAA"
decryptionKey="7D27FEC08...CF3771C74CE3"
validation="3DES" />


2. Changed in each root web.config to be identical.
Example:



<authentication mode="Forms">
<forms name=".CommunityServer"
loginUrl="security/Login.aspx"
protection="All" timeout="20"
path="/" />
</authentication>


3. In the App A web.config I added the following:



<location path="main">
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>


4. In the App B web.config I added the following:



<authorization>
<deny users="?" />
</authorization>


According to the sites I have read on how to do this, the above changes
should be enough. I try the following:

1. When attempting to get to the /main directory of App A, I am
redirected to the login.

2. I successfully login. Using Tracing, I can see that my
.CommmunityServer cookie has been set.

3. I attempt to get to the virtual sub-directory (App B). I am
redirected to the login page.
4. Without logging in again, I go to the /main directory of App A and I
get there without being redirected. Viewing the Tracing output on the
page, I can see that my cookie is still set.

I have put the following code into the Application_AuthenticateRequest
event handler of App B's Global.asax file:

----------BEGIN CODE-------------------------



protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
bool cookieFound = false;

HttpCookie authCookie = null;
HttpCookie cookie;
string cookieNames = "";
for(int i=0; i < Request.Cookies.Count; i++)
{
cookie = Request.Cookies[i];

cookieNames = cookieNames + cookie.Name + "\n";
if (cookie.Name == FormsAuthentication.FormsCookieName)
{
cookieFound = true;
authCookie = cookie;
break;
}
}

// If the cookie has been found, it means it has been issued from either
// the windows authorisation site, is this forms auth site.
if (cookieFound)
{
// Extract the roles from the cookie, and assign to our current principal,
// which is attached to the
// HttpContext.
FormsAuthenticationTicket winAuthTicket =
FormsAuthentication.Decrypt(authCookie.Value);
string[] roles = winAuthTicket.UserData.Split(';');
FormsIdentity formsId = new FormsIdentity(winAuthTicket);
GenericPrincipal princ = new GenericPrincipal(formsId,roles);
HttpContext.Current.User = princ;
}
else
{
// No cookie found, we can redirect to the Windows auth site if we
// want, or let it pass through so
// that the forms auth system redirects to the logon page for us.
throw new ApplicationException(@"Invalid login from here.
FormsCookieName:"
+ FormsAuthentication.FormsCookieName + "\n" +
"CookieNames:" + cookieNames+ "\n");
}

}


-----------------END CODE----------------------------

The cookie with the name ".CommunityServer" is found, but when the line
calling "FormsAuthentication.Decrypt(authCookie.Value);" executes, I
get the following error:

-----------BEGIN ERROR-------------------------------
Bad Data.
Description: An unhandled exception occurred during the execution of
the current web request. Please review the stack trace for more
information about the error and where it originated in the code.

Exception Details: System.Security.Cryptography.CryptographicException:
Bad Data.

Source Error:

Line 100: // HttpContext.
Line 101: //throw new ApplicationException("CookieName: " +
authCookie.Name + "\n" + authCookie.Value);
Line 102: FormsAuthenticationTicket winAuthTicket =
FormsAuthentication.Decrypt(authCookie.Value);
Line 103: string[] roles = winAuthTicket.UserData.Split(';');
Line 104: FormsIdentity formsId = new FormsIdentity(winAuthTicket);

Source File: c:\dev\cs_bsinterns\web\global.asax.cs Line: 102

Stack Trace:

[CryptographicException: Bad Data.
]
System.Security.Cryptography.CryptoAPITransform._DecryptData(IntPtr
hKey, Byte[] rgb, Int32 ib, Int32 cb, Boolean fDone) +0

System.Security.Cryptography.CryptoAPITransform.TransformFinalBlock(Byte[]
inputBuffer, Int32 inputOffset, Int32 inputCount) +805
System.Security.Cryptography.CryptoStream.FlushFinalBlock() +40
System.Web.Configuration.MachineKey.EncryptOrDecryptData(Boolean
fEncrypt, Byte[] buf, Byte[] modifier, Int32 start, Int32 length) +139
System.Web.Security.FormsAuthentication.Decrypt(String
encryptedTicket) +114
CommunityServerWeb.Global.Application_AuthenticateRequest(Object
sender, EventArgs e) in c:\dev\cs_bsinterns\web\global.asax.cs:102

System.Web.SyncEventExecutionStep.System.Web.HttpApplication+IExecutionStep­.Execute()
+59
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean&
completedSynchronously) +87

---------------------------------------------------------------------------­-----
Version Information: Microsoft .NET Framework Version:1.1.4322.573;
ASP.NET Version:1.1.4322.573

-----------END ERROR---------------------------------

Solution

I added the following code to the login page of the primary
application, right after the code that authenticates the user



// Create cookie for CommunityServer
string userName = txtUserName.Text.Trim();
RememberEmail(userName);
HttpCookie userCookie = null;
if (Request.Cookies ["TrustedUserName"] == null)
{
userCookie = new HttpCookie("TrustedUserName");
userCookie.Value = txtUserName.Text.Trim();
userCookie.Expires = DateTime.Now.AddMinutes(20);
Response.Cookies.Add(userCookie);
}
else
{
Response.Cookies["TrustedUserName"].Value = userName;
Response.Cookies["TrustedUserName"].Expires = DateTime.Now.AddMinutes(20);
}



I then create a page titled CheckLoggedIn.aspx and added the following
code to the Code Behind .cs file:



string cookieName = "TrustedUserName";
HttpCookie authCookie = Context.Request.Cookies[cookieName];


if (null == authCookie)
{
Response.Redirect("../security/login.aspx");
}
else
{

LoginUser( authCookie.Value);

Response.Redirect("forums/");
}
}

private void LoginUser(string userName)
{
User userToLogin = null;

userToLogin = Users.FindUserByUsername(userName);
LoginUserStatus loginStatus;

if ( userToLogin.Username == null)
{ // User is not currently in CS database.

// Add user to database.
User newUser = new User();
newUser.Username = userName;
newUser.Email = userName;
newUser.AccountStatus = UserAccountStatus.Approved;
newUser.Password = "123456";
newUser.IsAnonymous = false;
Users.Create(newUser, false);

// Assign newly created user to userToLogin
userToLogin = newUser;
}

if (userToLogin != null)
{
loginStatus = LoginUserStatus.Success ;
}
else
{
loginStatus = LoginUserStatus.InvalidCredentials;
throw new ApplicationException("Invalid Username: " + userName);
}

bool enableBannedUsersToLogin =
CSContext.Current.SiteSettings.EnableBannedUsersToLogin;

// Change to let banned users in
//
if (loginStatus == LoginUserStatus.Success ||
(enableBannedUsersToLogin && loginStatus == LoginUserStatus.AccountBanned))
{
// Are we allowing login?
// TODO -- this could be better optimized
if (!CSContext.Current.SiteSetting s.AllowLogin && !userToLogin.IsAdministrator)
{
throw new CSException(CSExceptionType.UserLoginDisabled);
}

FormsAuthentication.SetAuthCookie(userToLogin.Username, false);

// Redirect to forums
Response.Redirect("forums");

}
}

Comments: Post a Comment

Links to this post:

Create a Link



<< Home
Content copyright ©2003-2006 Tod Birdsall