Walkthrough: Implementing a Pluggable SSO Provider

By default, Microsoft Office SharePoint Server 2007 provides the Microsoft Single Sign-On (SSO) service for storage and mapping of credentials for use in connecting with third-party or back-end systems. Many companies already have developed an in-house credential storage system or use a solution other than the Microsoft Single Sign-On service. As an alternative to maintaining credential mapping in two places, Office SharePoint Server 2007 provides a mechanism called pluggable SSO. This feature allows you to specify an alternate SSO provider to the standard SSO provider in Office SharePoint Server 2007.

Prerequisites

Before you build the SSO provider, you must set up your environment. This walkthrough assumes you have set up Office SharePoint Server 2007, installed a copy of the AdventureWorks 2000 database from the Microsoft Download Center, and have ensured that the domain name is LITWAREINC. If you are using a different domain name, you must adjust the code examples in this walkthrough.

The domain accounts and groups shown in the following table are assumed to be present.

ExternalPartners

Domain Group

InternalSales

Domain Group

Tom Tompson

Domain User

Jerry Jones

Domain User

InternalAccess

Domain User

ExternalAccess

Domain User

For complete instructions on how to set up the database and necessary user accounts, see the README.txt file provided with the AdventureWorks 2000 database.

Implementing a Single Sign-On Provider

Replacing the default SSO provider in Office SharePoint Server 2007 involves implementing the Microsoft.SharePoint.Portal.SingleSignon.ISsoProvider****interface, installing it into the global assembly cache, and registering the new SSO provider with Office SharePoint Server 2007.

You can register only one SSO provider for Office SharePoint Server 2007. Registering a new SSO provider replaces the default SpsSsoProvider class in Office SharePoint Server 2007. Because only one SSO provider can be in use at a time, it is recommended you stop the Microsoft Single Sign-On service when using a custom SSO provider.

You will need to implement the GetCredentials and GetSsoProviderInfo methods of the ISsoProvider interface to create a minimally functional SSO provider. This walkthrough shows you how to create a simple SSO provider and use it to access data through the Business Data Catalog.

In this walkthrough, our custom SSO provider maps users who are in the InternalSales group to the InternalAccess user account for retrieving product data from the AdventureWorks 2000 database.

Building the Provider

This section shows you how to build and register a simple SSO provider, and describes exception handling for the provider.

Downloads    To download the sample provider, see SharePoint Server 2007: Software Development Kit.

Example

You create an SSO provider assembly in Microsoft Visual Studio 2005 by creating a class library project. Add a reference to the Microsoft.SharePoint.Portal.SingleSignon.dll (found in the %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\12\ISAPI directory) to your project. Implement the ISsoProvider interface, as shown in the following example.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Services;
using Microsoft.SharePoint.Portal.SingleSignon;

namespace SampleSSOProvider
{
    /// <summary>
    /// SimpleSSOProvider
    /// </summary>
    public class SimpleSSOProvider: ISsoProvider
    {
        public Application.ApplicationInfo[] GetApplicationDefinitions()
        {
            //NOTE: Used by SpsSsoProvider, not necessary for SimpleSSOProvider
            throw new NotSupportedException();
        }
        public Application.ApplicationField[] GetApplicationFields(string AppID)
        {
            //NOTE: Used by SpsSsoProvider, not necessary for SimpleSSOProvider
            throw new NotSupportedException();
        }

public Application.ApplicationInfo GetApplicationInfo(string AppID)
        {
            Application.ApplicationInfo applicationInfo = new Application.ApplicationInfo("SimpleSSOProvider", "SimpleSSOProvider", Application.ApplicationType.GroupWindows, "sso@litwareinc.com");

            Application.ApplicationInfo applicationInfo = new Application.ApplicationInfo("SimpleSSOProvider","SimpleSSOProvider",Application.ApplicationType.GroupWindows,"sso@litwareinc.com", (SsoCredentialContents)((Int32)SsoCredentialContents.UserName + (Int32)SsoCredentialContents.Password + (Int32)SsoCredentialContents.WindowsCredentials));
            */

            return applicationInfo;
        }
        public Uri GetCredentialManagementURL(string AppID)
        {
            //NOTE: Used by SpsSsoProvider, not necessary for SimpleSSOProvider
            throw new NotSupportedException();
        }
        public SsoCredentials GetCredentials(string AppID)
        {
            //Note: Used by SpsSsoProvider, necessary for any SimpleSSO Provider. Implementation discussed in detail in the next section of this topic
        }
        public SsoCredentials GetCredentialsUsingTicket(string Ticket, string AppID)
        {
            //NOTE: Used by SpsSsoProvider, necessary for Simple SSO Provider when used by Excel Services.
            //TODO: Implement Ticket management code; currently just return SsoCredentials
            return GetCredentials(AppID);
        }
        public string GetCurrentUser()
        {
            //NOTE: Used by SpsSsoProvider, not necessary for SimpleSSOProvider
            throw new NotSupportedException();
        }

public SsoCredentials GetSensitiveCredentials(string AppID)
{
    //NOTE: Used by SpsSsoProvider, necessary for Simple SSOProvider when used by Excel Services

    //TODO: Implement Sensitive Credential method, for sample just returning basic credentials
    return GetCredentials(AppID);
}
        public SsoProviderInfo GetSsoProviderInfo()
        {
            //TODO: Used by SpsSsoProvider, necessary for any SimpleSSOProvider
        }
        public string GetTicket()
        {
            //NOTE: Used by SpsSsoProvider, necessary for SimpleSSOProvider when used by Excel Services
            //TODO: Implement Ticket management code; currently just return a string
            return "No Ticket Management";
        }
        public void PutIdentityOnRequest(ref System.Web.Services.Protocols.HttpWebClientProtocol request, string AppID)
        {
            //NOTE: Used by SpsSsoProvider, not necessary for SimpleSSOProvider
            throw new NotSupportedException();
        }
        public void PutIdentityOnRequestUsingTicket(ref System.Web.Services.Protocols.HttpWebClientProtocol request, string Ticket, string AppID)
        {
            //NOTE: Used by SpsSsoProvider, not necessary for SimpleSSOProvider
            throw new NotSupportedException();
        }
    }
}

At a minimum, you must implement the GetCredentials and GetSsoProviderInfo methods. The SimpleSSOProvider class that we created returns new credentials based on the current user and the application identifier (AppID) we supplied. We can obtain information about the current user using the CurrentPrincipal property of the thread we are executing on (System.Threading.Thread.CurrentPrincipal). The following code shows the implementation of the GetCredentials method.

public SsoCredentials GetCredentials(string AppID)
{
    //NOTE: Used by SpsSsoProvider, necessary for any SimpleSSOProvider
    System.Diagnostics.Trace.WriteLine("Entering SimpleSSOProvider::GetCredentials");
    System.Diagnostics.Trace.Indent();
    // Retrieve the logged in user's information
    string domain = System.Environment.UserDomainName;
    System.Diagnostics.Trace.WriteLine("User domain is " + domain);
    
    try {
        System.Diagnostics.Trace.WriteLine("Context user:" + System.Threading.Thread.CurrentPrincipal.Identity.Name);
        // Start building an SsoCredentials object to store two values - UserName and Password
        SsoCredentials creds = new SsoCredentials();
        creds.Evidence = new System.Security.SecureString[2];

        switch (AppID){
            case "AdventureWorks":
                System.Diagnostics.Trace.WriteLine("Application is AdventureWorks");

                if (System.Threading.Thread.CurrentPrincipal.IsInRole("InternalSales"))
                {
                    System.Diagnostics.Trace.WriteLine("User is in InternalSales? " + System.Threading.Thread.CurrentPrincipal.IsInRole("InternalSales"));
                    // Provide components for the InternalAccess account token
                    creds.Evidence[0] = MakeSecureString(domain + "\\InternalAccess");
                    creds.Evidence[1] = MakeSecureString("pass@word1");
                }
                else
                {
                    // Provide components for the ExternalAccess account token
                    creds.Evidence[0] = MakeSecureString(domain + "\\ExternalAccess");
                    creds.Evidence[1] = MakeSecureString("pass@word1");
                }
                break;

            default:
                throw new SingleSignonException(SSOReturnCodes.SSO_E_APPLICATION_NOT_FOUND);
        }

        // Put the UserName/Password values into the credential object
        creds.UserName = creds.Evidence[0];
        creds.Password = creds.Evidence[1];

        System.Diagnostics.Trace.Unindent();
        return creds;
    }
    catch(SingleSignonException ex) {
        System.Diagnostics.EventLog.WriteEntry("SimpleSSOProvider", "Caught SSO Exception: " + ex.ToString());
        throw;
    }
    catch(Exception ex) {
        System.Diagnostics.EventLog.WriteEntry("SimpleSSOProvider", "Caught Exception: " + ex.ToString());
        throw new SingleSignonException(SSOReturnCodes.SSO_E_EXCEPTION, ex);
    }
}

The SsoProvider implementation does not require SsoCredentialsContents, but certain other client applications might expect SsoCredentialsContents. In the example provided, Excel Services will attempt to connect to resources with a Windows logon using the UserName and Password that were set. If no value was provided for WindowsCredentials, the UserName and Password would be set on the connection string.

Name Description

None

No evidence provided.

UserName

Set if UserName exists.

Password

Set if Password exists.

Evidence

Set if using extended fields (up to five total, including UserName and Password)

MappedGroup

Set if application definition is a Group definition.

WindowsCredentials

Set if application definition is Windows Authentication.

The GetSsoProviderInfo method simply returns information about the provider, such as the Vendor name and Version, as shown in the following code.

        public SsoProviderInfo GetSsoProviderInfo()
        {
            //NOTE: Used by SpsSsoProvider, necessary for any SimpleSSOProvider
            SsoProviderInfo ssoProvInfo = new SsoProviderInfo();

            ssoProvInfo.AssemblyName = Assembly.GetExecutingAssembly().FullName;
            ssoProvInfo.Vendor = "AdventureWorks";
            ssoProvInfo.Version = "1.0";            

            return ssoProvInfo;
        }

If the SSO provider will be consumed by Excel Services, you must also provide an implementation for the GetCredentialsUsingTicket and GetTicket methods.

The SimpleSsoProvider class that we created shows a very simple example of an SSO provider. A real-life implementation must retrieve credentials from a secure repository and protect any values while they are stored in memory.

The SsoCredentials object returned by GetCredentials uses the SecureString class to store the UserName and Password properties, as well as all Evidence values. SecureString encrypts its data so it cannot be deciphered easily.

Exception Handling

Our SimpleSSOProvider throws an instance of SingleSignonException and uses standard SSOReturnCodes fields if it cannot properly determine the AppID. The following table shows some common SSOReturnCodes fields for several error cases.

Name Description

SSO_E_ACCESSDENIED

Access is denied.

SSO_E_CREDS_NOT_FOUND

Credentials could not be found for the requested user or application.

SSO_E_SSO_NOT_CONFIGURED

The SSO provider service is not configured properly.

SSO_E_APPLICATION_NOT_FOUND

The application definition cannot be found.

SSO_E_EXCEPTION

The SSO provider service threw an exception.

Registering the Provider

To install the SimpleSSOProvider, you must register it in the global assembly cache and then register it with the ProviderAdmin console application (located in the bin directory of the Office SharePoint Server 2007 installation). The ProviderAdmin application replaces the current SSO provider with the one you specify. In a server farm environment, you must register the new SSO provider with each computer in the farm. The following procedures show you how to register the provider and how to remove a custom provider and reinstate the original.

To register the SimpleSSOProvider

  • The ProviderAdmin tool takes the fully qualified assembly name and the name of the class that implements the ISsoProvider interface. To register the SimpleSSOProvider in our example, the ProviderAdmin tool executes the following command.

    Microsoft.SharePoint.Portal.SingleSignon.ProviderAdmin.exe 
    "SampleSSOProvider, Version=1.0.0.0, Culture=neutral, 
    PublicKeyToken=e447e624e7099fd1" 
    "SampleSSOProvider.SimpleSSOProvider"
    

To remove a custom SSO provider and reinstate the original SSO provider

  • To remove a custom SSO provider and reinstate the original SSO provider in Office SharePoint Server 2007, unregister the SSO provider by using the following command.

    Microsoft.SharePoint.Portal.SingleSignon.ProviderAdmin.exe /u
    

Accessing the Single Sign-On Provider

Web Parts or other components that need access to an SSO provider should no longer use the Credentials object. Using the Credentials object retrieves only the default SSO provider included with Office SharePoint Server 2007, even if you have registered a new provider by using the ProviderAdmin tool. To obtain a reference to the currently registered ISsoProvider, use the following procedure.

To obtain a reference to the currently registered SSO provider

  • Use the GetSsoProvider method on the SsoProviderFactory class to obtain a reference to the currently registered ISsoProvider. Your code can use the GetCredentials method on the ISsoProvider interface to obtain application credentials, as follows.

    ISSOProvider issop;
    issop = SsoProviderFactory.GetSsoProvider();
    SsoCredentials ssocred = issop.GetCredentials("AdventureWorks");
    

The SsoCredentials class provides access to credentials by way of the SecureString class. You can use a number of different methods to convert a SecureString instance to a usable format, such as the SecureStringToBSTR method, as shown in the following example.

ISsoProvider provider = SsoProviderFactory.GetSsoProvider();
SsoCredentials creds = provider.GetCredentials("AdventureWorks");
IntPtr pUserName = IntPtr.Zero;
try
{
pUserName = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(creds.UserName);
//NOTE: After this has been converted to a String object, it remains in 
//memory until the garbage collector collects it.
String userName = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(pUserName);
}
finally
{
// Free zero out and free the BSTR pointers.
if (IntPtr.Zero != pUserName)
{
System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(pUserName);
}
}

Using the Single Sign-On Provider from the Business Data Catalog

In addition to the access you have from Web Parts to the default SSO provider, you can also use your custom SSO provider from registered Business Data Catalog applications.

To use your custom SSO provider with the Business Data Catalog

  • To use your custom SSO provider with a database line-of-business (LOB) system, modify the Business Data Catalog schema to add the SsoApplicationId and SsoProviderImplementation properties to your LOBSystemInstance XML tag, as follows.

    <LobSystemInstance Name="AdventureWorks2000(SampleSSO)">
    <Properties>
    <Property Name="AuthenticationMode" Type="System.String">WindowsCredentials</Property>
    <Property Name="SsoApplicationId" Type="System.String">AdventureWorks</Property>
    <Property Name="SsoProviderImplementation" Type="System.String">SampleSSOProvider.SimpleSSOProvider, SampleSSOProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e447e624e7099fd1</Property>
    
    <!—Database connection properties elided -->
    </Properties>
    </LobSystemInstance>
    

Because our provider returns Windows credentials, the AuthenticationMode property is set to receive WindowsCredentials. When Business Data Catalog retrieves credentials from the SSO provider, it will execute a LogonUser() call to set up impersonation prior to attempting to gain access to the database.

In our example, a user is mapped to either the InternalAccess or ExternalAccess accounts for retrieving Product data from an AdventureWorks 2000 database.

For more information on Business Data Catalog schema, including the configuration necessary to implement SSO for Web service LOB systems, see LobSystemInstance in the Business Data Catalog: Metadata Model.

Next Steps

The ability to replace the default Office SharePoint Server 2007 SSO provider allows you to better integrate your SharePoint sites with investments already made in your enterprise. You can make use of pre-existing credential stores developed in-house or supplied as part of a third-party package. Your custom provider can then be accessed from Web Parts or Business Data Catalog objects to take full advantage of the custom SSO provider.