As somewhat of a continuation of this question, I'm having problems with dotnetopenauth.
I navigate to my relying party code and create the request, however when my provider receives the request OpenIdProvider.GetRequest()
returns null. I went through the code and as far as I can tell, this is because the openid payload (request.form) is not being delivered by my relying party; but I can't figure out why this is.
Code:
Relying Party:
public ActionResult Authenticate(string RuserName = "")
{
UriBuilder returnToBuilder = new UriBuilder(Request.Url);
returnToBuilder.Path = "/OpenId/Authenticate";
returnToBuilder.Query = null;
returnToBuilder.Fragment = null;
Uri returnTo = returnToBuilder.Uri;
returnToBuilder.Path = "/";
Realm realm = returnToBuilder.Uri;
var response = openid.GetResponse();
if (response == null) {
if (Request.QueryString["ReturnUrl"] != null && User.Identity.IsAuthenticated) {
} else {
string strIdentifier = "http://localhost:3314/User/Identity/" + RuserName;
var request = openid.CreateRequest(
strIdentifier,
realm,
returnTo);
var fetchRequest = new FetchRequest();
request.AddExtension(fetchRequest);
request.RedirectToProvider();
}
} else {
switch (response.Status) {
case AuthenticationStatus.Canceled:
break;
case AuthenticationStatus.Failed:
break;
case AuthenticationStatus.Authenticated:
//log the user in
break;
}
}
return new EmptyResult();
}
Provider:
public ActionResult Index()
{
IRequest request = OpenIdProvider.GetRequest();
if (request != null) {
if (request.IsResponseReady) {
return OpenIdProvider.PrepareResponse(request).AsActionResult();
}
ProviderEndpoint.PendingRequest = (IHostProcessedRequest)request;
return this.ProcessAuthRequest();
} else {
//user stumbled on openid endpoint - 404 maybe?
return new EmptyResult();
}
}
public ActionResult ProcessAuthRequest()
{
if (ProviderEndpoint.PendingRequest == null) {
//there is no pending request
return new EmptyResult();
}
ActionResult response;
if (this.AutoRespondIfPossible(out response)) {
return response;
}
if (ProviderEndpoint.PendingRequest.Immediate) {
return this.SendAssertion();
}
return new EmptyResult();
}
Logs:
RP: 1) http://pastebin.com/Pnih3ND7 2) http://pastebin.com/eBzGun9y
Provider: http://pastebin.com/YAUTBzHk
Interestingly enough the RP log says that localhost is untrusted...yet I added it to the whitelisted hosts in my web.config, and it was "working" yesterday...
EDIT: Okay, this is weird. Yesterday I was stepping through the DNOA source trying to find out what the problem is. I enabled log4net and it created the log file and left it blank. Today I set up log4net again - it logged fine but I had an error that didn't make sense (see above). I also wasn't able to step into the DNOA source. I removed and re-added the reference to dotnetopenauth.dll, and then my "original error" with the whitelisted hosts went away, I was able to step into the source, but the log file was blank again. And I stil have the problem with request.form not being populated...
EDIT2: Both my controllers are named "OpenIdController" (both on the RP and EP). My RP is running on localhost:1903, and my endpoint is running on localhost:3314.
EDIT3: After I made the changes you suggested things started working. The RP performs the discovery fine, but I have an issue when it actually makes the request.
The line IRequest i_request = OpenIdProvider.GetRequest();
works fine, but when I try to cast it: IAuthenticationRequest iR = (IAuthenticationRequest)i_request;
it gives me the following error:
System.InvalidCastException was unhandled by user code
Message=Unable to cast object of type 'DotNetOpenAuth.OpenId.Provider.AutoResponsiveRequest' to type 'DotNetOpenAuth.OpenId.Provider.IAuthenticationRequest'.
Source=Portal
StackTrace:
at Portal.Controllers.OpenIdController.Index() in Controllers\OpendIdController.cs:line 35
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass42.<BeginInvokeSynchronousActionMethod>b__41()
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.<>c__DisplayClass39.<BeginInvokeActionMethodWithFilters>b__33()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49()
This code is a bit of a hodge-podge between the two samples I found relevant. I want to set up an SSO type environment so the majority of the code I'm using is from \DotNetOpenAuth-4.1.0.12182\Samples\OpenIdWebRingSsoProvider\Code\Util.cs
(ProcessAuthenticationChallenge function). However, since that function expects an IAuthenticationRequest
but OpenIdProvider.GetRequest returns an AutoResponsiveRequest
I figured I'd be able to cast it in order to use the properties and methods of the IAuthenticationRequest
class. Obviously I was incorrect.
I'm not quite sure how to approach things at this point. Should I be using the sample code from the OpenIdProviderMVC sample? The key thing is that the login work like a single sign on, and the user is never actually prompted to enter an OpenId. I will only ever have one endpoint as well (although I will have multiple RP's).
Here's the link to the most recent RP logs: http://pastebin.com/enpwYqq3
EDIT4: I did what you suggested, and made some progress. My EP recieves the response and processes it as far as I can tell, but when it redirects back to the realm url it errors out.
012-10-10 13:55:01,171 (GMT-4) [25] ERROR DotNetOpenAuth.Messaging - Protocol error: An HTTP request to the realm URL (http://localhost:1903/) resulted in a redirect, which is not allowed during relying party discovery.
What exactly is the function of the Realm
as opposed to the ReturnTo
? Using the sample code, the Realm ends up being http://localhost:1903/
and the ReturnTo ends up being http://localhost:1903/OpenId/Authenticate
which seems fine. Why does the EP need to make a request to the realm? I'd have thought that it should simply be sending the assertion to the returnTo once it finished processing. If I manually set the Realm to http://localhost:1903/OpenId/Authenticate
then relyingParty.GetResponse()
returns null.
I do have my application set up to redirect when someone accesses the base url (http://localhost:1903
) - what code should I have running there to intercept the DNOA EP request?
New Logs:
RP: http://pastebin.com/L9K5Yft4
EP: http://pastebin.com/kBPWiUxp
I've also updated the code at the beginning of the question to better reflect the changes I've made.
EDIT5: Does the realm have to be the actual base URL of the application? That is, (http://localhost:1903
)? Given the existing architecture in place it is difficult to remove the redirect - I tried setting the realm to the base OpenId controller (http://localhost:1903/OpenId
) and testing manually did generate the XRDS document. However, the application seems to freeze, and the EP log reveals the following error:
2012-10-10 15:17:46,000 (GMT-4) [24] ERROR DotNetOpenAuth.OpenId - Attribute Exchange extension did not provide any aliases in the if_available or required lists.
As you can see from the relying party log:
Your Provider is hosted on localhost, which on a production server is not a safe OpenID. So by default localhost is disabled. You can allow it for local testing by adding localhost to your whitelist by adding this to your web.config file (with the appropriate
configSections
at the top):Your RP has very suspiciously odd code. While it is normal (in fact required) for the return_to and realm to both have the same Uri authority, the fact that the user-supplied identifier that you're passing in as the first parameter to
OpenIdRelyingParty.CreateRequest
has the same host and port as your relying party is very odd. Normally the identifier you pass in would be a URL hosted by the provider. Now, I don't know if port 3314 is your RP or your OP, but either way, one of these port numbers in your RP code looks wrong.Secondly, discovery on the user identifier fails with a null reference exception (according to v2 of your RP logs). That would prevent the login request from ever reaching your Provider. The fact that your Provider is getting called but with a non-existent OpenID request suggests that
http://localhost:3314/OpenId/
is actually your OP Endpoint (the URL of your OpenID Provider's action method that reads OpenID requests). That would be inappropriate. The URL you should pass to yourOpenIdRelyingParty.CreateRequest
method's first parameter should, again, be a user's OpenID URL -- not an OP Endpoint. Check out the OpenIdProviderMvc sample's User controller for an example of how to set up a user OpenID URL. Then use that URL as the first arg toCreateRequest
and I think you'll be good.Thirdly, once your Provider receives a non-null request, you can't always cast it to
IAuthenticationRequest
. Not all OpenID messages are authentication messages. Some are part of the underlying OpenID protocol. If you look at the OpenIdProviderMvc sample's OpenID controller, you should notice that there are conditional casts to deal with the different message types. You should have similar message handling in your controller.Since you're going for the SSO scenario, the significant difference in your controller would presumably be:
IAuthenticationRequest.Realm
property against a whitelist of the RPs included in your SSO web ring, and only provide positive assertions when the Realm qualifies. This mitigates the attack where once your server is set up, anyone could set up a site that quietly uses your OpenID Provider to identify a user of a random Internet web site if they belong to your org, which would violate their privacy.Fourthly, the HTTP request that the OP is sending to your RP's "realm" URL is part of a process OpenID calls "RP discovery". It's there to mitigate "open redirector" attacks. You should adjust your RP's base URL to not redirect, but rather return an XRDS document when the RP discovery request comes in. You can still redirect for the normal browser case. You can see an example of how to do this in the OpenIdRelyingPartyMvc sample's HomeController.