Say you have ADFS configured (or any trusted provider for that matter) in your web application and you use ADFS roles to grant permissions to the sites in it. When users log in everything works correctly but then an admin wants to check the permissions of a certain user. And then… why does SharePoint say that the user doesn't have permissions on the site?
And I know that my user a1@adfs has permissions because I have the role "ADFS test users" in my visitors group and I know that the user can access it the site without problems! And I even checked some days before and SharePoint showed that the user had permissions to the site.
So, how come SharePoint is telling me now that the user doesn't have permissions?
SharePoint's external token
SharePoint stores in the content database a token of the user, per site collection, where it saves a set of claims that represent the user. This set of claims contains, among other things, what groups/roles the user is a member of. As you might be guessing now, when we check the permissions of a user, we use this set of claims to compute the permissions.
Other non-interactive operations like alerts, not only "Check permissions", use this token of the user when doing security trimming.
The external token has also a timestamp of when it was last updated. We don't want to have stale tokens in our databases, so we will update it from time to time. How often it is updated depends on the operation that forces the refresh.
In one of my previous posts (SharePoint 2013 authentication lifetime settings) I explained how often we refresh this token (token-timeout) and how that value can be changed.
How does SharePoint refresh the external token?
At this point, you can guess that if we are not getting the expected results when we check the permissions of a user, then it means that the external token of the user doesn't have all of the user's groups/roles in it and that's why the dialog is showing less permissions than expected. But why is the external token missing the roles?
When SharePoint retrieves the token from the database and decides that it needs to update the token (the last token update is beyond the token timeout), it asks the local STS to build a new token of the user. When the web application uses Windows authentication, the local STS just needs to contact the AD domain where the user account resides and it gets all the information it needs from there, builds the new token and saves it in the content database. This is why when the web application uses Windows authentication, it just works. There is a scenario where it may not: the user was just added to a group right after the token was updated.
However, in our original scenario, we are using ADFS authentication, so when the local STS tries to get the information about the user, it just doesn't know where to look for it and then it can only build the minimal token it can with the little information he can get from the user. This means that the token will not contain any roles and thus it will make "Check permissions" fail to compute the correct permissions.
Does this mean that the external token will always be missing the roles? How come it sometimes work? As I said above, we are dealing here with non-interactive logons of the user. In the case of an interactive logon (i.e., the user is actually accessing the site), the interactive identity of the user holds all the claims of the user, and if the login happens to trigger a refresh of the token (by default, if the token is older than 60 minutes), then the token will be refreshed with this interactive identity and then it will contain all the claims as expected. From this point, "Check permissions" will work as expected until the token is refreshed again.
What the local STS actually does when building the token (either in a interactive or not logon) is the following:
- It gets all of the registered claim providers and keeps those providers that can do claim augmentation (SPClaimProvider.SupportsEntityInformation returns true).
- For each of those providers that do augmentation, then calls their FillClaimsForEntity() method.
- The providers will return a list of claims to add to the identity and the STS will aggregate them in the final token.
The problem with SharePoint's default claim providers is that none support augmenting the claims of an trusted provider identity. If you think about it, it makes sense because there is no certain way to understand where the information of this user comes from, so it's impossible to know who to contact to get it when we are refreshing the token of the user.
These are the default claim providers in an out-of-the-box SharePoint farm:
User Profile Claim Provider True
As you can see, only System supports claims augmentation. In SharePoint 2013 we also have User Profile Claim Provider that supports claims augmentation, but it only works on Windows identities, so it cannot help with our problem.
In this case, the solution is to create a custom claim provider that will understand how our farm is built and will do the claims augmentation. The general idea of such a claim provider would be:
- When it gets the identity to augment, understand if it has to do the augmentation or not.
- If it does, get the information from the user. This can be as simple as getting this information from AD with DirectoryServices or maybe do a LDAP query to whatever directory stores the information.
- When it has the information from the user, build the role claims from it and return them to STS.
I have created a sample Visual Studio project with a claim provider that does the above. You can find the code here: https://github.com/jesusfer/FedRolesCP
For the cases explained above, the custom claim provider can be the starting point for a more specific provider. The example can be directly used in an environment such as:
- The SharePoint farm and the users reside in the same forest.
- The ADFS server is configured with Active Directoy as authentication and user backend.
The master branch of the project is compatible with SharePoint 2013. However, there is a tag with a SharePoint 2010 compatible version.
Several of our customers have already implemented successfully a custom claim provider starting from this code to solve problems with alerts, when checking user's permissions and even with SharePoint 2013 Apps authentication. Nearly everywhere where trusted role identities are involved.