Update 22-09-2011: Code samples posted here
In the last few months I’ve spend a lot of time messing with claimsbased authentication. a lot of it have been toward implementing single sign on though various social providers. But some of it have also been involving working with SharePoint 2010 and CRM 2011 and developing and calling my own WCF services from both applications and websites.
I wanted to document what I learn, as I learned it, on this blog but the time just hasn’t been on my side, but I do how ever spend a little time on a piece of code I wrote that I think a lot people will find useful and can copy’n’paste a bit from.
Most of the actions and information I need in different places are all wrapped up in 1 WCF service, I call this WCF service from both other WCF services and websites and from various applications. I found it was easier for me to just create an Class Library that could talk with this WCF service and then reference that from all my projects.
Most of the code is about handling active federation against any kind of claimsbased identity provider, but I also put in a bit of code to handle passive federation that is needed when working with SharePoint 2010 though code.
I have client class that in a simple way make authentication against an identity provider and receiving a claims token back. I want to go though how to use this client to get a token and what can happen, since I have seen A LOT of forum post from people getting these errors and not getting an answer that fits.
We start by creating an instance of the client and choosing what kind of encryption we want to use.
if we look in Microsoft.IdentityModel.SecurityTokenService.KeyTypes we see we can use Asymmetric, Symmetric or Bearer. Tons of post out there about this.
If you use Asymmetric you as requestor need to supply a key to encrypt the claims with. ( set "UseKey” )
If you use Symmetric the identity provider have all ready been told what certificate to use, to encrypt the claims with.
If you choose Bearer. The token get signed, but claims will not be encrypted. If a token signing certificate have been assigned on the Relying Party, claims will simply not be included at all.
When you request a token, the token gets signed (not encrypted) with a certificate installed on the Identity Provider ( ADFS ). If you add a certificate on a Relying Party Trust (RP) on the ADFS server, the claims inside the token gets encrypted with with that certificate. Only host/applications that have access to the private key of that certificate can now decrypt the token and read the claims. You don’t need to read the claims in order to authenticate your self. For instance if you have a WCF Service you want to call from within an application. You can from within that application still request a token from the ADFS server and then access the WCF service with that Token. As long as the WCF service have access to the private key and can read the claims, your application don’t need it.
If you choose Symmetric but the Relying Party on the ADFS have not been assigned a certificate to encrypt the claims with you will get
ID3037: The specified request failed.
and in the event log on the ADFS server they would also see
ID4007: The symmetric key inside the requested security token must be encrypted. To fix this, either override the SecurityTokenService.GetScope() method to assign appropriate value to Scope.EncryptingCredentials or set Scope.SymmetricKeyEncryptionRequired to false.
Either request a Bearer token or Asymmetric (not sure if you can this?) token, or add a certificate on the RP
To make it simple. If you want to make absolutely sure your ADFS server only issues tokens to the hosts you have given the certificate with private key too, sign the tokens with this certificate by taking the public part of the certificate and save to a file and then assign it on this tab.
If you need to authenticate from many places and don’t want to struggle with distributing a certificate including its private key around. or if you don’t care others can read the claim ( you need to successfully authenticate in order to get the claims in the first place so in theory they should have access to it anyway ) leave this field blank.
To add the certificate to the ADFS server, on the computer you have the certificate you want to use for signing claims with, open certificates and add Local computer or user, depending on where you have the certificate installed. Right click it and choose open
Accept the defaults and save the file. Then use this file when adding a certificate on the RP on the ADFS server.
If you choose to get an Bearer token you cannot reuse this key to authenticate to other RP’s by authentication with the issued token, That will fail with
The signing token XXXX has no keys. The security token is used in a context that requires it to perform cryptographic operations, but the token contains no cryptographic keys. Either the token type does not support cryptographic operations, or the particular token instance does not contain cryptographic keys. Check your configuration to ensure that cryptographically disabled token types (for example, UserNameSecurityToken) are not specified in a context that requires cryptographic operations (for example, an endorsing supporting token).
When you have a token and you want read the claims inside, you will often see errors like
ID4022: The key needed to decrypt the encrypted security token could not be resolved. Ensure that the SecurityTokenResolver is populated with the required key.
Or from a asp.net website
ID4036: The key needed to decrypt the encrypted security token could not be resolved from the following security key identifier 'XXXXX'. Ensure that the SecurityTokenResolver is populated with the required key.
I think its pretty self explaining but there's a ton of forum post’s out there where people ask for help. Again, you have a token, its valid, you can authenticate your self with it, but when you try to read the token you get the above error. You get it be course a certificate has been added on the RP on the ADFS and you haven't given WIF the certificate including private key, needed to decrypt it. If using my code, just load it and add it on the TokenSigningCertificate Property. If you see this error on a websites you are probably missing the serviceCertificate in web.config
And while at those errors. if you get something down the line of
ID4175: The issuer of the security token was not recognized by the IssuerNameRegistry. To accept security tokens from this issuer, configure the IssuerNameRegistry to return a valid name for this issuer.
you are missing the certificate from the Identity Provider ( ADFS )
Back to the code, Next I choose what I want to access (realm) and who to authenticate me (identity provider / ADFS) and how I want to authenticate
AuthenticateBy can be either Certificate, IssuedToken, UserName or Windows. Just for fun I showed other ways to authenticate in the remarks. On that is particular interesting is the ActAs . This is what you would normally do from within an asp.net application that needs to call an WFC service on behalf of the user. Either Authenticate by Windows or username/password form within the asp.net application and then attach the user’s bootstrap token. you need permission to do this of course. That is what the Delegation Authorization Rules are for on the RP Claims rule dialog
Back to the code, so inside the client class I have my authenticate function.
most of this code can be reused against any kind of Claimsbased authentication identity provider but for now I have only done the logic for ADFS and SharePoint.
GetADFSTokenUsernamemixed and GetADFSTokenKerberos is almost the same, GetADFSTokenIssuedToken is a bit more tricky
So here they are
I need to wrap up a few more loose ends and add a few comments but the class library and a simple test application will be available for download on this blog in a few days