Issue with Typescript express application utilizing express-openid-connect wherein cookies are not being retained, resulting in an infinite loop of redirects

For a while now, I've been facing a block with no resolution in sight for this particular issue. Hopefully, someone out there can lend a hand.

Background

I have a TS express application running on Firebase functions. Additionally, I utilize a custom domain (hosted via Firebase Hosting) that directs to my function so I can access it through a custom domain api.myserver.com. My application caters to multiple businesses, each having its dedicated URL like https://company.api.myserver.com.

One of my clients requested support for SSO integration with Okta. Following Okta's tutorial/sample project at this link, it involves a simple jsExpress app using express-openid-connect.

In my local environment, I simulate the business-specific URL by appending the company prefix to the URL provided by Firebase emulator, e.g.

http://company.api.localhost:5001/project-id/us-central1/function-name

Everything runs smoothly in localhost; I successfully connect to my Okta environment via SSO, receive the /callback request post-login, and get redirected to the target URL as expected.

The Issue

Upon deploying to production and testing it on my actual domain (along with the company subdomain) https://company.api.myserver.com, I encounter an unexpected behavior. While I can login with my credentials, the execution flow lands me in an infinite loop - redirecting back to the /login endpoint within auth(config), rather than progressing further.

Following extensive debugging, here are a couple of key observations:

  1. Cookies are being received at my /callback endpoint but they do not persist upon reaching the target URL, resulting in empty req.cookies.
  2. To investigate a potential URL mismatch due to Firebase provisioning, I added logging to print the URL received using
    req.protocol + "://" + req.get("host") + req.originalUrl
    , which surprisingly reflects the URL issued by Firebase instead of my custom domain! For instance,
    https://us-central1-project-id.cloudfunctions.net/login
    is seen instead of company.api.myserver.com (uncertainty prevails whether this triggers cookie persistence issues).

Desperate after exhausting forums, debugging sessions, and consultations even to chatGPT, I turn to this platform for assistance. Any insights?

Below are snippets of relevant code segments along with explanatory comments:

server.ts

export const app = express();
app.set("trust proxy", true);
// various middleware configurations

app.use(function (req, res, next) {
  // user authentication setup
});

app.use(genAuthenticateUser);
app.use(genValidateEnterprise);

authConfig()

async function authConfig(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
): Promise<void> {
  // cookies verification
  // authentication handling logic

  // initial setup for authentication middleware
  const authMiddleware = auth(config);
  authMiddleware(req, res, next); // redirection point
}

Answer №1

Firebase Hosting is positioned behind a CDN, and when employing a Cloud Function or Cloud Run to deliver content to Firebase Hosting, your express instance also resides behind the CDN. This setup offers the advantage of enabling you to cache responses from your server-side code in order to reduce the frequency of invocations.

Nevertheless, a key requirement of this configuration is that cookies must be labeled as __session, as it is the sole cookie preserved in the request by the Firebase Hosting CDN. Regrettably, many users have run into issues due to this stringent cookie policy not being replicated by the Firebase Emulators, therefore escaping detection during local execution.

By examining the documentation for the ConfigParams object passed into the constructor of express-openid-connect, it's apparent that the default cookie used by that library is named appSession.

To utilize express-openid-connect with Express behind the CDN, ensure to specify the cookie name in your configuration object:

const config = {
  // ... other config
  session: {
    name: "__session"
  },
  // ... other config
};

Verify that your auth-related endpoints include a Cache-Control header set to private. It may not be dealt with by express-openid-connect by default, but you could potentially address it within afterCallback() and similar hooks.

res.setHeader('Cache-Control', 'private');

Note: The Cloud Functions/Run Invoker (that operates your code) likewise utilizes Express internally before handling the request. This can be observed by reviewing its public code version and the 1st gen Cloud Functions documentation (this applies to 2nd gen Cloud Functions as well). Before handling the request, the following actions are performed:

Middleware injection includes:

  • JSON body parser - bodyParser.json({ ... }) (for both 'application/json' and 'application/cloudevents+json')
  • Plain text body parser - bodyParser.text({ ... }) (for 'text/plain')
  • URL-encoded bodies, with extended: true - bodyParser.urlencoded({ ... })
  • All other data handled through req.rawBody as a Buffer by bodyParser.raw({ ... }).
  • Express 'trust proxy' setting activated.
  • Express 'x-powered-by' feature disabled.
  • Express 'etag' functionality deactivated.

In addition, HTTP request handlers will automatically return HTTP 404 for requests to /favicon.ico and /robots.txt (these files should be deployed in the Firebase Hosting public folder anyway).

Typically, static resources should be deployed to Firebase Hosting instead of alongside your Cloud Function/Cloud Run code. Hosting will serve static resources efficiently without creating unnecessary load on your code base.

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

Steps to execute an Angular directory within a project

Click here for imageWhenever I try to run ng serve command from the directory, it doesn't seem to work and just takes me back to the same directory. I'm having trouble running my Angular project. Any suggestions on how to solve this issue? ...

The Nextjs next-auth implementation with URL '/api/auth/callback/cognito' encountered a 502 Bad Gateway error while in production

I'm facing a challenge while trying to deploy a nextjs app that utilizes 'next-auth' with AWS Cognito. Interestingly, the app runs smoothly when tested locally using either next dev or next start. However, upon deploying it on the producti ...

Discover the use of dot notation for accessing nested properties

In the deps array below, I aim to enforce type safety. Only strings allowed should be in dot notation of ${moduleX}.${moduleX service} // Modules each have a factory function that can return a services object (async) createModules({ data: { factory: ...

Exploring the world of Vue and Pinia: managing and accessing data like

While delving into Vue and Pinia, I encountered a data management issue on the user side. On my main page, I showcase categories and auction items. However, upon navigating to a specific category in the catalog, the data for auction items remains in the st ...

Is there a way to create a Typescript function that can automatically return either a scalar or array value without requiring the caller to manually cast the output

Challenge Looking for a solution to the following problem: save<T>(x: T | T[]) { if (x instanceof Array) { // save array to database } else { // save entity to database } return x } // client code let obj: SomeType = { // values here ...

What is the process for importing a component at a later time?

I am attempting to import components with a delay in a seamless manner. My goal is to import the components discreetly so that they load smoothly in the background while viewing the homepage. I experimented with lazy loading, but found that it caused dela ...

Inquiring about Vue 3 with TypeScript and Enhancing Types for Compatibility with Plugins

I've been struggling to find a working example of how to implement type augmentation with Vue3 and TypeScript. I have searched for hours without success, trying to adapt the Vue2 documentation for Vue3. It appears that the Vue object in the vue-class ...

Having trouble setting a default value for your Angular dropdown? Looking for alternative solutions that actually work?

Objective: Customize the default value for a dropdown menu to switch between English (/en/) and Spanish (/es/) addresses on the website. Challenge: Despite extensive research, including consulting various sources like Angular 2 Dropdown Options Default Va ...

An error occurred in TypeScript when trying to use the useState function with a string type. The ReferenceError indicates that

import React, { FunctionComponent, useState, useEffect } from 'react' const SearchBar: FunctionComponent = () => { const [searchValue, setSearchValue] = useState<string>('') const [isSuggestionOpen, setIsSuggestionO ...

What is the best way to retrieve the post JSON data in the event of a 404 error?

When my service call returns a 404 error, I want to display the server's message indicating the status. The response includes a status code and message in JSON format for success or failure. This is an example of my current service call: this._trans ...

Unable to transfer the form value to the service and City value cannot be updated

I am new to the world of Angular and I am attempting to create a basic weather application. However, I am encountering issues when trying to pass the city value from the form on ngSubmit to the API service. I have attempted to use an Emitter Event to trans ...

What is the best way to encode a type that necessitates a specific superclass and interface implementation?

In order to achieve my goal, I need to extend a library class that is part of the React components I am creating. Here's an example of the original library class I'm working with: class ReactComponent<T, U> { render() { return "some ht ...

Determine the index of a specific character within a string using a "for of" loop

How can I obtain the position of a character in a string when it has been separated programmatically using a for...of loop? For instance, if I wish to display the position of each character in a string with the following loop: for (let c of myString) { ...

Implementing Firebase for OTP verification in an Express API backend

Currently I am working on authenticating phone numbers using Firebase OTP in my NodeJS Express API. The data is being stored in MongoDB and now I also need to integrate Firebase. Can anyone provide guidance on how to add Firebase to my NodeJS application ...

transferring expressjs cookie via JSON format

I came across the data in the express API reference expressjs documentation on cookies According to the documentation, cookies can be sent as JSON res.cookie('cart', { items: [1,2,3] }); So I decided to give it a try. The cookie worked fine wh ...

Error: A variable is potentially 'undefined' (ts2532), even though I have just assigned a value to it

Encountering Object is possibly 'undefined'. ts(2532) errors in TypeScript for an optional parameter, despite being clearly defined... interface Foo { keyValue?: Record<string, string> } const a: Foo = { keyValue: { key1: 'value&apo ...

A different approach to routing in Next.js with getServerSideProps

Let's say I have a dynamic route in my application, with the name [id] Typically, I use getServerSideProps in the pages router to validate any properties passed to the route. It usually looks something like this: export async function getServerSidePr ...

Build upon a class found in an AngularJS 2 library

Whenever I attempt to inherit from a class that is part of a library built on https://github.com/jhades/angular2-library-example For example, the class in the library: export class Stuff { foo: string = "BAR"; } And the class in my application: exp ...

Using Angular template to embed Animate CC Canvas Export

Looking to incorporate a small animation created in animate cc into an angular template on a canvas as shown below: <div id="animation_container" style="background-color:rgba(255, 255, 255, 1.00); width:1014px; height:650px"> <canvas id="canv ...

Integrate incoming websocket information with state management in React

I am facing a challenge in creating a timeseries plot with data obtained from a websocket connection. The issue arises when new values overwrite the previously stored value in the state variable. const [chartData, setChartData] = React.useState(null) Cu ...