IIS Node and Windows Authentication: IIS-Website Keeps prompting for Credentials - node.js

I have an IIS Website that runs on localhost:3000 using NodeJS, Express, passport and passport-windowsauth.
When I try to access the URL, it keeps prompting for user credentials - even if I type in the correct credentials. It simply pops up again. If I choose "Cancel", I obviously get the "Unauthorized". Struggling with this error since 3 full days and think about just giving up. I tried nearly every possible solution I could find, with no success.
And what's strange: This error only occurs on Windows-VM (Virtualbox). On a normal Windows-OS it works like a charm, with the exact same code and same IIS settings. This is driving me crazy.
What I tried:
granting Full Access for IUSR / IUSRS group, put NTLM first, enabled Kernel Mode Authentication and set Extended Protection as "Accept" (suggested here)
change Application Pool Identity to Local Service, Local System or Managed System
added localhost to "Trusted Sites" in Internet Options (even though that felt strange), as suggested here
tried enabling Anonymus Authentication with IUSR
did security loopback check (suggested here and here)
Rebooting after every action I did
did iisreset after every action I did
checking IIS Node in Handler Mappings
adding localhost manually to "Hosts"-file
overrideModeDefault = Allow for Windows Authentication (and Anonymus Authentication as well) in applicationHost.config
Also, I have no custom error pages that could be related to the problem.
server.js:
const express = require('express');
const server = express();
const passport = require('passport');
const WindowsStrategy = require('passport-windowsauth');
server.use(passport.initialize());
// https://stackoverflow.com/questions/55296233/nodejs-express-passport-iis
passport.use('MyAuthStrategy', new WindowsStrategy({
integrated: true
},
function(profile, done) {
if (profile) {
user = profile;
return done(null, user);
}
else {
return done(null, false);
}
}
));
//https://stackoverflow.com/questions/19948816/passport-js-error-failed-to-serialize-user-into-session
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
const port = process.env.PORT || 3000;
// use windows-authentication
server.get('/',
passport.authenticate('MyAuthStrategy'),
function (req, res) {
res.json(req.user);
});
server.listen(port, () => {
console.log(`Listening on ${port}`);
});
And the web.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<!--
All appSettings are made available to your Node.js app via environment variables
You can access them in your app through the process.env object.
process.env.<key>
-->
<!-- Unconmment the below appSetting if you'd like to use a Virtual Directory -->
<!-- <add key="virtualDirPath" value="" /> -->
</appSettings>
<system.webServer>
<!-- Remove the modules element if running on IIS 8.5-->
<modules runAllManagedModulesForAllRequests="false" />
<httpErrors existingResponse="PassThrough"></httpErrors>
<iisnode node_env="%node_env%" nodeProcessCountPerApplication="1" maxConcurrentRequestsPerProcess="1024" maxNamedPipeConnectionRetry="100" namedPipeConnectionRetryDelay="250" maxNamedPipeConnectionPoolSize="512" maxNamedPipePooledConnectionAge="30000" asyncCompletionThreadCount="0" initialRequestBufferSize="4096" maxRequestBufferSize="65536" uncFileChangesPollingInterval="5000" gracefulShutdownTimeout="60000" loggingEnabled="true" logDirectory="iisnode" debuggingEnabled="true" debugHeaderEnabled="false" debuggerPortRange="5058-6058" debuggerPathSegment="debug" maxLogFileSizeInKB="128" maxTotalLogFileSizeInKB="1024" maxLogFiles="20" devErrorsEnabled="true" flushResponse="false" enableXFF="false" configOverrides="iisnode.yml" watchedFiles="web.config;*.js" nodeProcessCommandLine="C:\Program Files\nodejs\node.exe" />
<!--
Before the handlers element can work on IIS 8.5
follow steps listed here https://github.com/tjanczuk/iisnode/issues/52
-->
<handlers>
<add name="iisnode" path="server.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<!-- Don't interfere with requests for node-inspector debugging -->
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^server.js\/debug[\/]?" />
</rule>
<!-- First we consider whether the incoming URL matches a physical file in the /public folder -->
<rule name="StaticContent" patternSyntax="Wildcard">
<action type="Rewrite" url="public/{R:0}" logRewrittenUrl="true" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
</conditions>
<match url="*.*" />
</rule>
<!-- All other URLs are mapped to the Node.js application entry point -->
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True" />
</conditions>
<action type="Rewrite" url="server.js" />
</rule>
<rule name="SocketIO" patternSyntax="ECMAScript">
<match url="socket.io.+" />
<action type="Rewrite" url="server.js" />
</rule>
</rules>
</rewrite>
<directoryBrowse enabled="true" />
<security>
<authentication>
<anonymousAuthentication enabled="false" userName="IUSR" />
<windowsAuthentication enabled="true" useKernelMode="true">
<extendedProtection tokenChecking="Allow" />
<providers>
<clear />
<add value="NTLM" />
<add value="Negotiate" />
</providers>
</windowsAuthentication>
</authentication>
</security>
</system.webServer>
<system.web>
<authentication mode="Windows" />
<authorization>
<allow users="*" />
</authorization>
<identity impersonate="false" />
</system.web>
</configuration>
Here the installed Features:
Any suggestions will be greatly appreciated. I really don't know what to do.

IIS-Website Keeps prompting for Credentials
After testing, I found that this situation will only appear when the wrong credentials are entered. So you must make sure to enter the correct windows credentials.
If you are using a domain account, you must also provide the domain where the domain account is located when providing credentials:

Related

Serve Angular client and Express Sever on IIS without breaking urls

I used angular on the client-side, and nodejs on server-side, when running them using webstorm IDE they behave as expected but now I'm trying to put them on is and I'm encountering some troubles.
My main goal is to make just the client and server work together so if you have another suggestion which I have not tried, please comment.
In the IIS I had to put both server and client under the same URL (obviously) so I have deployed my angular app and copied the content of the dist folder into folder name "wwwroot" to my server (express).
Here is my code:
app.js files:
app.use(express.static(__dirname + '/wwwroot'));
app.post('/home', (req, res) => {
// some content here
});
app.get('/home', (req, res) => {
// some content here
});
const server = http.createServer(app);
server.listen(port, () => console.log('Listening to port: ' + port));
my web.config file:
<configuration>
<system.webServer>
<handlers>
<add name="iisnode" path="app.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<rule name="nodejs">
<match url="(.*)" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
</conditions>
<action type="Rewrite" url="/app.js" />
</rule>
</rules>
</rewrite>
<security>
<requestFiltering>
<hiddenSegments>
<add segment="node_modules" />
<add segment="iisnode" />
</hiddenSegments>
</requestFiltering>
</security>
</system.webServer>
</configuration>
Now, it would work just fine but I have 1 main problem which I can't solve when I'm trying to reload a page via a URL,
e.g. "http://www.automationcompare.com:4040/home;urlId=23119.06799149191"
then I get: Cannot GET /home;urlId=23119.06799149191.
I think the server should be set up to return the base HTML page no matter the url. You need to use url rewrite rule like below sample:
<system.webServer>
<rewrite>
<rules>
<rule name="Angular Routes" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="./index.html" />
</rule>
</rules>
</rewrite>
</system.webServer>
For further information about your problem, you can refer to this post. I think it is similar to your problem.

URL Rewrite not working - React SPA on Azure Web App (node)

I'm running a create-react-app build on an Azure Web App (node LTS). The app uses React Routes, and runs great from the index screen and as long as you are navigating the route links through the app. However, if you refresh a page, or navigate directly to a non-root route, you get 404. It seems to me like my web.config rewrite (of which I've tried many variations) is not being honored. I'm sure I'm just missing something obvious. Any suggestions?
index.js file
var express = require('express');
var server = express();
var options = {
index: 'index.html'
};
server.use('/', express.static('/home/site/wwwroot', options));
server.listen(process.env.PORT);
web.config (edit with updated version)
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<webSocket enabled="false" />
<handlers>
<add name="iisnode" path="index.js" verb="*" modules="iisnode"/>
</handlers>
<rewrite>
<rules>
<rule name="React Routes">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="True"/>
<add input="{REQUEST_URI}" pattern="/api" negate="true" />
</conditions>
<action type="Rewrite" url="/index.html" />
</rule>
</rules>
</rewrite>
<security>
<requestFiltering>
<hiddenSegments>
<remove segment="bin"/>
</hiddenSegments>
</requestFiltering>
</security>
<httpErrors existingResponse="PassThrough" />
</system.webServer>
</configuration>
File Structure (/site/wwwroot/)
index.html
web.config
static/ (css, js)

How can I get LOGON_USER info from windows auth without having the username password prompt appear?

I am using iisnode to run my express, nodejs application on Windows Server 2016. I only need the LOGON_USER (username) of the client computer connecting to my app (This is on a company network). When connecting to my app it prompts the user for username password. My understanding is when using windows authentication I can access the clients credentials without having them login to my app since they have already logged on to their computer?
I have disabled anonymous authentication and enabled Windows authentication in IIS on my app under Default Web Site. I have followed these instructions to promote some server variable ex. LOGON_USER. When I brows to my apps site I am prompted with the login and username popup. What do I have to to to get access to the clients username/computer name without having them provide their credentials again. I don't even need to have them authenticated I just need the username from the computer they are accessing my app from.
web.config.
</appSettings>
<system.webServer>
<!-- Remove the modules element if running on IIS 8.5-->
<modules runAllManagedModulesForAllRequests="false" />
<!-- <httpErrors existingReponse="PassThrough"></httpErrors> -->
<iisnode node_env="%node_env%"
nodeProcessCountPerApplication="1"
maxConcurrentRequestsPerProcess="1024"
maxNamedPipeConnectionRetry="100"
namedPipeConnectionRetryDelay="250"
maxNamedPipeConnectionPoolSize="512"
maxNamedPipePooledConnectionAge="30000"
asyncCompletionThreadCount="0"
initialRequestBufferSize="4096"
maxRequestBufferSize="65536"
uncFileChangesPollingInterval="5000"
gracefulShutdownTimeout="60000"
loggingEnabled="true" logDirectory="iisnode"
debuggingEnabled="true" d
ebugHeaderEnabled="false"
debuggerPortRange="5058-6058"
debuggerPathSegment="debug"
maxLogFileSizeInKB="128"
maxTotalLogFileSizeInKB="1024"
maxLogFiles="20"
devErrorsEnabled="true"
flushResponse="false"
enableXFF="false"
promoteServerVars="AUTH_USER,AUTH_TYPE,LOGON_USER,REMOTE_USER,REMOTE_HOST"
configOverrides="iisnode.yml"
watchedFiles="web.config;*.js"
nodeProcessCommandLine="C:\Program Files\nodejs\node.exe" />
<handlers>
<add name="iisnode" path="server/dist/index.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<!-- Don't interfere with requests for node-inspector debugging -->
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^server/dist/index.js\/debug[\/]?" />
</rule>
<!-- First we consider whether the incoming URL matches a physical file in the /public folder -->
<rule name="StaticContent" patternSyntax="Wildcard">
<action type="Rewrite" url="client/build/{R:0}" logRewrittenUrl="true" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
</conditions>
<match url="*.*" />
</rule>
<!-- All other URLs are mapped to the Node.js application entry point -->
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True" />
</conditions>
<action type="Rewrite" url="server/dist/index.js" />
</rule>
</rules>
</rewrite>
<directoryBrowse enabled="false" />
</system.webServer>
<system.web>
<authentication mode="Windows" />
<authorization>
<allow users="*" />
<deny users="?" />
</authorization>
<identity impersonate="false" />
</system.web>
ugh...Figured it out. I was using the ip address for my site http://ip.address/myapp. It will always prompt for login and password if the url has periods in it. So I canged the ip to the server name and it cleared up the problem. http://myservername/myapp

Azure AppService NodeJs with virtual directory

I have a website built on Angular7 with server side rendering deployed on an Azure App Service. I had to add a web.config file in order to make the server.js run.
Here's the web.config file
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<webSocket enabled="false" />
<handlers>
<add name="iisnode" path="server.js" verb="*" modules="iisnode"/>
</handlers>
<rewrite>
<rules>
<!-- Do not interfere with requests for node-inspector debugging -->
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^server.js\/debug[\/]?" />
</rule>
<!-- All other URLs are mapped to the node.js site entry point -->
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
</conditions>
<action type="Rewrite" url="server.js"/>
</rule>
</rules>
</rewrite>
<security>
<requestFiltering>
<hiddenSegments>
<remove segment="bin"/>
</hiddenSegments>
</requestFiltering>
</security>
<httpErrors existingResponse="PassThrough" />
</system.webServer>
</configuration>
This site is deployeed to mysite.com and everything works fine.
I now need to create a virtual directory mysite.com/app to hold a different application (on the old AngularJS). Without the server side rendering I just create the virtual directory on Azure Portal and everything works fine. Because of the server side and the "redirection" to server.js the virtual directory is no longer working.
Is there any Rule to put on the web.config file to ignore the requests of /app, not to run the nodejs server?
I got my response on another question (credit to dana) so I've just added the rule
<rule name="ignore app application" stopProcessing="true">
<match url="^app" />
<action type="None" />
</rule>
before the other rules. This way if the url typed is mysite.com/app the node server won't be "activated" and the virtual directory works as expected.

How to host a service and static content in iisnode

I have my website working within express. But now I need to host it on IIS. How can I get my routing to work with IISNode?
I was able to move my static content to a /public folder (html, front end js, etc). I didn't want to do this, but got that part working.
I moved my server side logic to /server.
I had previously had .Net Web API style routing working, where my services were hosted at /api. I moved these to /server/api.
Whenever I try to request my API, I get a 404. How can I get this working with IISNode?
app.use("/api", require("./api"));
// routes/api/index.js
var router = require('express').Router();
router.use('/questionsets', require('./questionsets'));
module.exports = router;
var router = require('express').Router();
router.use('/questionsets', require('./questionsets.js'));
module.exports = router;
// routes/api/questions.js
var router = require('express').Router();
router.get('/', function (req, res) {
...
});
router.get('/:id', function (req, res) {
...
});
module.exports = router;
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<security>
<requestFiltering>
<hiddenSegments>
<remove segment="bin" />
</hiddenSegments>
</requestFiltering>
</security>
<handlers>
<add name="iisnode" path="bin/www" verb="*" modules="iisnode" />
</handlers>
<iisnode loggingEnabled="false" />
<httpErrors errorMode="Detailed" existingResponse="Replace">
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" prefixLanguageFilePath="" path="/" responseMode="ExecuteURL" />
</httpErrors>
<rewrite>
<rules>
<rule name="LogFile" patternSyntax="ECMAScript" stopProcessing="true">
<match url="iisnode" />
</rule>
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^bin\/www\/debug[\/]?" />
</rule>
<rule name="StaticContent" patternSyntax="ECMAScript" stopProcessing="true">
<match url=".*" />
<action type="Rewrite" url="{C:1}" logRewrittenUrl="true" />
<conditions>
<add input="{REQUEST_URI}" pattern=".*?\/(.*)" />
</conditions>
</rule>
<rule name="DynamicContent" patternSyntax="ECMAScript">
<match url=".*" />
<conditions>
<add input="{{REQUEST_FILENAME}}" matchType="IsFile" negate="True" />
</conditions>
<action type="Rewrite" url="server/app.js" logRewrittenUrl="true" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Source for url rewriting configuration
You need to install Application Request Routing here , and the documentation here.
Simply putting your server side files under /server would not work.
Start your express server, let's say listenning to http://localhost:3200/ ,
route request '/' to 'localhost:3200' will just route each request to your express server.

Resources