« Troubleshooting ASP.NET authentication, authorization, and security issues | Main | Tough morning for competitive ideas… OurPictures and BlogJet »

ASP.NET Forms Authentication with SSL/https

So you read the available documentation an put together the basic Forms authentication framework for an ASP.NET application (for which there’s a good article by Abel Banda on O’Reilly) and you say to yourself, boy it really sucks that these passwords are being sent over the internet unencrypted; which leads to the next logical step of using SSL for the login.aspx page.

The steps to follow are these:

1.      Add a server certificate to the web site root folder (Internet Information ServicesàDefault Web SiteàPropertiesàDirectory SecurityàServer Certificate…)

2.      Create folder in your web app for which you will require SSL access. Call it “ssl”.

3.      Move your login.aspx file to ssl/login.aspx.

4.      Make sure the NTFS security settings on the web application folder includes the anonymous IIS user account (IUSR_machine typically).

5.      Disable Integrated Windows authentication and make sure Anonymous access is enabled for the web app (Internet Information Servicesàweb app folderàPropertiesàDirectory SecurityàAnonymous access and authentication controlàEdit…)

6.      Require SSL for your ssl folder (Internet Information Servicesàssl folderàPropertiesàDirectory SecurityàServer Certificate…àcheck the “Require Secure Channel” box.)

7.      Modify the loginURL attribute of the Forms element in your web.config file to reference the new login.aspx location with an absolute url beginning with “https://”.

8.      Test your application.

At this point if you’re like me, a few things will begin to annoy you. First you’ll be reminded that you can’t debug under Visual Studio 2003 without Integrated Windows authentication. That’s okay. Leave it off for now. Just open a browser and manually try to hit your test page.

Again, if you’re like me, you should be getting a 401.2 error attempting to get the login.aspx page.

You might even try directly accessing the login page from the browser with https – no good. And you might try changing the loginUrl back to a relative url (“ssl/login.aspx”) and requesting your test page with https. Wow! That works!?! Hmm… You might also test what happens if you change the “deny users=”?”” to “deny users=”bob””. Again it works accessing each page with https, but you’ve given up forcing the redirect to the login page. If you actually tried any of these things, put things back the way they were and move on to the solution.

Add this in front of your existing <system.web> element in the web.config file:

<location path="ssl/login.aspx">

     <system.web>

         <authorization>

             <allow users="?" />

         </authorization>

     </system.web>

 </location>

 

This block tells the authorization logic to explicitly allow the anonymous user to access the ssl/login.aspx page and overrides the blanket deny element that comes later. There seems to be some special case code that handles relative login URLs but fails on absolute login URLs.

Change your loginUrl attribute back to an absolute “https://”  url and the authentication should work.

One annoyance remains: The ReturnUrl passed to the login page doesn’t include the request scheme (the “http://” part) so you end up stuck in SSL after you login.

So I tried taking charge of the redirect URL with the following code. Thanks to Scott Hanselman for the xml timeout hack, and yes, it needs better packaging for production:

System.Xml.XmlDocument x =new System.Xml.XmlDocument();

x.Load(Request.PhysicalApplicationPath + "web.config");

System.Xml.XmlNode node = x.SelectSingleNode("/configuration/system.web/authentication/forms");

int timeout = int.Parse(node.Attributes["timeout"].Value, System.Globalization.CultureInfo.InvariantCulture.NumberFormat);

 

string userData = "Place application specific data for this user here.";

 

FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(

      1,

      UserName.Value,

      System.DateTime.Now,

      System.DateTime.Now.AddMinutes(timeout), // Should be timout from web.config

      PersistCookie.Checked,

      userData,

      FormsAuthentication.FormsCookiePath);

 

string encTicket = FormsAuthentication.Encrypt(ticket);

 

Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));

 

string url = FormsAuthentication.GetRedirectUrl(UserName.Value, PersistCookie.Checked);

 

Uri uri = Request.Url;

 

url = uri.Scheme.Replace("https", "http") + "://" + uri.Host + url;

 

Response.Redirect(url, true);

 

That does the trick but generates a very annoying Security Alert dialog (“You are about to be redirected to a connection that is not secure.”).

To avoid this warning which is intended to remind naïve users that they are being taken away from the protection of https by the never-to-be-trusted server you must arrange for the client to request the shift back to non-SSL traffic.

Instead of the “Response.Redirect(url, true)” at the end of the previous block of code, replace it with:

(FindControl("RedirectUrl") as System.Web.UI.HtmlControls.HtmlInputHidden).Value = url;

 

And merge the following bit of code into the login.aspx file:

            <script language="javascript">

            <!--

            function DoOnLoad() {

                  if (Form1.RedirectUrl.value)

                        window.navigate(Form1.RedirectUrl.value);

            }

            -->

            </script>

      </HEAD>

      <body onload="DoOnLoad()">

            <form id="Form1" method="post" runat="server">

                  <input type=hidden id="RedirectUrl" name="RedirectUrl" runat="server" />

 

That should do it. Nothing could be simpler…. J

The following notes are from Jason Loader,

2005-04-08

Hi Tone,

You know, you're the only person whose posted a solution to this problem I could find. Anyway, I was getting a windows login prompt/401.2 error (I was using Anonymous/Integrated Authentication on IIS), which was odd, as my application uses role-based security, but I'd already given anonymous/all users access to the login page via a location element. I tried what you suggested, but it still didn't work (it did get rid of the windows login prompt though), I couldn't find an explicit solution to the problem, but it would appear that the answer is actually in the Duwamish 7.0 sample.

You need to give the login page subdirectory it's own web config file that gives access to it for all users (that's all that needs to be in there) if you want to hard code the absolute url in the forms loginurl e.g.

In the application web.config file, you'd put -

<forms loginUrl="https://[your server]/[your application directory]/Secure/login.aspx" name="AuthCookie" timeout="30" slidingExpiration="true" path="/" protection="All">

</forms>

In the Secure subdirectory web.config file, you'd put -

<system.web>

  <authorization>

    <allow users="*" />

  </authorization>

</system.web>

then remove the location section for the login page from the main app config file (the bit below) -

<location path="Secure/Logon.aspx">

  <system.web>

    <authorization>

      <allow users="?"/>

    </authorization>

  </system.web>

</location>

From what I can tell, it's probably something to do with ASP.NET not picking up the authentication from the config file for the sub directories correctly when using absolute urls, as it works when you use relative urls and SSL for the whole site.

However, you still had to change the config file if you moved the directories, changed the server name etc. With a bit of playing about, I found that you can force Forms Authentication to use SSL without hard-coding HTTPS in the loginURL by using the following code in globals.aspx -

Sub Application_EndRequest(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.EndRequest

Dim responseURI As System.Uri

Dim responseURIBuilder As System.UriBuilder

Dim pageName As String

' check to see if we're being redirected to the login page

If Not Response.RedirectLocation Is Nothing AndAlso Response.RedirectLocation.Length > 0 Then

' load the redirect into the uri

responseURI = New System.Uri(Request.Url,

Response.RedirectLocation)

pageName = GetPageFromURL(responseURI.AbsolutePath).ToLower()

If pageName = "login.aspx" AndAlso responseURI.Scheme <> "https"

Then

' force it to be HTTPS

responseURIBuilder = New System.UriBuilder(responseURI)

responseURIBuilder.Scheme = "https"

' remove the port number from the url and write it back

Response.RedirectLocation = Replace(responseURIBuilder.ToString(), ":" & responseURIBuilder.Port & "/",

"/")

ElseIf pageName <> "login.aspx" AndAlso responseURI.Scheme = "https" Then

' force it to be HTTP

responseURIBuilder = New System.UriBuilder(responseURI)

responseURIBuilder.Scheme = "http"

' remove the port number from the url and write it back

Response.RedirectLocation = Replace(responseURIBuilder.ToString(), ":" & responseURIBuilder.Port & "/",

"/")

End If

End If

End Sub

GetPageFromURL is a function which gets the page from a URL string, there's plenty of examples out there if you need to do the same.

What Duwamish does is automatically prefix absolute urls in the aspx files so you can turn SSL on and off through web.config, but I prefer to do that in the vb code so I can use relative links. One thing I haven't done is check that the login page is always being requested over secure sockets and if not force it to be, but you can get round that through IIS configuration (just in case you don't configure IIS and someone tries to access the login page directly). The next step is to make this configurable, like Duwamish is, and possibly picking up the HTTP/HTTPS ports if you've changed them (I've only tried it on default ports with IE).

Cheers,

Jason Loader

Software Developer, GVAS

Wealth Management Software PLC

E-mail: jason.loader@wms-plc.com

 

2005-04-08

one thing to note is that the duwamish sample doesn't actually work by using an absolute url in the loginURL, it uses a relative one and then absolute urls for it's links on the pages and redirects in the code. I am suprised about the Forms Authentication not behaving correctly though, as I thought they would just do what I'm doing and simply re-write the response.redirectlocation (although that would probably cause problems as it redirects using relative paths, which may not work if you'd redirected to a different site. maybe that's something to play about with later). i also found that if you're re-writing the url, you don't even need a config file in the subdirectory.

when i get round to rolling this into our application, if there's any changes or further issues i'll give you a shout.

Cheers

Jason

 

2005-04-11

there's a bit of a glitch in the solution i sent you. if you try to open the project in vs.net you sometimes get an error to do with redirect from a non-secure to a secure connection, which then stops the project from opening. the reason is that when vs.net opens the project, it tries to get a file called get_aspx_ver.aspx, which it looks like it dynamically creates to check if ASP.NET is running on the server. The reason it fails is due to forms authentication and the code in globals.asax. As we haven't been authenticated yet, we're redirected to the login page, but our code forces it to be via HTTPS, which VS.NET can't handle. There are several solutions to this -

1. Remove the forms authentication from web.config before opening the project and then put it back in.

2. Delete the contents of the projects bin directory before opening.

3. Put the following in web.config to allow access to the temporary file -

<location path="get_aspx_ver.aspx">

<system.web>

<authorization>

<allow users="?"/>

</authorization>

</system.web>

</location>

4. Add a condition into our code to always use HTTP for the above page.

Sub Application_EndRequest(ByVal sender As Object, ByVal e As

System.EventArgs) Handles MyBase.EndRequest

' check to see if we're being redirected to the login page

Dim responseURI As System.Uri

Dim responseURIBuilder As System.UriBuilder

Dim pageName As String

If Not Response.RedirectLocation Is Nothing AndAlso Response.RedirectLocation.Length > 0 Then

' load the redirect into the uri

responseURI = New System.Uri(Request.Url,

Response.RedirectLocation)

pageName =

RetrieveRequestPage(responseURI.AbsolutePath).ToLower()

If RetrieveRequestPage(Request.Url.AbsolutePath).ToLower() <> "get_aspx_ver.aspx" _

AndAlso pageName = "login.aspx" _

AndAlso responseURI.Scheme <> "https" Then

' force it to be HTTPS

responseURIBuilder = New System.UriBuilder(responseURI)

responseURIBuilder.Scheme = "https"

' remove the port number from the url

Response.RedirectLocation = Replace(responseURIBuilder.ToString(), ":" & responseURIBuilder.Port & "/",

"/")

ElseIf pageName <> "login.aspx" AndAlso responseURI.Scheme = "https" Then

' force it to be HTTP

responseURIBuilder = New System.UriBuilder(responseURI)

responseURIBuilder.Scheme = "http"

' remove the port number from the url

Response.RedirectLocation = Replace(responseURIBuilder.ToString(), ":" & responseURIBuilder.Port & "/",

"/")

End If

End If

End Sub

I'd go with 4, but you'll have to do one of the others first to be able to open the project to make the change (and you may need to restart the web service too)...

Cheers

Jason

Comments

I have a problem: Followed your example until step 8, but I didn't got the 401.2 error.
Instead the browser gets stuck and nothing happens. After a long period of time the browser returns that server is down. I can't understand why, another detail is that IE tells me that it will go over a secure connection, but that's all it happens.

Damn, I found the problem, after 3 hours of googling and swearing...My IIS was set up to run on 8090 port and doing https://localhost:8090/app/ssl/login.aspx didn't worked.
Does 8090 port in the tail overrides 443 port or something?

Anyway...good job, altough was the only tutorial I could find.
Greets.

Nice tutorial.

Now, how would I handle adding SSL when I have a single default.aspx page that loads different controls based on querystring values, and where a number of those controls would have to operate under a certificate. Is it possible to switch SSL on for certain controls and off for others?

Would simply having the controls in a SSL directory suffice?

Thanks

Hi Tony,

If you start with a non-SSL request to default.aspx, there's no way to switch to SSL without having the client initiate a new request using SSL. One way or another, you're going to have to have the client make a new request using https:// instead of http://.

Typically that's done by returning a redirect to a different page, but there's nothing to prevent you from redirecting back to yourself with an https:// scheme instead of the initial http://.

- Tone

Hi,

I noticed lots of talk about switching protocols. I agree that it's awkward, but read this and all will become clear.

http://www.codeproject.com/aspnet/WebPageSecurity_v2.asp

I have included this project "as is" in several of my solutions and it works like a charm.

Hopefully you find it equally useful :)

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)