Authentication in SharePoint 2007
In SharePoint 2007 it was possible to enable forms based authentication next to windows authentication only by extending a web application and basically running two separate applications on the same content database.
side from the problems with forms based authentication in SharePoint 2007, there were also some usability issues.
- The two sites are accessed by a different url. For internal users the url could be https://intranet, for external users https://intranet.company.com.
This is especially a problem when sending links to document or pages. The links point to a different site depending on if an internal or external user copied the link. This lead to a lot of confusion and unexpected errors - Email alerts sent by SharePoint itself can contain the wrong urls
- Smartparts and some other customisations need to be deployed twice
- Changes to the web.config need to be duplicated
Authentication in SharePoint 2010
SharePoint 2010 comes with a nice new feature that aims to solve this problem: Mixed Authentication. It allows for the configuration of multiple authentication providers (Windows authentication, forms authentication, trusted Identity providers) together using the same url, without having to extend the web application. Both external and internal users would access the web site on https://intranet.company.com for example.
By default the user has to choose the authentication method when upon logging in.
While this is very nice, and a great improvement over the previous version, the downside is that there is no more transparent authentication in an intranet environment.
With the correct browser settings is it possible to log on automatically when using windows authentication.
In Internet Explorer it can be configured in the security settings of the Local Intranet zone. These settings can also be pushed through group policies.
If the intranet is configured correctly, or “detected automatically”, all login attempts will transparently use the windows identity.
Each time a user tries to access the intranet, he is greeted by the “user friendly” choice above. Each time he tries to open a document stored on the intranet, he gets the same login popup.
In an intranet environment, this is simply unacceptable.
The solution for SharePoint 2010
Looking to improve on this situation we found a great blog post by Bryan Porter. By using a custom login page and custom PowerShell snap-in he was able to automatically choose the authentication provider based on the IP address of the user logging in.
The solution consists of two parts
- A custom PowerShell snap-in that is used to manage the mappings between IP addresses and authentication providers. The mapping is stored in the Hierarchical Object Store, on the level of the Web Application.
- A custom sign-in page. When the custom sign-in page is loaded it will first check the IP address of the user. Then it will check if the address is mapped to an authentication provider. If it is mapped, the user will be redirected to the sign-in page of that provider. In other words, if the mapping is found the “Select the credentials you want to use to logon to the SharePoint site” step of the sign in process is automated.
In order to use Bryan’s solution we added some features:
- Wild card mapping. Authentication providers can now be mapped to wildcard IP range, for example 192.168.0.*
- IPv6 support.
- Fix the redirection to make “Sign in as a different user” work correctly
After installing the web application can be configured to automatically use Windows Authentication for a certain range of IP’s, and forms authentication for the others.
Update: The project is now available on codeplex: http://spautomaticsignin.codeplex.com/
We are currently using this solution for several customers.
Deployment
All commands are executed in the SharePoint 2010 Management Shell
The custom login page is deployed to the SharePoint Root: “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\IDENTITYMODEL\LOGIN”
The assembly containing the code-behind of the login page and the powershell cmdlet code are deployed to the GAC
-
Add the solution to the solution store
stsadm -o addsolution -filename OrbitOne.SharePoint.Claims.SignIn.wsp
-
Deploy the solution to all web applications
stsadm -o deploysolution -name OrbitOne.SharePoint.Claims.SignIn.wsp -immediate -allowgacdeployment
stsadm -o execadmsvcjobs
-
Register the assembly
C:\WINDOWS\Microsoft.NET\Framework64\v2.0.50727\installutil /assemblyname "OrbitOne.SharePoint.Claims.SignIn, Culture=neutral, Version=1.0.0.0, PublicKeyToken=3c7a593397c60142"
Deployment of an updated version
-
Retract and delete the existing solution
stsadm -o retractsolution -name OrbitOne.SharePoint.Claims.SignIn.wsp -immediate
stsadm -o execadmsvcjobs
stsadm -o deletesolution -name OrbitOne.SharePoint.Claims.SignIn.wsp
-
Deploy the new solution
stsadm -o addsolution -filename OrbitOne.SharePoint.Claims.SignIn.wsp
stsadm -o deploysolution -name OrbitOne.SharePoint.Claims.SignIn.wsp -immediate -allowgacdeployment
stsadm -o execadmsvcjobs
-
Configuration
In the SharePoint 2010 Management Console:
load the snap-in
Add-PSSnapin ClaimsSignInAdmin
Create a configuration object. Note: the url is the url of your web application
$config = Get-SPSignInConfiguration –webapplication “http://www.website.com”
Mappings can be managed with the following commands
-
View all configured mappings
$config.ProviderMappings
Configure the custom sign-in page
Configuration of a custom sign-in page is now a fully supported feature. In SharePoint 2007 it was possible to do this in the web.config, in Sharepoint 2010 it is a settings in the Central Administration.
Application Management -> Manage Web Applications
Select one - > Authentication providers –> select the Zone (usually Default)
Is there a way to do this from Powershell? It would make the deployment a lot faster.
Code
The IP address to Authentication provider mappings are stored in the Hierarchal Object Store associated with a web application. To create a custom Persisted Object for SharePoint all that is needed is to inherit from SPPersistedObject and mark the fields to persist with the “Persisted” attribute.
public class SignInConfiguration : SPPersistedObject
{
[Persisted()]
private Dictionary<string, string> m_providerMappings = new Dictionary<string, string>();
public SignInConfiguration(): base()
{ }
public SignInConfiguration(string name, SPPersistedObject parent): base(name, parent)
{ }
public SignInConfiguration(string name, SPPersistedObject parent, Guid id): base(name, parent, id)
{ }
public Dictionary<string, string> ProviderMappings
{
get { return m_providerMappings; }
}
}
To add this object to a web application or to get an existing one from a web application a custom PowerShell cmdlet is used
[Cmdlet("Get", "SPSignInConfiguration", DefaultParameterSetName = "DefaultSet")]
public class SPCmdletGetSignInConfigObject : SPCmdlet
{
private SPWebApplicationPipeBind m_webAppPipeBind;
protected override void InternalProcessRecord()
{
SPWebApplication webApp = m_webAppPipeBind.Read();
SignInConfiguration sc = webApp.GetChild<SignInConfiguration>("SignInConfig");
if (sc == null)
{
sc = new SignInConfiguration("SignInConfig", webApp);
}
sc.Update();
base.WriteObject(sc);
}
[ValidateNotNull]
[Parameter(Mandatory=true, ValueFromPipeline=true, Position=0)]
[Alias(new string[] { "WebApplication", "WebApp"})]
public SPWebApplicationPipeBind Identity
{
get { return m_webAppPipeBind; }
set { m_webAppPipeBind = value; }
}
}
A SignInConfiguration object is returned for the specified web application. If the configuration does not exist yet a new one will be created. This gives us a reference to the SingInConfig object in the Powershell environment. Any changes are persisted after calling the Update() method on the object.
Finally, in the code behind of the login page the mappings are retrieved, the IP address of the request is checked against the mappings, and if an authentication provider is found the user is redirected to the provider’s sign-in page.
protected override void OnLoad(EventArgs e)
{
if (SPContext.Current == null) return;
if (SPContext.Current.Site == null) return;
if (SPContext.Current.Site.WebApplication == null) return;
SPWebApplication app = SPContext.Current.Site.WebApplication;
SignInConfiguration config = app.GetChild<SignInConfiguration>("SignInConfig");
SPAlternateUrl u = app.AlternateUrls[Request.Url];
SPUrlZone zone = u.UrlZone;
string components = Request.Url.GetComponents(UriComponents.Query, UriFormat.SafeUnescaped);
SPIisSettings settings = app.IisSettings[zone];
string ip = IpNetworking.GetIP4Address();
ip = Regex.Replace(ip, @"^(?<Prefix>(\d{1,3}\.){3})\d{1,3}$", "${Prefix}*");
if (config != null && config.ProviderMappings.ContainsKey(ip))
{
string targetProvider = config.ProviderMappings[ip];
foreach (SPAuthenticationProvider provider in settings.ClaimsAuthenticationProviders)
{
if (string.Compare(provider.DisplayName, targetProvider, true, System.Globalization.CultureInfo.CurrentUICulture) == 0
|| string.Compare(provider.ClaimProviderName, targetProvider, true, System.Globalization.CultureInfo.CurrentUICulture) == 0)
{
string url = provider.AuthenticationRedirectionUrl.ToString();
if (provider is SPWindowsAuthenticationProvider)
{
components = EnsureReturnUrl(components);
}
SPUtility.Redirect(url, SPRedirectFlags.Default, this.Context, components);
}
}
}
else
{
SPUtility.Redirect("/_forms/default.aspx", SPRedirectFlags.Default, this.Context, components);
}
base.OnLoad(e);
}