Wednesday, June 11, 2008

Why Yahoo! says your OpenID site's identity is not confirmed

Are you building an OpenId 2.0 relying party site and having your visitors who use Yahoo! as their Provider see this message?

Warning: This website has not confirmed its identity with Yahoo! and might be fraudulent. Do not share any personal information with this website unless you are certain it is legitimate.

Here is what you need to do to get rid of this warning:

  1. Write an XRDS document and save it to your web site.  Put the URL of your login page(s) in it.
  2. Advertise your XRDS document by putting a special link on your home page (or whatever page your Realm URL points to).

Let's start with authoring your XRDS document.  Here is a template you can copy and paste:

<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS
    xmlns:xrds="xri://$xrds"
    xmlns:openid="http://openid.net/xmlns/1.0"
    xmlns="xri://$xrd*($v*2.0)">
    <XRD>
        <Service priority="1">
            <Type>http://specs.openid.net/auth/2.0/return_to</Type>
            <URI>http://nerdbank.org/RP/login.aspx</URI>
        </Service>
    </XRD>
</xrds:XRDS>

You should replace the URI element text with whatever the fully-qualified URL is of your return_to page.  This may be your login page, or if you have a separate URL for receiving the Provider's authentication response, that is your return_to URL and should be placed in the URI element.  If you have multiple return_to pages, you should repeat the URI xml element, providing a unique URI element in each one. 

If you have thousands of login pages (because it's a blog or something) then technically you should just put as much of the URL in as is true for all your login pages (such as just http://nerdbank.org).  Unfortunately (well, it's probably for increased security) Yahoo! isn't satisfied with very generic URLs and may still put up their warning, so make your URL as specific as possible.

Save your XRDS document to some location on your web site.  Say... http://nerdbank.org/xrds.aspx.  It doesn't have to end with any particular extension.  But you should program your web site to indicate that the XRDS document has a Content-Type of "application/xrds+xml" so that Yahoo or any other Provider knows what it is looking at.

Now you must advertise your XRDS document from your Realm URL.  Your Realm is typically the root URL of your web site.  For example, if your root was http://nerdbank.org/, you will need to modify your default page that responds to that request (i.e. default.aspx, index.html, etc.).  A big warning about your Realm URL (the one you send to the Provider): the realm URL must not cause any redirects. That is, if your realm is http://nerdbank.org/RP, but that URL redirects to http://nerdbank.org/RP/ (note the trailing slash), the XRDS discovery will fail and Yahoo (and others) may report your site as identity not confirmed. Touchy? Yes, but for good security reasons. This means that in your original OpenId request, you must be careful to set your realm URL to end with that trailing slash if your server requires it.

Add the following line inside the HEAD tags of your Realm page:

<meta http-equiv="X-XRDS-Location" content="http://nerdbank.org/xrds.aspx"/>

That is enough for some Providers, but may not be enough for all.  Your Realm URL page should also include this HTTP response header:

X-XRDS-Location: http://nerdbank.org/xrds.aspx

You should be done.  Try logging into your site using "yahoo.com" again to verify that the warning has gone away.

13Dec09 Update: An RP verification diagnostic tool you can use to help pinpoint your precise issue is now available.

Wednesday, June 04, 2008

When NOT to use the C# "as" keyword

Both C++ and C# offer the cast () operator.  C# also offers the "as" operator syntax which does almost the same thing and is considered by some to look prettier and be more C#'ish.  But using the "as" keyword has downsides that you may not know about.

Consider the following two methods. Please skip the question of "Why would you take an object and cast to a string instead of just casting to a string?" as this is a contrived example to illustrate the differences between () and the as keyword, and in real applications there are many different reasons for casting down.

class SomeType {
    bool IsTooLong1(object obj) {
        string someString = obj as string;
        return someString.Length > 20;
    }

    bool IsTooLong2(object obj) {
        string someString = (string)obj;
        return someString.Length > 20;
    }
}

If you pass an object that is not a string, IsTooLong1 will throw a NullReferenceException whereas IsTooLong2 will throw an InvalidCastException. Obviously an InvalidCastException helps you more when you are debugging to find the problem as it tells you immediately that the wrong type of object was passed to something. The NullReferenceException could be thrown if you passed null into the method or the wrong type of object, so besides the fact that NullReferenceException doesn't immediately suggest that it could be a bad cast, it also leaves ambiguity as to the cause of the exception.

Why the difference? The "as" keyword attempts to cast the object and if the cast fails null is returned silently. The () cast operator will throw an exception immediately if the cast fails.

There are good applications to use each operator. The above shows an example of where the () operator is more appropriate. But here's an example of where the "as" keyword is better:

class SomeType {
    int someField;
    // The numeric suffixes on these methods are only added for reference later
    public override bool Equals1(object obj) {
        SomeType other = obj as SomeType;
        if (other == null) return false;
        return someField == other.SomeField;
    }
    public override bool Equals2(object obj) {
        if (obj == null) return false;
        // protect against an InvalidCastException
        if (!(obj is SomeType)) return false;
        SomeType other = (SomeType)obj;
        return someField == other.SomeField;
    }
}

The Equals1 method above is more efficient (and easier to read) than Equals2, although they get the same job done. While Equals1 compiles to IL that performs the type checking and cast exactly once, Equals2 compiles to do a type comparison first for the "is" operator, and then does a type comparison and cast together as part of the () operator. So using "as" in this case is actually more efficient. The fact that it is easier to read is a bonus.

In conclusion, only use the C# "as" keyword where you are expecting the cast to fail in a non-exceptional case. If you are counting on a cast to succeed and are unprepared to receive any object that would fail, you should use the () cast operator so that an appropriate and helpful exception is thrown.