Is there a way for my api controller to get the IIdentity of the account who initiated the call to the api controller when the api-controller is using windows-authentication ?
Ok. I figured out the issue. Thanks to this post.
How to get Windows user name when identity impersonate="true" in asp.net?
//Start Quote//
With <authentication mode="Windows"/>
in your application and Anonymous access enabled in IIS, you will see the following results:
System.Environment.UserName: Computer Name
Page.User.Identity.Name: Blank
System.Security.Principal.WindowsIdentity.GetCurrent().Name: Computer Name
//End Quote
So I'll also include a full answer.......to show the issue and some possible settings that need to be tweaked.
Go and download this mini example.
https://code.msdn.microsoft.com/ASP-NET-Web-API-Tutorial-8d2588b1
This will give you a quick "WebApiTier" called ProductsApp (ProductsApp.csproj).
If you want to do it yourself....just create a WebApi Controller...that returns some Products.
public class ProductsController : ApiController
{
Product[] products = new Product[]
{
new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },
new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },
new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }
};
[IdentityWhiteListAuthorization]
public IEnumerable<Product> GetAllProducts()
{
return products;
}
}
Open the above .sln.
Add a new "class library" csproj called "WebApiIdentityPoc.Domain.csproj".
Create a new class in this library.
namespace WebApiIdentityPoc.Domain
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
}
Delete (or comment out)
\ProductsApp\Models\Product.cs
Add a (project) reference in ProductsApp to WebApiIdentityPoc.Domain.
Fix the namespace issue in
\ProductsApp\Controllers\ProductsController.cs
//using ProductsApp.Models;
using WebApiIdentityPoc.Domain;
namespace ProductsApp.Controllers
{
public class ProductsController : ApiController
{
(You're basically moving the "Product" object to another library so the Server and the Client can share the same object.)
You should be able to compile at this point.
..........
Add a new "Console Application" projec to the solution.
WebApiIdentityPoc.ConsoleOne.csproj
Use Nuget to add "Newtonsoft.Json" reference/library to the WebApiIdentityPoc.ConsoleOne.csproj.
Add the references (Framework or Extensions using right-click/add references on the "/References folder in the csproj)
System.Net.Http
System.Net.Http.Formatting
System.Net.Http.WebRequest (this one is may not be needed)
Add a project reference to WebApiIdentityPoc.Domain.
In "Program.cs" in the Console App, paste this code: .............
namespace WebApiIdentityPoc.ConsoleOne
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using WebApiIdentityPoc.Domain;
public class Program
{
private static readonly string WebApiExampleUrl = "http://localhost:47503/api/Products/GetAllProducts"; /* check ProductsApp.csproj properties, "Web" tab, "IIS Express" settings if there is an issue */
public static void Main(string[] args)
{
try
{
System.Security.Principal.WindowsIdentity ident = System.Security.Principal.WindowsIdentity.GetCurrent();
if (null != ident)
{
Console.WriteLine("Will the Identity '{0}' Show up in IdentityWhiteListAuthorizationAttribute ???", ident.Name);
}
RunHttpClientExample();
RunWebClientExample();
RunWebClientWicExample();
}
catch (Exception ex)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
Exception exc = ex;
while (null != exc)
{
sb.Append(exc.GetType().Name + System.Environment.NewLine);
sb.Append(exc.Message + System.Environment.NewLine);
exc = exc.InnerException;
}
Console.WriteLine(sb.ToString());
}
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
}
private static void RunWebClientExample()
{
/* some articles said that HttpClient could not pass over the credentials because of async operations, these were some "experiments" using the older WebClient. Stick with HttpClient if you can */
WebClient webClient = new WebClient();
webClient.UseDefaultCredentials = true;
string serviceUrl = WebApiExampleUrl;
string json = webClient.DownloadString(serviceUrl);
IEnumerable<Product> returnItems = JsonConvert.DeserializeObject<IEnumerable<Product>>(json);
ShowProducts(returnItems);
}
private static void RunWebClientWicExample()
{
/* some articles said that HttpClient could not pass over the credentials because of async operations, these were some "experiments" using the older WebClient. Stick with HttpClient if you can */
System.Security.Principal.WindowsIdentity ident = System.Security.Principal.WindowsIdentity.GetCurrent();
WindowsImpersonationContext wic = ident.Impersonate();
try
{
WebClient webClient = new WebClient();
webClient.UseDefaultCredentials = true;
string serviceUrl = WebApiExampleUrl;
string json = webClient.DownloadString(serviceUrl);
IEnumerable<Product> returnItems = JsonConvert.DeserializeObject<IEnumerable<Product>>(json);
ShowProducts(returnItems);
}
finally
{
wic.Undo();
}
}
private static void RunHttpClientExample()
{
IEnumerable<Product> returnItems = null;
HttpClientHandler handler = new HttpClientHandler()
{
UseDefaultCredentials = true, PreAuthenticate = true
};
////////WebRequestHandler webRequestHandler = new WebRequestHandler();
////////webRequestHandler.UseDefaultCredentials = true;
////////webRequestHandler.AllowPipelining = true;
////////webRequestHandler.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequired;
////////webRequestHandler.ImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Identification;
using (HttpClient client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string serviceUrl = WebApiExampleUrl;
HttpResponseMessage response = client.GetAsync(new Uri(serviceUrl)).Result;
var temp1 = response.ToString();
var temp2 = response.Content.ReadAsStringAsync().Result;
if (response.IsSuccessStatusCode)
{
Task<IEnumerable<Product>> wrap = response.Content.ReadAsAsync<IEnumerable<Product>>();
if (null != wrap)
{
returnItems = wrap.Result;
}
else
{
throw new ArgumentNullException("Task<IEnumerable<Product>>.Result was null. This was not expected.");
}
}
else
{
throw new HttpRequestException(response.ReasonPhrase + " " + response.RequestMessage);
}
}
ShowProducts(returnItems);
}
private static void ShowProducts(IEnumerable<Product> prods)
{
if (null != prods)
{
foreach (Product p in prods)
{
Console.WriteLine("{0}, {1}, {2}, {3}", p.Id, p.Name, p.Price, p.Category);
}
Console.WriteLine(string.Empty);
}
}
}
}
You should be able to compile and run and see some Products display in the Console App.
.....
In "ProductsApp.csproj", Add a new Folder.
/WebApiExtensions/
Under this folder, add a new file:
IdentityWhiteListAuthorizationAttribute.cs
Paste in this code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
namespace ProductsApp.WebApiExtensions
{
public class IdentityWhiteListAuthorizationAttribute : System.Web.Http.AuthorizeAttribute
{
public IdentityWhiteListAuthorizationAttribute()
{
}
private string CurrentActionName { get; set; }
public override void OnAuthorization(HttpActionContext actionContext)
{
this.CurrentActionName = actionContext.ActionDescriptor.ActionName;
base.OnAuthorization(actionContext);
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var test1 = System.Threading.Thread.CurrentPrincipal;
var test2 = System.Security.Principal.WindowsIdentity.GetCurrent();
////string userName = actionContext.RequestContext.Principal.Name;/* Web API v2 */
string dingDingDingUserName = string.Empty;
ApiController castController = actionContext.ControllerContext.Controller as ApiController;
if (null != castController)
{
dingDingDingUserName = castController.User.Identity.Name;
}
string status = string.Empty;
if (string.IsNullOrEmpty(dingDingDingUserName))
{
status = "Not Good. No dingDingDingUserName";
}
else
{
status = "Finally!";
}
return true;
}
}
}
Decorate the webapimethod with this attribute.
[IdentityWhiteListAuthorization]
public IEnumerable<Product> GetAllProducts()
{
return products;
}
(You'll have to resolve the namespace).
At this point, you should be able to compile....and run.
But dingDingDingUserName will be string.Empty. (The original issue that spanned this post).
Ok..
Click (left-click once) the ProductsApp.csproj in the Solution Explorer.
Look at the properties tab. (This is not the "right-click / properties ::: This is the properties that show up (default would be in the bottom right of VS) when you simply left-click the ProductsApp.csproj.
You'll see several settings, but there are two of interest:
Anonymous Authentication | Enabled
Windows Authentication | Enabled
(Note, the above is how these settings show up in the VS GUI. They show up like this in the .csproj file)
<IISExpressAnonymousAuthentication>enabled</IISExpressAnonymousAuthentication>
<IISExpressWindowsAuthentication>enabled</IISExpressWindowsAuthentication>
If you set
Anonymous Authentication | Disabled
(which shows up in the .csproj like this:
<IISExpressAnonymousAuthentication>disabled</IISExpressAnonymousAuthentication>
<IISExpressWindowsAuthentication>enabled</IISExpressWindowsAuthentication>
)
VOILA! The "dingDingDingName" value should show up.
The link I have above .. points to the anonymous-authenication-enabled to being the issue.
But here is a long example to show the direct effects...in regards to HttpClient.
One more caveat I learned along the way.
If you cannot alter the
Anonymous Authentication Enabled/Disabled
Windows Authentication Enabled/Disabled
settings, then you need to adjust the "master settings".
In IIS Express, this will be in a file like:
C:\Users\MyUserName\Documents\IISExpress\config\applicationhost.config
The “master settings” need to allow the local settings to be overridden.
<sectionGroup name="security">
<section name="anonymousAuthentication" overrideModeDefault="Allow" />
<!-- Other Stuff -->
<section name="windowsAuthentication" overrideModeDefault="Allow" />
</sectionGroup>
The authentications themselves need to be turned on at a master level.
<security>
<authentication>
<anonymousAuthentication enabled="true" userName="" />
<windowsAuthentication enabled="true">
<providers>
<add value="Negotiate" />
<add value="NTLM" />
</providers>
</windowsAuthentication>
</authentication>
(Full IIS will have similar settings in
C:\Windows\System32\inetsrv\config\applicationHost.config
)
Bottom line:
HttpClient can send over the WindowsIdentity of the process running the HttpClient code....using HttpClientHandler AND if the WebApiTier is set for WindowsAuthentication AND Anonymous-Authentication turned off.
Ok. I hope that helps somebody in the future.