Help with custom SessionStore in NodeJS SDK

Hello everyone!

Looking for some help here. Not really sure that it’s a bug, so didn’t want to go to GitHub first. I’m currently working on setting up a custom Session Store in an ExpressJS application that will store session in a PostgreSQL database. I wanted to integrate the connect-pg-simple library into my application, but it hasn’t be as seamless as I was expecting.

Below is a snippet that details how I am setting up the middleware.

server.use(auth({
            auth0Logout: false,
            authorizationParams: {
                redirect_uri: openIDConnectConfig.OAUTH2_REDIRECT_URI,
                response_type: 'code',
                scope: 'openid email phone',
            },
            authRequired: false,
            baseURL: 'http://localhost:8080',
            clientID: openIDConnectConfig.OAUTH2_CLIENT_ID,
            clientSecret: openIDConnectConfig.OAUTH2_CLIENT_SECRET,
            issuerBaseURL: openIDConnectConfig.OAUTH2_ISSUER_BASE_URL,
            routes: {
                login: false,
                logout: false,
                callback: '/v1/oauth2/callback'
            },
            secret: Buffer.from(openIDConnectConfig.OAUTH2_SECRET).toString('base64'),
            session: {
                store: new pgSession({ pool, tableName: 'session' }) as unknown as SessionStore,
                cookie: {
                    httpOnly: true
                }
            }, 
        }));

I have a controller setup with various auth methods inside that I’ve been using for login/logout/etc.

import { Http } from "@status/codes";
import { randomInt } from "crypto";
import { Request, Response } from "express";
import { Get, HttpCode, JsonController, OnUndefined, Req, Res } from "routing-controllers";
import { Service } from "typedi";

@Service()
@JsonController('/oauth2')
export class OpenIDConnectAuthenticationController {

    @Get('/login')
    @OnUndefined(Http.Ok)
    async login(@Res() response: Response): Promise<void> {
        await response.oidc.login({ returnTo: 'http://localhost:8080' });
    }

    @Get('/logout')
    @OnUndefined(Http.Ok)
    async logout(@Res() response: Response): Promise<void> {
        await response.oidc.logout();
    }

    @Get('/is-authenticated')
    @HttpCode(Http.Ok)
    isAuthenticated(@Req() request: Request): boolean {
        return request.oidc.isAuthenticated();
    }

    @Get('/user-info')
    @HttpCode(Http.Ok)
    userInfo(@Req() request: Request): Promise<Record<string, unknown>> {
        return request.oidc.fetchUserInfo();
    }

    @Get('/random-number')
    @HttpCode(Http.Ok)
    randomNumber(): number {
        return randomInt(1_000);
    }
}

This code works fine, mostly just test code to play around with the library. Not going to be used in production or anything. I’ve been running into a problem when calling the isAuthenticated or randomNumber methods. Whenever I call either one of those methods, I end up getting the following error.

Error [ERR_STREAM_WRITE_AFTER_END]: write after end
    at new NodeError (internal/errors.js:322:7)
    at writeAfterEnd (_http_outgoing.js:694:15)
    at ServerResponse.end (_http_outgoing.js:815:7)
    at ServerResponse.resEnd [as end] (webpack://express-cognito/./node_modules/express-openid-connect/lib/appSession.js?:362:19)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
Emitted 'error' event on ServerResponse instance at:
    at writeAfterEndNT (_http_outgoing.js:753:7)
    at processTicksAndRejections (internal/process/task_queues.js:83:21) {
  code: 'ERR_STREAM_WRITE_AFTER_END'
}

So, I ended up thinking, maybe I was just setting up the postgres session store incorrectly. So I decided to write my own custom session store that looks like this…

export class InMemorySessionStore implements SessionStore {

    private store: Map<string, SessionStorePayload>;

    constructor() {
        this.store = new Map<string, SessionStorePayload>();
    }

    get(sid: string, callback: (err: unknown, session?: SessionStorePayload) => void) {
        this.store.has(sid) ? callback(null, this.store.get(sid)) : callback(null, null);
    }

    set(sid: string, session: SessionStorePayload, callback?: (err?: unknown) => void) {
        this.store.set(sid, session);
        callback();
    }

    destroy(sid: string, callback?: (err?: unknown) => void) {
        if(this.store.has(sid)) {
            this.store.delete(sid);
        }
        callback();
    }

}

Again, this isn’t going to be production code or anything. I’ve mostly just been trying to toy around with the library and learn how things work.

Interestingly enough, if I plug this InMemorySessionStore into the auth middleware, I still get that ERR_STREAM_WRITE_AFTER_END error message. I thought the code was pretty simple, so I started digging into the express-openid-connect code a little bit, and realized that inside of appSession.js, the following was causing the problem…

// line (349)
if (isCustomStore) {
      const id = existingSessionValue || generateId(req);

      onHeaders(res, () =>
        store.setCookie(req[REGENERATED_SESSION_ID] || id, req, res, { iat })
      );

      const { end: origEnd } = res;
      res.end = async function resEnd(...args) {
        try {
          /* This await is what seems to be causing the issue */
          /* Removing it fixes the issue, and doesn't cause any */
          /* Noticeable problems with either SessionStore */
          await store.set(id, req, res, {
            iat,
          });
          origEnd.call(res, ...args);
        } catch (e) {
          // need to restore the original `end` so that it gets
          // called after `next(e)` calls the express error handling mw
          res.end = origEnd;
          process.nextTick(() => next(e));
        }
      };
    } else {
      onHeaders(res, () => store.setCookie(req, res, { iat }));
    }

So I noticed that without that await everything seems to be ok. The header issue goes away and login/logout still seem to function appropriately. Sessions are now being stored either in memory or in a PostgreSQL database I’m running locally while doing all this.

Does anyone have any experience with setting up these custom session stores? Am I doing something wrong? Looking at the SessionStore interface, it looks like get/set/destroy all return void and aren’t returning Promise<void>. They are also being fed callbacks, so I wouldn’t think that these should be returning promises. So why the await? Why the async function?

I’m rather new to JavaScript/TypeScript, so I could be doing something wrong or just not understanding why the await is there. Just looking for help with the issue.

Thanks for all the help!


  • Which SDK this is regarding: NodeJS
  • SDK Version: 2.7.2
  • Platform Version: Windows 11
  • Code Snippets/Error Messages/Supporting Details/Screenshots:

I ended up moving this to GitHub as well. Not sure if the NodeJS SDK folks look at this as often, or if this is really for community questions