Search code examples
node.jsadalpassport-azure-ad

Invalid state on azure, but working locally


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

My configuration is exactly the same (apart from redirectUrl) as I'm using the same tenant for local testing as well as in Azure yet it still fails. I've set up the proper reply urls and the authentication returns back to my application.

Here is my config:

{
  identityMetadata: `https://login.microsoftonline.com/${tenantId}/.well-known/openid-configuration`,
  clientID: `${clientId}`,
  responseType: 'id_token',
  responseMode: 'form_post',
  redirectUrl: 'https://localhost:3000/auth/oidc/return',
  allowHttpForRedirectUrl: false,
  scope: [ 'openid' ],
  isB2C: false,
  passReqToCallback: true,
  loggingLevel: 'info'
}

I'm using the OIDCStrategy.

My authentication middleware:

passport.authenticate('azuread-openidconnect', {
  response: res,
  failureRedirect: '/auth/error',
  customState: '/'
});

I've compared the encoded state on the authorizerequest vs the returned response and they differ in the same way locally as well as on Azure, yet Azure is the only one complaining. Examples of how the states differ:

Azure:
Request state:  CUSTOMEwAuZcY7VypgbKQlwlUHwyO18lnzaYGt%20
Response state: CUSTOMEwAuZcY7VypgbKQlwlUHwyO18lnzaYGt

localhost:
Request state:  CUSTOMTAYOz2pBQt332oKkJDGqRKs_wAo90Pny%2F
Response state: CUSTOMTAYOz2pBQt332oKkJDGqRKs_wAo90Pny/

I've also tried removing customState completely yet it still fails.

Anyone know what's going on here? Am I configuring it incorrectly?

Edit: It appears that this may not be an issue with passport-azure-ad. I'm not sure yet, but some debugging revealed that there is no set-cookie header on the login request to my app. The session is created, but no cookie is set thus the returning response is unable to look up the session info including the state and compare them. The result is that it reports invalid state since it's unable to retrieve data from the session.


Solution

  • Turns out the problem was that the session was never properly created thus there was no state for process-azure-ad to compare. The reason for this was that I had configured express-session to use secure session cookies under the assumption that since I was connecting through the https://...azurewebsites.net address the connection was secure. This is not technically the case though.

    Azure runs a load balancer in front of the Web Application effectively proxying connections from the outside to my app. This proxy is where the secure connection is terminated and then traffic is routed unencrypted to my application.

    Browser -(HTTPS)> Load balancer -(HTTP)> Application
    

    The result is that node did not report the connection as secure unless a set the configuration option trust proxy:

    app.set('trust proxy', true);
    

    When this option is set express will check the X-Forwarded-Proto header for which protocol was used to connect to the proxy server (in this case the load balancer). This header contains either http or https depending on the connection protocol.

    For Azure though this is still not sufficient. The Azure load balancer does not set the X-Forwarded-Proto header either. Instead it uses x-arr-ssl. This is not a big problem though as iisnode (the runtime I'm using to run node on IIS in Azure) has an option called enableXFF that will update the X-Forwarded-Proto header based on the external protocol of the connection. Setting both these options enables express-session to set the secure cookie keeping the session stored and allowing passport-azure-ad to store and compare state information on authentication.

    PS: Big thanks to Scott Smiths blog + comments for providing the answer: http://scottksmith.com/blog/2014/08/22/using-secure-cookies-in-node-on-azure/