Question

Currently I am using OpenID (via C# and DotNetOpenAuth) to create a single sign-on feature between App A ('their app') and App B ('my app').

I wish to make use of attribute exchange to acquire data their app offers. Their API documents how AX requests should look:

openid.ns.ax              = http://openid.net/srv/ax/1.0
openid.ax.type.studentids = http://theirapp.com/path/student-ids

Note the .ax namespace. That seems in line with OpenID's AX specs.

This is how I am using DotNetOpenAuth to request and acquire attributes, as suggested by SO and a gazillion other sources:

FetchRequest ax = new FetchRequest();
ax.Attributes.AddRequired("http://theirapp.com/path/student-ids");
request.AddExtension(ax);

and

string sIDs = String.Empty;
if (fetch != null)
    sIDs = fetch.GetAttributeValue("http://theirapp.com/path/student-ids");

I was confounded when responses came back totally ignoring AX requests. After observing the query string parameters we were sending them, I'm thinking it's actually not their fault:

openid.ns:http://specs.openid.net/auth/2.0
openid.ns.alias3:http://openid.net/srv/ax/1.0
openid.alias3.required:alias1
openid.alias3.mode:fetch_request
openid.alias3.type.alias1:http://theirapp.com/path/student-ids
openid.alias3.count.alias1:1

What the hell, DotNetOpenAuth? Where did 'alias3' come from? That's supposed to be 'ax'. I can't tell if the app I'm working with is overly anal about AX namespaces, or DotNetOpenAuth isn't paying attention to mandatory OpenID protocol.

So, after all this build-up, my questions:

  1. I need my AX requests to be in the namespace openid.ax, not openid.alias3. How do I force DotNetOpenAuth to do that?
  2. The API requires requested properties be named just so -- in this case, 'student-ids'. Above, they're getting default names like 'alias1'. How do I force DotNetOpenAuth to label properties with custom names?
  3. Who's in the right here: the API for requiring openid.ax or DotNetOpenAuth for not caring?
Was it helpful?

Solution

Thanks spamguy. I figured out a hack to work around the issue and am posting it here for others that may encounter the same problem with the limitations of PowerSchool's OpenID provider.

You need to build the request normally with DotNetOpenAuth, then extract the redirect response from the request. Once you have this, you can pull the Location header and overwrite the namespace values as needed.

This example is for an MVC application. If you need to do this in a WebForms app, simply take the logic from the method and replace the line return redirectResponse.AsActionResult(); with redirectResponse.Send(). You will need to wrap this in the usual try...catch... for ThreadAbortExceptions.

namespaces used:

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Mvc;
using System.Web.Security;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
using DotNetOpenAuth.OpenId.RelyingParty;

hack:

const string NS_DCID = "http://powerschool.com/entity/id";
const string NS_USERTYPE = "http://powerschool.com/entity/type";

static readonly OpenIdRelyingParty openid = new OpenIdRelyingParty();

private ActionResult SubmitOpenIdRequest(string openid_identifier)
{
    var request = openid.CreateRequest(openid_identifier);

    var ax = new FetchRequest();

    // any modification to the following attributes will require a corresponding change in the string replacement hack
    ax.Attributes.AddRequired(NS_DCID);
    ax.Attributes.AddRequired(NS_USERTYPE);

    request.AddExtension(ax);

    var redirectResponse = request.RedirectingResponse;

    // PowerSchool is violating the attribute exchange specification, requiring specific namespaces and elements to function
    // Need to overwrite the values generated by OpenID to patch PowerSchool's incorrect provider implementation
    redirectResponse.Headers["Location"] = redirectResponse.Headers["Location"]
        .Replace("openid.ns.alias3", "openid.ns.ax")
        .Replace("openid.alias3.required=dcid%2Calias2", "openid.ax.required=dcid%2Cusertype")
        .Replace("openid.alias3.mode", "openid.ax.mode")
        .Replace("openid.alias3.type.alias1", "openid.ax.type.dcid")
        .Replace("openid.alias3.count.alias1", "openid.ax.count.dcid")
        .Replace("openid.alias3.type.alias2", "openid.ax.type.usertype")
        .Replace("openid.alias3.count.alias2", "openid.ax.count.usertype");

    return redirectResponse.AsActionResult();
}

Be warned: this is a fragile hack. Internal changes in DNOA or changes to the FetchRequest can break the replacement logic.

OTHER TIPS

Question #1:

I need my AX requests to be in the namespace openid.ax, not openid.alias3. How do I force DotNetOpenAuth to do that?

DotNetOpenAuth doesn't let you force the alias of a specific extension. To require a specific one is contrary to the OpenID 2.0 specification. Even the AX extension itself states this in section 1.1

openid.ns.<extension_alias>=http://openid.net/srv/ax/1.0

The actual extension namespace alias should be determined on a per-message basis by the party composing the messages, in such a manner as to avoid conflicts between multiple extensions. For the purposes of this document, the extension namespace alias for the attribute exchange service will be "ax".

Question #2:

The API requires requested properties be named just so -- in this case, 'student-ids'. Above, they're getting default names like 'alias1'. How do I force DotNetOpenAuth to label properties with custom names?

Just like for your first question, DotNetOpenAuth doesn't provide a way for you to coerce aliases of individual attributes because the AX extension doesn't allow for those types of requirements. The type of the attribute is given in the URI. The alias of that attribute is definable by the relying party and the OpenID Provider is supposed to look at the Type URI of the attribute, and accept whatever alias comes from the relying party.

Question #3:

Who's in the right here: the API for requiring openid.ax or DotNetOpenAuth for not caring?

DotNetOpenAuth is correct. If indeed the alias differences between the documentation you're looking at for this student-id exchange and what DotNetOpenAuth are the root cause of the problem you're seeing with the AX attributes being ignored, then the fault lies with a poor implementation of an OpenID Provider being run at the server. Poor implementations of OpenID Providers are very scary -- not just for poor interop, but because they suggest there may be any of many possible security issues as well given the lack of care in implementing the spec properly.

I suggest you try using Fiddler or some other HTTP sniffer to intercept an outbound request, fix up all the aliases to match what the docs suggest they should be, and see if it fixes the problem. If so, the OpenID Provider should be fixed and you can contact them to request they upgrade to a newer version of whatever library they're using (hopefully that alone will fix it). If "fixing" the aliases does not fix the problem, then you can move along to investigating other possible reasons for the interop failure.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top