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 ?
My "castController.User.Identity" is (of type) WindowsIdentity. But it is "empty". Empty, as is : IsAuthenticated = false, and an empty UserName. It isn't null, it is "empty".
My "WebTier" is an IIS application running with an custom AppPool and the IIdentity which runs the custom AppPool is something like "mydomain\myServiceAccount". I'm trying to get the "castController.User.Identity.Name" value to be this service account.
(I guess it could be any client who is able to connect to my WebApiTier with a valid windows-account, but I'm mentioning this just in case it could be throwing a weird monkey wrench)
My "WebTier" (Mvc Application) has this method:
You'll notice 2 ways I'm using UseDefaultCredentials. (Aka, I've been trying to figure this out for a bit)
private async Task<HttpResponseMessage> ExecuteProxy(string url)
{
HttpClientHandler handler = new HttpClientHandler()
{
UseDefaultCredentials = true
};
handler.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 (var client = new HttpClient(handler)) /* i've tried webRequestHandler too */
{
Uri destinationUri = new Uri("http://localhost/MyVirtualDirectory/api/mycontroller/mymethod");
this.Request.RequestUri = destinationUri;
return await client.SendAsync(this.Request);
}
}
"WebApiTier" Setup.
web.config
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
<authentication mode="Windows" />
"WebApiTier" Code
public MyController : ApiController
{
[ActionName("MyMethod")]
[MyCustomAuthorization]
public IEnumerable<string> MyMethod()
{
return new string[] { "value1", "value2" };
}
}
public class MyCustomAuthorizationAttribute : System.Web.Http.AuthorizeAttribute
{
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;
/* the above is "empty" */
////string userName = actionContext.RequestContext.Principal;/* Web API v2 */
string userName = string.Empty;
ApiController castController = actionContext.ControllerContext.Controller as ApiController;
if (null != castController)
{
userName = castController.User.Identity.Name;
/* the above is "empty" */
}
return true;
}
}
}
Again. I'm not doing a "double hop" (that I've read about in a few places).
Both tiers are on the same domain (and local development, they're on the same machine)....
The funny thing is that I've read this ( How to get HttpClient to pass credentials along with the request? ) and the "problem" reported there is EXACTLY how I want mine to work. (?!?!).
For development, the "WebApiTier" is running under full IIS. For "WebTier", I've tried it under IIS-Express and full-fledge IIS.
I also ran a console app program with this code:
Console App
IEnumerable<string> returnItems = null;
HttpClientHandler handler = new HttpClientHandler()
{
UseDefaultCredentials = true
};
handler.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;
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string serviceUrl = "http://localhost/MyVirtualDirectory/api/mycontroller/mymethod";
HttpResponseMessage response = client.GetAsync(new Uri(serviceUrl)).Result;
var temp1 = (response.ToString());
var temp2 = (response.Content.ReadAsStringAsync().Result);
if (response.IsSuccessStatusCode)
{
Task<IEnumerable<string>> wrap = response.Content.ReadAsAsync<IEnumerable<string>>();
if (null != wrap)
{
returnItems = wrap.Result;
}
else
{
throw new ArgumentNullException("Task<IEnumerable<string>>.Result was null. This was not expected.");
}
}
else
{
throw new HttpRequestException(response.ReasonPhrase + " " + response.RequestMessage);
}
Same result as the other code. An "empty" Windows Identity.
I also went through this
http://www.iis.net/configreference/system.webserver/security/authentication/windowsauthentication
just as a sanity check.
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://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.
Open the above .sln.
Add a new "class library" csproj called "WebApiIdentityPoc.Domain.csproj".
Create a new class in this library.
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
(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)
Add a project reference to WebApiIdentityPoc.Domain.
In "Program.cs" in the Console App, paste this code: .............
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:
Decorate the webapimethod with this attribute.
(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:
(Note, the above is how these settings show up in the VS GUI. They show up like this in the .csproj file)
If you set
(which shows up in the .csproj like this:
)
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
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.
The authentications themselves need to be turned on at a master level.
(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.