Getting a token from ADFS (ex Geneva Server) using WCF
July 17th, 2009
I’ve been doing some tests to get a token from ADFS (Geneva Server) using Windows Identity Foundation WSTrustClient. In this case we are using the UserNameMixed endpoint that expects a WS-Security UsernameToken (notice the MessageCredentialType.UserName).
internal static ClaimsIdentityCollection RequestTokenWithUsernameMixed()
{
var binding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential, false);
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Message.EstablishSecurityContext = false;
var credentials = new ClientCredentials();
credentials.UserName.UserName = "Mary";
credentials.UserName.Password = "Passw0rd!";
var endpoint = "https://mygenevaserver/Trust/13/UsernameMixed";
var client = new WSTrustClient(binding, new EndpointAddress(new Uri(endpoint)), TrustVersion.WSTrust13, credentials);
var request = new RequestSecurityToken();
request.RequestType = "http://schemas.microsoft.com/idfx/requesttype/issue";
request.AppliesTo = new EndpointAddress("http://localhost/activerp");
var token = client.Issue(request) as GenericXmlSecurityToken;
var claims = token.ToClaimsIdentityCollection(TrustVersion.WSTrust13, CertificateUtility.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "CN=Geneva Signing Certificate - WIN-66EYOLL2BVY"), CertificateUtility.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "CN=WMSvc-WIN-66EYOLL2BVY"));
return claims;
}
Here is another one using the WindowsMixed endpoint (notice the MessageCredentialType.Windows and no username and password set)
internal static ClaimsIdentityCollection RequestTokenWithWindowsMixed()
{
var binding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential, false);
binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
binding.Security.Message.EstablishSecurityContext = false;
var credentials = new ClientCredentials();
var endpoint = "https://mygenevaser/Trust/13/WindowsMixed";
var client = new WSTrustClient(binding, new EndpointAddress(new Uri(endpoint)), TrustVersion.WSTrust13, credentials);
var request = new RequestSecurityToken();
request.RequestType = "http://schemas.microsoft.com/idfx/requesttype/issue";
request.AppliesTo = new EndpointAddress("http://localhost/activerp");
var token = client.Issue(request) as GenericXmlSecurityToken;
var claims = token.ToClaimsIdentityCollection(TrustVersion.WSTrust13, CertificateUtility.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "CN=Geneva Signing Certificate - WIN-66EYOLL2BVY"), CertificateUtility.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "CN=WMSvc-WIN-66EYOLL2BVY"));
return claims;
}
You can use this together with the CreateChannelWithIssuedToken extension method (as shown in a previous post).
MVP.Renew()
July 16th, 2009
Almost forgot to post about this. On July 1st. I got renewed as an MVP! Thanks Microsoft for the recognition and especially Fernando Garcia Lorea (MVP lead) for keeping us updated and focused with everything related to the program.
Now, let’s go back to work…
OpenID – WS-Fed Protocol Transition STS
July 14th, 2009
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
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.
- The user browse the app
- 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
- 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.
- The user will provide its OpenID identifier and the STS will issue an authentication request against the OpenId Provider
- The OpenID Provider will ask for a password and will return an authentication response to the STS
- 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.
- 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.
- 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.
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
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.
.NET Service Bus – Remote Desktop over Firewalls!
July 10th, 2009
Today was holiday in Argentina but I had to work on some pending stuff (yeah, lucky me). I didn’t want to travel to the office but I had to access a SQL Server that was hosted at Southworks LAN and we don’t have inbound ports open to connect to our workstations through RDP (port 3389). So…. the .Net Service Bus came to the rescue! Last week David Aiken told me about this cool project hosted on codeplex http://socketshifter.codeplex.com. He told me “these people are streaming video over the service bus”…
So yesterday before leaving the office I opened up the socketshifter server, configured my service bus account and allowed some ports to be redirected. Today I connected from home and here is a nice screenshot of RDP to a Southworks LAN machine (connected to localhost:1000). Isn’t it cool??!!?!
For those of you who are wondering how these work, the code is reeeeallly simple because all of the hardlifting is made by the service bus. There is a client and a server that will redirect a stream of bytes from port a to port b (using plain sockets). The socket shifter client will establish a session to the server via the service bus using the NetTcpRelayBinding. The rest of the story is bytes flowing around
This is the client running on my laptop:
And this is the configuration on the client
<configuration>
<appSettings>
<add key="solutionName" value="southworks-magnolia"/>
<add key="password" value="…."/>
<add key="servicePath" value="sb://southworks-magnolia.servicebus.windows.net/rd"/>
<add key="localPort" value="1000" />
<add key="remoteHost" value="localhost"/>
<add key="remotePort" value="3389"/>
</appSettings>
</configuration>
The server needs to be installed as a Windows Service to be able to redirect 3389. I also tried accessing the web server (IIS7) that is running on the LAN and it worked (need to configure the remoteport to 80). So that means that I can expose a web server that runs on my desktop machine and show stuff to customers without asking IT to move my machine to the DMZ!! Clemens did something similar couple of months ago.
This is a very cool use of the Service Bus. Go ahead and try it yourself! Download the latest source code.