asp:feature
Languages: C# |
VB
Technologies: Security
| Authentication | Passport
Authenticate
Users with Microsoft Passport
Make
User Logins Simple and Consistent
By Steve
Turley
Passport
is the key technology used by .NET My Services to authenticate users on a Web
site. Adding Passport to your ASP.NET site is easy to do and gives the user a
consistent, convenient method of logging in without having to remember yet
another user name and password. ASP.NET wraps much of the older Passport SDK to
provide a single object and simple configuration.
Writing
an ASP.NET site enabled with .NET Passport is relatively simple: You install
the .NET Passport SDK, register your site, install your .NET Passport site key,
change your s ite's authentication mode to Passport, and deny access to
unauthorized users. (These last two items occur in the site's web.config file.)
That's it. Your site now requires all users to sign in with their .NET Passport
account before they can see any content. Follow along, and this article will
show you how it's done.
The User's Experience
Imagine
you have a site, www.mysite.com, with a home
page named default.aspx. The site's virtual directory contains a web.config
file enables Passport authentication and blocks any access to the site until
the user has signed into his or her .NET Passport account:
<configuration>
<system.web>
<authentication
mode="Passport">
<redirectUrl="internal"/>
</authentication>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
For
example, a user named John browses to your site. Because he hasn't signed into
his .NET Passport account during this session, the first thing John sees is the
screen shown in FIGURE 1.
FIGURE 1: This is the default page
unauthorized users will see when trying to access the site without signing into
their Passport account. However, you might wish to construct a more attractive
and user-friendly page.
When
clicking on the Sign
In button, which those
familiar with Passport refer to as a scarab, John sees the page shown in FIGURE
2.
FIGURE 2: The Passport server provides the .NET Passport sign-in page. Passport
validates the user's name and password and it returns a ticket to the client.
Now John
enters his .NET Passport credentials. If he doesn't have a .NET Passport
account, he can get one here. He also can access pages on the Microsoft .NET
Passport site to learn more about .NET Passport. Once he enters his
credentials, which consist of his e-mail address and password, John clicks on
the Sign
In button. Voil .
He finally sees the page he originally requested, www.mysite.com/default.aspx.
After he signs in, John can browse the site at his leisure. Moreover, he can
browse to any .NET Passport site, even sites in completely separate domains,
without signing in again. After he finishes, he signs out by clicking on the
.NET Passport scarab again or simply closing his browser.
A .NET
Passport site needs a few more things before it is considered a valid site that
you can make public. The SDK help file offers a complete checklist. Look for
the topic Reviewing
the Checklist. One
important requirement is that every site using .NET Passport displays the
sign-in and sign-out scarab on every page. You'll see how to do this later, but
for now, consider what's happening during John's visit to your site: John
requests www.mysite.com/default.aspx. ASP.NET
notices that unauthorized users are denied by virtue of entries in the
web.config file:
<authorization>
<deny
users="?"/>
</authorization>
ASP.NET
notices the site uses Passport authentication and unauthenticated users are to
be sent to the internal page for authentication:
<authentication
mode="Passport">
<redirectUrl="internal"/>
</authentication>
ASP.NET
automatically redirects the user to the built-in, internal authentication page.
This is the first page John sees-the one that denies him access. This page
doesn't authenticate John, but instead, it simply has a link (the scarab) to
another page. Next, John clicks on the Passport sign-in scarab, which sends him
to the .NET Passport sign-in page provided by the Microsoft .NET Passport
servers. John signs in.
Now
Passport authenticates John by comparing his sign-in information (e-mail
address and password) with the information in its database. If John presents
valid credentials, the Passport server sets some cookies on his browser and
(finally) sends him to the page he originally wanted. This time, ASP.NET sees
that John is authenticated (his browser has the Passport cookies) and lets him
view the page. Once John's browser has the Passport cookies, he can visit any
.NET Passport site without signing in again. For tighter security, sites can
control automatic sign-in if they want by requiring, for example, that the user
signs in within a certain time limit, and a site could also force John to sign
in again even though he has valid credentials. In addition, John is able to
delete the cookies at any time by signing out explicitly or by closing his
browser.
Notice
that during John's visit, the entire Web site is protected. Regardless of the
page John requests, ASP.NET would stop him and require him to sign in. But,
once signed in, he has free access to the entire site. Sometimes this is
exactly what you want, but sometimes it isn't. Here are two easy ways to pick
and choose which pages you want to protect: You could put all the protected
pages in a separate directory and set the authentication and authorization
requirements in it while leaving your main directory open to everyone; or you
could use <location> tags in your web.config to require
authentication for specific pages only. But this gets cumbersome if you have
many pages to protect.
Only
ASP.NET (e.g.., aspx, asmx, ascx, and so on) pages are protected by web.config
settings. HTML and ASP pages are not affected and are accessible to anyone.
Passport Identity
Every
client that accesses your Web site has an Identity object associated
with it. The Identity is available to your ASP.NET code as Context.User.Identity.
Simple unauthenticated pages have a GenericIdentity, which provides a
few properties such as Name, AuthenticationType, and IsAuthenticated.
When Passport authentication is used, the page has a PassportIdentity,
which is significantly more powerful. Among the most important differences is
that the Name property is a unique, 16-digit, hexadecimal string that
identifies the user. FIGURE 3 shows some of the interesting members of the PassportIdentity
class (see the .NET Framework documentation for compete details).
|
Property
or Method
|
Data
Type
|
Comment
|
|
AuthenticationType
|
String property
|
"Passport"
|
|
Error
|
Property
|
Passport
error code
|
|
GetFromNetworkServer
|
Boolean property
|
True when the client has just
returned from signing in; otherwise False
|
|
HexPUID
|
String property (2.x SDK only)
|
The
unique Passport User Identity (PUID) for this user
|
|
IsAuthenticated
|
Boolean property
|
True if the user is signed in
|
|
Name
|
String property
|
Same
as HexPUID
|
|
TicketAge
|
Int property
|
Number
of seconds since the user signed in
|
|
GetCurrentConfig
|
String method (2.x SDK only)
|
Returns
configuration information from the registry
|
|
GetIsAuthenticated
|
Boolean method
|
Similar
to IsAuthenticated property but allows testing for timeout
|
|
GetProfileObject
|
Method
|
Returns
information from the user's profile
|
|
LogoTag2
|
Method
|
Displays
the scarab
|
|
LoginUser
|
Method
|
Redirects
user to sign-in server
|
FIGURE
3: This is a
partial list of properties and methods the PassportIdentity object
provides.
By using
a page's PassportIdentity object, you can make your pages more useful
and powerful. You must use a minimum set of its features to meet the Passport
requirements before you can publish your site. For example, Passport requires
you to display the sign-out scarab on every protected page. The LogoTag2
method does this for you.
Tactical Authentication
The Web
site described at the beginning of this article uses hard authentication, so
it's all or nothing. By using subdirectories or <location> tags,
you can control access to individual pages. But you might want even finer
control of the protected areas. For example, imagine your site is accessible to
everyone but offers additional or personalized content to authenticated users.
MSN.com is a perfect example of this. Anyone can view it, but users who sign in
have additional, personalized content and layout. Passport makes this level of
control easy: Remove the <deny
users="?"/> section
from your web.config file, then use the PassportIdentity.IsAuthenticated
flag to control the portions of the page you want to customize. Use the .NET
PUID to identify the user and look up details for personalizing the page.
In a
basic example, the web.config file looks like this:
<configuration>
<system.web>
<authentication
mode="Passport"/>
</system.web>
</configuration>
Notice
that no one is denied access; anyone can view any page. It's up to each page to
determine whether users are authenticated.
FIGURE 4
shows a simple page that displays the user's PUID and some special content if
they sign in.
...
<script runat=server>
protected void
Page_Load()
{
if (!Page.IsPostBack)
{
try
{
if ( !(Context.User.Identity is
PassportIdentity) )
{
authLabel.Text = "Error:
Passport not installed.";
return;
}
// Expire the page to avoid the
browser's cache
Response.Cache.SetCacheability(
HttpCacheability.NoCache);
bool bSSL = Request.IsSecureConnection;
StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0}{1}{2}",
(bSSL ? "https://" :
"http://"),
Request.ServerVariables["SERVER_NAME"],
Request.ServerVariables["SCRIPT_NAME"]);
String sThisPage = sb.ToString();
// -1 or null means use default values
from registry
int TimeWindow = -1;
int ForceLogin = -1;
string coBrandArgs = null;
int lang_id = -1;
int bSecure = (bSSL ? 1 : 0);
string NameSpace = null;
int KPP = -1;
int SecureLevel = 0; // 0=None, 10=SSL, 100=SSL+PIN
PassportIdentity id =
(PassportIdentity)Context.User.Identity;
// Remove cookies passed on query string
// after a user logs in.
if (id.GetFromNetworkServer)
{
Response.Redirect(sThisPage);
}
// display the sign-in scarab
scarab.Text =
id.LogoTag2(Server.UrlEncode(sThisPage),
TimeWindow, ForceLogin,
coBrandArgs,lang_id,
bSecure, NameSpace, KPP, SecureLevel);
if (id.IsAuthenticated)
{
protectedContent.Visible = true;
authLabel.Text = "You're
authenticated on site " +
id.GetCurrentConfig("SiteID") + ".";
if (id.HasProfile("core"))
{
authLabel2.Text = "Your
PUID is " +
id.Name + ".";
}
}
}
catch (Exception e)
{
authLabel.Text = "ERROR:
Unhandled exception - " +
e.Message;
}
}
}
</script>
...
FIGURE
4: This excerpt
from a simple Passport-enabled page uses the PassportIdentity's LogoTag2
method to display the scarab and the IsAuthenticated property to control
the page's content.
The IsAuthenticated
property is used to control the content and visibility of the content on the
page. If the user is authenticated, the text of authLabel and authLabel2
is modified. The page also uses a panel server control to contain a block of
material that is invisible to unauthenticated users.
The PUID
is guaranteed to be unique for each user. The sample page in FIGURE 4 doesn't
do much with it, but it's easy to see how you could use the PUID to provide an
index into a database to obtain the user's preferences or other information you
could use to further customize the page.
In the
2.x version of the Passport SDK, the PUID string is available as both
the PassportIdentity.Name and PassportIdentity.HexPUID
properties. In version 1.4, it is available from the user's profile as MemberIDHigh
and MemberIDLow. To construct the full PUID, you need to combine them:
string PUID =
String.Format("{0:X8}{1:X8}",
id.GetProfileObject("MemberIdHigh"),
id.GetProfileObject("MemberIdLow")
);
The PUID
also can fine-tune site access. Use it in the web.config file to allow or deny
access to specific PUIDs:
<location
path="admin.aspx">
<system.web>
<authorization>
<allow
users="00030008000ABCD"/>
<deny users="*"/>
</authorization>
</system.web>
</location>
For
example, you might use this to make an administration page available only to a
restricted set of users.
Put It All Together
If
you're going to display the scarab and check authentication on each page, you
will need to add code like that contained in FIGURE 4 to every page on your
site. The obvious way to do this is to encapsulate it all in a user control and
add the control to every page. Now, the sample page becomes what you see in
FIGURE 5.
<%@ Register
TagPrefix="passport" TagName="scarab"
Src="Figure7_cs.ascx" %>
<html>
<head>
<title>Simple
PassportControl Page</title>
<script
runat="server">
Protected Sub
Page_Load()
If (pp.IsAuthenticated)
Then
protectedContent.Visible = True
authLabel.Text = "You are
authenticated on site " & _
pp.GetCurrentConfig("SiteID")
& "."
authLabel2.Text = "Your PUID is
" & pp.PUID & "."
End If
End
Sub</script>
</head>
<body>
<H2>Simple
PassportControl Page</H2>
<passport:scarab
id="pp" runat="server"/>
<p>
<b><asp:label
id="authLabel" text="" runat="server">
You are not
authenticated. Please sign in with your
.NET Passport.
</asp:label></b>
<br>
<asp:label
id="authLabel2" text="" runat="server"/>
</p>
<asp:panel
id="protectedContent" runat="server"
visible="false">
This material
is only visible to authenticated users.
</asp:panel>
</body>
</html>
FIGURE
5: You can
simplify this page by encapsulating all the Passport code into a user control.
Find the source code for the user control on the online version of this
article.
When you register your site with Passport, you might have
specified a Logout URL. This is the URL the user is sent to after signing out
from Passport. Be sure this page doesn't require hard authentication. If it
does, the user will sign out, only to be forced to sign in again to see the
logout page - not a friendly design. You also will want to ensure that your
co-branding material, privacy policy, terms of use, and customer-support pages
are freely accessible.
Signing Out
When a
user signs out of .NET Passport by clicking on the scarab in its sign-out mode,
it is your site's responsibility to delete any cookies it created. The .NET
Passport servers keep track of the sites the user visits and automatically
request an expire-cookie page (also called the Logoutgif page) from each site
when the user clicks on the sign-out scarab. You must provide this page on your
site, and you provide its URL to .NET Passport when you register the site.
ASP.NET provides a convenient method that makes writing your expire-cookie page
very easy: Simply call the PassportIdentity.SignOut method. It takes one
argument, which is the path to a gif of a green check mark. The check mark is
displayed when the browser deletes the cookies successfully and signs out of
your site.
Recent
privacy rules from the Platform for Privacy Protection (P3P) are designed,
among other things, to protect browsers from third-party cookies - cookies
created by something other than the site the browser is visiting. Passport's
cookies fall into this category and will not be deleted by browsers such as
Internet Explorer 6.0 that honor P3P. In order for your site to work with
Internet Explorer 6.0 and other P3P-aware browsers, each page on your site that
performs a Set-Cookie operation must provide P3P headers. Unless your site
creates its own cookies, the only time you need to worry about this is in the
expire-cookie page. You must add a P3P mini-header to the page so it will be
allowed to delete the cookies. Here is a complete expire-cookie page in VB
.NET:
<%
Response.Cache.SetCacheability(HttpCacheability.NoCache)
Response.AppendHeader("P3P", _
"CP=""DSP
NOI""")
System.Web.Security.PassportIdentity.SignOut( _
"signoutcheckmark.gif")
%>
This is
only an example and it might not represent the security policy of your site. CP="NOI DSP" is the minimum policy required to
delete the .NET Passport cookies, but that might misrepresent your site's
actual policy if used on a public site. See http://msdn.microsoft.com/library/default.asp?url=/workshop/security/privacy/overview/createprivacypolicy.asp
for more details on P3P headers. Don't use CP="TST" as described in some versions of the Passport
documentation - it won't work.
ASP.NET 1.0 uses the Passport Cookie Path to determine how
to delete the cookies. If this path is blank, the cookies might not be deleted
properly. Use the Passport Administration Utility to ensure the Cookie Path is
set to some non-empty value. Set it to "/" if it's currently empty.
Here are
some final things to consider when developing authentication procedures with
Passport:
When developing
ASP.NET pages, it's common to browse your pages from the server using the
domain name localhost. If you're developing your site within Visual Studio
.NET, this domain is used automatically. Unfortunately, if you do this with a
Passport page, the GetFromNetworkServer redirect won't work. You should
specify the true domain name or machine name of the server.
Recent releases of the .NET Passport SDK supported a
feature called Inline Sign-In. It allowed you to create your own sign-in page
instead of having the user redirected to the Passport login servers. This
feature turned out to be a security risk, and Passport dumped it. If you use
it, you should remove it and revert to the standard sign-in procedure.
.NET
Passport 1.4 and 2.1 will not work for authenticating Web Services. A future
release will address this need. In addition, .NET Passport works best if
Session State is not running: Prohibit your server and browsers to cache the
page, and don't save the PassportIdentity object in an object of application
or session scope; instead, get a new instance on each page.
The .NET
Framework and .NET Passport are built into the Windows .NET Server products, so
to enable .NET Passport for your ASP.NET Web site, all you need to do is
specify <authentication
mode="Passport"/>
in the web.config file. In addition, Windows XP and Internet Explorer 6.0 are
.NET Passport-aware. Windows XP and later systems recognize .NET Passport
authentication and handle some of the process automatically. The first time you
access a .NET Passport site from Windows XP, you'll notice the new .NET
Passport pop-up (see FIGURE 6).
FIGURE 6: .NET Passport is integrated
closely with Windows XP. Signing into Passport on XP with Internet Explorer 6.0
is handled locally and doesn't involve a redirect to the Passport servers.
The
client system now handles the authentication user interface instead of
redirecting you to the .NET Passport login servers. For this to work, the
client system must know which .NET Passport environment to authenticate
against. Normally, this is the production environment. But when you're
developing a site that hasn't yet been released to the world, you're working in
the pre-production environment. If you browse to your site from a Windows XP
client, you'll see an error like the one in FIGURE 7.
FIGURE 7: You must configure your Windows XP
and Internet Explorer 6.0 clients to authenticate against the same environment
your server uses. You'll see this error if they're mismatched.
You must
configure your XP and Internet Explorer 6.0 clients to authenticate against the
same environment your server uses. The SDK 2.1 help document has details. Look
for the topic named .NET Passport SDK: System
Requirements. Although
your client system is configured for the test environments, you won't be able
to browse live sites. This is such a pain that I do most of my testing with a
Windows 2000 client.
.NET
Passport has much more functionality than I could cover in this single article.
Take the time to dig into all .NET Passport has to offer. Download the SDK at http://www.passport.com and get the help
file from the Microsoft Developer Network (http://msdn.microsoft.com/downloads/default.asp).
Select Software
Development Kits and
then Microsoft
Passport SDK from the
menu. Explore, have fun, and create the next killer Web application.
The files referenced in this article are available for download.
Steve Turley
is a software development engineer on Microsoft's ASP.NET team. He has been a
part of the .NET team from its beginning and has seen it go through at least
five code name changes. Steve has more than 20 years of experience writing code
for microcomputers and still has his Northstar Horizon with the wooden case.
Contact him at mailto:sturley@microsoft.com.