node express on Azure: authentication failed due to: In collectInfoFromReq: invalid state received in the request
I have an Azure Active Directory tenant that I wish to authenticate with from my Node.js application running on an Azure App Service instance. I'm using passportjs and passport-azure-ad to do this.
Locally everything works fine. I can authenticate with the Azure AD tenant and it returns back to my page correctly. However on Azure it fails with the error:
authentication failed due to: In collectInfoFromReq: invalid state received in the request
solution:
In most cases, using secure cookies works just fine. As long as you are using a secure endpoint over HTTPS, the server application will send back cookies to the client with the secure flag set (assuming you have it configured to do so).
When using systems like Heroku and Azure, the secure endpoint is terminated before your application and proxied to you. This means that your application is actually not running on a secure port or protocol. The way your application knows the connection could be considered secure is from the addition of the X-Protocol-Proto header from the proxy.
In most cases this is just fine because web application frameworks like Express will watch for this header to know the application is running behind a reverse proxy. If you have configured Express to trust this proxy it will consider the request secure. Once it considers it secure, it will allow the setting of cookies that are marked to be secure.
There is a problem with this on Azure that isn’t obvious at first. The Azure proxy does not add the X-Protocol-Proto header. Instead it adds x-arr-ssl. This means that even though the request could be considered secure, Express does not know that so it will not set any cookies in the response that are marked as secure.
In order to get secure cookies to work in Azure you will need to trick Express.
What you need to do is add the following anonymous function as middleware before you attempt to set any secure cookies.
//Tell Express that we're running behind a //reverse proxy that supplies https for you app.set('trust proxy', 1); //Add middleware that will trick Express //into thinking the request is secure app.use(function(req, res, next) { if(req.headers['x-arr-ssl'] && !req.headers['x-forwarded-proto']) { req.headers['x-forwarded-proto'] = 'https'; } return next(); });