3
votes

I'm creating a website .NET, which will provide specific service for its users. Lets call it service1.com. In future, there will be service2.com and they will share database. I also want to make a user friendly authentication by providing login with Google, Facebook, Twitter + custom signup in case users do not have existing with Google, etc. so they can sign up. I will be using DotNetOpenAuth library. So scenario is in a way similar to SO.

Now there are immediate problems with Google: it offers different claimed identifier for different realm - that means I can't use it as ID for users, but have to add Email into the picture to resolve users (like SO is doing).

Besides that it means I have to support Facebook Connect, Twitter and every additional login option on every service site.

So I came up with idea of OpenID proxy:

  • I create OpenID provider openid.service.com with its own separate database
  • service1.com and service2.com are its relying parties and share their own database
  • openid.service.com can authenticate users in various different ways:
    • new account with openid.service.com (basic sign up like SO)
    • OpenID (Gmail, Yahoo,...)
    • Facebook Connect
    • Twitter
    • ...
  • login options and sign up forms will be displayed in iframe on every service website (like SO signup is)
  • once openid.service.com authenticates user by any of options it provides claimed identifier to relying parties callback

So final result would look something like this:enter image description here

PROS:

  • services can use claimed identifier supplied from openid.service.com to identify which user is logging in
  • services can be simply added to desired domains
  • adding more login options is trivial since openid.service.com handles it
  • Google claimed identifiers do not change because openid.service.com handles authentication

CONS:

  • chain of authentication is longer (services do not authenticate directly with Google, etc.)
  • ???

Is this a decent way to implement such a system and are there additional drawbacks to this?

1

1 Answers

2
votes

Yes, that is a reasonable way to solve this problem. In fact I think StackOverflow does the same thing. The custom user database for folks not using OpenID would also be at openid.service.com, so actually they would be using OpenID in that case too, although they wouldn't know it.

One thing to note is that openid.service.com in your example will not be able to assert the original Claimed Identifier from Google and others, because the secondary RPs will not accept the assertion from your intermediary as it has no authority over those claimed ids. Instead what you can do is an no-authentication OpenID between your openid.service.com and each of your auxiliary RPs. Yes, OpenID actually has a flow where there is no claimed identifier passing from the OP to the RP -- only extensions. So you can use DotNetOpenAuth FetchRequest and FetchResponse extensions to request the user's actual claimed_id between your auxiliary RPs and your central service. And the auxiliary RPs must be very careful to only accept assertions that come from your central RP.

Here's a sketch of the code between auxiliary RP and your central one:

Step 1: RP requests authentication of user from openid.service.com

OpenIdRelyingParty rp;
var request = rp.CreateRequest("https://openid.service.com/");
request.IsExtensionOnly = true;
var fetchRequest = new FetchRequest();
fetchRequest.Attributes.AddRequired("http://openid.service.com/useridentity");
request.AddExtension(fetchRequest);
request.RedirectToProvider();

Step 2: openid.service.com, acting as OP, receives request (being an OP is a huge repsonsibility... this is by all accounts a very incomplete sample. You should refer to the OpenIdProvider samples that are available.)

OpenIdProvider op;
IRequest request = op.GetRequest();
/// ...
IAnonymousRequest anonRequest = request as IAnonymousRequest;
if (anonRequest != null) {
    // The key part for this sample snippet is that you make sure the RP asking
    // is one of your own, since you're not following the typical OpenID flow.
    if (!IsWhiteListedRealm(hostRequest.Realm)) {
        anonRequest.IsApproved = false; // reject all RPs that aren't in the whitelist
    } else {
        // Perhaps here is where you'll start your double role as an RP
        // to authenticate the user if there isn't already a FormsAuth cookie.
    }
}

return op.PrepareResponse(request).AsActionResult()

Step 3: Auxiliary RP receives the response from your proxy OpenID service.

OpenIdRelyingParty rp;
var response = rp.GetResponse();
if (response != null) {
    if (response.Provider.Uri.Authority != "openid.service.com") {
        throw new Exception(); // only assertions from our own OP are trusted.
    }

    if (response.Status == AuthenticationStatus.ExtensionsOnly) {
        var fetchResponse = response.GetExtension<FetchResponse>();
        string claimedId = fetchResponse.GetAttributeValue("http://openid.service.com/useridentity");
        FormsAuthentication.RedirectFromLoginPage(claimedId);
    }
}

These samples are not complete. And the above do not apply many of the normal practice security mitigations for web sites filling these roles. This is a merely a sketch that outlines some of the additional steps your apps should take given your particular situation.