Claims Tips 4: Learning About Claims-Based Authentication in SharePoint 2010
Summary: Learn five tips that are related to claims-based authentication in Microsoft SharePoint 2010, including calling a claims-aware WCF service, getting the original real issuer, converting role security identifiers (SIDs) to group names, and getting all claims providers and user claims information.
Last modified: May 16, 2011
Applies to: Business Connectivity Services | Open XML | SharePoint Designer 2010 | SharePoint Foundation 2010 | SharePoint Online | SharePoint Server 2010 | Visual Studio
Provided by: Steve Peschka, Microsoft Corporation
Contents
Overview of the Scope of This Article
Tip 1: Calling a Claims-Aware WCF Service from a SharePoint 2010 Claims Site
Tip 2: Getting the Real Original Issuer for an Identity Claim in SharePoint 2010
Tip 3: Getting All Claims Providers Associated with a Web Application in SharePoint 2010
Tip 4: Getting All User Claims During Claims Augmentation in SharePoint 2010
Tip 5: Using a SharePoint 2010 Claims Provider to Convert Role SIDs to Group Names
-
-
This article provides tips for and answers to frequently asked questions that are related to claims-based authentication in Microsoft SharePoint 2010. It also provides tips and guidance to help solve problems related to using and configuring claims, and points to other resources for more information.
I have been doing some end-to-end integration work on SharePoint 2010 and other applications, using claims-based authentication to pass identity across application boundaries. One of the specific points I have worked on is being able to make a call to a claims-aware Windows Communication Foundation (WCF) endpoint from a SharePoint Web Part, and having that claims information pass over.
The first trick is making your WCF service claims-aware, and that is really outside the scope of what I am writing here. Eric White did a great four-part article on this process. For more information, see WCF: Building WCF Web Services for SharePoint 2010 Business Connectivity Services (Part 1 of 4).
Assuming that you have all that set up, now you need to call the WCF web service from a SharePoint Web Part (in this example). The tricky thing here is making this call from a SharePoint site that is secured with claims authentication. In this case I am using a site that supports both Windows claims and Security Assertion Markup Language (SAML) claims.
I found something curious when I first logged on as a Windows user. When my Web Part made a call to the web service, it failed with this error: "SOAP security negotiation failed. See inner exception for more details." When you look at the inner exception, it says, "The profile for the user is a temporary profile."
At first, this error struck me as nonsensical—I was, after all, logged on to the site by using the same Windows credentials that I had used to log on to my computer. In thinking about it some more, I realized it probably is accurate. I am technically logged on as domain\speschka, but after I go into SharePoint 2010, I am transformed into a Windows claims user, and I am now 0#.w|domain\speschka.
So, how do we work around this and pass our claims over to the WCF service?
There are a few nice claims helper functions in the Microsoft.SharePoint namespace. One of these is the SPChannelFactoryOperations class. It has a helper function named CreateChannelActingAsLoggedOnUser that I used to connect to my WCF service.
From Visual Studio, I started by adding a service reference to my WCF endpoint. In this case my service reference name was AzureData, and it points to another server that is running Customers.svc. I start by creating the endpoint address that my WCF service is running as an instance of my service proxy. The following code shows how it is done.
Now that I have that in place, I have to call a method on the channel of my proxy that enables it to use a federated client identity. You can do that with the following simple single line of code.
SharePoint 2010 also has a method for doing this. Instead of using the ConfigureChannelFactory method off the ChannelFactory class, you can instead use the following method from the SharePoint API. I have found both approaches work fine.
Now I can go ahead and create the channel by using the claims credentials of the logged-on user. That is done by using the CreateChannelActingAsLoggedOnUser method.
In this case, I want to get an instance of the interface that the WCF endpoint implements—that is why my variable is of type AzureData.ICustomers. When I call the CreateChannelActingAsLoggedOnUser method, I must tell it what kind of interface it is creating the channel for, so again, I use the AzureData.ICustomers interface. Finally, to create the channel as the logged-on user, I give it an existing channel reference and the endpoint that it is going to create the channel to. So I just pass in the ChannelFactory property of my proxy instance that I created earlier, along with the endpoint address that I defined up front. After I have done this work, I can call into my WCF endpoint.
Because claimsWCF is my instance of the interface, I can call whatever methods have been defined for it. With a debugger attached to both the Web Part and the WCF service, I can see the call coming from SharePoint 2010 over to the service, and in the service I can enumerate all the claims that I got when I logged on to SharePoint, including custom claims that I added with a custom claims augmentation component (my favorite basketball team claim from my series on writing a custom claims provider).
I have done this task a variety of ways over the past year but finally decided that I should just do things the "right" way. The task is simple enough—when your custom claims provider is being invoked, for example during claims augmentation, and you want to know what kind of user they are—Windows claims, forms-based authentication claims, or SAML claims—how should we do that? Rather than give you the different variety of ways I have used in the past, the following is code that summarizes the right way to do it.
// Get the claims provider manager. SPClaimProviderManager cpm = SPClaimProviderManager.Local; // Get the current user so that); if (loginType == SPOriginalIssuerType.Windows) { // Do Windows claims. } else if ((loginType == SPOriginalIssuerType.TrustedProvider) || (loginType == SPOriginalIssuerType.ClaimProvider)) { // Do SAML claims. }
The code is straightforward, so I do not have a lot of commentary to add. In this case, the "entity" parameter being used in the DecodeUserIdentifierClaim method is passed in as part of my override of the FillClaimsForEntity method (that is, augmenting claims) in my custom claims provider. The method shown here should work anywhere in a custom claims provider.
I have been asked this question a couple of times before, which is why I want to be able to programmatically find out what claims providers are being used with my web application. This question is usually asked to mean what SPTrustedIdentityTokenIssuer value is being used. The method that I am demonstrating reveals the SPTrustedIdentityTokenIssuer value and custom claims providers that are not enabled by default (because if a claims provider is enabled by default, the claims provider is used everywhere).
The first thing to understand is, if you are wondering what is enabled for a web application, you are thinking about it incorrectly (and this is probably why you have had a hard time finding this information). Your claims providers are going to be applied at the zone level, not the web-application level. So, given a SharePoint site URL, how do we figure out this information?
To start with, get a new SPSite object based on the URL that you are interested in.
After you have the SPSite object, you can get the web application and the zone.
With that information, you can get the SPIisSettings information for the zone, which is where most of the interesting information resides.
After I have the zone information, I can get both the authentication providers and the claims providers for that zone. They are found in the following two properties: ClaimsAuthenticationProviders and ClaimsProviders. Remember that each ClaimsAuthenticationProviders property has only a very small subset of the information that you get if you call Get -SPTrustedIdentityTokenIssuer in Windows PowerShell.
If you really want to get the core underlying object, take your ClaimsAuthenticationProviders value and get a SPTrustedLoginProvider from it. Fortunately, that is not too hard either. The following is an example where I query for a list of SPTrustedLoginProviders by using LINQ; notice that in this example I am interested only in the SAML claims providers (also known as the SPTrustedIdentityTokenIssuer object in Windows PowerShell for SharePoint 2010.).
// Get the token service manager so that we can retrieve the appropriate // trusted logon trusted logon provider. SPTrustedLoginProvider loginProv = lp.First(); } } }
For completeness, following is the entire code block. In this particular scenario, I was looking for all the SPTrustedIdentityTokenIssuer objects that are associated with a zone. For each SPTrustedIdentityTokenIssuer object, I create a string with the name of the trusted login provider and the URL to which you would get redirected to authenticate when using that trusted login provider.
using (SPSite theSite = new SPSite("")) { // Get the web application. SPWebApplication wa = theSite.WebApplication; // Get the zone for the site. SPUrlZone theZone = theSite.Zone; // Get the settings that are associated with the zone. SPIisSettings theSettings = wa.GetIisSettingsWithFallback(theZone); // If this is not a SAML claims authentication site then quit. if (!theSettings.UseTrustedClaimsAuthenticationProvider) { MessageBox.Show("This is not a SAML claims authentication site"); return; } // Clear the list of providers. ProviderLst.Items.Clear(); // Get the token service manager so that we can retrieve the appropriate // trusted login login provider. SPTrustedLoginProvider loginProv = lp.First(); // Get the logon information. string provInfo = prov.DisplayName + " - " + loginProv.ProviderUri.ToString(); // Add the logon information to the list. ProviderLst.Items.Add(provInfo); } } } }
A fairly constant hurdle when doing claims augmentation in SharePoint 2010 has been trying to figure out what claims a user has when your custom claims provider is invoked to do claims augmentation. The claims you want to augment for a person may depend on the value of other claims that the user has. For example, if the person belongs to the role "Domain Administrator", add the role claim "Uber User", otherwise add the claim "Standard User".
After being frustrated by this for a long time, my good friends, Israel Vega and Matt Long, finally came up with the solution to this sticky problem (and deserve 100% of the credit for this solution, so thank you!).
One of the underlying problems in trying to get to this information outside the parameters provided when your claims provider is invoked for augmentation is that you do not have any access to an HttpContext object to look at the collection of claims. Israel correctly figured this out and figured out the alternative, which is using the OperationContext object. So what is the OperationContext object?
In a nutshell, what we care about is that the OperationContext object enables you to access incoming message headers and properties. So, how does this help us? Well, when your custom claims provider is invoked for augmentation, you can get to this incoming message information. The following XML shows what the incoming message information looks like.
<s:Envelope xmlns: <s:Header> <a:Action s:</a:Action> <a:MessageID>urn:uuid:85a0daaa-2288-4d0a-bda8-5fac05ea61cf</a:MessageID> <a:ReplyTo> <a:Address></a:Address> </a:ReplyTo> <a:To s:</a:To> </s:Header> <s:Body> <trust:RequestSecurityToken xmlns: <wsp:AppliesTo xmlns: <a:EndpointReference> <a:Address></a:Address> </a:EndpointReference> </wsp:AppliesTo> <trust:KeyType></trust:KeyType> <trust:OnBehalfOf> <saml:Assertion <saml:Conditions <saml:AudienceRestrictionCondition> <saml:Audience>urn:sharepoint:fc1<>testuser@vbtoys.com</saml:AttributeValue> </saml:Attribute> <saml:Attribute <saml:AttributeValue>pOregon Marketing</saml:AttributeValue> <saml:AttributeValue>Domain Users</saml:AttributeValue> <saml:AttributeValue>pSales</saml:AttributeValue> <saml:AttributeValue>Portal People</saml:AttributeValue> </saml:Attribute> <saml:Attribute <saml:AttributeValue>testuser</saml:AttributeValue> </saml:Attribute> <saml:Attribute <saml:AttributeValue>testuser@vbtoys.com</saml:AttributeValue> </saml:Attribute> </saml:AttributeStatement> <saml:AuthenticationStatement <saml:Subject> <saml:SubjectConfirmation> <saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod> </saml:SubjectConfirmation> </saml:Subject> </saml:AuthenticationStatement> >5Qvu+bltcbkln</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>VUSrynYjN8NOcUexqJOCblahblah</ds:SignatureValue> <KeyInfo xmlns=""> <X509Data> <X509Certificate>MIIFlzCCBH+gAwIBAgIKHmbHMKIFVNKOVC>N</X509Certificate> </X509Data> </KeyInfo> </ds:Signature> </saml:Assertion> </trust:OnBehalfOf> <trust:RequestType></trust:RequestType> </trust:RequestSecurityToken> </s:Body> </s:Envelope>
Now that we have a chunk of XML with our claims embedded inside, it is simple to unwrap them and use them during augmentation. The following code shows how to do that.
using System.Xml; private class IncomingClaim { public string claimType { get; set; } public string claimValue { get; set; } public IncomingClaim(string claimType, string claimValue) { this.claimType = claimType; this.claimValue = claimValue; } } protected override void FillClaimsForEntity(Uri context, SPClaim entity, List<SPClaim> claims) { // Get the request envelope with the claims information. string rqst = System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString(); // Create a list to store the results. List<IncomingClaim> incomingClaims = new List<IncomingClaim>(); // Create an XML document for parsing the results and load the data. XmlDocument xDoc = new XmlDocument(); xDoc.LoadXml(rqst); // Create a new namespace table so that we can query. XmlNamespaceManager xNS = new XmlNamespaceManager(xDoc.NameTable); // Add the namespaces that we are using. xNS.AddNamespace("s", ""); xNS.AddNamespace("trust", ""); xNS.AddNamespace("saml", "urn:oasis:names:tc:SAML:1.0:assertion"); // Get the list of claim nodes. XmlNodeList xList = xDoc.SelectNodes("s:Envelope/s:Body/trust:RequestSecurityToken/trust:OnBehalfOf/saml:Assertion/saml:AttributeStatement/saml:Attribute", xNS); // Get the matches. if ((xList != null) && (xList.Count > 0)) { // Enumerate through the matches to get the claim list. foreach (XmlNode xNode in xList) { // Get the claim type first. string currentClaimType = xNode.Attributes["AttributeNamespace"].Value + "/" + xNode.Attributes["AttributeName"].Value; // Each claim type will have one to many child nodes with the values. foreach (XmlNode claimNode in xNode.ChildNodes) { incomingClaims.Add(new IncomingClaim(currentClaimType, claimNode.InnerText)); } } } // Now you can do whatever you want with the list of claims and complete // your augmentation. }
That is pretty much it. The code is straightforward and verbosely commented out. If you look at it, and copy the XML into an XML editor (like the one that is included with Visual Studio), it is pretty clear how it works. At this point, it is just XML—nothing complicated.
There is also an interesting side effect that you can implement based on this pattern, which my blog reader Luis A. points out. The set of claims that you get in the RequestMessage method includes everything that came from your IP-STS, including any claims that the SharePoint STS drops if there is no corresponding claim mapped. So, if you know which claims SharePoint is going to drop and you want to keep them anyway, you can just augment them yourself again with your custom claims provider. Just pull the claim values and types out of the RequestMessage method and add them back in.
Windows claims users do not have this message associated with their request, but you can get a WindowsIdentity object for them by using the ServiceSecurityContext property of the OperationContext object. From that, you can do all the things that are available with a WindowsIdentity, like getting the list of groups that the user belongs to, and so on. Following is an example.
// Do Windows claims. WindowsIdentity wid = System.ServiceModel.OperationContext.Current.ServiceSecurityContext.WindowsIdentity; // Get a reference to the groups that the user belongs to. IdentityReferenceCollection grps = wid.Groups; // Enumerate each group (which will be represented as an SID). // NOTE: This includes all groups – built-in, local, domain, and so on. foreach (IdentityReference grp in grps) { // Get the plain name of the group. SecurityIdentifier sid = new SecurityIdentifier(grp.Value); NTAccount groupAccount = (NTAccount)sid.Translate(typeof(NTAccount)); string groupName = groupAccount.ToString(); // For domain groups, remove the domain\ prefix from the group // name, to have feature parity with SAML users. if (groupName.Contains("\\")) groupName = groupName.Substring(groupName.IndexOf("\\") + 1); // Add the claim. incomingClaims.Add(new IncomingClaim("", groupName)); }
Ensure that you add a using statement for the System.Security.Principal namespace to get the WindowsIdentity object, the IdentityReference object, the NTAccount object, and so on to resolve correctly. The code is fairly straightforward. I am just getting the list of groups for the user and putting them in my custom collection of claims as a standard role claim.
Thanks again Israel and Matt, for sharing this valuable information.
You may find yourself in a scenario where the Active Directory directory service is not available across your entire solution scope. For example, if you have a solution that starts out in SharePoint 2010, but then has to fetch data or other resources from some repository that is not secured by the same Active Directory that you logged in with (or with any Active Directory).
A specific example is if you want to make a call to Windows Azure, and you want to use the security information that you got when you connected to your SharePoint site (which is secured with your local Active Directory). Setting aside for a moment the features of Access Control Service (ACS) in Windows Azure (which is a much longer and involved discussion), think about the set of claims that you get when you hit your SharePoint site.
If you are a Windows user, you get your individual claims and augmented claims from custom providers, and for each Windows group that you belong to, you get a "groupsid" claim, where the value of the claim is the security identifier (SID) of the group. That all works great when you are working with Active Directory, but when you go out to some other resource that does not understand or does not have any kind of relationship with your local Active Directory, that SID value is basically meaningless.
Now compare that to a SAML token user—when you log on you get your individual claims and augmented claims from custom providers. But, for each group that you belong to (you could be a Windows user who logs on via Active Directory Federation Services (AD FS) 2.0 or any federated user who gets group claims), you get a "role" claim and the value is the name of the group (or at least that is how the value is most commonly represented). What is nice about the SAML scenario is now, when you go to some other source that does not know your local Active Directory (or whatever your identity provider is), you have a nice ubiquitous set of identifiers—they are all strings, so it is a lot easier to make authorization decisions.
In fact, if you are writing Microsoft .NET code, you can use these role claims to make permission demands down to the individual methods in your public endpoints. For example, you can place a PrincipalPermission demand that says only sales managers can call this method. If a user does not have a claim for that role, they simply get an access denied error when trying to call it.
Following is an example of what demand looks like.
That brings us back to the point of this tip—how can we get this same kind of experience for our Windows claims users? To try and tackle this, I wrote a small and fairly simple custom claims provider. It looks at the identity claim of the user who is logging on and, if it is a Windows claims user, it goes out to the domain that is specified in the domain\username attribute and does a couple of lookups. The first lookup gets the distinguished name of the user, and the second lookup gets all of the group memberships that the user has.
It is also worth noting that this code runs in the same process as the SharePoint STS, which is the farm account, so you must ensure that the farm account has rights to read the domain information for the forests where your users are coming from. The net of this is, when you use this provider, your Windows claims users have one standard "role" claim for each Active Directory group that they belong to.
Functionally, this makes it easy to make authorization decisions for all of these users now, as you slide across different application and data center boundaries. Rather than trying to describe the code in this article, I am attaching the entire project including source code on my blog post. If you want to see how it looks, open the project in Visual Studio. If you just want to use the provider, you can take the compiled assembly, register it in the global assembly cache, register the feature (I have also included a feature.xml file), and you are good to go. The code is a sample, provided "as-is" and not meant for a production environment.
For more information, see the following resources:
Steve Peschka's Blog: Share-n-dipity
Security Cmdlets (SharePoint Foundation 2010)
Claims Tips 1: Learning About Claims-Based Authentication in SharePoint 2010
Claims Walkthrough: Writing Claims Providers for SharePoint 2010
Forms Authentication in SharePoint Products and Technologies (Part 1): Introduction
Incoming Claims: Signing into SharePoint
Claims-Based Identity Term Definitions
Microsoft.SharePoint.Administration.Claims
Planning, Upgrade, Migration, Administration, Configuration and Setup
Security In SharePoint 2010 Video Presentations
SharePoint 2010 Advanced Developer Training
Security Resource Center | SharePoint 2010
SharePoint Developer Center