c# - HttpClient calling a Windows-Authenication ApiController Method...but no WindowsIdentity coming along for the ride -
is there way api controller iidentity of account initiated call api controller when api-controller using windows-authentication ?
my "castcontroller.user.identity" (of type) windowsidentity. "empty". empty, : isauthenticated = false, , empty username. isn't null, "empty".
my "webtier" iis application running custom apppool , iidentity runs custom apppool "mydomain\myserviceaccount". i'm trying "castcontroller.user.identity.name" value service account.
(i guess client able connect webapitier valid windows-account, i'm mentioning in case throwing weird monkey wrench)
my "webtier" (mvc application) has method:
you'll notice 2 ways i'm using usedefaultcredentials. (aka, i've been trying figure out 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 */ { 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; /* above "empty" */ ////string username = actioncontext.requestcontext.principal;/* web api v2 */ string username = string.empty; apicontroller castcontroller = actioncontext.controllercontext.controller apicontroller; if (null != castcontroller) { username = castcontroller.user.identity.name; /* above "empty" */ } return true; } }
}
again. i'm not doing "double hop" (that i've read in few places).
both tiers on same domain (and local development, they're on same machine)....
the funny thing i've read ( how httpclient pass credentials along request? ) , "problem" reported there how want mine work. (?!?!).
for development, "webapitier" running under full iis. "webtier", i've tried under iis-express , full-fledge iis.
i ran console app program 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 null. not expected."); } } else { throw new httprequestexception(response.reasonphrase + " " + response.requestmessage); }
same result other code. "empty" windows identity.
i went through this
http://www.iis.net/configreference/system.webserver/security/authentication/windowsauthentication
just sanity check.
ok. figured out issue. post.
how windows user name when identity impersonate="true" in asp.net?
//start quote//
with in application , anonymous access enabled in iis, see 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 include full answer.......to show issue , possible settings need tweaked.
go , download mini example.
https://code.msdn.microsoft.com/asp-net-web-api-tutorial-8d2588b1
this give quick "webapitier" called productsapp (productsapp.csproj).
if want yourself....just create webapi controller...that returns 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 above .sln.
add new "class library" csproj called "webapiidentitypoc.domain.csproj".
create new class in 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 (project) reference in productsapp webapiidentitypoc.domain.
fix namespace issue in
\productsapp\controllers\productscontroller.cs
//using productsapp.models; using webapiidentitypoc.domain; namespace productsapp.controllers { public class productscontroller : apicontroller {
(you're moving "product" object library server , client can share same object.)
you should able compile @ point.
..........
add new "console application" projec solution.
webapiidentitypoc.consoleone.csproj
use nuget add "newtonsoft.json" reference/library webapiidentitypoc.consoleone.csproj.
add references (framework or extensions using right-click/add references on "/references folder in csproj)
system.net.http system.net.http.formatting system.net.http.webrequest (this 1 may not needed)
add project reference webapiidentitypoc.domain.
in "program.cs" in console app, paste 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 issue */ public static void main(string[] args) { try { system.security.principal.windowsidentity ident = system.security.principal.windowsidentity.getcurrent(); if (null != ident) { console.writeline("will identity '{0}' show 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 exit"); console.readline(); } private static void runwebclientexample() { /* articles said httpclient not pass on credentials because of async operations, these "experiments" using older webclient. stick httpclient if 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() { /* articles said httpclient not pass on credentials because of async operations, these "experiments" using older webclient. stick httpclient if 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); } { 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 null. 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 able compile , run , see products display in console app.
.....
in "productsapp.csproj", add new folder.
/webapiextensions/
under folder, add new file:
identitywhitelistauthorizationattribute.cs
paste in 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 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 webapimethod attribute.
[identitywhitelistauthorization] public ienumerable<product> getallproducts() { return products; }
(you'll have resolve namespace).
at point, should able compile....and run.
but dingdingdingusername string.empty. (the original issue spanned post).
ok..
click (left-click once) productsapp.csproj in solution explorer.
look @ properties tab. (this not "right-click / properties ::: properties show (default in bottom right of vs) when left-click productsapp.csproj.
you'll see several settings, there 2 of interest:
anonymous authentication | enabled windows authentication | enabled
(note, above how these settings show in vs gui. show in .csproj file)
<iisexpressanonymousauthentication>enabled</iisexpressanonymousauthentication> <iisexpresswindowsauthentication>enabled</iisexpresswindowsauthentication>
if set
anonymous authentication | disabled
(which shows in .csproj this:
<iisexpressanonymousauthentication>disabled</iisexpressanonymousauthentication> <iisexpresswindowsauthentication>enabled</iisexpresswindowsauthentication>
)
voila! "dingdingdingname" value should show up.
the link have above .. points anonymous-authenication-enabled being issue.
but here long example show direct effects...in regards httpclient.
one more caveat learned along way.
if cannot alter
anonymous authentication enabled/disabled windows authentication enabled/disabled
settings, need adjust "master settings".
in iis express, in file like:
c:\users\myusername\documents\iisexpress\config\applicationhost.config
the “master settings” need allow local settings overridden.
<sectiongroup name="security"> <section name="anonymousauthentication" overridemodedefault="allow" /> <!-- other stuff --> <section name="windowsauthentication" overridemodedefault="allow" /> </sectiongroup>
the authentications need turned on @ master level.
<security> <authentication> <anonymousauthentication enabled="true" username="" /> <windowsauthentication enabled="true"> <providers> <add value="negotiate" /> <add value="ntlm" /> </providers> </windowsauthentication> </authentication>
(full iis have similar settings in
c:\windows\system32\inetsrv\config\applicationhost.config
)
bottom line:
httpclient can send on windowsidentity of process running httpclient code....using httpclienthandler and if webapitier set windowsauthentication and anonymous-authentication turned off.
ok. hope helps in future.
Comments
Post a Comment