Nate
Nate

Reputation: 83

Using DotNetOpenAuth as OpenID Provider in a customer httphandler assembly

I have an assembly that acts as a custom HttpHandler (implements IHttpHandler and is registered with an block in web.config) that I'd like to have provide openid provider functionality. In looking at the DotNetOpenAuth samples, I've modeled my handler after the OpenIdOfflineProvider - as this sample is (at its roots at least) a simple HttpListener with all provider functionality in code. I am at the point where i can provide my url to a relying party (the webforms sample rp) and see the IAuthenticationRequest. If i set IAuthenticationRequest.IsAuthenticated = true, then authentication succeeds.

However - at this point I'd like to redirect the user to a custom page for login (as i require more than simple username/password) before setting IAuthenticationRequest.IsAuthenticated accordingly. I attempted to do this the 'web forms' way with this:

private void DoAuthentication(IAuthenticationRequest request, HttpContext context) 
{    
    if (context.Request.IsAuthenticated)
    {
        request.IsAuthenticated = true;
    }
    else
    {                
        FormsAuthentication.RedirectToLoginPage();
    }
}

This works in-so-much as the client browser does get redirected and the user can login, effectively setting the forms authentication cookie, but when the login page does a FormsAuthentication.RedirectFromLoginPage() it feels like all context is lost (i just see the /provider page and IAuthenticateRequest is null). At this point, as you'd expect, with the cookie set, if I resubmit my url to a relying party, things work correctly and authentication succeeds.

Is there a way to maintain context necessary so that this can work? Or another way to effectively require a user to hit a form page before the IAuthenticationRequest is handled?

Upvotes: 1

Views: 762

Answers (1)

Andrew Arnott
Andrew Arnott

Reputation: 81801

Yes, there's certainly a way. You just need to keep the instance of IAuthenticationRequest around. You can either keep it in memory or use the BinaryFormatter to serialize it to increase scalability and deserialize it when the user has logged in. In ASP.NET, this instance is stored in the Session dictionary.

When you use FormsAuthentication.RedirectToLoginPage, that just sends the 301 response back to the client with a query string argument of ?ReturnUrl=provider.ashx. When the login page then calls FormsAuthentication.RedirectFromLoginPage it uses that ReturnUrl parameter to know where to redirect the user to next.

So to make this all work, typically the flow will be:

  1. The Provider handler sees the incoming authentication request and immediately stores it in the user session (if you don't have Session available in your handler and don't want to add it, you have to manually use cookies to track this).
  2. Your Provider then redirects the user to the "decision" page, where the user is typically presented with a "are you sure you want to login to RP?"
  3. Your decision page is rigged to require a logged in user, so if the user hasn't logged in, you then call RedirectToLoginPage. Note that because you executed this method from this decision page, the user will return to this decision page after login.
  4. The user logs in. Login page calls RedirectFromLoginPage.
  5. The decision page now sees the logged in user, and looks up the IAuthenticationRequest from the Session. If the user trusts the RP asking for the login, you can immediately set IsAuthenticated=true and redirect the user back to the RP. Otherwise, the decision page renders HTML to the user to present the decision to the user. You then respond to that by setting IsAuthenticated=true or false and redirecting the user back to the RP.

Does that make sense on how you could do this at least part of this from a custom handler? Probably only the OP Endpoint itself will be your custom handler, and everything else in this flow (3-end) will be standard ASPX pages.

Upvotes: 1

Related Questions