Windows identity asp net

Время на прочтение7 мин

Количество просмотров8.1K

Для начала расскажу, что приложение, которое я разрабатывал, долго существовало на небольшом «подстольном» сервере в виде прототипа, которым в работе пользовалось небольшое число сотрудников. По прошествии некоторого времени, руководство приняло решение тиражировать это приложение в пром – с переносом на пром-сервер и организацией доступов к нему сотрудникам всего структурного подразделения.

Естественно, как это всегда бывает, сопровождение выдало нам список требований, которым должны соответствовать приложения, размещаемые на пром-серверах. Одним из таких требований было реализация авторизации по учетной записи Windows, а старую авторизацию по логину/паролю использовать было нельзя. О том, с какими подводными камнями мы столкнулись в ходе реализации такой, казалось бы, простой фичи, и как мы их решили, и пойдет речь в этом посте. Как я и упомянул ранее, в начальной точке этой истории у нас было классическое MVC-приложение. Информация о пользователях, их ролях (Admin, Common) и доступах к определенным действиям и процедурам хранилась в БД MS SQL. Упрощенно структуру этого сегмента БД можно представить вот так:

По названию таблиц можно догадаться, что в самом приложении эта связка таблиц захватывалась Entity Framework 6, а после использовалась подсистемой ASP.NET Identity. В начале сессии пользователю выводилась форма для входа, в которую он вводил свои учетные данные, после чего происходил редирект на домашнюю страницу приложения. Далее, исходя из того, какие доступы у данного пользователя прописаны в БД, и какими привилегиями он обладает, система подстраивала UI под эти данные.

Авторизация была реализована с помощью HTML-форм путём применения стандартного хелпера Html.BeginForm, отсылающего введенные данные по нажатию кнопки Submit. Вот как это выглядело с точки зрения кода:

@using (Html.BeginForm("Login", "Auth", FormMethod.Post, new { @class = "form-signin" }))
{
    @Html.AntiForgeryToken()
    <div class="form-group form-ie">
        <span class="oi oi-person"></span>
        @Html.TextBoxFor(x => x.Login, new { @class = "form-control", @placeholder = "Логин", @id = "username" })
        @Html.ValidationMessageFor(x => x.Login)
    </div>

    <div class="form-group form-ie">
        <span class="oi oi-lock-locked"></span>
        @Html.PasswordFor(x => x.Password, new { @class = "form-control", @placeholder = "Пароль", @id = "inputPassword" })
        @Html.ValidationMessageFor(x => x.Password)
    </div>
    <input type="submit" class="btn btn-mybtn-lg btn-my btn-block text-uppercase" value="Войти" />
}

Далее логин с паролем передавались в контроллер авторизации AuthController, который в себе хранил UserManager, SignInManager и AppDbContext (пронаследованный от IdentityDBContext) из ASP.NET Identity. Вот как выглядел код этого контроллера.

[AllowAnonymous]
[RoutePrefix("Auth")]
public class AuthController : Controller
{
	private AppDbContext _dbContext;
	private ApplicationSignInManager _signInManager;
	private ApplicationUserManager _userManager;
	public ApplicationSignInManager SignInManager
	{
		get
		{
			return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
		}
		private set
		{
			_signInManager = value;
		}
	}

	public ApplicationUserManager UserManager
	{
		get
		{
			return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
		}
		private set
		{
			_userManager = value;
		}
	}

	public AppDbContext DbContext
	{
		get
		{
			return _dbContext ?? HttpContext.GetOwinContext().Get<AppDbContext>();
		}
		private set
		{
			_dbContext = value;
		}
	}

	public AuthController()
	{
	}

	[HttpGet]
	public ActionResult Index()
	{
		return View(new AuthViewModel());
	}

	[HttpPost]
	[ValidateAntiForgeryToken]
	public async Task<ActionResult> Login(AuthViewModel model)
	{
		var result = await SignInManager.PasswordSignInAsync(model.Login, model.Password, false, false);
		if (result == SignInStatus.Success)
		{
			return RedirectToAction("Index", "Home");
		}
		Log.Warning("Ошибка авторизации: Неправильный логин или пароль");
		ModelState.AddModelError("Password", "Неправильный логин или пароль");
		return View("Index", model);
	}

	private IAuthenticationManager AuthenticationManager
	{
		get
		{
			return HttpContext.GetOwinContext().Authentication;
		}
	}

	[HttpGet]
	[ValidateAntiForgeryToken]
	public ActionResult LogOff()
	{
		AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
		return RedirectToAction("Index", "Auth");
	}
}

Сам факт авторизации в системе в других контроллерах проверялся посредством применения фильтра-нотации [Authorize], а принадлежность к роли – посредством применения [Authorize(Roles = “role1”)].

[Authorize]
public class HomeController : Controller
{
	private AppDbContext _dbContext;

	public AppDbContext DbContext
	{
		get
		{
			return _dbContext ?? HttpContext.GetOwinContext().Get<AppDbContext>();
		}
		private set
		{
			_dbContext = value;
		}
	}

	public HomeController()
	{
	}

	[Authorize(Roles = "Common, Admin")]
	public ActionResult Index()
	{
		///something is happening
		return View();
	}
}

Как заметит знакомый с вышеописанным стеком человек, не происходит вообще ничего необычного – это базовые элементы, знакомые каждому ASP.NET-разработчику.

Итак, после получения требования об изменении порядка авторизации, мы стали менять его. Для тех, кто с этим не знаком — в ASP.NET существуют следующие типы авторизации, которые можно поставить как с конфига, так и с помощью шаблона Visual Studio при создании проекта:

  1. Без авторизации;

  2. Авторизация на основе отдельных учётных записей (логин+пароль, классика)

  3. Авторизация с помощью Active Directory, Microsoft Azure или Office 365.

  4. Авторизация с помощью учётной записи Windows.

Так как у нас нет возможности использовать Active Directory ввиду требований сопровождения, остаётся один вариант – авторизация с помощью УЗ Windows.

Поигравшись немного со сменой способа авторизации в пустых приложениях и убедившись, что в них всё работает, я сделал то же самое с нашим приложением, заменив authentication mode на «Windows» в web.config.

Итак, настало время прогона. Изначально я предполагал, что после изменения авторизации можно будет подгонять логин пользователя в SignInManager, после чего проводить авторизацию по-старому (только без пароля) – т.е., что SignInManager будет маппить логин с таблицей AspNetUsers и вносить в контекст текущей пользовательской сессии соответствующий AspNetIdentity. Для чистоты эксперимента я удалил себя из таблицы с пользователями. Иии…я все равно спокойно авторизовался. Покопавшись в переменных, я понял, что при смене authentication mode на «Windows» используется другой вид Identity: не AspNetIdentity, а WindowsIdentity. При использовании WindowsIdentity любой пользователь, который вошёл в Windows – априори авторизован, причем автономно – никакой связи с БД и EF не наблюдалось. Это означало, что если ничего не исправить, то…

Ну вы поняли ?

Так как Active Directory мы использовать не могли, текущий вариант не работал, а опыта в написании и модификации систем авторизации у меня не было – плюс, на эту фичу было отведено мало времени – я закопался в документацию по ASP.NET Identity и Windows Identity. Как оказалось – это было правильное решение.

Итак, как можно подружить ASP.NET Identity + EF и Windows Identity:

  1. Сделать еще один класс – назовем его CustomAuthenticationFilter — и пронаследовать его от ActionFilterAttribute и IAuthenticationFilter.

В AuthorizeAttribute содержится метод OnAuthentication который можно переопределить в дочернем классе. В нём мы захватываем логин пользователя из Windows Identity, прикрепленного к контексту AuthenticationContext – затем с помощью контекста Entity Framework получаем доступ к таблице с пользователями и проверяем, есть ли пользователь в списке. Если его нет – в методе вернуть false.

Затем из AuthorizeAttribute в нашем классе необходимо переопределить обработчик событий OnAuthenticationChallenge, который позволяет задать реакцию системы в случае, если метод OnAuthentication, переопределенный ранее выдаст false. В нашем случае мы будем перенаправлять пользователя на страницу, где сообщим ему, что к приложению необходимо получить доступ (401).

public class CustomAuthenticationFilter : ActionFilterAttribute, IAuthenticationFilter
{
	public void OnAuthentication(AuthenticationContext filterContext)
	{
		var dbContext = filterContext.HttpContext.GetOwinContext().Get<AppDbContext>();

		var username = filterContext.HttpContext.User.Identity.Name;

		var userMatches = dbContext.Users.Where(x => x.UserName == username);

		if (string.IsNullOrEmpty(username) || userMatches.Count() != 1)
		{ 
			filterContext.Result = new HttpUnauthorizedResult();
		}
	}

	public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
	{
		if (filterContext.Result == null || filterContext.Result is HttpUnauthorizedResult)
		{
			filterContext.Result = new RedirectToRouteResult(
				 new RouteValueDictionary{
					 { "controller", "Error" },
					 { "action", "NotAuthorized" }
			});
		}
	}
}
  1. Для того, чтобы сделать вариант, предполагающий дополнительную проверку роли, помимо проверки факта наличия пользователя, необходимо в том же или новом классе пронаследоваться от AuthorizeAttribute. Для упрощения чтения я сделал новый класс.

Идеология здесь следующая:

Делаем конструктор, в который извне передаем список разрешенных ролей, например, { “Admin”, “Common”}.

Переопределяем метод AuthorizeCore, в котором реализуем поиск пользователя по образцу предыдущего класса, а потом через тот же контекст EF достаем список ролей пользователя и матчим его с тем списком, который прилетает через конструктор. Если матч есть – пользователь «достоин».

  • Далее переопределяем обработчик HandleUnauthorizedRequest, где мы выдаем пользователю стилизованную ошибку 403.

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
	private readonly string[] allowedRoles;
	public CustomAuthorizeAttribute(params string[] roles)
	{
		allowedRoles = roles;
	}
	protected override bool AuthorizeCore(HttpContextBase httpContext)
	{
		var dbContext = httpContext.GetOwinContext().Get<AppDbContext>();

		var username = httpContext.User.Identity.Name;
		var userMatches = dbContext.Users.Where(x => x.Name == username);

		if (!string.IsNullOrEmpty(username) && userMatches.Count() == 1)
		{
			var userId = userMatches.First().Id;
			var userRole = (from u in dbContext.Users
							join r in dbContext.Roles on u.Roles.FirstOrDefault().RoleId equals r.Id
							where u.Id == userId
							select new
							{
								r.Name
							}).FirstOrDefault();

			foreach(var role in allowedRoles)
			{
				if (role == userRole.Name) return true;
			}
		}

		return false;
	}

	protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
	{
		filterContext.Result = new RedirectToRouteResult(
			new RouteValueDictionary
			{
				{ "controller", "Home" },
				{ "action", "AccessDenied" }
			});
	}
}

А теперь магия – я думаю, вы уже догадались, что с помощью этих двух классов мы разработали фильтры, аналогичные [Authorize] и [Authorize(Roles = “role1”)].

Таким образом, изначально столкнувшись с невозможностью ASP.NET Identity и Windows Identity работать из коробки вместе, я переопределил сами фильтры, отредактировав их логику до той, что мне требуется. Надеюсь, вам поможет информация из этого поста, если вы столкнетесь с аналогичной ситуацией. Удачи!

In this article we will cover Windows Authentication.

Contents

  • Definitions of few keywords to understand Windows Authentication.
  • What is Windows Authentication.
  • Why Windows Authentication.
  • How Windows Authentication is implemented in ASP.NET Application.
  • Configuring impersonation in an application.

Authentication: Authentication is the process of determining the identity of a user based on the user’s credentials. The user’s credentials are usually in the form of user ID and password, which is checked against any credentials’ store such as database. If the credentials provided by the user are valid, then the user is considered an authenticated user.

Authorization: After successful authentication, deciding which resources a user can access based on their identity and checking whether the authenticated user has sufficient rights to access the requested resource is authorization.

Impersonation: Impersonation is a process in which user accesses the resources(Ex:Files,DB…) by using the identity of another user.

Ex: If anonymous(not logged in/not Authenticated) access is enabled for a website in IIS, then IIS runs all the users’ requests using the identity of the IUSR_machinename account, which is created by IIS. This is the default option in IIS.

WindowsIdentity: It represents the current Windows User.

Authentication Providers

In ASP.NET authentication is done by both IIS and ASP.NET. ASP.NET implements authentication through authentication providers that contains the code necessary to authenticate the requestor’s credentials. There are three types of authentication providers built into ASP.NET. They are:

  1. Windows Authentication Provider.
  2. Forms Authentication Provider.
  3. Passport Authentication Provider.

Windows Authentication Provider: Provides information on how to use Windows authentication in conjunction with Microsoft Internet Information Services (IIS) authentication to secure ASP.NET applications.

Why Windows Authentication:

  1. Windows authentication is generally used if the users accessing the application belong to same organization.
  2. This authentication method uses Windows accounts for validating users’ credentials. This type of authentication is very good for intranet Web sites where we know our users.

How Windows Authentication is Implemented in ASP.NET Application

With this type of authentication, initially IIS performs the authentication through one of its authentication options (e.g., basic, digest, Integrated Windows, or some combination of them). After successful authentication, IIS passes the credentials of the authenticated user to the ASP.NET thread. Selection of appropriate identity for the ASP.NET worker thread is performed by using the process defined under the ASP.NET Impersonation section. Based on the credentials supplied by IIS, windows identity is created by WindowsAuthenticationModule module in ASP.NET. This identity is set as current user identity (setting the security information for the current HTTP request)for the application. This is the default authentication mode in ASP.NET and it is set in web.config file of the application using below code:

<system.web>
  <authentication mode="Windows"/>
</system.web>

Although the Windows Authentication mode sets the value of the current User property to a WindowsIdentity based on the credentials supplied by IIS. The Windows identity supplied to the operating system used for permission checking, such as NTFS file permissions, or for connecting to a database using integrated security is the identity of the ASP.NET process. On Microsoft Windows 2000 and Windows XP Professional, this is the identity of the ASP.NET worker process, which is the local ASPNET account. On Windows Server 2003, this is the identity of the IIS Application Pool that the ASP.NET application is part of. Which is the NETWORK SERVICE account.

We can configure the Windows identity of our ASP.NET application as the Windows identity supplied by IIS by enabling impersonation. Here ASP.NET application impersonates the identity supplied by IIS for all tasks that the Windows operating system authenticates, including file and network access.

Configuring Impersonation in an Application

In machine.config file it is configured as below:

<processModel enable="true" username="machine"
password="AutoGenerate" ....... />

In the above line, «machine» is a special value that causes the ASP.NET worker process to run under the less-privileged account, ASPNET.

IIS always maps a user request to some Windows account; in case of anonymous access, this is IUSR_machinename account or any other account that has been defined to be used with anonymous access; in the case of Windows authentication, this is the account whose credentials are provided by the Web site user.

  • If impersonation is enabled and any specific Windows account has not been listed in the Web.config file for impersonation, then the ASP.NET worker thread runs under the client identity passed to it by IIS.
  • If impersonation is not enabled, then the ASP.NET worker thread runs under the identity of the ASP.NET worker process (which has been defined by using the <processModel> tag in the Web.config file)
  • If impersonation is enabled and a specific Windows account has been listed in the Web.config file for impersonation, then the ASP.NET worker thread runs under the identity generated using that account.

To enable impersonation for our Web application, in the application’s Web.config file set the impersonate attribute of the identity element to true, as below:

<system.web>
  <authentication mode="Windows"/>
  <identity impersonate="true"/>
</system.web>

There are three ways of defining impersonation:

  • <identity impersonate=»true»/> This means impersonation for the ASP.NET worker thread is enabled.
  • <identity impersonate=»true» name=»username» password=»password»/> This means impersonation for the ASP.NET worker thread is enabled, but the worker thread will run under the identity that will be generated by using the credentials specified by username and password attributes.
  • <identity impersonate=»false»/> This means impersonation for the ASP.NET worker thread is not enabled.

Windows Authentication Provider

The authentication and authorization processes in ASP.NET are pretty simple and can be implemented in the code. Any request in ASP.NET is first authenticated and then authorized by IIS.

In ASP.NET authentication is done by both IIS and ASP.NET.

In ASP.NET there are different ways in which authentication is performed as discussed below:

  • Anonymous Access: There is no authentication performed and the user is treated as anonymous user by iis. This is the default authentication mode.

ASP.NET implements authentication through authentication providers that contains the code necessary to authenticate the requestor’s credentials. There are three types of authentication providers built into ASP.NET.

  1. Windows Authentication Provider: Provides information on how to use Windows authentication in conjunction with Microsoft Internet Information Services (IIS) authentication to secure ASP.NET applications. This is the default authentication mode in ASP.NET and it is set in web.config file of the application using below code:
    <system.web>
     <authentication mode="Windows"/>
    </system.web>

    Here, the identity supplied by IIS is treated as authenticated user in an ASP.NET application.

    • IIS provides a number of authentication mechanisms to verify user identity as follows: Anonymous Authentication: IIS allows any user to access the ASP.NET application.
    • Basic Authentication: The Windows user name and password has to be provided to connec and this information is sent over the network in plain text, and, hence, this is an insecure method of authentication.
    • Digest Authentication: It is the same as basic authentication except that the password is hashed before it is sent across the network.
    • Integrated Windows Authentication: In this kind of authentication technique, passwords are not sent across the network. The application here uses either the kerberos or challenge/response protocols to authenticate users.

    Based on the credentials supplied by IIS, windows identity is created by WindowsAuthenticationModule module in ASP.NET. This identity is set as current user identity for the application.

  2. Forms Authentication Provider: Provides information on how to create an application-specific login form and perform authentication using your own code. In ASP/NET forms authentication is also implemented using ASP.NET membership and ASP.NET login controls, which together provide a way to collect user credentials, authenticate them, and manage them, using little or no code.
  3. Passport authentication Provider: Provides information about the centralized authentication service provided by Microsoft that offers a single login and core profile services for member sites.

Authentication

Authentication is the process of determining the authenticity of a user based on the user’s credentials. Whenever a user logs on to an application, the user is first authenticated and then authorized. The application’s web.config file contains all of the configuration settings for an ASP.NET application. It is the job of the authentication provider to verify the credentials of the user and decide whether a particular request should be considered authenticated or not. An authentication provider is used to prove the identity of the users in a system. ASP.NET provides three ways to authenticate a user:

  • Forms authentication
  • Windows authentication
  • Passport authentication

Hence, ASP.NET contains the three respective authentication providers to support the above authentication modes.

Forms Authentication

This authentication mode is based on cookies where the user name and the password are stored either in a text file or the database. After a user is authenticated, the user’s credentials are stored in a cookie for use in that session. When the user has not logged in and requests for a page that is insecure, he or she is redirected to the login page of the application. Forms authentication supports both session and persistent cookies. Authentication modes can be specified in the application’s web.config file as shown below:

Listing 1

<configuration>
  <system.web>
    <authentication mode="[Windows/Forms/Passport/None]">
    </authentication>
  </system.web>
</configuration>

The following needs to be specified in the application’s web.config file for using Forms Based Authentication in ASP.NET:

Listing 2

  
  <configuration>
    <system.web>
      <authentication mode="Forms"/>
      <forms name="login"loginUrl="login.aspx" />
      <authorization>
          <deny users="?"/>
      </authorization>
    </system.web>
  </configuration>

Note: The statement <deny users=»?»> in the web.config file as stated in Listing 2 implies that all permissions are granted only to the authenticated users. The users who are not authenticated are not granted any permission. The symbol «?» indicates all Non Authenticated and Anonymous users.

Generally the user’s credentials are stored in the database and the entered credentials are verified using those that are stored in the database. Typically, the user enters the username and the password, clicks the login button and the form validates the values against values from the database. This is shown in the code snippet below:

Listing 3

if (Verify (txtUserName.Text, txtPassword.Text))
{
  FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, False);
    else
  lblMessage.Text = "Invalid login name orpassword specified...";
}
  
private Verify(string userName, string password)
{
      //Usual Code to connect to the DB
      // and verify the user's credentials
}

The static method RedirectFromLoginPage creates an authentication ticket and is used to redirect an authenticated user back to the originally requested URL or the default URL. The authentication ticket creates a persistent cookie that becomes a part of the HttpResponse object. Later, when the user tries to access a page in a restricted folder, the ASP.NET framework uses the cookie to retrieve the ticket and determine whether the user has access to that particular resource. The first parameter to this method identifies the user while the second is used to specify whether the user’s authentication cookie needs to be persisted across multiple site visits.

The user’s credentials can be also be specified in the web.config file as shown below:

Listing 4

<configuration>
    <system.web>      
    <authentication mode="Forms">
    <forms loginUrl="login.aspx">
        <credentialspasswordFormat="Clear">
            <user name="Joydip"password="Joydip" />
        </credentials>
    </forms>
    </authentication>
    <authorization>
    </system.web>
</configuration>

An authentication system is how you identify yourself to the computer. The goal behind an authentication system is to verify that the user is actually who they say they are.

Authorization

Once the system knows who the user is through authentication, authorization is how the system decides what the user can do.

Authentication is to check about user.Through authentication check the user exist or not. It check that user exist or not.

Authorization check that authentic user have proper permission or not to access that particular page or services.

Authentication is the process of determining the authenticity of a user based on the user’s credentials. Whenever a user logs on to an application, the user is first authenticated and then authorized. The application’s web.config file contains all of the configuration settings for an ASP.NET application. It is the job of the authentication provider to verify the credentials of the user and decide whether a particular request should be considered authenticated or not. An authentication provider is used to prove the identity of the users in a system. ASP.NET provides three ways to authenticate a user:

How Authentication and Authorization Works:

The following section lists the sequence of events that take place in the authentication and authorization process when a new request arrives. The IIS first checks the validity of the incoming request. If the authentication mode is anonymous (default) then the request is authenticated automatically. But if this authentication mode is overridden in the web.config file settings, the IIS performs the specified authentication check first before the request is passed on to ASP.NET.

Now ASP.NET checks whether Impersonation is enabled or not. If impersonation is enabled, ASP.NET executes with the identity of the entity on behalf of which it is performing executing the task. If impersonation is not enabled, the application runs with the identity of the IIS local machine’s identity and the privileges of the ASP.NET user account. ASPNET or NETWORK SERVICE is the default ASP.NET unprivileged account on Windows XP and Windows Server 2003, respectively. Now, the identity that has already been authenticated and verified is used to request resources from the operating system. Then ASP.NET performs an authorization check on the requested resources and if the user is authorized, it returns the request through IIS.

Application security plays a major role in building robust applications. The application should be able to restrict or limit access to the resources based on the user’s credentials and even disallow access to resources to unauthorized users of the system. This article just gave a basic idea about ASP.NET’s in-built Authentication and Authorization support.

Authentication and Authorization are security concepts.

There are four different kinds of Windows authentication options available that can be configured in IIS:

  • Anonymous Authentication: IIS runs all the users’ requests using the identity of the IUSR_machinename account which is created by IIS. This is an example of Impersonation wherein user accesses the resources by using the identity of the another user. IIS doesn’t perform any authentication check. IIS allows any user to access the ASP .NET application.
  • Basic Authentication: For this kind of authentication, a Windows user name and password have to be provided to connect. However, this information is sent over the network in plain text and hence this is an insecure kind of authentication. Basic Authentication is mostly supported by older, non-IE browsers.
  • Digest Authentication: It is same as Basic Authentication but for the fact that the password is hashed before it is sent across the network. However, to be using Digest Authentication, we must use IE 5.0 or above.
  • Integrated Windows Authentication: In this kind of authentication technique, passwords are not sent across the network. The application here uses either the kerberos or challenge/response protocols to authenticate users. Kerberos, a network authentication protocol, is designed to provide strong authentication for client-server applications. It provides the tools of authentication and strong cryptography over the network to help to secure information in systems across entire enterprise.

Using impersonation, the ASP.NET engine will operate under the identity IIS passes to it. If anonymous access is allowed in IIS, ASP.NET will run under the IUSR_ComputerName account that IIS uses. If anonymous access is not allowed, ASP.NET will take on the identity of the authenticated user. Impersonation can also be configured so that a single, static user account is used for all requests

This article covers

  • ASP.NET security fundamentals.
  • Various ways of controlling ASP.NET Web applications security.
  • Different security options provided by both IIS and ASP.NET together.

Security Fundamentals

Impersonation: Impersonation is a process in which user accesses the resources(Ex:Files,DB…) by using the identity of another user.

Ex: If anonymous access is enabled for a website in IIS, then IIS runs all the users’ requests using the identity of the IUSR_machinename account, which is created by IIS. This is the default option in IIS.

Authentication: It is the process of identifying a user. the security infrastructure collects the user’s credentials, usually in the form of user ID and password, checks those credentials against any credentials’ store. If the credentials provided by the user are valid, then the user is considered an authenticated user.

Authorization: After successful authentication , decides which resources a user can access based on their identity and checks whether the authenticated user has sufficient rights to access the requested resource.

ASP.NET Security

Security is implemented in ASP.NET using IIS and Windows operating system. Most of the IIS security settings can be replaced with configuration files.

Whenever IIS receives requests for some ASPX page, ASMX Web Service, or any other resource that is associated with ASP.NET, it uses the IIS applications mappings to send the request to ASP.NET. ASP.NET applications use configuration files for security and other Web application settings.

ASP.NET applications on a particular machine inherit their security configuration from a file named machine.config. Each ASP.NET application can in turn override some of the settings in machine.config using an application-level configuration file named Web.config.

ASP.NET works with IIS and the Windows operating system in order to implement the security services. In ASP.NET, most of the IIS settings have been replaced with configuration files. However, security settings made in IIS are still valid in some of the areas because IIS is still handling the process of accepting users’ requests. In fact, whenever IIS receives requests for some ASPX page, ASMX Web Service, or any other resource that is associated with ASP.NET, it uses the IIS applications mappings to send the request to ASP.NET.

ASP.NET applications use configuration files for security and other Web application settings. All ASP.NET applications on a particular machine inherit their security configuration from a file named machine.config present in C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG folder. Each ASP.NET application can in turn override the settings in machine.config using an application-level configuration file named Web.config.

All the ASP.NET files such as .aspx, .asmx are mapped to aspnet_isapi.dll that means ASP.NET is an ISAPI server extension DLL.

Since aspnet_isapi.dll is a DLL file, it will be mapped into the address space of the process hosting this DLL which is an IIS process — inetinfo.exe. inetinfo.exe process runs under the SYSTEM account, and since we have aspnet_isapi.dll running in the IIS address space, it means all the Web site users will be running their requests under this account!

Let me answer this question.

aspnet_isapi.dll runs under the SYSTEM account and It forwards the requests to the ASP.NET application worker process, aspnet_wp.exe. This worker process runs under a less-privileged account called ASPNET. This worker process performs the actual request processing and returns the results back to the aspnet_isapi.dll that returns it to the IIS.

In machine.config fileit is configured as below:

<processModel
enable="true" username="machine"
 
password="AutoGenerate"
....... />

In the above line, «machine» is a special value that causes the ASP.NET worker process to run under the less-privileged account, ASPNET.

there is one instance of aspnet_wp.exe per processor that serves all the Web applications hosted on a box. This worker process always runs under the security context defined by the <processModel> tag. The aspnet_wp.exe creates a separate thread, called worker thread, for servicing each client request. We have to distinguish between the server process and the worker threads in order to understand the impersonation and security details of ASP.NET.

Security is concerned with protecting users accessing web applications and the data that pass between them.

The most common aspect of security is user authentication, where a user attempts to prove their identity, and authorization, where a service decides which resources a user can access based on their identity.

In ASP.NET, the users requesting a webpage are, by default, anonymous. There are different techniques available for determining the identity of an anonymous user. by default, Web applications allow for anonymous users.

When a request comes in for an ASP.NET Web page, the request is sent to the Web server software (IIS), which performs authentication and authorization.

If the user is not authenticated, or does not have access, the request is not processed further and an appropriate message will be returned. If, however, the request passes IIS’s authentication and authorization, the request will be sent to the ASP.NET engine, which can impose its own authentication and authorization schemes.

  1. By default, IIS allows anonymous access thus requests are automatically authenticated. However, this can be overridden for each application within IIS. Otherwise authentication is performed.
  2. The authenticated user request is passed to ASP.NET.
  3. ASP.NET checks whether Impersonation is enabled or not. By default impersonation is not enabled in ASP .NET.
    1. If impersonation is enabled, ASP.NET executes with the identity of the user credentials in web.config file.
    2. If impersonation is not enabled, the application runs with the privileges of the ASPNET user account.
    3. Finally, the identity that has been authenticated and checked for in the previous steps is used to request resources from the OS.
    4. Forms Authentication Provider
      The forms authentication provider uses custom HTML forms to collect authentication information. As an ASP.NET developer using forms authentication, you must write your own logic/code to check the user’s supplied credentials against a database or some other data store. When a user is successfully identified via forms authentication, the user’s credentials are stored in a cookie for use during the session.

By default, the ASP.NET engine operates under the ASPNET user account. Impersonation is a means by which you can have the ASP.NET engine operate under the authenticated user’s user account.

Authentication is a process of identifying a user.

Authorization is the process of determining if an authenticated user has access to the resource(s) they requested.

Typically, authentication is achieved by the user credentials that verify the user’s identity.

Whenever Websever(IIS) receives the request for any resource such as an aspx page/asmx page … It forwards the requests to aspnet_isapi.dll and since this DLL file is hosted in the address space of IIS, it runs under the SYSTEM account. But it forwards the requests to the ASP.NET application worker process, aspnet_wp.exe. This worker process performs the actual request processing and returns the results back to the aspnet_isapi.dll that returns it to the IIS.

Important point to note here is that ASP.NET worker process runs under the ASPNET account which is less privileged account than the system account.

Actually There is one instance of aspnet_wp.exe per processor that serves all the Web applications hosted on a box. This worker process always runs under the security context defined by the <processModel> tag in machine.config file. The aspnet_wp.exe creates a separate thread, called worker thread, for handling each client request. With ASP.NET impersonation, the thread servicing the client request can also execute with the identity of the client.

IIS always maps a user request to some Windows account; in case of anonymous access, this is IUSR_machinename account or any other account that has been defined to be used with anonymous access; in the case of Windows authentication, this is the account whose credentials are provided by the Web site user. After successful authentication, IIS forwards this logged-in user’s identity to the ASP.NET worker thread. Now the ASP.NET worker thread has the following three options:

  • It can run under the identity defined by the <processModel> tag.
  • It can run under the client identity passed to it by IIS.
  • It can run under the identity of the user whose credentials have been listed for impersonation.

Now the decision depends on the impersonation settings for the ASP.NET application.

  • If impersonation is enabled and any specific Windows account has not been listed in the Web.config file for impersonation, then the ASP.NET worker thread runs under the client identity passed to it by IIS.
  • If impersonation is not enabled, then the ASP.NET worker thread runs under the identity of the ASP.NET worker process (which has been defined by using the <processModel> tag in the Web.config file)
  • If impersonation is enabled and a specific Windows account has been listed in the Web.config file for impersonation, then the ASP.NET worker thread runs under the identity generated using that account.

Impersonation for ASP.NET applications can be set up by using the <identity> tag in the Web.config file. We can specify impersonation in the following three ways:

  • <identity impersonate=»true»/> This means impersonation for the ASP.NET worker thread is enabled.
  • <identity impersonate=»true» name=»username» password=»password»/> This means impersonation for the ASP.NET worker thread is enabled, but the worker thread will run under the identity that will be generated by using the credentials specified by username and password attributes.
  • <identity impersonate=»false»/> This means impersonation for the ASP.NET worker thread is not enabled.

Security has always been a top issue for all kinds of applications, especially Web applications. Web apps are accessible to almost the entire universe and are open to attack.

Web Services is a current hot topic because of its interoperability, ease of consumption, use of standard Web protocols, seamless integration with heterogeneous systems, etc. Therefore more platforms are now incorporating Web Services into their architecture. And with that greater amount of use, the need for security also increases.

Since Web Services are part of ASP.NET, and these are hosted by ASP.NET runtime, anything relevant to ASP.NET will also be true for Web Services.

This article will discuss:

  • Security concepts
  • ASP.NET security
  • How to control security by running the ASP.NET application worker process (aspnet_wp.exe) using an account with weaker privileges than the Local System account
  • Different security schemes offered by ASP.NET and Internet Information Server (IIS), and how ASP.NET and IIS work together to provide security to Web apps, including Web Services
  • Some code examples on how to use these security schemes for Web Services

Setting Up the Sample Applications

I have created a project in C# (CSWebservices) that contains some Web Services. This project will be used for the demonstration of different security schemes. There is also a C# Web site (CSWebsite) that has some Web pages for invoking methods on our Web Services.

To install the applications:

  • Extract all the code from attached ZIP file.
  • Create two virtual directories named CSWebservices and CSWebsite, and map the CSWebservices and CSWebsite virtual directories to the CSWebservices and CSWebsite physical directories on your hard drive.

Creating Windows User Accounts

To test Windows security, a valid Windows account is needed. Create a user account called Test on your computer. You can do so by running the Computer Management application from Control Panel -> Administrative Tools -> Computer Management. In the Computer Management application, right click on the Users node under the Local Users and Groups node, select New User… option from the context menu, enter «Test» in the User name text box, enter some password in Password and Confirm password text boxes, uncheck the User must change password at next logon check box, and check the User cannot change password check box. Your screen should look like the following:

Click the Create button. The Test user has been created successfully. Follow the same steps and create another user called Test2.

Security Concepts

Before proceeding further, I will describe some concepts that represent the key functionalities that a security system must provide.

Impersonation

Sometimes we want users’ requests to be run in the security context of some other user identity. For that we use impersonation. Impersonation is a process in which a user accesses the resources by using the identity of another user. An example of impersonation is the use of the IUSR_machinename account that is created by IIS. When a Web site has anonymous access enabled, then IIS runs all the users’ requests using the identity of the IUSR_machinename account.

Authentication

Authentication is a process in which the security infrastructure makes sure that the users are who they say they are. In order to do this, the security infrastructure collects the user’s credentials, usually in the form of user ID and password, checks those credentials against any credentials’ store. If the credentials provided by the user are valid, then the user is considered an authenticated user.

Authorization

Once we have authenticated the user and the user has valid credentials, it is time to check authorization.

Authorization is a process in which the security infrastructure checks whether the authenticated user has sufficient rights to access the requested resource. If user has sufficient rights to access a resource, for example, the user has «write rights» on a file, then the operation succeeds; otherwise the operation fails.

ASP.NET Security

ASP.NET works with IIS and the Windows operating system in order to implement the security services. In ASP.NET, most of the IIS settings have been replaced with configuration files. However, security settings made in IIS are still valid in some of the areas because IIS is still handling the process of accepting users’ requests. In fact, whenever IIS receives requests for some ASPX page, ASMX Web Service, or any other resource that is associated with ASP.NET, it uses the IIS applications mappings to send the request to ASP.NET.

ASP.NET applications use configuration files for security and other Web application settings. All ASP.NET applications on a particular machine inherit their security configuration from a file named machine.config. Each ASP.NET application can in turn override some of the settings in machine.config using an application-level configuration file named Web.config.

So what really is an ASP.NET application? To find the answer to this question, start the Internet Services Manager, right click on the Default Web Site node, select Properties from the context menu, go to the Home Directory tab on the Web site properties dialog box, and click the Configuration… button. It will pop up the following dialog box.

image005.jpg

As you can see in the above dialog box, the Web Services files, Web form files, and all of the other .NET file types are mapped to the aspnet_isapi.dll. This signifies that the ASP.NET is an ISAPI server extension DLL.

This is very important point. Since aspnet_isapi.dll is actually a DLL file, it will be mapped into the address space of the process hosting this DLL. This process is an IIS process — inetinfo.exe!! A user must be thinking that inetinfo.exe runs under the SYSTEM account, and since we have aspnet_isapi.dll running in the IIS address space, it means all the Web site users will be running their requests under this account!

Let me answer this question. Yes, aspnet_isapi.dll runs under the SYSTEM account, but it does not do much in terms of processing Web requests, rather it forwards the requests to the ASP.NET application worker process, aspnet_wp.exe. This worker process performs the actual request processing and returns the results back to the aspnet_isapi.dll that returns it to the IIS.

ASP.NET Worker Process

Aspnet_isapi.dll forwards requests to the ASP.NET worker process. If you have .NET Framework Beta 2 installed on your machine, then the worker process will be running under the SYSTEM account, but since the Release to Manufacturer version (RTM) of .NET, worker process runs under a less-privileged account called ASPNET.

Find the machine.config file on your hard drive and search for a tag called <processModel>. In beta versions of .NET, it would look as follows:

<processModel enable=»true» username=»System»

password=»AutoGenerate» ……. />

In the RTM version of .NET, it would look as follows:

<processModel
  enable="true" username="machine"
password="AutoGenerate"
  ....... />

In the above line, «machine» is a special value that causes the ASP.NET worker process to run under the less-privileged account, ASPNET.

In order to avoid running the Beta 2 ASP.NET process under the SYSTEM account, change the username and password to some other valid Windows account. For example, the

<processModel
  enable="true" username="MyDomain\Testuser"
  password="userpassword" ...... />

line will cause the ASP.NET worker process to run under the privileges assigned to the MyDomain\Testuser. It is always recommended to use a specific account that has fewer privileges (like the ASPNET account used by the RTM version of .NET) for the ASP.NET worker process. In this way, even if your server gets hacked, the intruder may not be able to harm your server due to the lesser privileges assigned to the account.

If you plan on using a custom Windows account for the worker process, then you must make sure that the account has proper rights on different directories because ASP.NET needs to read and write files to/from different directories. The custom Windows account should have at least:

  • Read rights on application directory
  • Read rights on %installroot% hierarchy to make it possible to access the system assemblies
  • Read/Write rights on the %installroot%\ASP.NET Temporary Files directory
  • Read/Write rights on the %temp% directory

ASP.NET Impersonation

An understanding of ASP.NET impersonation is important before going into the details of authorization and authentication. Therefore, first we will discuss ASP.NET impersonation in this section.

Before delving into the details of ASP.NET impersonation, I would like to clarify one important point that many folk are not aware of. Actually there is one instance of aspnet_wp.exe per processor that serves all the Web applications hosted on a box. This worker process always runs under the security context defined by the <processModel> tag. The aspnet_wp.exe creates a separate thread, called worker thread, for servicing each client request. We have to distinguish between the server process and the worker threads in order to understand the impersonation and security details of ASP.NET.

With ASP.NET impersonation, the thread servicing the client request can optionally execute with the identity of the client. Let me explain it in detail.

IIS always maps a user request to some Windows account; in case of anonymous access, this is IUSR_machinename account or any other account that has been defined to be used with anonymous access; in the case of Windows authentication, this is the account whose credentials are provided by the Web site user. After successful authentication, IIS forwards this logged-in user’s identity to the ASP.NET worker thread. Now the ASP.NET worker thread has the following three options:

  • It can run under the identity defined by the <processModel> tag.
  • It can run under the client identity passed to it by IIS.
  • It can run under the identity of the user whose credentials have been listed for impersonation.

Now the decision depends on the impersonation settings for the ASP.NET application.

  • If impersonation is enabled and any specific Windows account has not been listed in the Web.config file for impersonation, then the ASP.NET worker thread runs under the client identity passed to it by IIS.
  • If impersonation is not enabled, then the ASP.NET worker thread runs under the identity of the ASP.NET worker process (which has been defined by using the <processModel> tag in the Web.config file)
  • If impersonation is enabled and a specific Windows account has been listed in the Web.config file for impersonation, then the ASP.NET worker thread runs under the identity generated using that account.

Impersonation for ASP.NET applications can be set up by using the <identity> tag in the Web.config file. We can specify impersonation in the following three ways:

  • <identity impersonate=»true»/> This means impersonation for the ASP.NET worker thread is enabled.
  • <identity impersonate=»true» name=»username» password=»password»/> This means impersonation for the ASP.NET worker thread is enabled, but the worker thread will run under the identity that will be generated by using the credentials specified by username and password attributes.
  • <identity impersonate=»false»/> This means impersonation for the ASP.NET worker thread is not enabled.

ASP.NET Authentication

As stated above, ASP.NET and IIS securities go hand in hand. Therefore ASP.NET authentication also relies on the settings that we make in IIS. ASP.NET offers following types of authentications:

  • Windows authentication
  • Forms authentication
  • Passport authentication
  • None

The authentication option for the ASP.NET application is specified by using the <authentication> tag in the Web.config file, as shown below:

<authentication
  mode="Windows | Forms | Passport | None">

other authentication options

</authentication>

Windows Authentication

As the name implies, this authentication method uses Windows accounts for validating users’ credentials. This type of authentication is very good for intranet Web sites where we know our users.

With this type of authentication, initially IIS performs the authentication through one of its authentication options (e.g., basic, digest, Integrated Windows, or some combination of them). After successful authentication, IIS passes the identity of the authenticated user to the ASP.NET thread. Selection of appropriate identity for the ASP.NET worker thread is performed by using the process defined under the ASP.NET Impersonation section of this article.

We can secure a Web Service by using one of the following Windows authentication schemes:

  • Integrated Windows authentication
  • Basic and basic with SSL authentication
  • Digest authentication
  • Client Certificate authentication

Integrated Windows Authentication

Integrated Windows authentication is a secure way of passing a user’s credentials on wire. It can use either NT LAN Manager (NTLM) or Kerberos authentication. This is the best scheme that can be used for intranet environments using Windows, but this scheme cannot be used for Internet because it works only with Windows clients.

In order to set up Integrated Windows authentication for our Web Services, we have to specifically tell IIS to use Integrated Windows authentication. We can do this by using the Internet Services Manager application. Run this application from Control Panel -> Administrative Tools -> Internet Services Manager; in the left-hand pane, select the CSWebservices node under the Default Web Site node; in the right-hand pane, right click on the IWAWebservice.asmx Web Service, select Properties from the context menu and go to the File Security tab. Following is the File Security tab:

image006.jpg

Click Edit button in Anonymous access and authentication control group box and it will popup the following dialog box:

By default, Anonymous access is checked. This means IIS will not stop anyone from accessing our Web Service; in fact no authentication will be performed at all. The only entity that might stop a user from accessing this Web Service is the NT File System (NTFS) permissions. NTFS is a file system that has security capabilities built into it. NTFS always checks the security credentials of the identity that is trying to access the file. If NTFS permissions require some authentication, then IIS will challenge the user for credentials.

We don’t want this. Therefore, uncheck the Anonymous access check box and leave the Integrated Windows authentication box checked.

Apart from that, change the <authentication> tag in the Web.config file and set the mode attribute to «Windows»; similarly set the impersonate attribute of the <identity> tag to «true». Following is the snippet from the updated Web.config file:

<system.web>
    <authentication mode="Windows"/>
    <identity impersonate="true"/>
</system.web>

This secures our Web Service with Integrated Windows authentication. In order to test whether our Web Service has been set up, browse the IWAWebservice.asmx file from a browser. You might need to browse the Web Service from a computer that is not part of your domain. Why? Because if you browse it from a computer on which someone has already logged into the domain, the browser will transparently pass your logged-in user’s credentials in response to the server challenge, and no password dialog box will be shown.

Browsing the IWAWebservice.asmx file from a computer that is not connected to your domain causes the following dialog to pop up.

image008.jpg

This shows that our Web Service will only be accessible if you provide valid Windows account credentials.

Similarly, if we don’t pass valid credentials and try to access the Web Service in the btnTestIWA_Click handler in the testws.aspx page, then it will generate the following error.

image009.jpg

Following is the code that generates the error because we are not passing credentials with our method invocation request:

CSWebservices.IWAWebservice
  objws = 
new
  CSWebservices.IWAWebservice() ;
CSWebservices.MyIdentity
  objIdentity ;
objIdentity
  = objws.GetMyIdentity() ;

In order to access a Web Service that has been protected by some authentication mechanism, we use the NetworkCredential object to pass those credentials. Following is the complete code from the testws.aspx page, which accesses the IWAWebservice.asmx Web Service by passing the valid credentials of the test user that we created for testing purposes:

private void btnTestIWA_Click(object sender, System.EventArgs e)
{
	// Create Web Service object.
CSWebservices.IWAWebservice objws = 
new CSWebservices.IWAWebservice() ;

	// Create credentials object.	
NetworkCredential objCredential = 
new NetworkCredential("Test", "test", "yourdomain") ;

// Let Web Service know about your credentials.
objws.Credentials = objCredential ;
	
	// Call method on Web Service.
CSWebservices.MyIdentity objIdentity ;
	objIdentity = objws.GetMyIdentity() ;
	
	........................
}

So after updating the code in btnTestIWA_Click handler, if we browse to testws.aspx page and click on the Test Integrated Windows Authentication button, we should see a page similar to the following.

image010.jpg

Basic and Basic with SSL Authentication

The problem with Integrated Windows authentication is it uses the NTLM/Kerberos protocol for authentication purposes. For users to have one of these protocols, they must be Windows clients. (For example, Unix clients do not understand NTLM.) Some of our Web Service clients may not be aware of this protocol and will not be able to access our Web Service! We are limiting our clients. One way to circumvent this problem is to use basic authentication.

The basic authentication mechanism is different from Integrated Windows authentication because it does not require clients to compute hash for the authentication purposes.

Basically during the Integrated Windows authentication process, the client machine computes a hash value by encrypting the user’s credentials and sends it to the server. Instead, in basic authentication, the user is prompted for a username and password. This information is then transmitted to the server, but first it is encoded using base64 encoding. Most of the browsers, proxy servers, and Web servers support this method, but it is not secure. Anyone who knows how to decode a base64 string can decode users’ credentials.

Basic authentication itself is not secure, but if we use it with the secure hypertext transfer protocol (HTTPS), which encrypts all communication between the client and server, it becomes quite useful. The beauty of this option is we can easily use it on the Internet without facing any problem from proxy servers, Web servers, etc. We can enable basic authentication by using the Authentication Methods dialog box. Now we will enable basic authentication for the BAWebservice.asmx Web Service. Open the Authentication Methods dialog box for the BAWebservice.asmx Web Service, uncheck the Anonymous access and Integrated Windows authentication check boxes, and check the Basic authentication (password is sent in clear text) check box. The dialog should look like the following.

Click OK. We had already enabled Windows authentication in the Web.config file, therefore we don’t have to do anything more. We have successfully enabled basic authentication for our Web Service.

Now if we try to access the BAWebservice.asmx file from a browser, the following dialog box will be displayed.

image012.jpg

We are not using Integrated Windows authentication, therefore this dialog box will be shown even on a machine logged into the domain.

Code for accessing the BAWebservice.asmx Web Service is the same as the code used for IWAWebservice.asmx. The following page will be displayed after clicking the Test Basic Authentication button on the testws.aspx page:

image013.jpg

Digest Authentication

Digest authentication is a new type of authentication that is available on Windows 2000 domains, and only IE5 or later clients can use it. In this type of authentication, user’s credentials are not sent on the wire in the form of text, rather credentials are encrypted using a hashing mechanism called Message Digest 5 (MD5). This is a good option for Internet Web Services, but the client and server requirements limit its adoptability. On the server side, you will need the Windows 2000 domain to have all the user accounts stored in the Active Directory; on the client side, you have to have either the .NET platform or IE 5.0 or later.

If we plan to use a custom Windows account for the worker process, then we must make sure that the account has proper rights on different directories because ASP.NET needs to read and write files to/from different directories.

  • Forms Authentication: With forms authentication, custom logic can be built into an ASP.NET application. The following happens when forms authentication is used in an ASP.NET application:
    • When a user requests a page for the application, ASP.NET checks for the presence of a special session cookie.
    • If the cookie is present, ASP.NET assumes the user is authenticated and processes the request.
    • If the cookie isn’t present, ASP.NET redirects the user to a web form where the custom logic has been built into the code. The authentication checks can be incorporated into the web form, and when the user is authenticated ASP.NET needs to be informed of the same by setting a property. Once this is done, ASP.NET creates the special cookie to handle any subsequent requests.

.NET framework policy defines what resources code in executing assemblies may access.

Policy in .NET Framework is automatically installed on every machine for each user account.

It can be deployed across windows domains via Group Policy.

The default security policy shipped with .NET Framework is is intended to create a safe execution environment for a typical end user. It can be customized by sufficiently privileged administrative accounts to address unique needs.

Windows Authentication in ASP.NET Core

Last Modified: 2017-03-28

Using Windows Authentication in ASP.NET Core Web Applications

ASP.NET Core Windows Authentication

Note that some of the content does not apply to RC1 or earlier versions and may not apply to later versions either.

General

  • https://docs.asp.net/en/latest/security/authentication/index.html
  • https://blogs.msdn.microsoft.com/webdev/2016/03/11/first-look-authentication-in-asp-net-core/
  • https://docs.asp.net/en/latest/fundamentals/servers.html
  • https://docs.asp.net/en/latest/publishing/iis.html
  • https://github.com/aspnet/Announcements/issues/204

Enable Windows Authentication

The server running the application must be configured to enable windows authentication and disable anonymous authentication.
If anonymous authentication is enabled, then it will be used by default and no user information is collected or required.

Hosting Options

  • IIS + Kestrel: Windows authentication is configured in IIS (or Properties\launchSettings.json when debugging with Visual Studio and IIS Express).
  • WebListener: Windows authentication is configured in web host builder programmatically.

At the time of writing, windows authentication only works when the server is hosted on the Windows platform (IIS and WebListener are Windows-only).

Take a look at ASP.NET Core Hosting for setting up either hosting option.

Sources:

  • https://docs.asp.net/en/latest/fundamentals/servers.html

WebListener

When using WebListener, you need to set up the authentication scheme in WebListener options in Program.cs:

using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Net.Http.Server;

public static void Main(string[] args)
{
	var host = new WebHostBuilder()
		.UseWebListener(options =>
		{
			options.ListenerSettings.Authentication.Schemes = AuthenticationSchemes.NTLM; // <--
			options.ListenerSettings.Authentication.AllowAnonymous = false; // <--
		})
		.UseStartup<Startup>()
		.Build();

	host.Run();
}

Note: installing package Microsoft.Net.Http.Server from NuGet is required for accessing the AuthenticationSchemes class.

Sources:

  • https://github.com/aspnet/Announcements/issues/204
  • http://stackoverflow.com/questions/37694211/windows-authentication-with-asp-net-core

IIS Integration

When using IIS Integration (Express or not), there are some configuration options that you can tweak.
Add configuration in Startup.cs in the ConfigureServices method:

services.Configure<IISOptions>(options => {
	//options.AuthenticationDescriptions holds a list of allowed authentication schemes
	options.AutomaticAuthentication = true;
	options.ForwardClientCertificate = true;
	options.ForwardWindowsAuthentication = true;
});

All three options default to true at least when running on IIS Express through Visual Studio.

Source: https://docs.asp.net/en/latest/fundamentals/servers.html

IIS Express (when Debugging from Visual Studio)

In visual studio, right-click into the project properties and select the Debug tab.
Check “Enable Windows Authentication” and uncheck “Enable Anonymous Authentication”

The values are stored in Properties\launchSettings.json:

{
  "iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": false,
    ...
  },
  ...
}

Making this change also forces forwardWindowsAuthToken to true in web.config (aspNetCore-element under system.webServer) each time you start the app in debug mode.

IIS

Enable windows authentication in IIS application host configuration file which can be found in the system32\inetsrv directory.

NOTE: IIS Express application configuration file lives in $(solutionDir)\.vs\config\applicationhost.configsource when using Visual Studio 2015 (or %userprofile%\documents\iisexpress\config\applicationhost.config or somewhere else when using an earlier version).
TODO not verified using IIS Express directly. The configuration does not affect the behaviour of IIS Express when debugging through Visual Studio.

The correct section can be found in configuration -> system.webServer -> security -> authentication -> windowsAuthentication.

The configuration should look as follows.

<windowsAuthentication enabled="true">
    <providers>
        <add value="Negotiate" />
        <add value="NTLM" />
    </providers>
</windowsAuthentication>

TODO May have to remove the Negotiate provider as per http://stackoverflow.com/questions/36946304/using-windows-authentication-in-asp-net?

Windows authentication can also be enabled using the Internet Information Services Manager:
Go to the site’s Authentication settings, enable Windows Authentication and disable Anonymous Authentication.

Make sure that the forwardWindowsAuthToken is set to true in web.config (aspNetCore-element under system.webServer).

Sources:

  • https://docs.asp.net/en/latest/publishing/iis.html
  • http://www.codeproject.com/Tips/1022870/AngularJS-Web-API-Active-Directory-Security
  • http://stackoverflow.com/questions/4762538/iis-express-windows-authentication
  • http://stackoverflow.com/questions/36946304/using-windows-authentication-in-asp-net
  • http://www.danesparza.net/2014/09/using-windows-authentication-with-iisexpress/

Identity Impersonation

TODO For accessing further resources such as an SQL DB or other APIs with windows authentication.

Sources:

  • http://stackoverflow.com/questions/35180871/asp-net-core-1-0-impersonation
  • https://aleksandarsimic.wordpress.com/2016/07/21/asp-net-core-1-0-iis-impersonation/

Accessing User Information

CSHtml

You can access user identity in .cshtml files by using, for example:

<pre>@Html.Raw(Json.Serialize(User, new Newtonsoft.Json.JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore }))</pre>
<p>Name: @User.Identity.Name</p>
<p>Authenticated: @User.Identity.IsAuthenticated</p>

If you need to access the HttpContext, you need to add the HttpContextAccessor service in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
  ...
  services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
  ...
}

And in cshtml:

@inject IHttpContextAccessor httpContextaccessor

<pre>@Html.Raw(Json.Serialize(HttpContextAccessor.HttpContext.User.Identity, new Newtonsoft.Json.JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore }))</pre>

Source: http://stackoverflow.com/questions/38945678/access-cookie-in-layout-cshtml-in-asp-net-core

In MCV or WebAPI Controllers

var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var userName = User.FindFirstValue(ClaimTypes.Name);
var userName2 = User.Identity.Name;

Requires package Microsoft.AspNetCore.Identity

Sources:

  • http://stackoverflow.com/questions/30701006/how-to-get-the-current-logged-in-user-id-asp-net-core

JavaScript

There is no way that I came across to get at the windows user information directly in JavaScript, except by injecting through script tags and cshtml.

Source: http://stackoverflow.com/questions/3013692/getting-windows-username-with-javascript

Calling API Methods from JavaScript

Make sure you include credentials in calls, e.g. with fetch:

fetch("/api/SampleData/WeatherForecasts", { credentials: 'include' })
  .then(response => { ... });

Local groups:

  • Local groups are written without the domain part or prefixed with the host name: <group> or <hostname>\<group>.
  • Built-in local groups (e.g. BUILTIN\Administrators) are not recognized by name.
    You have to write the corresponding SID instead.
  • You can find out the SIDs by using the PsGetSid tool: https://technet.microsoft.com/en-us/sysinternals/bb897417.
  • The BUILTIN\Administrators group is not recognized even when using the correct SID.

Group membership shows as role membership in ASP.NET Core.
You can enforce group membership directly with the Authorize attribute, with an authorization policy, or programmatically in the controller methods.

Authorize Attribute

Add [Authorize(Roles = @"<domain>\<group>")] attribute (or [Authorize(Roles = @"<domain>\<group1>,<domain>\<group2>")] for multiple allowed roles) to the controller or method.

Sources:

  • https://docs.asp.net/en/latest/security/authorization/roles.html

Authorization Policy

Add a new policy to service configuration in ConfigureServices method in Startup.cs:

services.AddAuthorization(options =>
{
  options.AddPolicy("RequireWindowsGroupMembership", policy => policy.RequireRole(@"<domain>\<group>"));  
});

To get the required group name from settings, add the group name into appsettings.json (note the double backslashes):

{
  "Logging": {
    ...
  },
  "WindowsGroup": "<domain>\\<group>"
}

Then read it in when configuring authorization:

services.AddAuthorization(options =>
{
  var windowsGroup = Configuration.GetValue<string>("WindowsGroup");
  options.AddPolicy("RequireWindowsGroupMembership", policy =>
  {
    policy.RequireAuthenticatedUser(); // Policy must have at least one requirement
    if (windowsGroup != null)
      policy.RequireRole(windowsGroup);
  });
});

Use a comma-separated string for multiple allowed roles: <domain>\<group1>,<domain>\<group2>.

Finally, add the authorize-attribute on the controller or method: [Authorize(Policy = "RequireWindowsGroupMembership")]

Sources:

  • https://docs.asp.net/en/latest/security/authorization/roles.html

The policy syntax allows for more elaborate authorization scenarios with custom requirements, such as activity/permission-based authentication

  • https://docs.asp.net/en/latest/security/authorization/policies.html
  • https://lostechies.com/derickbailey/2011/05/24/dont-do-role-based-authorization-checks-do-activity-based-checks/
  • http://benjamincollins.com/blog/practical-permission-based-authorization-in-asp-net-core/
  • http://benfoster.io/blog/asp-net-identity-role-claims

Programmatically

Check for role membership in controller method and return 403 Forbidden status code if not authorized.

[HttpGet("[action]")]
public IActionResult SomeValue()
{
  if (!User.IsInRole(@"Domain\Group")) return StatusCode(403);
  return Ok("Some Value");
}

Note that the return type of the method must be IActionResult.

Browser Settings

If you need automatic windows authentication, then you may have to enable it specifically in the client browser

  • IE (TODO verify same works in EDGE)
    • Advanced -> Enable Integrated Windows Authentication in Internet Options
    • Security -> Local intranet -> Custom level -> User Authentication -> Automatic logon / Prompt for user name and password
  • Chrome
    • Chrome uses settings in Windows’ internet options so the IE options should sufficesource
  • Firefox
    • about:config -> network.automatic-ntlm-auth.trusted-uris -> add url of application

Sources:

  • http://www.codeproject.com/Tips/1022870/AngularJS-Web-API-Active-Directory-Security
  • http://stackoverflow.com/questions/36946304/using-windows-authentication-in-asp-net

Different Domain or No Domain Binding

TODO I did not get this to work from a remote site, with or without VPN connection (flashes a new console window and dies instantly, unable to capture error message)

If you are developing on a computer that is not bound to a domain, or is bound to a different domain that the app should authenticate against, you can run the server like so:

runas /netonly /user:<user> "<command> <args...>"

where <user> is domain\username or username@domain.

IIS: you must establish trust between the two domains to be able to run app pools under a user in different domain than the server.

IIS: does this work at all when running as network service??

Sources:

  • http://codebetter.com/jameskovacs/2009/10/12/tip-how-to-run-programs-as-a-domain-user-from-a-non-domain-computer/
  • http://stackoverflow.com/questions/4762538/iis-express-windows-authentication
  • http://stackoverflow.com/questions/5331206/how-to-run-iisexpress-app-pool-under-a-different-identity
  • http://stackoverflow.com/questions/22058645/authenticate-against-a-domain-using-a-specific-machine/22060458#22060458
  • https://forums.iis.net/t/1213147.aspx?How+I+can+run+IIS+app+pool+by+domain+account+
  • https://blogs.msdn.microsoft.com/ssehgal/2009/06/23/running-iis6-app-pools-under-a-domain-account-identity/

Probably almost all of you have developed or are developing ASP.NET applications that allow users to manage their own data and resources in a multi-user environment. These will require that each user has his own user name and password, which he uses to log into the web application, and access his information.

To accomplish this, you may be using, or have used, ASP.NET Forms authentication. The user enters his username and password in the login page and, after they are authenticated against some database tables, he is ready to operate.

In this article I would like to propose a different schema that relies on users’ Windows accounts rather than Forms authentication, and show the benefits that this approach can offer.

We will consider only those ASP.NET applications that are owned by an organization in which all users have their own Windows account, maybe stored in the company’s Active Directory.

Authentication and Authorization

When we create a web application, we want to expose the application’s users to information. This might be text, data, documents, multimedia content, and so on. Sometimes, we also need to manage access to this information, restricting certain users’ access to some of them. This is where authentication and authorization come in.

Before presenting this Windows account authentication and authorization proposal, I would like to define what authentication and authorization mean, the difference between the two and how the .NET Framework manages them. If you are already confident with these concepts you can skip to the next section.

Authentication

Generally speaking, Authentication is the ability to identify a particular entity. The need for authentication occurs when we have some resources that we want to make available to different entities. We store these resources in a centralized place and instruct the system that manages them to prevent entities that we don’t recognize from having access. Anonymous authentication refers to a situation in which we grant access to resources to all users, even if we don’t know them.

In web applications, we expose resources to users. We authenticate each user by requesting his credentials, normally a username and password, that we have assigned to him, or that he got during what we call the registration process.

The .NET Framework uses the following authentication terminology:

  1. Principal: this represents the security context under which code is running. Every executing thread has an associated principal.
  2. Identity: this represents the identity of the authenticated user. Every Principal has an associated identity.

It also defines the following classes, contained in the System.Security assembly:

  1. GenericPrincipal, WindowsPrincipal
  2. GenericIdentity, WindowsIdentity

As their names suggest, WindowsPrincipal and WindowsIdentity are related to Principals and Identities associated with a Windows account, while GenericPrincipal and GenericIdentity are related to generic authentication mechanisms. GenericPrincipal and WindowsPrincipal implement the IPrincipal interface, while GenericIdentity and WindowsIdentity implement the IIdentity interface.

Authorization

Authorization is the ability to grant or deny access to resources, according to the rights defined for the different kinds of entities requesting them.

When dealing with Windows Operating System, and its underlying NTFS file system, authorizations are managed by assigning to each object (files, registry keys, cryptographic keys and so on) a list of the permissions granted to each user recognized by the system.

This list is commonly called the “Access Control List” or ACL (the correct name is actually “Discretionary Access Control List” or DACL, to distinguish it from the “System Access Control List” or SACL). The ACL is a collection of “Access Control Entries” or ACEs. Each ACE contains the identifier for a specific user (“Security Identifier” or SID) and the permissions granted to it.

As you probably already know, to view the ACL for a specific file, you right-click the file name, select Properties and click on the Security tab. You will see something like this:

Figure 1: ACL editor for a demo file.

The “Group or user names” section lists all the users and groups, by name, which have at least one ACE in the ACL, while the “Permissions” section lists all the permissions associated with a specific group or user (or, rather, with its SID). You can modify the ACL by pressing the Edit button.

To view the ACL of a specific file using the .NET Framework, you can use the FileSecurity class that you can find under the System.Security.AccessControl namespace. The following example shows how to browse the ACL of a file named “C:\resource.txt”:

 FileSecurity f = File.GetAccessControl(@»c:\resource.txt»);

AuthorizationRuleCollection acl = f.GetAccessRules(true, true, typeof(NTAccount));

 foreach (FileSystemAccessRule ace in acl)

 {

      Console.WriteLine(«Identity: « + ace.IdentityReference.ToString());

 Console.WriteLine(«Access Control Type: « + ace.AccessControlType);

 Console.WriteLine(«Permissions: « + ace.FileSystemRights.ToString() + «\n»);

By running this code in a console application, you get the following output:

Figure 2: Output of a console application that lists the ACEs of a demo file.

Authentication in IIS 7 and 7.5

With definitions out the way, we’re ready to see how to setup a Windows account authentication and authorization schema in an ASP.NET application. First, we’ll look at how authentication with Windows accounts works.

It’s important to note that this type of authentication doesn’t involve the ASP.NET engine. It works at the Internet Information Server (IIS) level instead, so all that’s required is the correct IIS configuration. The authentication types available in IIS can be viewed by using the IIS Manager:

1268-Figure3.jpg

Figure 3: List of all authentication methods implemented in IIS 7.0 and 7.5.

Anonymous Authentication: this is the most commonly used type of authentication. With it, all users can access the web site.

ASP.NET Impersonation: this is not really an authentication method, but relates to authorizations granted to a web site’s users. We will see later how impersonation works.

Basic Authentication: this is a Windows account authentication, in the sense that the user needs to have a username and password, recognized by the operating system, to use the application. When the user calls a web page, a dialog box asking for his credentials appears. If the user provides valid credentials for a valid Windows account, the authentication succeeds. This type of authentication is not considered secure because authentication data is transmitted to the server as plain text.

Digest Authentication: this is similar to Basic Authentication, but more secure. Authentication data is sent to the server as a hash, rather than plain text. Basic Authentication and Digest Authentication are both standardized authentication methods. They are defined in RFC 2617.

Forms Authentication: this is ASP.NET’s own authentication, based on the login page and the storage of users’ credentials in a database, or similar location.

Windows Authentication: this type of authentication uses the NTLM or Kerberos Windows authentication protocols, the same protocols used to log into Windows machines. As for Basic Authentication and Digest Authentication, the credentials provided by the user must match a valid Windows account.

There are two other authentication methods that I have not mentioned here: Active Directory Client Certificate Mapping Authentication and IIS Client Certificate Mapping Authentication. Both use the X.509 digital certificate installed on the client; how they work is outside the scope of this article.

For the purpose of this article, we can use Basic Authentication, Digest Authentication or Windows Authentication, each of which relies on Windows accounts. When they’re used, the current executing thread is associated with a Principal object that is able to give us information about the authenticated user. I wrote a simple application that shows you how to do that. Its source code is available at the top of this article as a zip file.

The application defines a method, called WritePrincipalAndIdentity(), which give us the following information:

  1. The name of the authenticated user.
  2. The user’s role, by checking its role membership.
  3. The type of authentication performed.

The method’s body is given by:

/// <summary>

/// Explore the authentication properties of the current thread.

/// </summary>

public void WritePrincipalAndIdentity()

{

IPrincipal p = Thread.CurrentPrincipal;

IIdentity i = Thread.CurrentPrincipal.Identity;

WriteToPage(«Identity Name: « + i.Name);

    WriteToPage(«Is Administrator: « + p.IsInRole(@»BUILTIN\Administrators»));

WriteToPage(«Is Authenticate: « + i.IsAuthenticated);

WriteToPage(«Authentication Type: « + i.AuthenticationType);

WriteToPage(«&nbsp»);

}

Where the WriteToPage() method is a helper method that encapsulates the logic needed to write text inside the page.

Rather than using Thread.CurrentPrincipal, we could use the User property of the Page object to achieve the same result. I prefer to use the Thread.CurrentPrincipal, to point out that the principal is always associated with the executing thread. The importance of this will be clearer in the Role-Based Security Paragraph.

When we run this application, using, for example, digest authentication (remembering to disable the anonymous authentication) the logon window ask us for our credentials.

1268-Figure4.jpg

Figure 4: Logon dialog box. To access the web site we need a valid account defined in a domain named CASSANDRA.

If we provide a valid account defined in the CASSANDRA domain we will able to log on to the application. Once we’ve provided it, we obtain something like this:

Figure 5: Demo web application’s output.

Figure 5 shows that the identity of the user who performed the request has been authenticated. It also shows his user name is “CASSANDRA\matteo”, the domain account used to perform the request, that the authentication method used was “Digest Authentication”, and that the user is not an administrator.

Suppose that we need to write a web application that associates the user with his own data, for example a list of contacts or some appointments. It easy to see that, at this stage, we have all the information needed to manage all the data (contacts or appointments) related to a single user. If we save all of them in a database using the username (or better a hash of it) provided by the authentication stage as the table key, we are able to fill all the application’s web pages with only the user’s specific content, as we do with Forms authentication. This is possible without having to write any lines of code.

Another important advantage comes from the fact that, by using the Principal object, we are able to check if an authenticated user belongs to a specific security group. With this information, we can develop applications that are “role-enabled”, in the sense that we can allow a specific user to use only the features available for his role. Suppose, for example, that the web application has an admin section and we want to allow only administrators to see it: we can check the role of the authenticated user and hide the links to the admin page if the user is not an administrator. If we use Active Directory as container for users’ credentials, we can take advantage of its ability to generate group structures flexible enough to generate role-based permissions for even very heterogeneous kinds of users.

However, from a security point of view, authentication alone is not enough. If, for example, we hide the link to the admin page for non-administrator users, they can nonetheless reach the admin page using its URL, breaking the security of the site. For this reason, authorization plays a very important role in designing our application. We will now see how to prevent this security issue occurring.

Authorization in ASP.NET Applications

Suppose that we have a file, “resource.txt”, inside the web application root that we want to make available only to administrators. We can prevent users who aren’t administrators from accessing the file by setting up its ACL properly. For simplicity, let’s say we want to prevent “CASSANDRA\matteo” accessing it. Figure 6 shows how to do that:

Figure 6: ACL for the CASSANDRA\matteo user with denied permissions.

We have denied the Read and Read & execute attributes to the CASSANDRA\matteo account, but we want to see what happens when our demo application tries to open the file. To do so, we add a new method to it:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

/// <summary>

/// Check if a resource can be loaded.

/// </summary>

public void CanLoadResource()

{

FileStream stream = null;

 try

{

stream = File.OpenRead(Server.MapPath(«resource.txt»));

WriteToPage(«Access to file allowed.»);

}

 catch (UnauthorizedAccessException)

{

WriteException(«Access to file denied.»);

}

finally

{

if (stream != null) stream.Dispose();

}

}

The CanLoadResource() method tries to open resource.txt, in order to read its content. If the load succeeds, the “Access to file allowed.” message is written on the page. If an UnauthorizedAccessException exception is thrown, the message “Access to file denied.” is written on the page, as an error. The WriteException() method is a helper method used to write an exception message on the page.

Now we launch our application with authorizations set as in Figure 6 and use “CASSANDRA\matteo” to log into the application. Doing that, we obtain something that should sound strange:

1268-Figure7a.jpg

Figure 7: Logon with user CASSANDRA\matteo with permissions as in Figure 6.

As you can see in the Figure 7, resource.txt can be loaded by the application even if the credentials provided for the login refer to an account that has no permissions to access it.

This happens because, in this case, the Application Pool associated with the web application works in Integrated mode, which relates authentication and authorization to different users. Specifically, authentication involves the user identified by the credentials provided, while authorization involves the user account used by the Application Pool associated with the application. In our example, the Application Pool uses the NETWORK SERVICE account, which has permission to access the file.

We’ll try to deny these permissions by modifying the ACL of the resources.txt file:

Figure 8: ACL for the NETWORK SERVICE account with denied permissions.

If we launch our application, we now obtain:

1268-Figure9a.jpg

Figure 9: Logon with user CASSANDRA\matteo, still with the permissions in Figure 8.

As you can see, the file is no longer available, demonstrating that the authorization process involves the NETWORK SERVICE account.

To use authorization at the authenticated user level, we need to use Impersonation. With impersonation, we are able to allow the Application Pool to run with the permissions associated with the authenticated user. Impersonation only works when the Application Pool runs in Classic Mode (in Integrated mode the web application generates the “500 – Internal Server Error” error). To enable impersonation, we need to enable the ASP.NET Impersonation feature, as noted in Figure 3 and the discussion that followed it.

If we switch our Application Pool to Classic Mode (enabling the ASP.NET 4.0 ISAPI filters, too) and enable ASP.NET impersonation, the demo application output becomes:

1268-Figure10.jpg

Figure 10: Logon with user CASSANDRA\matteo, with permissions as in Figure 8 and Application Pool in Classic Mode.

We are now able to load resource.txt even if the NETWORK SERVICE account has no permissions to access it. This shows that the permissions used were those associated with the authenticated user, not with the Application Pool’s identity.

To take advantage of Integrated mode without having to abandon impersonation, we can use a different approach: running our application in Integrated mode and enabling impersonation at the code level when we need it. To do so, we use the WindowsImpersonationContext class, defined under the System.Security.Principal namespace. We modify the CanLoadResource() method as follows:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

/// <summary>

/// Check if a resource can be loaded.

/// </summary>

public void CanLoadResource()

{

FileStream stream = null;

WindowsImpersonationContext imp = null;

 try

{                IIdentity i = Thread.CurrentPrincipal.Identity;

imp = ((WindowsIdentity)i).Impersonate();

stream = File.OpenRead(Server.MapPath(«resource.txt»));

WriteToPage(«Access to file allowed.»);

}

 catch (UnauthorizedAccessException)

{

WriteException(«Access to file denied.»);

}

finally

{

if (imp != null)

{

imp.Undo();

imp.Dispose();

}

if (stream != null) stream.Dispose();

}

}

With the modification added, we can force the application to impersonate the authenticated user before opening the file. To achieve this, we have used the Impersonate() method of the WindowsIdentity class (the class to which the Identity property belongs). With it, we have created a WindowsImpersonationContext object. This object has a method, Undo(), that is able to revert the impersonation after the resource has been used.

If we try to run our application with permissions as in Figure 8, we see that we are able to access resource.txt even if the Application Pool is working in Integrated Mode.

Now we can resolve the security issue presented earlier. If we want to use Windows accounts to develop a “role-based” application, we can use authentication to identify the user requesting resources and we can use authorization, based on the user’s identity, to prevent access to resources not available for the user’s role. If, for example, the resource we want to protect is a web page (like the admin page), we need to set its ACL with the right ACEs, and use impersonation to force the Application Pool to use the authenticated user’s permissions. However, as we have seen, when the Application Pool uses Integrated mode, impersonation is available only at code level. So, although it’s easy in this situation to prevent access to resources (like the resource.txt file) needed by a web page, it’s not so easy to prevent access to a web page itself. For this, we need to use another IIS feature available in IIS Manager, .NET Authorization Rules:

1268-Figure7.jpg

Figure 11: .NET Authorization Rules feature of IIS7 and IIS7.5.

.NET Authorization Rules is an authorization feature that works at ASP.NET level, not at IIS or file system level (as for ACLs). So it permits us to ignore how IIS works and use Impersonation both in Integrated Mode than in Classic Mode.

I leave you to test how it works.

Role-Based Security

A further advantage of using Windows account authentication is the ability to use a .NET Framework security feature called Role-Based Security.

Role-Based Security permits us to protect our resources from unauthorized authenticated users. It relies on checking if an authenticated user belongs to a specific role that has authorization to access a specific resource. We have already seen how to do that: use the IsInRole() method of the thread’s Principal object.

The .NET Framework security team decided to align this type of security check to Code Access Security (which I wrote about in previous articles) by defining a programming model similar to it. Specifically, a class named PrincipalPermission, found under the System.Security.Permissions namespace, has been defined. It permits us to check the role membership of an authenticated user both declaratively (using attributes) and imperatively (using objects), in the same manner as CAS checks.

Suppose that we want resource.txt to be readable only by administrators. We can perform a declarative Role-Based security check in this way:

/// <summary>

///  Load a resource

/// </summary>

[PrincipalPermissionAttribute(SecurityAction.Demand, Name = «myname», Role = «administrators»)]

public void LoadResource()

{

.....

where “myname” is the username that we want to check.

If declarative Role-Based security is not what we need (because, in this case, we need to know the identity of the user first), we can use an imperative Role-Based security check:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

/// <summary>

/// Load a Resource

/// </summary>

public void LoadResource()

{

           try

     {

       // Create a PrincipalPermission object.

      PrincipalPermission permission =

       new PrincipalPermission(Thread.CurrentPrincipal.Identity.Name, «Administrators»);

     // Demand this permission.

      permission.Demand();

.....

     }

      catch (SecurityException e)

      {

      .....

      }

}

In both cases, if the user does not belong to the Administrators group, a security exception is thrown.

The PrincipalPermission class doesn’t add anything to our ability to check the permission of an authenticated user. In my opinion, the IsInRole() method gives us all the instruments we need, and is simpler to use. Despite this, I’ve included PrincipalPermission in this discussion for completeness. Maybe this is the same reason that the .NET development team added this type of class to the .NET Framework base classes.

I end this section by mentioning that Role-Based Security can even be implemented in desktop applications. In this case, the authenticated user is a user that logs into the machine.

When a desktop application starts, by default, the identity of the authenticated user is not “attached” to the executing thread. The Principal property of the current thread and the Identity property of the Principal property are set to GenericPrincipal and GenericIdentity respectively, and the Name property of the Identity property is empty.

If we launch the following code in a Console application:

static void Main(string[] args)

{

Console.WriteLine(«Type of Identity: « + Thread.CurrentPrincipal.Identity.GetType());

Console.WriteLine(«Identity Name: « + Thread.CurrentPrincipal.Identity.Name);

}

We get:

1268-Figure8.jpg

Figure 12: Default Identity in a Console Application.

So we see that the application is not able to recognize the user who has logged-in.

This is, however, a feature we can turn on. We need to modify the previous code as follows:

static void Main(string[] args)

{

AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

Console.WriteLine(«Type of Identity: « + Thread.CurrentPrincipal.Identity.GetType());

Console.WriteLine(«Identity Name: « + Thread.CurrentPrincipal.Identity.Name);

}

Launching the application, we now get:

1268-Figure9.jpg

Figure 13: Identity in a Console Application.

As you can see from Figure 13, Identity has been initialized with a WindowsIdentity object and the reference to the logged user has been added. We are now able to use Role-Based Security even in desktop applications.

Conclusion

In this article we have seen how Windows accounts can be used to implement authentication and authorization in ASP.NET applications. Even if this type of approach is rarely used, Forms Authentication being the commonly adopted solution, it can have a lot of advantages:

  1. Less code to develop and maintain. Authorization and authentication with Windows accounts does not require the developer to write specific code for the management of user credentials, authorizations, password recovery and so on.
  2. Centralization of user credentials, access rights, password policies, role-based policies and identity management in general. All the security information related to a specific user is stored in a centralized place, Active Directory. When a new employee arrives at an organization, permissions have to be added only in the Directory structure, not in each web server used by the company, making the authorization process simpler to manage.
  3. More security. In a decentralized security environment, sometimes users have to remember more than one username and password. Sometimes they are forced to write them down to remember them. Security experts think this is one of the most dangerous security issues. Moreover, if an employee with, say, ten accounts for ten different applications, stored in ten different places, leaves an organization, it’s easy to forget to remove all their credentials, allowing them to access, or even steal confidential data.

Introduction

It’s common for growing companies to offer multiple web sites and services. Initially, each site may have its own login system, or at the very least, certainly its own login page. As their user base grows, they may eventually find the need to consolidate their network of sites under a common login page and authentication framework. Examples of this include Google (Gmail, Drive, Store, FeedBurner), Microsoft (Outlook, Live, Bing), Yahoo (Mail, Finance, Answers), as well as many others. Unauthenticated users, accessing any of the associated web sites, are redirected to a common login page. The login page usually resides on a completely separate web service and domain, such as login.yahoo.com, login.live.com, or accounts.google.com. In the world of C# ASP .NET, this process is called federated authentication, and can be implemented using the Windows Identity Foundation library.

The Windows Identity Foundation (WIF) comes built into the .NET 4.5 framework. This makes it easy to get started, without the need for installing additional libraries. In this tutorial, we’ll walk through the steps of implementing federated authentication with single sign-on. We’ll create a C# MVC4 ASP .NET web site and a common authentication web site identity provider (also called a Secure Token Service or STS). The identity provider web site will provide the login form, using forms authentication, and redirect back to the calling web site.

At the end of this tutorial, we’ll have a web site using single sign-on via the Windows Identity Foundation that redirects users to login via a common authentication provider. Additional sites may be scaled in, using the same identity provider web site as needed.

Getting Started

We’ll start by creating our identity provider web site (STS). This web site will display the login form and allow a user to enter their username/password. After signing in, the user will be redirected back to the calling web site, as a fully authenticated user.

To get started, we’re going to need an SSL certificate for the identity provider. Specifically, we need the public key. We’ll host the public key as a metadata file on our identity provider web site (the relying party web sites will point to this metadata url in their web.config files). Luckily, we can generate a test cert fairly easily.

Creating an Identity Provider Certificate

First things first. We begin the process by creating an initial SSL certificate that will be used by the identity provider. A test certificate can be created in a variety of ways, such as by using IIS to generate a test cert or by using a tool, such as Pluralsight’s SelfCert creator.

For this example, we’ll use the SelfCert tool and create a test certificate. When creating the certificate, specify the value for “X.500 distinguished name” to be: “cn=customsts.dev”. Choose to save the cert in the location “LocalMachine” under the store “My”. This will ensure the certificate is accessible by the identity provider, when running under the LocalSystem application pool (we’ll get to that in a bit).

Single Sign-on SelfCert tool to create a test certificate

If you get an access violation error while trying to store the certificate, you can simply save the certificate to your desktop and then double-click it to import and store it. Again, be sure to select “Local Machine” as the storage location.

With the certificate created and stored, next we need to grab the public key. To do this, open a command prompt and type “mmc.exe”. Then click File->Add/Remove Snap-in. Select Certificates and click Add. Select “Computer account” and click Next, then click Finish.

Navigate to Certificates/Personal/Certificates and you should see “customsts.dev” located in the list along the right-side. Double-click the certificate to open the properties. Click the “Details” tab and then click “Copy to File” to export the certificate.

In the export wizard, select “No, do not export the private key”, since we’re only interested in the public key. Then choose “Base-64 encoded X.509 (.CER)” as the export type. Save the exported file to your desktop and open it in Notepad. We’ll need the contents of this key for our identity provider web site’s metadata file.

Creating the Identity Provider Web Site

Hooray, we’ve got a public key. Although that was quite a bit of work, we’re now ready for the next step. It’s time to create the STS web site that will host our login form.

We’ll begin by creating a new MVC4 C# ASP .NET web site named “SingleSignOn”. In the web project, create a new folder called “Federation”. Inside the folder, add a new XML file called “metadata.xml”. For the contents of this file, paste in the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

<entitydescriptor entityid="http://customsts.dev/" id="_70a250d5-e3e1-494a-a392-7ed1736f3180" xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
<roledescriptor protocolsupportenumeration="http://docs.oasis-open.org/wsfed/federation/200706" xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="fed:SecurityTokenServiceType">
<keydescriptor use="signing">
<keyinfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<x509data>
<x509certificate>
!!! PASTE THE CONTENTS OF YOUR EXPORTED PUBLIC KEY HERE !!!
!!! Everything between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- !!!
</x509certificate>
</x509data>
</keyinfo>
</keydescriptor>
<contactperson contacttype="administrative">
<givenname>Your Name</givenname>
</contactperson>
<fed:claimtypesoffered>
<auth:claimtype optional="true" uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
<auth:displayname>Name</auth:displayname>
<auth:description>The name of the subject.</auth:description>
</auth:claimtype>
<auth:claimtype optional="true" uri="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
<auth:displayname>Role</auth:displayname>
<auth:description>The role of the subject.</auth:description>
</auth:claimtype>
</fed:claimtypesoffered>
<fed:securitytokenserviceendpoint>
<endpointreference xmlns="http://www.w3.org/2005/08/addressing">
<address>http://customsts.dev/</address>
</endpointreference>
</fed:securitytokenserviceendpoint>
<fed:passiverequestorendpoint>
<endpointreference xmlns="http://www.w3.org/2005/08/addressing">
<address>http://customsts.dev/</address>
</endpointreference>
</fed:passiverequestorendpoint>
</roledescriptor>
</entitydescriptor>

Note the placeholder for your public key string in the above xml. You should copy and paste the public key string from your exported certificate file into the appropriate space above. Be sure not to include the “Begin Certificate” and “End Certificate” tags. Also, take note not to accidentally copy the first xml descriptor line twice (a common mistake when copying/pasting xml).

Configuring the Identity Provider to Support Windows Identity Foundation

Next, we need to add the appropriate settings to our web.config file to enable Windows Identity Foundation. We’ll start by adding the following appSettings keys:

1
2
3
<add key="IssuerName" value="http://customsts.dev/" />
<add key="SigningCertificateName" value="CN=customsts.dev" />
<add key="EncryptionCertificate" value="" />

Next, set the Federation folder available for anonymous users. All users will need to be able to read this file in order to login with single sign on. We’ll deny access to all other pages on the STS identity provider web site, as follows:

1
2
3
4
5
6
7
<location path="Federation">
<system.web>
<authorization>
<allow users="*">
</allow></authorization>
</system.web>
</location>

Next, since our single sign-on common web site will use forms authentication, we need to enable it as follows:

1
2
3
4
5
6
<authentication mode="Forms">
<forms loginurl="/login" slidingexpiration="true" timeout="2880">
</forms></authentication>
<authorization>
<deny users="?"></deny>
</authorization>

Adding the Identity Provider Login Controller

We’re now ready to start coding the identity provider’s web controllers. First, add a reference to your project for System.IdentityModel and System.IdentityModel.Services.

Next, create a new LoginController, which will serve as the login form on the web site.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[HttpGet]
public ActionResult Index(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}

[HttpPost]
public ActionResult Index(LoginModel loginModel, string returnUrl)
{
if (ModelState.IsValid)
{
if (loginModel.Username == "user" && loginModel.Password == "password")
{
FormsAuthentication.SetAuthCookie(loginModel.Username, true);
return Redirect(returnUrl);
}
else
{
ModelState.AddModelError("", "The username or password provided is incorrect.");
}
}

ViewBag.ReturnUrl = returnUrl;

return View(loginModel);
}

In the above code, we have a simple hard-coded authentication check. You will probably want to extend this to authenticate against a database or web service. Note, we set the FormsAuthentication cookie, which sets up the MVC ASP .NET User object. After authentication has completed, we redirect to the returnUrl. In this case, it will actually be our Windows Identity Foundation redirect url, where our controller will set the necessary federation properties.

We’re using the following simple LoginModel for the login form:

1
2
3
4
5
6
7
public class LoginModel
{
[Required]
public string Username { get; set; }
[Required, DataType(DataType.Password)]
public string Password { get; set; }
}

With the login controller defined, we can create the associated login view, using the LoginModel as its backing class.

After authenticating and logging in, our Login controller method redirects to the returnUrl. Since we’re using Windows Identity Foundation, the returnUrl will be our root url and will include several QueryString parameters that indicate the type of WIF action that is being performed. In this case, the action will be “wsignin1.0”. Therefore, we’ll now begin creating the HomeController to process this message.

Adding the Identity Provider Home Controller

As was seen in the Login Controller, after authenticating, the user is redirected to the root page on the identity provider web site. It is here that federated authentication will take place via the Windows Identity Foundation framework.

WIF will provide several querystring parameters in the url, indicating the type of action being performed. The common ones include: wsignin1.0 and wsignout1.0. We can process these actions in our HomeController with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public const string Action = "wa";
public const string SignIn = "wsignin1.0";
public const string SignOut = "wsignout1.0";

public ActionResult Index()
{
if (User.Identity.IsAuthenticated)
{
var action = Request.QueryString[Action];

if (action == SignIn)
{
var formData = ProcessSignIn(Request.Url, (ClaimsPrincipal)User);
return new ContentResult() { Content = formData, ContentType = "text/html" };
}
else if (action == SignOut)
{
ProcessSignOut(Request.Url, (ClaimsPrincipal)User, (HttpResponse)HttpContext.Items["HttpResponse"]);
}
}

return View();
}

private static string ProcessSignIn(Uri url, ClaimsPrincipal user)
{
var requestMessage = (SignInRequestMessage)WSFederationMessage.CreateFromUri(url);
var signingCredentials = new X509SigningCredentials(CustomSecurityTokenService.GetCertificate(ConfigurationManager.AppSettings["SigningCertificateName"]));


var config = new SecurityTokenServiceConfiguration(ConfigurationManager.AppSettings["IssuerName"], signingCredentials);

var sts = new CustomSecurityTokenService(config);
var responseMessage = FederatedPassiveSecurityTokenServiceOperations.ProcessSignInRequest(requestMessage, user, sts);

return responseMessage.WriteFormPost();
}

private static void ProcessSignOut(Uri url, ClaimsPrincipal user, HttpResponse response)
{
var requestMessage = (SignOutRequestMessage)WSFederationMessage.CreateFromUri(url);

FederatedPassiveSecurityTokenServiceOperations.ProcessSignOutRequest(requestMessage, user, requestMessage.Reply, response);
}

The first item to note is our definition of actions, via the querystring parameters, for signing in and signing out. For sign-in, a formdata string is created, which gets returned back to the browser as a form post, thus completing the sign-in process. This technique is described in detail in a great blog post at Building a simple custom STS using VS2012 & ASP.NET MVC.

The HomeController uses a utility security class, CustomSecurityTokenService.cs (provided via the author in the above link), to handle processing the sign-in message. This class inherits from SecurityTokenService and provides the required methods for processing the sign-in request.

Note, since the MVC controller requires access to the HttpResponse object, we’ll need to store a copy of this earlier on in the MVC ASP .NET request chain. We can do this within BeginRequest in Global.asax.cs as shown below. The HttpContext.Items object is persisted for a single HTTP request, which is just long enough for us to access it in the controller.

1
2
3
4
protected void Application_BeginRequest(Object sender, EventArgs e)
{
HttpContext.Current.Items.Add("HttpResponse", Response);
}

Windows Identity Foundation Redirect Flow

If you monitor the network requests, upon accessing a protected page on the relying party web site, you’ll see the following series of request created from our Login and Home controllers:

  1. User accesses a protected page on the relying party web site:
1
2
3
4
5
http://localhost/user
302 redirect
http://customsts.dev/?wa=wsignin1.0&wtrealm...
/login?ReturnUrl=
200 OK

The final “200 OK” request is where our identity provider web site login form displays, allowing the user to enter their credentials on the central login form. After the user enters their details and submits the form, the following series of requests are issued:

  1. User enters their credentials on the central login page and clicks Submit:
1
2
3
4
5
6
7
POST http://customsts.dev/login?returnUrl=
302 redirect
http://customsts.dev/?wa=wsignin1.0&wtrealm...
200 OK
POST http://localhost:667 <= This is where the form data is posted back
302 redirect
http://localhost

The first POST checks the username and password and sets the FormsAuthentication cookie. It then issues a redirect to the returnUrl. Since we’re using Windows Identity Foundation, the returnUrl leads us to the root page on our identity provider web site. Since the user has been properly authenticated via forms authentication at this point, the root page processes the federated authentication and issues a form POST back to the relying web site, with the WIF STS security token (trust:RequestSecurityTokenResponseCollection) as part of the form post data.

The process flow is completed on the relying party site, upon processing the STS token and authenticating the user on the initiating relying party site.

Creating a Relying Party Web Site

With our central single sign-on web site defined using C# MVC ASP .NET Windows Identity Foundation and STS, we can now create our relying party web site (a web site that authenticates using the single sign-on identity provider site). To begin, create a new C# MVC4 ASP .NET web site. We’ll need to configure the web.config to use Windows Identity Foundation WIF. This can be done with the Visual Studio 2012 Identity and Access add-on or it can be configured manually, as shown below.

Above the appSettings section in the web.config, add the following configSection declarations:

1
2
3
4
<configsections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
</configsections>

Add the following appSettings:

1
2
3
<add key="ida:FederationMetadataLocation" value="http://customsts.dev/federation/metadata.xml" />
<add key="ida:Issuer" value="http://customsts.dev/" />
<add key="ida:ProviderSelection" value="productionSTS" />

Note, the first appSetting key provides the public url to our metadata XML file, created in the first step of this tutorial.

Next, if any pages on this web site will be public (not require a logged-in user), you can define them as being available to anonymous users with a location block, as follows:

1
2
3
4
5
6
7
<location path="public">
<system.web>
<authorization>
<allow users="*">
</allow></authorization>
</system.web>
</location>

The above code sets the path /public available for all (including non-authenticated) users. For all other pages, we’ll define them as protected, requiring a login. We’ll also disable the default authentication, allowing WIF to take over handling authentication. Add the following code under the system.web section:

1
2
3
4
<authorization>
<deny users="?" />
</authorization>
<authentication mode="None" />

Alternatively, if you want all pages to be public, except for certain folders, you can use the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<location path="user">
<system.web>
<authorization>
<deny users="?">
</deny></authorization>
</system.web>
</location>

<system.web>
...
<authorization>
<allow users="*" />
</authorization>
<authentication mode="None" />
...
</system.web>

Under the section system.webServer, we’ll need to define the WIF authentication modules, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<modules>
<remove name="FormsAuthentication" />
<add name="WSFederationAuthenticationModule" precondition="managedHandler" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<add name="SessionAuthenticationModule" precondition="managedHandler" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</modules><system.identitymodel>
<identityconfiguration>
<audienceuris>
<add value="http://localhost:667/" />
</audienceuris>
<issuernameregistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<trustedissuers>
<add name="http://customsts.dev/" thumbprint="123456789588c015487c239d8e37ee7adabcdefa" />

</trustedissuers>
</issuernameregistry>
<certificatevalidation certificatevalidationmode="None">
</certificatevalidation></identityconfiguration>
</system.identitymodel>
<system.identitymodel.services>
<federationconfiguration>
<cookiehandler requiressl="false">
<wsfederation issuer="http://customsts.dev/" passiveredirectenabled="true" realm="http://localhost:667/" reply="http://localhost:667/" requirehttps="false">
</wsfederation></cookiehandler></federationconfiguration>
</system.identitymodel.services>

Note, you can find the thumbprint string for the above configuration by opening mmc.exe (as shown in the steps in the first part of this tutorial), opening the properties of the identity model certificate, click the Details tab, and scroll down to the Thumbprint field. You’ll probably have to type this value in manually (mmc doesn’t appear to allow copy/paste), so type it carefully.

Also, add a reference to your project for System.IdentityModel and System.IdentityModel.Services.

Creating the Relying Party Controllers

We’ll create a plain HomeController to serve as the root landing web site page. This page will contain a link pointing to /login, allowing the user to manually sign in.

1
2
3
4
5
6
7
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}

For the login page, we’ll create a LoginController. We won’t actually need a view for this page, since we’ll be immediately redirecting to the identity provider web site. However, MVC will throw an error if a view does not exist. A default view can be added for the LoginController. Inside the login controller Index method we’ll add a call to the Windows Identity Foundation redirect call. This manually redirects the user to the central STS single sign-on login web page. Alternatively, you could simply direct the user to a protected page, such as /user, in which case the web.confing “location” permissions will automatically direct him to the identity provider login page, as well. You can test both scenarios by clicking the Sign In link and by navigating directly to /user as an unauthenticated user. Both actions will lead to the central federated authentication login page.

1
2
3
4
5
6
7
8
public class LoginController : Controller
{
public ActionResult Index()
{
FederatedAuthentication.WSFederationAuthenticationModule.RedirectToIdentityProvider("customsts.dev", "http://localhost:667/user", true);
return View();
}
}

Our third controller will be for protected (logged-in) user content. The UserController will have a main Index page, where we’ll simply display @User.Identity.Name for the authenticated logged-in user, and a Logout method. The Logout method will delete the cookie on the relying party web site and on the identity provider web site. It does this through another flow of redirects to the identity provider, in order to properly delete both cookies and clear out the authentication ticket.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UserController : Controller
{
public ActionResult Index()
{
return View();
}

public ActionResult Logout()
{
// Load Identity Configuration
FederationConfiguration config = FederatedAuthentication.FederationConfiguration;

// Sign out of WIF.
WSFederationAuthenticationModule.FederatedSignOut(new Uri(ConfigurationManager.AppSettings["ida:Issuer"]), new Uri(config.WsFederationConfiguration.Realm));

return View();
}
}

Setting Up IIS to Host the STS Identity Provider

With the web sites developed, we’ll want to try testing out the login process. First, you should set the proper security on the web folders by ensuring the user IIS_IUSRS has read access to both the identity provider STS web site and the relying party web site.

Next, open IIS and create a new Application Pool. Set the “Identity” property on the application pool to run under “LocalSystem”, so it can access the certificate (that we stored in the first step of this tutorial).

Create a new web site under “Sites”, named “customsts.dev”. Point it to the folder for your identity provider web site project. For the bindings, use the host name “customsts.dev” and choose port 80. Since we’re using a custom host name, we’ll also have to modify the system32/drivers/etc/hosts file to include a setting for 127.0.0.1 customsts.dev

Note, be sure to set the application pool for the customsts.dev web site to use the LocalSystem identity, as mentioned above.

Next, create a new web site under “Sites”, named “relyingparty1”. Point it to the associated folder in your web project. Set this site to run on port 667 (or one of your choosing — if you change the port, adjust the settings in the web site code accordingly).

Testing Single Sign-On

At last, we can open a web browser and navigate to http://localhost:667. You should see the public home page with a link to Sign In. Clicking the Sign In link directs the user to the central single sign-on STS site. Alternatively, navigating directly to the user-protected page http://localhost:667/user also directs the user to the central STS site, but does so via the authorization web.config setting.

Upon logging in, you’ll be redirected back to the relying party web site. This time, you’ll be automatically logged in and have access to the /user page. We’ve successfully completed single sign-on with forms authentication in C# MVC ASP .NET with Windows Identity Foundation and STS.

Troubleshooting Tips

While configuring single sign-on, you may come across a variety of issues or errors, many related to security and permissions. I’ve included some of the more common ones that I’ve experienced while configuring my own setup for enterprise single sign-on.

Error: Keyset does not exist.

Solution: The AppPool user needs permissions to the certificate store (the certificate must be added with a private key, then select the certificate, right-click certificate, select All Tasks, Manage Private Keys) or simply set the user to LocalSystem. Keep in mind the security implications of using LocalSystem for the certificate user, although this usually resolves this error.

Error: The issuer of the security token was not recognized by the IssuerNameRegistry. To accept security tokens from this issuer, configure the IssuerNameRegistry to return a valid name for this issuer.

Solution: Select web.config in Visual Studio, right-click web.config and choose “Open With”, select “Binary Editor”. Look for hidden control characters in the thumbprint. Verify thumbprint is all upper-case characters. Verify name (next to thumbprint) matches certificate issue name “CN=sub.domain.com” in web.config for single sign-on site and client relying party site. It is recommended to re-type the thumbprint manually into the web.config, using all capital letters (do not copy and paste the thumbprint from the certificate properties dialog into the web.config, as this will copy hidden control characters into the web.config thumbprint and cause the above mentioned error).

Error: Key not valid for use in specified state.

Solution: This error is due to the federated cookie being assigned to ‘/‘ on the same domain (probably localhost, during development). This error should go away on different hosted domains. You may also wish to try editing the settings for the client relying party site in IIS, by clicking Advanced Settings and setting LoadUserProfile=true.

Error: No certificate was found for subject Name CN=login.domain.com.

Solution: Use a fully distinguised CN name, including ALL parts: “CN=login.domain.com, O=one, W=two, T=three, Z=four” (no spaces, except one space after each comma).

Helpful Links

There are many bits and pieces of code samples for implementing Windows Identity Foundation and single sign-in in C# MVC ASP .NET with forms authentication. Many are customized instances for specific scenarios. For more details, check out the following helpful pages:

Using Claims-Identity with SimpleMembership in ASP.NET MVC
How To: Build Claims-Aware ASP.NET MVC Web Application Using WIF
Federated Identity with Multiple Partners
Building a simple custom STS using VS2012 & ASP.NET MVC

Download @ GitHub

Source code for the project is available on GitHub.

About the Author

This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Tascam us 366 driver windows
  • Как запустить приложение через командную строку windows 10
  • Не удается установить обновление windows из за ошибки 2149842973
  • Usb vid 090c pid 1000 rev 1100 драйвер windows 10
  • Bittorrent client for windows