Creating a layered design in an application is a fundamental element of modern software architecture. The goal is to promote the Separation of Concerns (SoC) design principle. Separation of Concerns The ideas behind SoC date back to Dijkstra's 1974 paper "On the role of scientific thought" . In computer science, separation of concerns (sometimes abbreviated as SoC) is a design principle for separating a computer program into distinct sections. Each section addresses a separate concern, a set of information that affects the code of a computer program. A concern can be as general as "the details of the hardware for an application", or as specific as "the name of which class to instantiate". A program that embodies SoC well is called a modular program. Modularity, and hence separation of concerns, is achieved by encapsulating information inside a section of code that has a well-defined interface. - Wikipedia SoC is a broad design principal th...
Sometimes it's useful to impersonate another user's credentials in ASP.NET. There are several scenarios where this is useful, most involve Windows Authentication in different parts (or all) of an application's architecture.
One scenario is using Windows Authentication in the Presentation Layer and needing to pass those credentials down to the next layer of the application (such as a WebAPI or WCF Service). When running an ASP.NET application in IIS, the default credentials the code executes under will the be credentials for the Application Pool. Sometimes that is fine; other times you may want to run some logic with a different set of credentials.
This technique only works when running an application in Windows.
When the user is authenticated in the UI application, you can store their WindowsIdentity data in a Scoped object. Any time you need to impersonate that user, you can do so using their WindowsIdentity object.
public class AuthenticationData
{
public IIdentity? WindowsIdentity { get; set; }
}
Configure that object for Dependency Injection in Program.cs. We also need to configure an HttpClient that has UseDefaultCredentials set to true and expose the IHttpContextAccessor as follows:
Below is an example of making an impersonated API call, so the Window Authenticated ASP.NET UI can call a Windows Authenticated API. In reality, the UI should rarely used Windows Authentication, however, it more often makes sense for internal APIs to use Windows Authentication.
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly AuthenticationData _authenticationData;
public List<WeatherForecast> WeatherForecastData { get; set; } = new List<WeatherForecast>();
public IndexModel(ILogger<IndexModel> logger, IHttpClientFactory httpClientFactory, AuthenticationData authenticationData)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
_authenticationData = authenticationData;
}
private HttpClient GetClient()
{
// retrieve the client we initialized in Program.cs that supports Windows Authentication
return _httpClientFactory.CreateClient("WindowsAuthenticationClient");
}
public async Task OnGet()
{
try
{
if (_authenticationData.WindowsIdentity != null)
{
var user = (WindowsIdentity)_authenticationData.WindowsIdentity;
if (user != null)
{
await WindowsIdentity.RunImpersonated(user.AccessToken, async () =>
{
var httpClient = GetClient();
var result = await httpClient.GetAsync("https://localhost/ImpersonationSampleAPI/WeatherForecast");
if (result.IsSuccessStatusCode)
{
var returnedWeatherForecastData = await result.Content.ReadFromJsonAsync<List<WeatherForecast>>();
if (returnedWeatherForecastData != null)
{
WeatherForecastData = returnedWeatherForecastData;
}
}
else
{
_logger.LogError(HttpUtility.HtmlDecode(await result.Content.ReadAsStringAsync()));
}
});
}
else
{
WeatherForecastData = new List<WeatherForecast>();
}
}
}
catch (Exception ex) {
_logger.LogError(ex, ex.Message);
}
}
}
Here is an example of the API method that is being called. There is a Debug.WriteLine statement that will show the user credentials being passed into the API.