Ting Huang
A hack into the .NET authentication classes to extract a
component that can be called by legacy asp applications to generate the same
authentication token used in asp.net applications.
(Link to part I:
http://www.wwwcoder.com/main/parentid/258/site/2598/68/default.aspx)
Introduction
In part I of the article, I introduced a way to create a
single sign-on between asp and asp.net applications. Although the solution is
sound, it is not without shortcomings:
1. While the authentication token generated is different
each time, it is not time-stamped. Hence, there is no expiration by itself. This
could lead to a high risk of being maliciously reused when the token is
hijacked.
2. It is not a time-saving or effort-saving solution,
because additional development is required on both sides of asp and asp.net
applications to manage the authentication token.
It would be ideal if the forms authentication mechanism
from ASP.NET can be utilized by ASP application to generate or verify
authentication token efficiently. Additionally, the token generated should be
automatically recognized as a valid token by ASP.NET built-in forms
authentication mechanism as well.
Inspired by a Microsoft knowledge-base article at
http://support.microsoft.com/default.aspx?scid=kb;EN-US;891028 which makes a
web service to provide authentication methods for ASP application, I got the
idea of using the asp.net FormsAuthentication utilities directly in classic asp.
To circumvent the initialization problem, I extracted the core functionalities
apart from the FormsAuthentication utilities and removed the requirements for
HttpContext and other related classes that are only available in asp.net. The
asp scripts will be able to use simple COM interop to call into this
authentication component to retrieve as well as verify tokens.
How Forms Authentication Works in ASP.NET
What happens behind the scene of the form authentication
mechanism in ASP.NET is that when a client request goes through the http
pipeline, the FormsAuthenticationModule (a HttpModule) picks up the incoming
request and tries to extract authentication ticket from cookie. The process
requires decryption and reading configurations via HttpContext.
Solution
The solution is to extract the core functionalities apart
from the FormsAuthentication utilities in ASP.NET and remove the requirements
for HttpContext and other related classes and make them into a authentication
component that can be called by ASP application.
Here is how the solution looks like:

The following steps show how to get the solution working.
1. Synchronize the encryption and decryption key
Forms authentication in asp.net uses the <machineKey>
element in machine.config or web.config for encryption and decryption
authentication token:
<machineKey validationKey="AutoGenerate|value[,IsolateApps]"
decryptionKey="AutoGenerate|value[,IsolateApps] validation="SHA1|MD5|3DES"/>
If you have run ASP.NET applications in a server farm, you
probably know that the machine key has to be set with the same value on all
severs in the farm. This ensures that tokens generated on one of the servers can
be validated on another. For the same reason, it is necessary to synchronize the
encryption and decryption key in both ASP.NET forms authentication and the
custom authentication component.
The following link can be used to generate a machine key
setting:
http://www.eggheadcafe.com/articles/GenerateMachineKey/GenerateMachineKey.aspx
The following is an example of such setting:
<machineKey validationKey='6C0D4C65DBD3CF60C037E0BE43D2958954DA9AAA830D902B8481DDDF962A
4E392056DE5EEEEC0061673BB413DB533D6F8C61C70AD36C9F89F99DC0B3AD292B53'
decryptionKey='7EBC8C30854618249344BAF797E1B9DD119F249D459C006D'
validation='SHA1'/>
This is the only change has to be made in ASP.NET for the
single sign-on to work.
2. Create a COM wrapper for authentication
This is the ‘bare bone’ class for the authentication API.
public class AuthAPI
{
public AuthAPI()
{}
public void
Initialize()
{
FormsAuthentication.Initialize();
}
public void
Initialize1(
string
cookieName,
string cookieDomain,
string cookiePath,
bool slidingExpiration,
int timeout,
bool requireSSL
)
{
FormsAuthentication.Initialize(
cookieName,
cookieDomain,
cookiePath,
slidingExpiration,
timeout,
requireSSL
);
}
public void
Initialize2(
string cookieName,
string cookieDomain,
string cookiePath,
bool slidingExpiration,
string protection,
int timeout,
bool requireSSL,
string validationKey,
string decryptionKey,
string validationMode
)
{
FormsAuthentication.Initialize(
cookieName,
cookieDomain,
cookiePath,
slidingExpiration,
(FormsProtectionEnum)Enum.Parse(typeof(FormsProtectionEnum),
protection, true),
timeout,
requireSSL,
validationKey,
decryptionKey,
(FormsCryptoValidationMode)Enum.Parse(typeof(FormsCryptoValidationMode),
validationMode, true)
);
}
public string
GetAuthCookieValue(
string
userName,
bool
createPersistentCookie)
{
HttpCookie
cookie = FormsAuthentication.GetAuthCookie(userName, createPersistentCookie);
return cookie.Value;
}
public bool
IsAuthenticated(
string
cookieValue,
out
string newCookieValue,
out
DateTime expiration)
{
return
FormsAuthentication.IsAuthenticated(cookieValue, out
newCookieValue, out expiration);
}
}
Initialize2 method requires validationKey,
decryptionKey and validationMode as well as other parameters being passed in
order for the custom FormsAuthentication to be initialized. The two other
initialization methods will use the default keys in the class. To make the COM
call easier, don’t use overloads in C# for the initialization methods.
GetAuthCookieValue method works almost the same as
GetAuthCookie in ASP.NET FormsAuthentication class except that it doesn’t return
a HttpCookie object but rather a string value of the cookie to be written
directly to client’s browser.
IsAuthenticated method validates the incoming cookie if it
is a valid authentication cookie. If a renew action is required for the cookie
on the caller, it will return value in the newCookieValue parameter, otherwise,
it will be empty. The expiration DateTime value gives convenience to the caller
when it performs the renew action.
3. Making a new asp authentication script
To retrieve the authentication cookie:
<% cookieName =
".ASPXAUTH"
cookieDomain =
"yourdomain"
cookiePath = "/"
Set ulogin =
Server.CreateObject("SingleSignon.AuthAPI")
uLogin.Initialize1 cookieName, cookieDomain,
cookiePath, 1, 20, 0
s = uLogin.GetAuthCookieValue(cookieName,
false)
Call AddCookie ( cookieName, s )
set ulogin = nothing
%>
<% Set ulogin =
Server.CreateObject("SingleSignon.AuthAPI")
isLogin =
uLogin.IsAuthenticated(GetCookie(uLogin.FormsCookieName, ""), newCookieValue,
expiration)
If not isLogin then
‘ user is not logged in, redirect
to login page
Else
‘ user is logged in
If not
isempty(newCookieValue) then
'renew
cookie
Call
AddCookie ( cookieName, newCookieValue, expiration )
End If
End If
Set ulogin = nothing
%>
Conclusion
In this article, a custom authentication component is
extracted from the ASP.NET forms authentication utilities to remove any
HttpContext initialization so that it can be called directly in classic ASP
scripts through COM interface. The authentication token obtained via this API
component works between ASP.NET and ASP application, with minimum setting in
ASP.NET’s forms authentication. It also works efficiently without a web service
workaround.
There are more that can be done based on this solution.
These include:
- Update the Authentication API to read configurations
from ASP.NET application. This can either be done via web service or direct
access to the config file in ASP.NET. The advantage is to avoid explicitly
passing encryption keys;
- Use Session or any other available solutions to
maintain the state, so that calling the Authentication API for every
authentication request in ASP can be avoided.
References
For more information, please see the following resources:
ASP.NET security overview (http://support.microsoft.com/default.aspx?scid=kb;EN-US;891028
)
Download the code!