Monday, April 28, 2008

Uri.AbsoluteUri and Uri.ToString() are NOT the same

If you're familiar with the System.Uri class you know that it has a couple of ways of becoming a string for purposes of communication: its AbsoluteUri property and its ToString() method.  What I didn't find out until very recently was the significant difference between the two.  Since the difference led to functional bugs in production code, I thought I'd share the knowledge to help you avoid the same fate.

Given a Uri instance constructed like this:

UriBuilder builder = new UriBuilder("http://somehost/somepath");
builder.Query = "somekey=" + HttpUtility.UrlEncode("some+value");
Uri someUri = builder.Uri;

Uri.ToString() will return the human-readable URL: http://somehost/somepath?somekey=some+value

Notice the plus sign in "some+value", the way we put it originally, before HttpUtility.UrlEncode encoded it.

Uri.AbsoluteUri on the other hand will return the encoded form as HttpUtility.UrlEncode returned it: http://somehost/somepath?somekey=some%2bvalue

Notice this time how the plus sign has been (is still) encoded.

So why is this important?  Well if you make a request with some+value, then the web server will receive "some value".  But if you make a request with some%2bvalue, then the web server will receive "some+value", which is what you originally intended.  Plus signs aren't the only symbol that can be misinterpreted, of course. 

What lesson can we derive from this?  Unless you're displaying a URL for diagnostic purposes (and perhaps even then) always use Uri.AbsoluteUri instead of Uri.ToString().

Incidentally, UriBuilder.ToString() does not decode the URL, so UriBuilder.ToString() is safe to use.  It's just Uri.ToString() that you should avoid.

Thursday, April 24, 2008

DotNetOpenId 2.1.0 released, adds Attribute Exchange support

DotNetOpenId had a double release tonight.  Version 2.0.1 is a maintenance release with a few minor bug fixes.  Version 2.1 adds built-in support for the Attribute Exchange extension, but introduces a couple of small breaking changes.  Check out the VersionChanges page to decide which version is right for you.

Download the new versions here.

Tuesday, April 22, 2008

Enhancing the ASP.NET MVC OpenID login experience

In a previous post, I present an example of how to accept OpenIDs for logins on your ASP.NET MVC site.  To keep the sample simple, I left out a feature that people very quickly noticed was lacking: getting registration data from the OpenID Provider (like email and postal address) so the user didn't have to type it in manually.  High demand leads me to follow up with this post on how to add those features.

To get your OpenID login page to automatically get some registration data from the OpenID Provider to make the first login experience for your visitors easy, you need to use the Simple Registration extension.  This extension is included with DotNetOpenId so you have nothing additional to download. 

Simple Registration allows you to ask for any of several predefined fields.  For the sake of this example, we'll ask for email and nickname.  I'll build on the sample I built up in the aforementioned previous post.

In your UserController's Authenticate method, in the "stage 2" area, where you call RedirectToProvider, change the stage 2 code to look like this:

// Stage 2: user submitting Identifier
var req = openid.CreateRequest(Request.Form["openid_identifier"]);
var fields = new DotNetOpenId.Extensions.SimpleRegistrationRequestFields();
fields.Email = DotNetOpenId.Extensions.SimpleRegistrationRequest.Request;
fields.Nickname = DotNetOpenId.Extensions.SimpleRegistrationRequest.Request;

What I've done here is split up the CreateRequest and RedirectToProvider method calls, so that in between them I could construct a SimpleRegistration extension and add it to the request.  If you tested this now, you'd see that during authentication your OpenID Provider would prompt the user for permission to send your nickname and email to the relying party web site.  But your web site doesn't do anything with the answer yet.  Let's fix that.

Under stage 3 in the same Authenticate method, in your switch statement's AuthenticationStatus.Authenticated case, just add a few lines before the RedirectFromLoginPage method call:

var fields = DotNetOpenId.Extensions.SimpleRegistrationFieldValues.ReadFromResponse(openid.Response);
string email = fields.Email;
string nickname = fields.Nickname;
FormsAuthentication.RedirectFromLoginPage(openid.Response.ClaimedIdentifier, false);

Of course you'd want to do something useful with the email and nickname like store them in a session or database or something.  That's outside the scope of this post.

But you see it's really quite easy to request this user profile data.  And you can make the code more readable by adding a "using" clause at the top of your file so you can drop all the fully-qualified references to the Simple Registration extension.

Wednesday, April 16, 2008

Why DotNetOpenID as your C# OpenID library of choice

In choosing an implementation of OpenID for your .NET web site, of course I would have to recommend DotNetOpenId, seeing as I spend a considerable amount of my spare time working on it.  But Samuli (in the comments on Troy's recent blog post regarding OpenID) recently asked me to give a sales pitch for DotNetOpenID over another implementation called ExtremeSwank. 

First I want to make clear that I respect John Ehn (of ExtremeSwank) and his very dedicated work to his library. He has no doubt spent countless hours and published all his work as free open-source software, for which he is to be commended. 

On with the sales pitch... (I'll abbreviate to ES and DNOI to refer to the two libraries)

  • DNOI supports Providers and Relying Party web sites.  ES only supports Relying Parties.
  • DNOI has several ASP.NET custom controls built-in that make relying party and provider scenarios very easy, should you choose to use them. (Please note that the ASP.NET controls are not required, and that the API-level OpenID support is still simple to use.)  ES has a couple of web user controls which simplify its API, but have to be copied into the hosting web site as source code -- since there's a bucketload of code in the code-behind for these controls, that's difficult to maintain with each successive version of the library for consumers to hand-upgrade in their web sites.
  • DNOI doesn't require an ASP.NET HttpContext, allowing all OpenID handling to be done on a backend server (useful for web farms).  ES requires the HttpContext, apparently disallowing this.
  • ES doesn't have any unit tests while DNOI has well over 100 to verify proper function and resistance against security exploits.
  • DNOI has been much more widely adopted, and therefore tested in the field.
  • DNOI has already been deployed on many shared hosting environments, and even web farms successfully.
  • DNOI has been scrubbed for public classes/members to minimize surface area for simple Intellisense discovery of how to use the library, for fewer breaking changes going forward, and for security.  It seems ES still has way too many classes marked Public. 
  • DNOI is prepared for future versions of OpenID (by abstracting away most of the protocol version differences in a dedicated class) so we should be able to rev for the next version quickly.
  • DNOI is prepared for localization (all our human-readable strings are in a resource file), and we've already had a volunteer to begin translation into at least one other language. [27Apr2008 Update: John of ES points out in comments below that ES is also localization ready in that it doesn't return or throw any strings at all -- just an enum so that the site author can provide the text to display.]
  • DNOI's approach to OpenID has been to implement the spec fully and to the letter so that we interoperate with all (so far) OpenID web sites, whereas ES's change history is ridden with 'now works with aol, ... now works with claimid, ... now works with ___....', which suggests to me that it's a gradual hit-or-miss thing.  ES also has a Quirks class that special cases interop with certain other web sites in order to work with them.  DNOI hasn't needed a Quirks class (at least not yet) because our implementation of the spec works with everything people have tested against. Now maybe there's a history behind the Quirks class and those other web sites were not implementing the spec properly but now are... I don't know for sure.  But I know DNOI doesn't have a class like this and works with all the sites ES mentions in its Quirks class.

There is one area where I bow to ExtremeSwank's better coverage.  While DotNetOpenID supports extensions in an extensible way (that is, you can use any extension you want with it), it currently only comes prepackaged with support for Simple Registration, leaving any other extension to have to be hand-coded by the hosting web site.  The difficulty of this depends largely on the complexity of the extension you want to use.  ExtremeSwank apparently has built-in support for several extensions out of the box including Simple Registration, Attribute Exchange, and Authentication Policy.  I expect DotNetOpenID to develop these extensions as standard offerings soon though, so this doesn't bother me too much.

Please note that while I've glossed over the ExtremeSwank codebase a couple of times, especially in writing this post, I have not ever used it in production.  While I believe what I've written above to be correct, if someone finds something I'm mistaken about please comment and I'll be happy to make note of it. This post is as-of today however, so if some of these things get fixed later, kudos to ExtremeSwank, but I'll leave the post as-is.

Monday, April 14, 2008

How to add OpenID to your ASP.NET web site (in C# or VB.NET)

Adding OpenID support to your VB.NET web site couldn't be easier.  Here is the easiest way:

  1. Download the DotNetOpenId library.
  2. Extract the DotNetOpenId.dll from the bin directory of the .zip file you downloaded.
  3. Make sure you're using FormsAuthentication.
    1. Open Web.config
    2. Find your <authentication> tag (or create it if you're sure it doesn't already exist) and change it if necessary so it looks something like this: (the really important part is just mode="Forms")
      <authentication mode="Forms">
      forms defaultUrl="/default.aspx" loginUrl="~/login.aspx"/>
  4. Add a Reference to DotNetOpenId.dll from your web site.
  5. Navigate to your Login.aspx page.
  6. Drag the OpenIdLogin control from your control Toolbox to the location on your page you want it to appear. NOTE: If the controls do not appear in your Toolbox, follow these steps to add them:
    1. Right-click somewhere on the Toolbox and click Choose Items... (it can take a while before the dialog shows up)
    2. In the .NET Framework Components tab, choose Browse.
    3. Select the DotNetOpenId.dll that you extracted from the zip file.
    4. Click OK.
    5. Several controls will get added to your Toolbox.  Drag the OpenIdLogin control to your page.
  7. You should have a couple of new lines in your .aspx file:
    <%@ Register Assembly="DotNetOpenId" Namespace="DotNetOpenId.RelyingParty" TagPrefix="RP" %>
    <RP:OpenIdLogin ID="OpenIdLogin1" runat="server" />
  8. If you're starting a brand new site and don't have a way to tell who is logged in on your site yet, go ahead and drag LoginName and LoginStatus controls to some new page that you can use to check your own logged-in status.
  9. Go ahead and run your app.  You're already done.

Now, this is admittedly the simplest case.  If you have an existing userbase with usernames and passwords, you'll want to build pages to help your users make the transition.  You can certainly support dual-mode authentication, but each web site is different and you'll have to design your own plan for doing that.  But if you find you need more flexibility with how the OpenID login control looks or works, check out the OpenIdTextBox control.  If there's demand (leave a comment on this post), I'll follow up with a post or two on how to customize this.

Sunday, April 13, 2008

Add OpenID login support to your ASP.NET MVC site

While this post assumes a C# web site, the steps will work for VB.NET just as well, but the syntax of the glue code will have to be adjusted slightly.

Because ASP.NET MVC changes frequently, this will be a high-level outline of what you must do to support OpenID, and the implementation details I will leave up to you and your particular version of MVC.  At the end I will show the code that makes the whole thing work as of the most recent version of MVC at the time of writing.

  1. Download DotNetOpenId and extract the DotNetOpenId.dll found in the bin directory of the download.
  2. Add a reference to that assembly from your ASP.NET MVC web site.
  3. Create or open the controller that will login users.  I will assume this controller is called "UserController".  Create two actions: Login and Authenticate.
    1. Login will just display the Login view.
    2. Authenticate will respond to two requests: the form submission of your visitor who is logging in, and the OpenID Provider response after the redirect.
  4. Your controller should look something like this:
    public class UserController : Controller {
        public void Login() {
            // Stage 1: display login form to user
        public void Authenticate() {
            var openid = new OpenIdRelyingParty();
            if (openid.Response == null) {
                // Stage 2: user submitting Identifier
            } else {
                // Stage 3: OpenID Provider sending assertion response
                switch (openid.Response.Status) {
                    case AuthenticationStatus.Authenticated:
                        FormsAuthentication.RedirectFromLoginPage(openid.Response.ClaimedIdentifier, false);
                    case AuthenticationStatus.Canceled:
                        ViewData["Message"] = "Canceled at provider";
                    case AuthenticationStatus.Failed:
                        ViewData["Message"] = openid.Response.Exception.Message;
  5. Create just one view: for the Login action you created.  The Authenticate action does not need a view as it will only redirect the user to other URLs. The content of it should resemble this:
    <% if (ViewData["Message"] != null) { %>
    <div style="border: solid 1px red">
        <%= Html.Encode(ViewData["Message"].ToString())%>
    <% } %>
    <p>You must log in before entering the Members Area: </p>
    <form action="Authenticate?ReturnUrl=<%=HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]) %>" method="post">
    <label for="openid_identifier">OpenID: </label>
    <input id="openid_identifier" name="openid_identifier" size="40" />
    <input type="submit" value="Login" />
  6. Adjust FormsAuthentication to use your Login action.
    1. Open your Web.config file.
    2. Find your <authentication> tag.
    3. Set the defaultUrl and loginUrl attributes of the <forms> child item:
      <authentication mode="Forms">
          <forms defaultUrl="/Home" loginUrl="/User/Login"/>

And that's pretty much it.  Of course you'll want to customize the code to fit what you're specifically trying to do, but this code is all you need to get OpenId authentication going.  I might add: getting OpenId authentication going securely, since there seems to be a rash recently of "cheap" OpenID implementations out there that try to implement just part of the spec and end up with insecure implementations.  This method uses the DotNetOpenId library, which has had a lot more effort to a secure and complete implementation of OpenId 1.x and 2.0.

By the way, I'm new to the MVC framework, so if you have comments on how this code can be made better or prettier, I'm all ears.

[22Apr08 Update: Check out a follow-up post that builds on this sample to pull registration data from the OpenID Provider]

Friday, April 11, 2008

An argument for the extra dependency of a library

Lately there has been some blogging activity around C# implementations of OpenID that are "so small you can just host the source code as a single class your web site."  This is argued to be a virtue because it keeps your web site from having to add another dependency to it.  I'm glad OpenID is gaining greater traction.  But I disagree with avoiding libraries.  Let me count the ways...

  1. A well-tested library is less likely to have security holes, and security holes tend to be patched and delivered regularly.
  2. A library contributed to over time and by many will likely have more features.
  3. Security fixes can often be applied by dropping in a new version of the library.
  4. If you implement your own version and a security hole exists, you'll never know what security holes you've got until your customer says he's suffered identity theft.
  5. If you ship your web site to others, even if you find and fix security holes in your version they won't get the updates very easily. 
  6. What is the cost of another DLL in your Bin directory?  Let's see... $0.  I don't understand why people hesitate at this step.  Even if you really can't stand the extra DLL, then copying the source code from the open-source library into your web site is always a possibility.  It's not advisable though, since once again integrating security fixes from the library will be more difficult for you, and if you ship the web site off to someone else, they can't just drop in the next version of the library you copied from to get security patches.

OpenID in particular is a difficult protocol to implement only part of and still have it be secure.  Much of what makes it secure is scattered throughout the spec and sections of it cannot be removed without risking also removing a security feature.

In the effort of encouraging a small C# implementation of OpenID, what we have is several web sites, including starter kits, that are adopting a dangerous implementation of OpenID that can be easily hacked so that someone can log into as anyone else without authority.  It's a serious proposition to take on implementing an authentication protocol -- one that must not be taken lightly. 

OpenID is a marvelous protocol and has a lot of promise to reduce the number of logins people have to remember.  But it's very unnerving to see web sites publishing or using these implementations.  What web sites can I trust to log in with my OpenID?  If I log into an insecure site, then someone else can log in as me later and steal my identity and who knows what else that's controlled by that web site.

Please, in the interest of protecting and furthering OpenID adoption and trust in the community, either use a library written by those who understand the OpenID spec thoroughly, or learn it thoroughly yourself before writing your own.

C# OpenID library DotNetOpenID 2.0 released

The open source OpenID C# library DotNetOpenID has been released.  This is a really exciting release that adds full support for OpenID 2.0 while preserving full backward compatibility for interoperating with OpenID 1.x.  It is a mature library with lots of helps for diagnostics and debugging, and a balance between simplicity and extensibility.  For a complete list of enhancements from the last release, check out the VersionChanges page.

Here are the highlights of this library and particularly this release:

We've had over 150 beta testers leading to this release and fixed several bugs along the way.  Thanks to all those who helped field test this release!

This is a release that I personally have worked very hard to build and write tests to make sure its as secure as possible.  Please consider supporting past and future development with a donation (any size).

Make a donation with PayPal

Tuesday, April 08, 2008

DotNetOpenId 2.0 Beta 2 has been released

New since v2.0 beta 1:
  1. Providers can send unsolicited assertions
  2. Providers can detect and respond to directed identity (identifier_select) requests from relying parties.
  3. Completely stateless relying party support (not even requiring application state -- we already had session-less)
  4. More Association members exposed to allow for serialized custom store support (for db-backends)
  5. Better signing security (64-byte key instead of 20-byte key)
  6. Added ImmediateMode property to OpenIdTextBox and OpenIdLogin controls.
  7. New OpenIdMobileTextBox control
  8. Several new samples (2 sites, several more pages) to demonstrate mobile controls, immediate mode, and custom stores.
  9. Exposed application memory stores for RPs and OPs so you can customize more without implementing your own store.
  10. Lots of new tests to verify correct behavior.
  11. Lots of little bug fixes.
There are a couple of minor breaking changes that will result in a compile-time error if you happen to use those features from v2 beta 1 or v1, but correcting the errors are trivial.

Saturday, April 05, 2008

OpenID 2.0 nonces and ASP.NET HttpRequestValidationException

The OpenId Authentication 2.0 spec's section on nonces allows for nonces to be made up of any and all ASCII characters in the range of 33-126 inclusive.  This includes the characters < and &, which are significant enough in HTML cross-site scripting attacks that ASP.NET does some validation on HTTP requests and throws HttpRequestValidationException if these characters show up in potentially dangerous ways. 

These special characters that ASP.NET checks for are just two of many in the allowed characters, so you usually won't see this exception, but occasionally your visitors will if you have an ASP.NET OpenId-enabled web site (whether your site is a Provider or Relying Party).  Here is a sample that I randomly saw while developing DotNetOpenId.

Server Error in '/' Application.

A potentially dangerous Request.QueryString value was detected from the client (openid.response_nonce="...T20:12:18Z<Gts#:[\").

Description: Request Validation has detected a potentially dangerous client input value, and processing of the request has been aborted. This value may indicate an attempt to compromise the security of your application, such as a cross-site scripting attack. You can disable request validation by setting validateRequest=false in the Page directive or in the configuration section. However, it is strongly recommended that your application explicitly check all inputs in this case.
Exception Details: System.Web.HttpRequestValidationException: A potentially dangerous Request.QueryString value was detected from the client (openid.response_nonce="...T20:12:18Z<Gts#:[\").

The spec cannot be changed to exclude these characters, so while DotNetOpenId can be altered to avoid emitting these characters as part of nonces to help lessen the problem, any ASP.NET web site that supports OpenId whether it uses DotNetOpenId for its library or not can receive requests with these characters from other web sites that are correctly implementing the OpenId 2.0 specification. 

Since you don't want your web site crashing on your visitors who are trying to log in, the only way to avoid this occasional exception is to disable request validation.  Since this exposes you to greater security threats (the XSS attacks that the validation was intended to prevent), you should only disable request validation on your dedicated login page and provider endpoint, and perform any of your own request validation on those pages that may be necessary.

You can disable request validation on your relying party login or provider endpoint page by adding the validateRequest="false" directive to your <@ Page ... > tag.

Thursday, April 03, 2008

DotNetOpenId 1.0.1 released

A maintenance release of DotNetOpenId 1.0 has just been posted to the DotNetOpenId project site.  The new version (1.0.1) contains just one fix from the 1.0.0 version that corrects a very small lifetime of associations issued by the DotNetOpenId provider.  Only those implementing OpenId Provider web sites need to upgrade from 1.0.0 to 1.0.1 -- those sites only using OpenId as a relying party are not affected by this bug.
In version 1.0.0: the provider issued associations that lived for only one minute
In version 1.0.1: the provider issues associations that live for 14 days.
Symptom: in version 1.0.0, a user that takes more than a minute to log in through his/her OpenId Provider powered by DotNetOpenId would get redirected to the relying party web site to see an error message due to an association expiration.  Some relying parties may reject associations that live only one minute anyway and switch to dumb mode, and thus be somewhat unaffected by this bug.
Anyone implementing a Provider with DotNetOpenId 1.0.0 is strongly urged to upgrade to 1.0.1.