Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET: Changing from Windows Authentication to Forms Authentication against AD

I have a functioning .NET MVC application that uses Windows Authentication. Because of our use of shared computers, Windows Authentication will not work for us; we need to switch to Forms Authentication, but we still want to authenticate against Active Directory. I've read various tutorials on this subject, but none of these seem to work and none show how to convert an existing Windows Authentication application to one that uses Forms Authentication against AD. What do I need to do to make this transistion?

Here is the web.config for my application:

<configuration>
  <configSections>
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <section name="Wellness.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    </sectionGroup>
  </configSections>
  <connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-Wellness-20130715090235;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-Wellness-20130715090235.mdf" providerName="System.Data.SqlClient" />
    <add name="tt" connectionString="Data Source=(localdb)\v11.0; Initial Catalog=tt-20130805140115; Integrated Security=True; MultipleActiveResultSets=True; AttachDbFilename=|DataDirectory|tt-20130805140115.mdf" providerName="System.Data.SqlClient" />
    <add name="WellnessEntities" connectionString="metadata=res://*/Models.WellnessModel.csdl|res://*/Models.WellnessModel.ssdl|res://*/Models.WellnessModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=MSSQL;initial catalog=Wellness;persist security info=True;user id=Wellness_User;password=xGopher2008;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
  </connectionStrings>
  <appSettings>
    <add key="webpages:Version" value="2.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="PreserveLoginUrl" value="true" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  </appSettings>

  <system.web>
    <httpRuntime maxRequestLength="10240"/>
    <customErrors mode="Off"></customErrors>

    <compilation debug="true" targetFramework="4.5">
      <assemblies>
        <add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      </assemblies>
    </compilation>
    <authentication mode="Windows" />
    <authorization>
      <allow roles="b-hive\AllStaff"/>
      <deny users="*"/>

    </authorization>
    <pages controlRenderingCompatibilityVersion="4.0">
      <namespaces>
        <add namespace="System.Web.Helpers" />
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Web.WebPages" />
      </namespaces>
    </pages>
    <profile defaultProvider="DefaultProfileProvider">
      <providers>
        <add name="DefaultProfileProvider" type="System.Web.Providers.DefaultProfileProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" applicationName="/" />
      </providers>
    </profile>
    <membership defaultProvider="DefaultMembershipProvider">
      <providers>
        <add name="DefaultMembershipProvider" type="System.Web.Providers.DefaultMembershipProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
      </providers>
    </membership>
    <roleManager defaultProvider="DefaultRoleProvider">
      <providers>
        <add name="DefaultRoleProvider" type="System.Web.Providers.DefaultRoleProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" applicationName="/" />
      </providers>
    </roleManager>
    <sessionState mode="InProc" customProvider="DefaultSessionProvider">
      <providers>
        <add name="DefaultSessionProvider" type="System.Web.Providers.DefaultSessionStateProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" />
      </providers>
    </sessionState>
  </system.web>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <handlers>
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="v11.0" />
      </parameters>
    </defaultConnectionFactory>
  </entityFramework>
  <applicationSettings>
    <Wellness.Properties.Settings>
      <setting name="Setting" serializeAs="String">
        <value />
      </setting>
    </Wellness.Properties.Settings>
  </applicationSettings>

</configuration>
like image 950
James Harpe Avatar asked Sep 19 '13 19:09

James Harpe


1 Answers

You have two options. One, use a provider and capitalize on the built-in framework infrastructure. Two, use directory services and code everything yourself. The latter will give you complete control and flexibility. The former will give you ease-of-implementation.

Using a provider:

(1) Specify forms-auth in your web.config:

<authentication mode="Forms">
    <forms name=".ADAuthCookie" loginUrl="~/Login.aspx" defaultUrl="~/Default.aspx" timeout="05"/>
</authentication>

(2) Add an LDAP connection string:

<connectionStrings>
    <add name="ADConnectionString" connectionString="LDAP://fqdn.co/DC=fqdn,DC=co"/>
</connectionStrings>

(3) Add a membership provider (provide the connection string name as the one defined above):

<membership defaultProvider="MyADMembershipProvider">
    <providers>
        <add name="MyADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, 
        Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="ADConnectionString" attributeMapUsername="sAMAccountName" />
    </providers>
</membership>

For the provider, you will have to find out the token and version based on your ASP.Net version.

(4) Create a login page (Login.aspx as specified in forms-auth loginurl) and use the asp.net's login control:

<asp:Login ID="LoginUser" runat="server" ....

(5) You are good to go.

Doing it yourself:

(1) Specify forms-auth in your web.config:

<authentication mode="Forms">
    <forms name=".MyAuth" loginUrl="~/Logon.aspx" defaultUrl="~/Default.aspx" timeout="05">   
    </forms>
</authentication>

(2) Obtain reference to System.DirectoryServices and System.DirectoryServices.AccountManagement

(3) Create a method for authentication in your logic layer (something on the lines of):

<DirectoryServicesPermission(Security.Permissions.SecurityAction.LinkDemand, Unrestricted:=True)> _
Public Shared Function Authenticate(ByVal domainName As String, ByVal userAlias As String, ByVal userPassword As String) As Boolean
    Try
        Dim context As PrincipalContext = New PrincipalContext(ContextType.Domain, domainName)
        If context.ValidateCredentials(userAlias, userPassword, ContextOptions.Negotiate) Then
            Return True
        Else
            Return False
        End If
    Catch ex As Exception
        Throw
    End Try
End Function

The above code snippet is in VB as I am not too confident on C#, but you get the idea.

(4) Create a login page and call this method on login from your code-behind:

isAuthenticated = LogicLayer.Authenticate(domainName, userName, userPassword)

(5) If successful, i.e. isAuthenticated returns true, then set the forms-auth cookie:

FormsAuthentication.SetAuthCookie(userName, isRememberMe)

(6) You are good to go.

Note:

Please note that using forms-auth will open you to security implications as the credentials will travel in text form over the wire. You will have to take care of appropriate security measures yourself. SSL will help you in an easiest way.

Also note that, you might have to take care of a few more things like <identity impersonate="true" /> to enable access from users' account instead of app-pool identity. You also, will need to set anonymous auth in IIS.

Edit:

I did not notice earlier that yours is an MVC app. Some of the points above are WebForms specific (like controls and code-behinds). So, please ignore that. Otherwise, I hope you got the idea behind it.

like image 103
Abhitalks Avatar answered Nov 15 '22 03:11

Abhitalks