Showing posts with label STS. Show all posts
Showing posts with label STS. Show all posts

Thursday, January 19, 2012

Custom ADFS Login Form for SharePoint 2010 Claims

This week I've been involved in creating a custom login page for SharePoint 2010 to bypass the standard "select a login method" page for multi-mode claims-enabled web-applications. What we wanted was similar to the Claims Login Web Part for SharePoint Server 2010 for Forms-Based Authentication (FBA) by Jeremy Jameson, but for a trusted ADFS 2.0 identity provider instead.


Having a custom login page allows you to stay in your site and avoid the passive STS authN redirect dance back and forth between SP and the ADFS STS for authentication. This requires you to use active mode (WS-Trust) rather than the passive mode used by SharePoint. Note that this active approach won't give you single sign-on, because you won't get the MSISAuth ADFS SSO cookies - it will simply authenticate you first and then give you the SharePoint FedAuth cookie.

The code you need to call ADFS to make it authenticate you, and thus issue a claims token for use with SharePoint, can be found at Using an Active Endpoint to sign into a Web Application by Dominick Baier or Making a web application use an active STS by Koen Willemse. The missing detail not shown in their code is the URL to the ADFS endpoint, which needs to match the chosen client credentials and security mode; when using UserNameWSTrustBinding and sending the username and password in the WCF message secured using SSL (i.e. mixed), the URL should be like "http://adfs.pzl/adfs/services/trust/13/usernamemixed/" including the important ending / slash to avoid "405 method not allowed" error from IIS.

protected void btnLogin_Click(object sender, EventArgs e)
{
    // authenticate with WS-Trust endpoint
    var factory = new WSTrustChannelFactory(
        new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
        new EndpointAddress(
"https://adfs.puzzlepart.com/adfs/services/trust/13/usernamemixed/"));
 
 
    factory.Credentials.UserName.UserName = txtUserName.Text;
    factory.Credentials.UserName.Password = txtPassword.Text;
 
    var channel = factory.CreateChannel();
 
    var rst = new RequestSecurityToken
    {
        RequestType = RequestTypes.Issue,
        AppliesTo = new EndpointAddress("urn:sharepoint:puzzlepart"),
        KeyType = KeyTypes.Bearer
    };
 
    var genericToken = channel.Issue(rst) as GenericXmlSecurityToken;
 
 
    // parse token
    var handlers = FederatedAuthentication.ServiceConfiguration
.SecurityTokenHandlers;
    var token = handlers.ReadToken(new XmlTextReader(
       new StringReader(genericToken.TokenXml.OuterXml)));
         
    SPSecurity.RunWithElevatedPrivileges(delegate(){
        SPFederationAuthenticationModule.Current
.SetPrincipalAndWriteSessionToken(token);
    });
    Response.Redirect("~/pages/default.aspx");
}

After getting authenticated against the ADFS STS when calling Issue on the WS-Trust channel with a RequestSecurityToken, the returned SAML security token must first be parsed and then written to a FedAuth cookie created from the SAML token. The SharePoint FAM wrapper will both set the thread principal and write the cookie, making the user a logged in SharePoint user.

Note how the writing of the cookie is wrapped with RunWithElevatedPrivileges to ensure that  it runs as the app-pool identity and not as the impersonated SharePoint user. This is to avoid the dreaded "CryptographicException: The system cannot find the file specified" error in the internal ProtectedDataCookieTransform call.

When calling ValidateToken you will run into the SecurityTokenException: Issuer of the Token is not a Trusted Issuer error if your STS is not trusted by SharePoint. SharePoint is configured to use its own SPPassiveIssuerNameRegistry and that will either validate against the built-in SharePoint STS or the set of trusted STS token issuers. See how to add your STS certificate(s) at SharePoint 2010 Claims-Based Auth with ADFS v2 by Eric Kraus. The trusted providers are apparently only used if the login page is located under the /_trust/ folder that is part of the above "redirect dance" when authenticating against a trusted identity provider.

Over at stack overflow, Matt Whetton had run into the same exception as us and solved it by replacing the passive <issuerNameRegistry> with the Windows Identity Foundation (WIF) ConfigurationBasedIssuerNameRegistry instead. The Configuration of WIF post shows how to add the set of certificate names and thumbprints to the <trustedIssuers> list. This is how your web.config list of trusted STS token issuers may look like:

<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, 
Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"> 
  <trustedIssuers> 
    <add thumbprint="1337133713371337" name="CN=adfs-puzzlepart" /> 
    <add thumbprint="0000000000000000" name="CN=SharePoint Security Token Service" />
  </trustedIssuers> 
</issuerNameRegistry> 

Remember to add the SharePoint self-issued certificates such as the "SharePoint Security Token Service" certificate to the list of trusted issuers, in addition to your own STS.

I strongly recommend putting your custom ADFS login page under /_trust/ to avoid having to change the SharePoint web.config files. We chose this approach to minimize risks.

Note that Fiddler seems to break the ADFS login process, at least when decrypting SSL. The customized rule provided in Fiddler and Channel Binding Tokens Revisited by Eric Lawrence alleviates this problem. Just make sure you click "remember my credentials" when logging in so that Fiddler can get it from the Windows Credential Manager.

Disclaimer: Note that even if things seems to work as normal after this configuration change, there is no guarantee that nothing was affected in the huge platform that SharePoint 2010 is. The combination of SP2010 claims and WIF is not very well documented, and any changes beyond supported configuration involves risks. Do not apply these changes if you are not sure that it will not break any of your SharePoint solutions or services.

Tuesday, January 06, 2009

RIP: Claim.Right killed by SAML

In several of my WCF projects we've used System.IdentityModel claims to express the permissions of a user to do authorization. A claim used to be a triplet: a resource type, the specific resource and an associated right. The right is typically just a fact about the resource such as "Rights.Identity", but it could also be an ACL-style permission for the resource instance.

In the new Microsoft.IdentityModel provided by Geneva (prerelease), the Claim.Right property is gone. At the same time, Geneva "simplifies" the use of claims to adhere to the classic .NET permission principal authorization model. To me this seems like a step back to Boolean role-based security: bool IsInRole(resource) / bool PermissionDemand(resource); losing some of the benefits that a richer claims-based security model promised through more expressive claimsets. Without the 'right' aspect of a claim, it is hard to express resource instance claims using Geneva claimsets.

Alas, SAML attributes only supports the "PossessProperty" right, so I guess that Geneva supporting SAML 1.1 and 2.0 is what killed Claim.Right.

Monday, November 03, 2008

I&AM: Understanding Geneva

At PDC last week, the "Geneva" platform for federated claims-based identity and access management (I&AM) was announced. Here are some useful resources to get started:
Don't let the federation part of this stop you from looking at Geneva, it is utilizing claims for access management and access control you should focus on initially, externalizing it to the identity metasystem. Let the support for distributed I&AM across the extended enterprise be a nice feature that you get for free.

Tuesday, November 06, 2007

WCF: Caching Claims using System.Web.Caching

We use a custom legacy STS (Security Token Service) that assigns a ticket to our consumer applications, that they again pass as to our WCF services for authentication. The passed SAML token contains only the ticket, so we must then use the validated ticket to generate the System.IdentityModel claims used for authorization.

As the services are stateless, each WCF operation must recreate the AuthorizationContext and thus generate the claim sets again. When you compose a set of services into a business process, the claim sets will get generated over and over again. To avoid this, and get better performance, I needed to cache the claim sets by STS ticket for a limited time.

Many might not know this, but the System.Web.Caching.Cache can be used even in systems that are not an ASP.NET application or even hosted by IIS/ASP.NET. You will still get all the goodies, such as cache expiration, dependencies, throwing stuff out of the cache in case of low memory, etc.

We now cache the claim sets in a singleton using a generic <T> getter and setter with ticket-based keys like this:

public static class EApprovalCache
{
private static System.Web.Caching.Cache _cache = HttpRuntime.Cache;

public static Cache Cache
{
get { return _cache; }
}

public static object Add<T>(string ticket, T value)
{
string key = typeof(T).Name +":" + ticket;
return _cache.Add(key, value, null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
}

public static T Get<T>(string ticket)
{
string key = typeof(T).Name +":" + ticket;
return (T)_cache[key];
}
}

The claim set is retrieved and added to the cache like this:

_authorizationContext = EApprovalCache.Get<EApprovalAuthorizationContext> (context.Ticket);
if (_authorizationContext == null)
{
_authorizationContext = new EApprovalAuthorizationContext(session);
EApprovalCache.Add<EApprovalAuthorizationContext> (context.Ticket, _authorizationContext);
}

The claims are cached for max one hour; but if the user logs out and in again, then the user will have gotten another ticket and the claim set would be generated from scratch and not read from the cache.

Wednesday, May 30, 2007

WCF: Security Architecture and Claims

At my WCF talk at NNUG last night, I didn't have the time to go into the security architecture of WCF in general or into identity model details such as STS, tokens, policies and claims.

To get an overview of these aspects, I recommend reading "The holy grail of Enterprise SOA security" by Matias Woloski.

To learn more about how to actually build an authorization system based on claims, you should read "Building a Claims-Based Security Model in WCF" part 1 and part 2 by Michele Leroux Bustamente.

[UPDATE] Read the Digital Identity for .NET Applications whitepaper by David Chappell to get a good overview of the involved technology.