Passport not retaining session when used with a proxy server

Situation

Currently, my frontend is built using React and the backend server utilizes SAML authentication process with Azure AD, which is handled by an Express server. The nginx server serves the React application and acts as a proxy passing all requests prefixed with /api to the backend server. This setup ensures that the backend server is only known to nginx and not directly exposed to the client.

Visual representation of the architecture

The issue I'm facing is that Passport does not seem to function correctly in this configuration. The sequence of events is as follows:

  1. The client initiates the authentication process by clicking on the authentication button, triggering a request to the /api/login route from React.
  2. NGINX forwards this request to the backend server, where Passport takes control.
  3. Upon successful login through the IDP form, Passport successfully handles the callback function, extracting the user profile from the IDP and passing it to the backend server.
  4. Subsequently, the backend server redirects the client to the /home route, but the session information is not retained or forwarded to the client, resulting in the client not being authenticated.

Code

Here is the initialization of Passport in app.js:

app.set('trust proxy', 1)
app.use(
  session({
    cookie: {
      path: '/',
      secure: true,
      httpOnly: false,
      sameSite: 'none',
      domain: `https://${process.env.FRONTEND_DOMAIN}`
    },
    resave: false,
    saveUninitialized: true,
    secret: 'TOI_MEME_TU_LE_C'
  })
)
app.use(passport.initialize())
app.use(passport.session())

Below is the callback route in Express:

router.post('/saml/acs', bodyParser.urlencoded({ extended: false }), (req, res, next) => {
  passport.authenticate('saml', (err, user) => {
    if (err) {
      let error = 'generic'
      if (err.message.includes('RequestDenied')) {
        error = 'request_denied'
      } else if (err.message.includes('unspecified')) {
        error = 'unspecified'
      }

      return res.redirect(`https://${process.env.FRONTEND_DOMAIN}/login?error=${error}`)
    }

    console.log(`${user.firstname} ${user.lastname} (${user.employeeId ?? '-> no employeeId <-'}) is connected.`)

    req.login(user, error => {
      if (error) {
        console.log('Error during login.', error)
        return next(error)
      }

      console.log('Login successful, is authenticated?: ' + req.isAuthenticated())

      return res.redirect(`https://${process.env.FRONTEND_DOMAIN}`)
    })
  })(req, res, next)
})

It's worth noting that both logs are displayed correctly. The second log confirms that req.isAuthenticated() returns true.

The issue arises when the client, despite successfully connecting, is not authenticated for the backend (specifically in the /me route):

router.get('/me', (req, res) => {
  // The code does not progress beyond this point, as both the method and the user object are absent in the request
  if (!req.isAuthenticated()) {
    return res.status(401).send({ error: "Not connected." })
  }

  if (!req.user) {
    return res.status(500).send({ error: "No user on the request." })
  }

  res.status(200).json(req.user)
})

I have attempted removing my custom error handling in the Passport callback and adjusting cookie settings, but these changes have had no effect.

Why is Passport failing to detect authentication correctly?

Answer №1

Your nginx configuration plays a crucial role in this situation. Without having access to that information, my response can only be a educated guess. If you are configuring the session with secure: true, it is essential to also set up nginx to forward the protocol. You can achieve this by adding the following line in your nginx configuration:

proxy_set_header X-Forwarded-Proto $scheme;

Some additional questions that you can consider to pinpoint the issue include:

  • Does the application function correctly on your local environment? If it does, the problem could be related to the nginx server.
  • Can you confirm if the issue persists after removing the secure: true configuration? If it does not, it indicates that nginx may not be indicating that requests are using https. In such cases, refer to the proxy_set_header directive mentioned above.
  • Is nginx serving the site over https or http? Keep in mind that using https is a prerequisite for enabling the secure: true attribute.

For further insights, you can refer to the discussions on Express-session Secure Cookies not working or explore the suggestions provided in this recipe: https://gist.github.com/nikmartin/5902176.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How should you correctly display the outcome of a mathematical function on a data property in a v-for loop in VueJS?

Recently, I've been developing a dice roller using Vue for a game project. The approach involves looping through different types of dice with v-for to create buttons and display the result in an associated div element. However, despite correct console ...

Error message in Vuex4: Unable to access 'state' property because it is undefined

Having trouble with Vue3 and Vuex4, I keep encountering this error: Uncaught TypeError: Cannot read properties of undefined (reading 'state') at ReactiveEffect.eval [as fn] (App.vue?3dfd:36) at ReactiveEffect.run (reactivity.esm-bundler.j ...

The issue with the trigger function in jQuery is specifically affecting Chrome browser

When attempting to load a modal window using a link like , I am encountering issues in Chrome, Safari, and IE. However, Opera and FF are functioning properly. The modal window is being called as an iframe. Other buttons that are supposed to open the modal ...

How can I transfer a PHP variable into a JavaScript variable nested within another PHP variable

After my php/html script generates the image, I am looking to display it without needing to reload the page. I currently call a javascript function within the PHP code to change the image. However, I am unsure how to pass the new image name to this javasc ...

Refresh jQuery CSS for active button whenever a different button is clicked

I want to enhance a group of buttons using jQuery. I aim to make the active button visually stand out so that users can easily identify it. These buttons are being created through a PHP foreach loop, as shown below: foreach($result as $row){ echo "< ...

Is jQuery.each() failing to function properly in Firefox and Internet Explorer?

Having trouble with the $.each function behaving differently across browsers. I have lists with background images, and I want the parent div to fade in once these images finish loading. My code seems correct as there are no JavaScript errors in the conso ...

Issues with utilizing a mongoose schema reference in a Node.js application

Having recently ventured into the world of Node, Mongoose, and Backend development, I encountered a puzzling issue. Despite following the exact code from a development course exercise files, my problem persists. The task at hand involves defining a Mongoos ...

Enhancing appearance with $refs?

Having trouble adding style to the $refs attribute. I keep getting an error message saying "Cannot set property 'cssText' of undefined." Is there a way to accomplish this task? I haven't come across anything similar to this issue before. th ...

Error: Unable to retrieve the value as the property is null

I'm a beginner in jQuery and I'm attempting to create a login form that displays a message when the user enters a short username. However, despite my efforts, the button does not trigger any action when clicked. Upon checking the console, it indi ...

What is the best way to implement setState in this scenario?

Receiving warnings in the console that say: Avoid changing state directly. Instead, use setState() method: react/no-direct-mutation-state When I tried changing this.state.turn = this.state.turn === 'X' ? 'O' : 'X'; to this.s ...

StyledTab element unable to receive To or component attributes

Is there a way to use tabs as links within a styled tab component? I've tried using the Tab component syntax with links, but it doesn't seem to work in this scenario: <Tab value="..." component={Link} to="/"> If I have ...

Encoding a JSON representation of an HTML list where all children are displayed at each parent item

Here is the structured list that I am currently working with: function convert( name_ref ) { name_ref = name_ref + " > ol > li"; var mylist = []; $(name_ref).each(function () { if ($(this).find('> ol > li').length){ myl ...

The connection named "Default" could not be located for use with TypeOrm and Express

I am currently facing an issue with my setup involving TypeORM. It seems that Express is being initialized before the connection to the database is established with TypeORM, causing an error message "Connection default not found." Here is a snippet of the ...

The success message is not being displayed after clicking the pay button using ReactJS, Stripe Payments, Node, and

Currently, I am working with ReactJS, Node.js, and Express while integrating the Stripe Payment API. However, I am facing an issue where after clicking the pay button and entering the dummy credit card details, the payment processing does not proceed as ex ...

The link button appears unselected without a border displayed

I am facing an issue with a link button in my code. Here is the snippet: <div class="col-2"> <a type="button" routerLink="auto-generate-schedules/generate" class="btn btn-primary mb-2">Generate Sche ...

When attempting to manage errors in mongoose.findOne, I am encountering the issue of receiving an 'Unhandled 'error' event'

Handling 'error' Events with Mongoose Attempting to handle errors in the mongoose.findOne() function led me to try using a second parameter in this manner: mongoose.findOne({ uuid: uuid }, (err,doc)). However, I encountered an issue where no err ...

When attempting to post data using jQuery, an object is encountered that does not have a parameterless constructor

Here is the code snippet I am using with jQuery to post data and register a band: var formData = new FormData(); var file = document.getElementById("CoverPicture").files[0]; formData.append("file", file); var Name = $('#Name').val(); var Genre = ...

Acquire the jQuery cookie with the power of AngularJS

I am currently utilizing jquery.cookie v1.4.1 to set a cookie in the following manner: $.cookie('userCookie', $("#user").val()); The value returned by $("#user").val() is typically something like 'username' Now, within an angular app ...

Tips for removing a checkbox and customizing a label's style when the label wraps around the checkbox input

Hello, I'm an advanced beginner so I appreciate your patience:) I've had success removing checkboxes and styling their labels when the for="" attribute is present. However, I'm facing a challenge with a form where the labels wrap the input ...

What is the most optimal method for scheduling API calls at varying intervals?

When making API calls in my Vue application, they are timing out when called simultaneously. I am using axios to send the data to a VueX store. I have resorted to using setTimeout to stagger the API calls at different intervals in order to avoid the timeo ...