In the last post I described the unique values you can find in a card, how they are (roughly) generated and how the certificate of the application (relying party) influences this generation.
Now which value do you use to uniquely identify your users?
In a lot of samples the PPID is used. The PPID is unique for each card/relying party pair – but it is not really a good secret. If you would solely base user identification on the PPID, anybody who knows a valid PPID could craft up a card with that claim and use it to log in to your application.
The CardSpace UI does a good job of not disclosing the complete PPID to the user which helps against phishing. But you still enter the realm of shared secrets – and that’s exactly what we try to get away from (think passwords). The secrecy of the PPID would depend on how secure applications store them in their databases. The same techniques as with passwords could be applied here (e.g. iterated salted hashing), but there are much better approaches.
What’s really secret in a card is the private key –this key is never transmitted to the relying party. Instead the card is signed with the private key, and the public key is sent alongside to verify this signature. The WCF plumbing and the TokenProcessor sample for ASP.NET do all the heavy lifting to verify the incoming cards using the public key.
That means that you could store the public key at registration time with your user record – and when the user logs in, you would compare the public key with the one you stored before. Keith was the first who wrote about this technique, Garret and Vittorio followed up here and here.
Now the key size can be quite big and could also change with new versions of CardSpace over time. This could become a problem with your database design. For storage it would be better to have a fixed key length – enter hashing.
Hashing produces fixed length outputs of variable length inputs, and in addition, you can combine multiple values of a card in the hash. This is often referred to as the “unique id” of a card. The TokenProcessor uses the PPID as a second value for the hash by default (but this can be changed using the IdentityClaimType appSetting in web.config).
Now which value(s) should you use for the unique id? Well it depends… (I know this is lame ;). But it really depends on your application. In classic applications you stored all the user details in your database (first name, last name, email etc..) and provided a UI for the user to change some of these values. In CardSpace these values come from the claims inside the card and in theory there is no reason to store them again – also the UI to change the values is provided by CardSpace itself. Some applications need the user details for offline processing, some don’t. Some applications don’t need private user details at all and just need some way to distinguish between users. And some applications don’t allow the user to change certain values after registration.
So the rule of thumb is, if changing certain values in a card would invalidate the account, combine these values together with the public key to the unique id. So e.g. if you absolutely wanna make sure that changing the email address in the card would render the card useless – and the user has to re-register, do a hash over the email claim and the public key. The next time the user would try to login with that card – he would not get recognized as a registered user in your system.
If you want to allow users to change all values in the card (or you don’t care about other values) do the hash over the PPID and the public key. In this case you either don’t store any user data and just re-read it from the card every time. Another approach would be to “cache” the data and update your cache on every login based on the incoming claims.
The attached sample is a bare-bones implementation of a register/login system using unique IDs. With that code you can play around with different ways to generate the unique ID. The code is also compatible with the TokenProcessor for ASP.NET which allows sharing users between ASP.NET and WCF.