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:
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.