Wouldn’t you like to show your users a custom error message instead of this generic one?
Now you can with the latest .Net Framework 3.5 release (Beta 2 as of this blog). Your Identity Provider can simply return a SOAP fault and CardSpace will display the Fault Reason Text. This feature is great because it enables you to present the user with help and support information such as phone numbers or URLs. Your error message can now look like this:
Your fault reason text can also be language specific. CardSpace will display the correct fault reason text based on the UI locale.
Frequently Asked Questions
What is the format of a SOAP message?
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Text xml:lang="en"> In English …</</s:Text>
<s:Text xml:lang="es-ES">In Spanish …</s:Text>
Note that this SOAP message must be secured just like a typical application message. That is, it must contain the necessary Security headers (with all the necessary signature and encryption requirements based on the binding). CardSpace will only display secured fault messages. It will not display unsecured fault messages.
Can I present rich HTML information?
CardSpace will only display plain text messages.
How do I return a custom error message with WCF?
Your code snippet will look something like this:
FaultReasonText defaultMessage = new FaultReasonText(@"Our record on file shows that you are not authorized to use this service. If you have forgotten your user name or your password, please visit
http://www.contoso.com/help for further assistance.
You can also call (123) 456-7890 to speak with one of our customer care representatives.", "en");
FaultReason reason = new FaultReason(defaultMessage);
throw new FaultException(reason);
The FaultReasonText has constructors that take the culture info (see http://msdn2.microsoft.com/en-us/library/system.servicemodel.faultreasontext.faultreasontext.aspx). The list of culture names is documented at http://msdn2.microsoft.com/en-us/library/system.globalization.cultureinfo.aspx. You can even do something like this:
FaultReasonText defaultMessage = new FaultReasonText(@"The default error message", "en");
FaultReasonText spanish = new FaultReasonText("(In Spanish) Español", "es-ES");
List<FaultReasonText> reasonInDifferentLanguages = new List<FaultReasonText>();
FaultReason reason = new FaultReason(reasonInDifferentLanguages);
throw new FaultException(reason);
There are a few caveats. Due to the way the WCF channel stack processes a SOAP message; the fault message will be sent as a secured fault message if you throw the FaultException in the following places:
1. Within the code path of ServiceAuthorizationManager.CheckAccessCore
2. Within the code path of the Identity Provider’s Issue operation.
If you want to show a friendly error message due to authentication failures, the above steps is not sufficient because the WCF channel stack will send back the exception as an unsecured fault message. Since CardSpace only displays secured fault messages, the user will not see the friendly message that you have sent back. Instead the user will see the generic version.
The workaround is to use the security extensibility points in WCF to delay throwing an exception until the WCF channel stack calls into the ServiceAuthorizationManager.CheckAccessCore. The steps below use the UserNamePasswordValidator as an example; the same principle applies to other types of validation as well:
1. Implement a custom UserNamePasswordValidator to validate the user name and password.
2. Implement a custom UserNameSecurityTokenAuthenticator to call your custom UserNamePasswordValidator. If the user name and password validation succeeds, return a custom IAuthorizationPolicy that will pull in other claims permitted for this user. If the password validation fails, return an invalid authorization policy. The job of this invalid authorization policy is to insert an “invalidusernamepassword” claim when called upon.
3. Implement a custom ServiceAuthorizationManager to check for the “invalidusernamepassword” claim in the AuthorizationContext. If it exists, throw your friendly FaultException message. If it does not exist, you can check the other claims to grant /deny access.
4. Wire up these customizations by implementing a custom ServiceCredentialsSecurityTokenManager and a custom ServiceCredentials. Then replace the ServiceCredentials on ServiceHost with the custom one.
For further details regarding the above (including code samples), do visit these resources:
· Govind’s blog on plugging in a custom UserNamePasswordAuthenticator. Govind’s office, by the way, is on the same floor as mine. It was really convenient for me to walk over and ask him questions about how the security layer works in the WCF channel stack J.
Is there a sample I can play around with?
Brian on our team is working on samples right now. He’ll post it once the samples are ready.
Cool new feature, isn’t it. Do let us know how else we can help you build a better Identity solution.
Dedicated to your success,
Tak Wai, Wong
Software Design Engineer in Test