• OpenID – WS-Fed Protocol Transition STS

    Published by on July 14th, 2009 10:44 am under Federation, Geneva, OpenID

    13 Comments

    I will go straight to the point in this post. This is a possible architecture if you want to allow OpenID authentication in a claims-aware WS-Federation-compatible web application. In this architecture there are three actors:

    • the web application (aka the relying party)
    • the OpenID provider (myopenid, Google, Yahoo, etc.)
    • the “protocol broker” STS that “translates” WS-Fed to OpenID and viceversa

    image

    These are the interactions that happen at login time:
    NOTE: the diagram shows the interactions in a conceptual fashion. In reality all these arrows are HTTP requests/responses that are originated in the user browser.

    1. The user browse the app
    2. The Geneva Framework WSFederationAuthenticationModule detects the user is anonoymous and is trying to access a protected page. So it will create a WS-Fed SignIn Request against an STS
    3. This STS, built with Geneva Framework, will provide a login page that will do the handshake against an OpenID Provider. To do this we use DotNetOpenAuth. In fact this STS will be an OpenID Relying Party.
    4. The user will provide its OpenID identifier and the STS will issue an authentication request against the OpenId Provider
    5. The OpenID Provider will ask for a password and will return an authentication response to the STS
    6. The STS will grab the claims issued by the OpenID provider and will put them in the ClaimsIdentity, generating a SAML token with OpenID claims.
    7. The STS will return a WS-Fed SignIn Response with the SAML token. The WSFederationAuthenticationModule will grab the token, validates it and generate a principal.
    8. The user can now access a restricted page because it’s authenticated. But also we have profile information (if the user filled his profile in the OpenID provider)

    So essentially what we’ve done is a Protocol Transition STS (don’t know if such term exists), that will transform WS-Fed to OpenID and viceversa.

    Aside
    This is powerful because we can now plug this STS with a Geneva Server and keep all of our applications WS-Fed compatible. Geneva Server can be used as an R-STS that all the applications will trust. This Geneva Server might be configured with different Identity Providers, one of them could be the “OpenID STS” that we’ve just described.

    Here are some screenshots working with myopenid.com and Google OpenId provider.

    image

    image

    image

    Notice that we are getting back some profile information (like email, full name, etc.). We are translating that profile info into SAML attributes that will be issued by the OpenID STS. The following code shows what we are doing on the OpenID STS login page. We translate the OpenID claims and store them in session to get them later from the STS.

    protected void OpenID_OnLoggedIn(object sender, OpenIdEventArgs e) { Dictionary<string, string> claims = GetClaims(e.Response); HttpContext.Current.Session.Add(“OpenIDClaims”, claims); } private Dictionary<string, string> GetClaims(IAuthenticationResponse response) { Dictionary<string, string> claims = new Dictionary<string, string>(); claims.Add(System.IdentityModel.Claims.ClaimTypes.Name, response.FriendlyIdentifierForDisplay); var claimsResponse = response.GetExtension<ClaimsResponse>(); if (claimsResponse == null) return claims; if (claimsResponse.BirthDate.HasValue) claims.Add(System.IdentityModel.Claims.ClaimTypes.DateOfBirth, claimsResponse.BirthDate.Value.ToString(“o”)); if (claimsResponse.Country != null) claims.Add(System.IdentityModel.Claims.ClaimTypes.Country, claimsResponse.Country); if (claimsResponse.Culture != null) claims.Add(“http://openid-custom/identity/claims/culture”, claimsResponse.Culture.ToString()); if (claimsResponse.Email != null) claims.Add(System.IdentityModel.Claims.ClaimTypes.Email, claimsResponse.Email); if (claimsResponse.FullName != null) claims.Add(“http://openid-custom/identity/claims/fullname”, claimsResponse.FullName); if (claimsResponse.Gender.HasValue) claims.Add(System.IdentityModel.Claims.ClaimTypes.Gender, claimsResponse.Gender.Value == Gender.Female ? “Female” : “Male”); if (claimsResponse.Language != null) claims.Add(“http://openid-custom/identity/claims/language”, claimsResponse.Language); if (claimsResponse.Nickname != null) claims.Add(“http://openid-custom/identity/claims/nickname”, claimsResponse.Nickname); if (claimsResponse.PostalCode != null) claims.Add(System.IdentityModel.Claims.ClaimTypes.PostalCode, claimsResponse.PostalCode); if (claimsResponse.PostalCode != null) claims.Add(System.IdentityModel.Claims.ClaimTypes.Locality, claimsResponse.TimeZone); return claims; }

    This is the code in the STS:

    protected override IClaimsIdentity GetOutputClaimsIdentity( IClaimsPrincipal principal, RequestSecurityToken request, Scope scope ) { ClaimsIdentity outputIdentity = new ClaimsIdentity(); if ( null == principal ) { throw new InvalidRequestException( “The caller’s principal is null.” ); } var openIdClaims = HttpContext.Current.Session[“OpenIDClaims”] as Dictionary<string, string>; foreach (var openIdClaim in openIdClaims) { outputIdentity.Claims.Add(new Claim(openIdClaim.Key, openIdClaim.Value)); } return outputIdentity; }

    Logging in with Google OpenID provider

    image

    image

    image

    Since Google does not provide profile info, we get a hash as a login name. We could use OAuth and fetch profile attributes from Google (like email, name, etc.) in the STS and fill more claims. But to do that you need to host the site publicly and register it at Google, it does not work in localhost.

    Summary

    In this post we showed how you can use Geneva Framework on your claims-aware applications and authenticate against OpenID which is a different protocol. What I really like about Geneva Framework is that it allows you to transition any authentication scheme with WS-Fed because it plays well with the ASP.NET pipeline. So it’s basically an adapter between *any* existing authentication investment and WS-Federation and SAML token. This reminds me of a blog post Vittorio wrote some time ago Enhance your ASP.NET Membership-based website by adding Identity Provider capabilities (hint: read the last paragraph of his post).

    Here is the code for the STS and a sample relying party created with the Claims-Aware Website template from Geneva Framework.

    Download

Tags