Convert ASP.NET WebForms with Windows Authentication to use AAD

Premier Developer

Developer

App Dev Manager Chris Westbrook tackles the topic of moving legacy ASP.NET Web Forms using integrated authentication to Azure.


Introduction / Goal

In this post we’re going to walk through updating an ASP.NET Web Forms application to use Azure Active Directory (AAD). Yes, you read that right, Web Forms. Why would we cover such an old tech you ask? I work with a lot of enterprise customers that have sizable portfolios of Intranet web sites using Web Forms and Windows Integrated Authentication that they would like to move to Azure PaaS; however, we’ve found that a lot of documentation on these topics doesn’t extend back to Web Forms and instead targets .NET Core and MVC. Although I do encourage my customers to consider a rebuild, that may not be feasible. Therefore, this post walks through some relatively minor tweaks that allow you to switch your site to use AAD for authentication and, if you want, AD group membership for authorization. These changes will enable deployment of those sites to Azure App Services.

To make the change we’re going to follow the below steps:

  1. Start with an ASP.NET Web Forms site and some other pre-requisites
  2. Ensure your site is setup to use SSL.
  3. Get the necessary OWIN NuGet packages.
  4. Add in some startup code to use the OWIN authentication libraries.
  5. Register your application in AAD.
  6. Update your web.config with appropriate OAuth values.
  7. Test.

Pre-requisites

Here are some assumptions I’m making as to the starting point for this article:

  • You have an existing ASP.NET Web Forms application deployed on an on-premise IIS server.
    • Or you’re going to make a new one to test on. (See first step below.)
  • The site is configured to use Windows Authentication.
  • The site uses AD groups for authorization.
  • You already have an Azure Active Directory setup with the users and groups that you need.
    • Typically, enterprises get this when they adopt Office 365, but that’s not the only way. You can also create a new AAD for this.
  • You have identified at least two AD groups to use to test membership checking.
    • Pick one you know you are in and one you know you are not in.
  • You’re using Visual Studio 2017.
    • I’ll be using v15.9.3.
  • You’ve already upgraded to a recent .NET framework.
    • I’ll be using 4.7.2. I’ve also done this with 4.6.2 but I haven’t researched the earliest version you could use.

Step 1: ASP.NET Web Forms Site

For demo purposes I’m going to start with a new site from the Visual Studio template. You may want to do the same to start with. Eventually though you’ll want to reproduce this on your actual site, in which case you’ll skip this step. If you’re not familiar with this process you can see the tutorial at https://docs.microsoft.com/en-us/aspnet/web-forms/overview/getting-started/creating-a-basic-web-forms-page. I named my project “WindowsAuthAppToAADDemo.” Be sure to change the Authentication mechanism to Windows Authentication as that’s the scenario we’re working with.

It will also be helpful to place some code on one of your pages that you can use to test. One possibility is to add some code like below to About.aspx:

<dl> <dt>IsAuthenticated</dt> <dd><%= HttpContext.Current.User.Identity.IsAuthenticated %></dd> <dt>AuthenticationType</dt> <dd><%= HttpContext.Current.User.Identity.AuthenticationType %></dd> <dt>Name</dt> <dd><%= HttpContext.Current.User.Identity.Name %></dd> <dt>Is in "group1"</dt> <dd><%= HttpContext.Current.User.IsInRole("yourgroup1here") %></dd> <dt>Is in "group2"</dt> <dd><%= HttpContext.Current.User.IsInRole("yourgroup2here") %></dd> <dl>

To make sure things are working this is a good time to run your project and navigate to the About page. Make sure you see True for IsAuthenticated, your domain\login for Name, and the appropriate true/false values for your group membership.

Step 2: SSL

When you’re using AAD you will be using OAuth. This process requires the use of SSL to be secure. Therefore, even in your local development environment, you’re going to need to use SSL. I’m using IIS Express, so I just used the properties pane of the web project to set SSL Enabled to true. Also make a note of your new URL with the auto-assigned port. You can find this in the web project Properties page, Web tab, Project Url field. Make sure to run and verify the site now opens using SSL.

Step 3: OWIN Packages via NuGet

Even though OWIN probably didn’t exist back when you wrote your Web Forms app, you can still use it and it will significantly simplify the process.

  1. In the Visual Studio menu, select Tools > NuGet Package Manager > Package Manager Console.
  2. Make sure your web project is selected in the Default project drop down.
  3. Run the below commands: install-package Microsoft.Owin.Host.SystemWeb install-package Microsoft.Owin.Security.OpenIdConnect install-package Microsoft.Owin.Security.Cookies

You can do a build to make sure nothing has gone awry yet. However, if you try to run you will get errors at this point. We need to continue with the next steps.

Step 4: Add Startup Authentication Code

Next, we need to add some code to the OWIN startup process and adjust the web.config. I have sample code shared in this gist: https://gist.github.com/cmw2/6a537a4bfa6636f8221a7fc572197988 which is discussed below:

  1. In your App_Start Folder, create a StartupAuth.cs file and replace its contents with what’s shown in the gist. Adjust namespace to match your project.
  2. In your main/root folder create a Startup.cs file and replace its contents again with what’s shown in the gist which is just calling the ConfigureAuth method from the above step. Or you can just add the call if you already have a Startup class doing other things.
  3. In your web.config we’ll need to turn off Windows Authentication, remove FormsAuthentication, and add appSettings values that are used by the above code. Again, you can see the details in the gist.

At this point you should be able to build with no compilation errors, but you still can’t run.

Step 5: Register your application in AAD

Now we need to register your application with your AAD.

  1. First follow the steps outlined in the docs here: https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app to create your registration.

For Redirect URI, use your local dev environment SSL URL. For my registration I used:

Name: WindowsAuthAppToAADDemo Supported Account Types: Accounts in this organizational directory only Redirect URI: Web, https://localhost:44356/ (yours will be different)

  1. Next let’s make sure it is configured to return ID tokens that our app will use to read information about the logged in user. In the left-hand blade menu for your registration,
    1. Click Authentication
    2. Under Advanced Settings check ID tokens.
    3. Click Save
  2. Finally, let’s make sure group membership is a part of our token. In the left hand blade menu for your registration, click Manifest, then
    1. Find: “groupMembershipClaims”: null,
    2. And change to: “groupMembershipClaims”: “SecurityGroup”,
    3. Click Save

Step 6: Update your web.config with appropriate OAuth values

From the Overview page of your app registration you’ll see some values that you can paste into the web.config entries we created earlier

  1. Application (client) ID GUID should be placed in the value property for ida:ClientId.
  2. Directory (tenant) ID GUID should be placed in the value property for ida:TenantId.

Finally, let’s get your domain name. Navigate to the Overview page of your AAD. One way is to click on the Azure Active Directory menu item in the far left. Near the top will show the name of your domain. It may be <something>.onmicrosoft.com or it may be your organization’s domain name, like microsoft.com or something similar. Once you have that, place that in the web.config for the ida:Domain setting.

Step 7: Test

It’s time to test. Go ahead and launch your site with the Run/Start Debugging button in Visual Studio. You should then get a login page like:

Go ahead and login. The first time you login you’ll get a prompt like below which you’ll need to Accept.

After that you’ll get your home page. If you’ve used the same demo app I’ve been using and pasted in the test HTML into About you should navigate to the About page and check your results.

You’ve probably noticed that your group membership checks don’t seem to be working. This is because the information sent to your app actually now has the group GUID, not the group name. Replace the name of the group with it’s GUID in the IsInRole check and you should see it start working again. (The GUID is shown as the Object ID of the group in AAD.)

Next Steps

As you deploy your app into other environments, that redirect URL of localhost will no longer be valid. You’ll need to make sure to update the correct value in your web.config as well as add it to the list of allowed redirects in the App Registration. You’ll find that in the Authentication settings above where we turned on the ID tokens in step 5.2 above.

4 comments

Comments are closed. Login to edit/delete your existing comments

  • Avatar
    Kevin Grigsby

    This is a great article and I did get it to work. However, I have an application that uses Microsoft Identity that I have inherited and have to integrate SSO into it. I was able to get your article to work, mostly. I see all of my claims once I log in! For some reason it always jumps to the Manage page and when it tries to look up the user, the ID it gets from AAD is clearly not the ID found in the AspNetUsers table. I was able to fake that by copying the AAD Id into a new record. So I thought I was golden. But implementing this article into my existing app seems to create a login loop. As if it is not remembering the token and trying again. After about 20 seconds or so of looping it finally fails. I can look into the AAD sign-ins and see they ALL succeeded. So I am not sure what is going on here. Any help would be greatly appreciated.

    • Avatar
      Ivan Miranda MarreroMicrosoft employee

      Kevin:

      I went through this, and though this may not necessarily work for you, this made it work for me:

      0. Make sure you have OWIN 3.1.0 or later, specifically Microsoft.Owin.Host.SystemWeb 3.1.0 (see bug #197 on OWIN (ASP.NET Katana) 3.0.1 libraries: https://blogs.aaddevsup.xyz/2019/11/infinite-sign-in-loop-between-mvc-application-and-azure-ad/) This will require you to update your Newtonsoft.Json library to 10.0.1 at least, if I recall correctly. If for any reason you are constrained to use older libraries consider the Kentor.OwinCookieSaver workaround: https://github.com/Sustainsys/owin-cookie-saver

      1. Make sure to set authentication method to “None” and “deny” anonymous access in the Web.config page:

          
            <authentication mode="None">
              <forms requireSSL="true">
            </authentication>
            <identity impersonate="false"  />
            <authorization>
              <deny users="?" />
            </authorization>
          

      2. I made sure to enforce all communications to be carried out with HTTPS (this and the old libraries were causing the redirect loop in my case for valid authorized users)
      3. Finally, for unauthorized users, I had to add a custom handler to the

      Global.PostAuthorizeRequest

      event handler list. The OWIN middleware that enables AAD authentication executes before this event is raised from the request processing pipeline, which comes after the

      Global.AuthorizeRequest

      stage in which the redirect to AAD takes place for authentication. In that custom handler, you need to define your logic for authentication (using Http.Context; keep in mind you can extract useful claims info from the AAD token and add it to the context for use here). But whatever your logic is, you need to make sure to NOT RETURN 401 Unauthorized in the response as that will also cause a redirect loop for unauthorized users. Use 403 Forbidden instead.
      On Global Init:

      public override void Init()
              {
                  base.Init();
      
                  if (AadAuthEnabled)
                  {
                      this.PostAuthorizeRequest += new EventHandler(Startup.Global_PostAuthorizeRequest);
                  }
              }
      public static void Global_PostAuthorizeRequest(object sender, EventArgs e)
              {
                  if (!yourAuthLogicDeterminedAuthorized())
                  {
                      HttpContext.Current.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                      HttpContext.Current.ApplicationInstance.CompleteRequest();
                  }
              }

      This was a while ago, so I don’t remember the exact explanation of why AAD got stuck here, but it had to do with the AAD auth cookie not being saved, either because of not using HTTPS or because of the bug referenced above, thus OWIN detecting 401, then redirecting, then detecting 401 again, then redirecting, and so on…

      Let me know if it works for you! Hope it is useful for anyone else going through this. And thanks to the author of this blogpost; everyone is happy on my team with the new AAD auth you helped me implement on very old code!

  • Avatar
    Ricardo Takeshi Barbosa

    How can I logout azure ad account?
    I try this code below, but doesn’t work.

    context.GetOwinContext().Authentication.
    SignOut(
    new Microsoft.Owin.Security.AuthenticationProperties { RedirectUri = “http://website” },
    CookieAuthenticationDefaults.AuthenticationType, OpenIdConnectAuthenticationDefaults.AuthenticationType);