TrilonIO / aspnetcore-angular-universal

ASP.NET Core & Angular Universal advanced starter - PWA w/ server-side rendering for SEO, Bootstrap, i18n internationalization, TypeScript, unit testing, WebAPI REST setup, SignalR, Swagger docs, and more! By @TrilonIO
https://www.trilon.io
MIT License
1.46k stars 433 forks source link

Windows Auth Breaks HttpCacheService #112

Open yanbu0 opened 7 years ago

yanbu0 commented 7 years ago

Checking the "Enable Windows Authentication" checkbox in Debug properties in VS 2015 breaks the HttpCacheService. To replicate, simply check the box, run debug and navigate to the rest-test page. Unchecking the box removes the error, and replacing httpCache with http remvoes the error.

The error from Node is: Exception: Call to Node module failed with error: Response with status: 401 null for URL: Microsoft.AspNetCore.NodeServices.HostingModels.HttpNodeInstance+d__7.MoveNext()

MarkPieszak commented 7 years ago

Can you try something else for me, go to Views/Home/Index.cshtml and remove theasp-prerender="bootstrap-server" attribute from the <root-app> tag?

I'm wondering if Windows Auth is breaking the entire App, or if it's something during the SSR (server-side rendering) part that it's breaking. Let me know!

yanbu0 commented 7 years ago

Yes, removing the asp-prerender-module="Client/dist/main-server" attribute also makes the error go away.

AbrarJahin commented 7 years ago

But it is not solution because we need pre-rendering

yanbu0 commented 7 years ago

True

MarkPieszak commented 7 years ago

Come via 4.1+ there will be an easier way to handle this. (State transfer method).

I'll leave this open as I haven't added HttpCacheService back in to see if it is still broken!

MarkPieszak commented 7 years ago

I have rewritten the Cache service as it's now HttpTransfer, want to give this another shot and see if it works with it now? Not sure why windows Auth breaks this regardless though 😞

yanbu0 commented 7 years ago

Thanks Mark! Its going to be a bit of work getting everything in my project updated to ng4, but I will update this thread with results.

MarkPieszak commented 7 years ago

Sounds good I can give it a shot with the base repo eventually as well, no worries!

yanbu0 commented 7 years ago

Mark,

I tested windows auth with the base repo today and it appears to work fine with TransferHttp.

Was a quick test, and I simply turned it on and dropped this code into the UsersController to validate it was pulling data from AD:

string login = User.Identity.Name;

I will be converting my project over to Angular 4+ here this week using this template, and will update further once things are working.

MarkPieszak commented 7 years ago

Excellent glad it's working! 🎁👍 That's great news. I still some cosmetic things to do with the vendor chunking and such for webpack but those will be easy for you cherry pick and add to your app when upgrading.

I'll close this one out for other, if it creeps up again we can re-investigate it. Cheers

yanbu0 commented 7 years ago

Hi Mark, sorry about reporting this fixed earlier but it is actually still broken. Now it is just failing silently and falling back to querying the data from the client side automatically. What appears to be happening is that Node is unable authenticate and that causes your code to cause the client to query from that side. I have replicated this issue in my own environment and thought it was something I broke, but I just replicated it in a new copy of the repro today as well, so it appears to be an issue with the transfer-http module itself. The only modification I made was to turn on windows auth.

You can see here that transfer-http is failing back silently to querying from the client, note the call to /api/users third from the bottom: image

And here you can see the window['TRANSFER_STATE'] variable is not getting set properly: image

Here is the same screenshot, with no changes other than windows auth being turned off: image

The error from Node in the Output window is as follows: Microsoft.AspNetCore.NodeServices:Error: ERROR Response { _body: '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> \n<html xmlns="http://www.w3.org/1999/xhtml"> \n<head> \n<title>IIS 10.0 Detailed Error - 401.2 - Unauthorized</title> \n<style type="text/css"> \n<!-- \nbody{margin:0;font-size:.7em;font-family:Verdana,Arial,Helvetica,sans-serif;} \ncode{margin:0;color:#006600;font-size:1.1em;font-weight:bold;} \n.config_source code{font-size:.8em;color:#000000;} \npre{margin:0;font-size:1.4em;word-wrap:break-word;} \nul,ol{margin:10px 0 10px 5px;} \nul.first,ol.first{margin-top:5px;} \nfieldset{padding:0 15px 10px 15px;word-break:break-all;} \n.summary-container fieldset{padding-bottom:5px;margin-top:4px;} \nlegend.no-expand-all{padding:2px 15px 4px 10px;margin:0 0 0 -12px;} \nlegend{color:#333333;;margin:4px 0 8px -12px;_margin-top:0px; \nfont-weight:bold;font-size:1em;} \na:link,a:visited{color:#007EFF;font-weight:bold;} \na:hover{text-decoration:none;} \nh1{font-size:2.4em;margin:0;color:#FFF;} \nh2{font-size:1.7em;margin:0;color:#CC0000;} \nh3{font-size:1.4em;margin:10px 0 0 0;color:#CC0000;} \nh4{font-size:1.2em;margin:10px 0 5px 0; \n}#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS",Verdana,sans-serif; \n color:#FFF;background-color:#5C87B2; \n}#content{margin:0 0 0 2%;position:relative;} \n.summary-container,.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;} \n.content-container p{margin:0 0 10px 0; \n}#details-left{width:35%;float:left;margin-right:2%; \n}#details-right{width:63%;float:left;overflow:hidden; \n}#server_version{width:96%;_height:1px;min-height:1px;margin:0 0 5px 0;padding:11px 2% 8px 2%;color:#FFFFFF; \n background-color:#5A7FA5;border-bottom:1px solid #C1CFDD;border-top:1px solid #4A6C8E;font-weight:normal; \n font-size:1em;color:#FFF;text-align:right; \n}#server_version p{margin:5px 0;} \ntable{margin:4px 0 4px 0;width:100%;border:none;} \ntd,th{vertical-align:top;padding:3px 0;text-align:left;font-weight:normal;border:none;} \nth{width:30%;text-align:right;padding-right:2%;font-weight:bold;} \nthead th{background-color:#ebebeb;width:25%; \n}#details-right th{width:20%;} \ntable tr.alt td,table tr.alt th{} \n.highlight-code{color:#CC0000;font-weight:bold;font-style:italic;} \n.clear{clear:both;} \n.preferred{padding:0 5px 2px 5px;font-weight:normal;background:#006633;color:#FFF;font-size:.8em;} \n--> \n</style> \n \n</head> \n<body> \n<div id="content"> \n<div class="content-container"> \n <h3>HTTP Error 401.2 - Unauthorized</h3> \n <h4>You are not authorized to view this page due to invalid authentication headers.</h4> \n</div> \n<div class="content-container"> \n <fieldset><h4>Most likely causes:</h4> \n <ul> \t<li>No authentication protocol (including anonymous) is selected in IIS.</li> \t<li>Only integrated authentication is enabled, and a client browser was used that does not support integrated authentication.</li> \t<li>Integrated authentication is enabled and the request was sent through a proxy that changed the authentication headers before they reach the Web server.</li> \t<li>The Web server is not configured for anonymous access and a required authorization header was not received.</li> \t<li>The "configuration/system.webServer/authorization" configuration section may be explicitly denying the user access.</li> </ul> \n </fieldset> \n</div> \n<div class="content-container"> \n <fieldset><h4>Things you can try:</h4> \n <ul> \t<li>Verify the authentication setting for the resource and then try requesting the resource using that authentication method.</li> \t<li>Verify that the client browser supports Integrated authentication.</li> \t<li>Verify that the request is not going through a proxy when Integrated authentication is used.</li> \t<li>Verify that the user is not explicitly denied access in the "configuration/system.webServer/authorization" configuration section.</li> \t<li>Check the failed request tracing logs for additional information about this error. For more information, click <a href="http://go.microsoft.com/fwlink/?LinkID=66439">here</a>. </li> </ul> \n </fieldset> \n</div> \n \n<div class="content-container"> \n <fieldset><h4>Detailed Error Information:</h4> \n <div id="details-left"> \n <table border="0" cellpadding="0" cellspacing="0"> \n <tr class="alt"><th>Module</th><td>&nbsp;&nbsp;&nbsp;IIS Web Core</td></tr> \n <tr><th>Notification</th><td>&nbsp;&nbsp;&nbsp;AuthenticateRequest</td></tr> \n <tr class="alt"><th>Handler</th><td>&nbsp;&nbsp;&nbsp;aspNetCore</td></tr> \n <tr><th>Error Code</th><td>&nbsp;&nbsp;&nbsp;0x80070005</td></tr> \n \n </table> \n </div> \n <div id="details-right"> \n <table border="0" cellpadding="0" cellspacing="0"> \n <tr class="alt"><th>Requested URL</th><td>&nbsp;&nbsp;&nbsp;http://localhost:53170/api/users</td></tr> \n <tr><th>Physical Path</th><td>&nbsp;&nbsp;&nbsp;C:\\Users\\travis.boyle\\Documents\\Visual Studio 2017\\Projects\\aspnetcore-angular2-universal-master\\api\\users</td></tr> \n <tr class="alt"><th>Logon Method</th><td>&nbsp;&nbsp;&nbsp;Not yet determined</td></tr> \n <tr><th>Logon User</th><td>&nbsp;&nbsp;&nbsp;Not yet determined</td></tr> \n <tr class="alt"><th>Request Tracing Directory</th><td>&nbsp;&nbsp;&nbsp;C:\\Users\\travis.boyle\\Documents\\IISExpress\\TraceLogFiles\\ASP2017</td></tr> \n </table> \n <div class="clear"></div> \n </div> \n </fieldset> \n</div> \n \n<div class="content-container"> \n <fieldset><h4>More Information:</h4> \n This error occurs when the WWW-Authenticate header sent to the Web server is not supported by the server configuration. Check the authentication method for the resource, and verify which authentication method the client used. The error occurs when the authentication methods are different. To determine which type of authentication the client is using, check the authentication settings for the client. \n <p><a href="http://go.microsoft.com/fwlink/?LinkID=62293&amp;IIS70Error=401,2,0x80070005,15063">View more information &raquo;</a></p> \n <p>Microsoft Knowledge Base Articles:</p> \n <ul><li>907273</li><li>253667</li></ul> \n \n </fieldset> \n</div> \n</div> \n</body> \n</html> \n', status: 401, ok: false, statusText: 'Unauthorized', headers: Headers { _headers: Map { 'cache-control' => [Object], 'content-type' => [Object], 'server' => [Object], 'x-sourcefiles' => [Object], 'www-authenticate' => [Object], 'x-powered-by' => [Object], 'date' => [Object], 'content-length' => [Object] }, _normalizedNames: Map { 'cache-control' => 'cache-control', 'content-type' => 'content-type', 'server' => 'server', 'x-sourcefiles' => 'x-sourcefiles', 'www-authenticate' => 'www-authenticate', 'x-powered-by' => 'x-powered-by', 'date' => 'date', 'content-length' => 'content-length' } }, type: 2, url: 'http://localhost:53170/api/users' }

yanbu0 commented 7 years ago

This actually appears to be a bug in Universal not specific to this repo.

MarkPieszak commented 7 years ago

Thank you for the extra details, do you have a sample repo I can look at that has everything setup so I can take a look?

Even just a fork of this with the windows Auth bug reproducing?

yanbu0 commented 7 years ago

So I did a ton of looking into this last week and it actually appears to be a bug in Universal itself, or at least an issue with it. Despite all appropriate headers being passed (best I can tell), windows auth fails from Node to Web Api, even with a straight http.get() call. It is not contained to your transferHttp methods. I created an issue on their github and linked it over to this one. I really hope someone can get this to work since the server>client data transfer is why I went with Angular on this project so I'm going to be kinda holding the bag if this flat out will not work!

I will try to create a fork that reproduces this today, but if you want to reproduce this all you have to do is turn on windows auth and drop an [Authorize] decorator on the web api controller.

yanbu0 commented 7 years ago

OK, created a fork: https://github.com/yanbu0/aspnetcore-angular2-universal

I can reproduce the error like so: clone locally, run npm install, webpack, then open by right clicking on the csproj and opening in VS2017, make sure windows auth is enabled by right clicking on the project and checking the boxes like so: image

Then, debug. Navigate to the /users route and you will see the error occur. You will get the below in the console from Node if you refresh the page.

image

MarkPieszak commented 7 years ago

So the problem here is that during the server-rendering, the Http calls in the app aren't setup to pass the correct Authentication needed. You need to make sure you pass those from HomeController -> main.server into some sort of Provider that you can inject into your Http headers during the server-render.

The issue is that you're also going to have to also pass this down to transferData so that your browser can grab them and use these cookies as well (and once again inject them into the Header).

If you remove Server-side rendering it'll make the process simpler, otherwise you'll have to use a little bit of dependency injection magic to make it all happen.

yanbu0 commented 7 years ago

@MarkPieszak,

OK, I'm pretty confused now. What do you mean when you say "pass the correct Authentication needed"? It looks like the server side request has all the correct headers. See screenshot below. Are you talking about something else? image

MarkPieszak commented 7 years ago

Sorry I meant do you have your http calls setup to use those headers in each of their calls during the server render?

isaacrlevin commented 7 years ago

@yanbu0 is this still an issue?

yanbu0 commented 7 years ago

I figured out a work around, but yeah, Windows Auth seems to not work very well still. I'm planning on updating to Ng 5 here shortly and will see if that fixes things.

isaacrlevin commented 6 years ago

@yanbu0 can you provide your workaround? 5.0 has issues so you won't get far, but 4.X fix would be helpful

yanbu0 commented 6 years ago

Yeah, upgrading to 5 hasn't happened yet, was breaking stuff, so I just went back to 4.x, and am waiting for the dust to settle.

Anyway, what I figured out is that the server side was always failing authentication, but the client side was not.

So I let the server side fail, the initial paint had no permissions or data, which sucks, but then create a token on the client side after the user authenticated and passed that in the http header for all future requests.

The initial server-side auth fails, but all other ones fall back to using the token if they can't grab the user from the context, and then pull the user permissions from the database. Kind of a hybrid windows auth/token auth setup. It seems kind of a hack to me and I don't like it, but I spent weeks fighting this problem and this was the only way I could get things to function.

I don't use AD groups for permissions, we have a separate, app specific, permission structure, so all I need to check to see if a user can perform an action is who they are, and that they are authorized to log in, so it works for me.

Honestly, I'm kind of frustrated with Angular at this point and am starting to wish I had gone another direction. Most of the reason I went with it over React, Vue, or some other framework was because it was an all in one package deal (and Google's name on it). I figured getting all the server side rendering, AoT, and whatnot working would be more straightforward with Angular since it was a single framework. I still can't get AoT to work and the server side rendering usefulness has been neutered by this issue. On top of that, the massive package size of the transpiled .js files is causing real usability issues.