13

I use the following code which works, However after a few success calls (5-10), we sometimes get an internal server error:

req.session["oidc:accounts.rvm.com"] is undefined

I've tried all the latest open source versions.

Error: did not find expected authorization request details in session, req.session["oidc:accounts.rvm.com"] is undefined
at /opt/node_app/app/node_modules/openid-client/lib/passport_strategy.js:125:13
at OpenIDConnectStrategy.authenticate (/opt/node_app/app/node_modules/openid-client/lib/passport_strategy.js:173:5)
at attempt (/opt/node_app/app/node_modules/passport/lib/middleware/authenticate.js:366:16)
at authenticate (/opt/node_app/app/node_modules/passport/lib/middleware/authenticate.js:367:7)
at /opt/node_app/app/src/logon.js:92:7 *******
at Layer.handle [as handle_request] (/opt/node_app/app/node_modules/express/lib/router/layer.js:95:5)
at next (/opt/node_app/app/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch (/opt/node_app/app/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/opt/node_app/app/node_modules/express/lib/router/layer.js:95:5)
at /opt/node_app/app/node_modules/express/lib/router/index.js:281:22

My code from the stack is:

at /opt/node_app/app/src/logon.js:92:7

Which is the end of the code here:

})(req, res, next);   // here is line 92 but not sure if it's related 

This is the full code (I pass the app which is simply an express server):

index.js

const express = require('express');
const logon = require('./logon');

const app = express();
const port = process.env.PORT || 4000;

logon(app)
  .then(() => {
    console.log('process started');
  });
app.use(express.json());

app.listen(port,
  () => console.log(`listening on port: ${port}`));

logon.js

const { Issuer, Strategy } = require('openid-client');
const cookieParser = require('cookie-parser');
const cookieSession = require('cookie-session');
const azpi = require('./azpi');
const bodyParser = require('body-parser');
const passport = require('passport');

module.exports = async (app) => {
  let oSrv;
  const durl = `${process.env.srvurl}/.well-known/openid-configuration`;
  try {
    oSrv = await Issuer.discover(durl);
  } catch (err) {
    console.log('error occured', err);
    return;
  }

  app.get('/', prs(), passport.authenticate('oidc'));

  const oSrvCli = new oSrv.Client({
    client_id: process.env.ci,
    client_secret: process.env.cs,
    token_endpoint_auth_method: 'client_secret_basic',
  });

  passport.serializeUser((user, done) => {
    done(null, user);
  });
  passport.deserializeUser((obj, done) => {
    done(null, obj);
  });

  const cfg = {
    scope: 'openid',
    redirect_uri: process.env.ruri,
    response_type: 'code',
    response_mode: 'form_post',
  };

  const prs = () => (req, res, next) => {
    passport.use(
      'oidc',
      new Strategy({ oSrvCli , cfg }, (tokenset, done) => {
        const claims = tokenset.claims();
        // first log
        console.log(`1. ------------User claims received------------);
        const user = {
          name: claims.name,
          id: claims.sub,
          id_token: tokenset.id_token,
        };
        return done(null, user);
      }),
    );
    next();
  };
  app.use(
    bodyParser.urlencoded({
      extended: false,
    }),
  );
  app.use(cookieParser('csec'));
  app.use(
    cookieSession({
      name: 'zta-auth',
      secret: 'csect',
    }),
  );

  app.use(passport.initialize());
  app.use(passport.session());

  app.get('/redirect', async (req, res, next) => {
    await passport.authenticate('oidc', async (err, user) => {
    // print second log
    console.log('2. ------------redirect Called!------------');
      if (err) {
        console.log(`Authentication failed: ${err}`);
        return next(err);
      }
      if (!user) {
        return res.send('no identity');
      }

      req.login(user, async (e) => {
        if (e) {
          console.log('not able to login', e);
          return next(e);
        }
        try {
          const url = await azpi.GetUsers(user.id_token);
          // print last log
          console.log('3. ------------user process finished successfully----');
          return res.redirect(url);
          
        } catch (er) {
          res.send(er.message);
        }
      });
    })(req, res, next);   //here is the error
  });
};

Sometimes when I debug, I see that the function is running out from GetUsers which is an async function and stops in })(req, res, next);, maybe it's an async issue.

We want to use this code in prod instead of the previous Java implementation.

If I can use another technique for oidc, please let me know.


UPDATE

Each should be a single call and log in this order:

1. ------------User claims received------------
2. ------------redirect Called!------------
3. ------------user process finished successfully----

But, when I get the error:

1. ------------User claims received------------
2. ------------redirect Called!------------
3. ------------user process finished successfully----

2. ------------redirect Called!------------
Authentication failed: Error: did not find expected authorization request details in session, req.session

All the successful calls have the right log order (1-3).

When it fails, the first call User claims received doesn't happen, just the second and the error.

If there is another way to achieve this (other lib's etc), please let me know.


I've found this library which may help as it doesn't use passport (I want to reduce deps to see where the problem is coming from).

When I try something like this:

app.use(
    auth({
     issuerBaseURL: `${URL}/.well-known/openid-configuration`,
     authorizationParams: {
    ...
     response_mode: 'form_post',
    }

I get this error: issuer response_mode supporting only "query" or "fragment", but when I run the code above (in the beginning of the post) with the same issuer and response_mode, everything is working, any ideas?

Daniel_Knights
  • 7,940
  • 4
  • 21
  • 49
Beno Odr
  • 1,123
  • 1
  • 13
  • 27
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/219472/discussion-on-question-by-beno-odr-node-with-express-session-issue). – Samuel Liew Aug 09 '20 at 03:37
  • One serious issue: you are calling `prs()` before defining the function, this would be expected to throw an exception. – Bergi Aug 12 '20 at 09:31
  • Also I don't know passport, but it looks rather weird that you'd create a `new Strategy` in a middleware on every request, although it seems to be independent from the `req` and `res`. I would assume `passport.use(…)` should be called only once. – Bergi Aug 12 '20 at 09:34
  • @Bergi - thanks, regard the `prs()` I've change it already and got the same results, regard the passport use not sure how to change my code, could you please provide an example as answer and I try it out? – Beno Odr Aug 12 '20 at 09:39
  • @Bergi `prs` would be hoisted. – James Aug 13 '20 at 19:38
  • 1
    @James [But still cause an exception](https://stackoverflow.com/q/31219420/1048572) – Bergi Aug 13 '20 at 20:30
  • @Bergi yeah, it's an expression in this case, good spot. – James Aug 13 '20 at 20:44

5 Answers5

3

The issue appears to be a race condition whereby if you happen to get two requests, in-flight, at the same time, when one finishes it is clearing the session cookie before the other has the chance to complete. For what it's worth, you aren't the only person with this this problem.

I don't think it's an issue with the library itself though, I think the problem lies more with the session library. You may want to try express-session library with the saveUninitialized / resave options set to false and check whether you still see the same issue e.g.

const session = require('express-session');
...
app.use(session({
  saveUninitialized: false,
  resave: false
});

The only difference between this library and the cookie-session one you are using is express-session only stores the session ID in a cookie, the data is stored server-side. If you find it works, then you can look at using a more production-grade store (the default setting is to run with an in-memory one).

FWIW - you only need to configure the strategy one time, looking at what it does, I'd be surprised if it's part of the problem but I'd fix that just incase

James
  • 80,725
  • 18
  • 167
  • 237
  • Thanks, i'll try to change the session and let you know, how would you change the code to use only one one strategy , could you please give me an example in my contexts ? – Beno Odr Aug 14 '20 at 07:00
  • I've tried the `express-session` and I got the same erorr :( – Beno Odr Aug 14 '20 at 07:23
  • @BenoOdr so I'm wondering....how much of a realistic problem will this be? Presumably this would only ever happen if you've got the same client initiating the same request simultaneously? Ironically, there probably is a way to avoid this - if you keep the Strategy per request, then you could override the `sessionKey` making it unique per request, that way you wouldn't ever have a clash. – James Aug 14 '20 at 18:13
  • It would be great if you provide example in my contexts how to do it and i'll ASAP. thanks – Beno Odr Aug 15 '20 at 07:53
  • @Beno just keep the code you have in the `logon.js`, but when creating the strategy, pass a `sessionKey` option as something unique each time e.g. [uuid](https://www.npmjs.com/package/uuid). Or even simpler, just pass in a random number (it's just to test). – James Aug 15 '20 at 09:58
2

We faced a similar issue but we had a more intermittent behavior, we had the error when logging in on Safari but not on Chrome.

From what I understood, this is because the session cookie is getting set when we are first authenticating, which stores the state, code-verifier (only if using PKCE flow) and other values the OIDC client needs to validate the authentication.

However, when the /callback URL is hit, the browser sends this session cookie to the server to complete authentication.

Whenever this cookie is not sent, thats when this error occurs, because the callback assumes this to be a new request and it crashes...

for us this was behaving in 2 ways.

  1. Cookie { Same-site: 'Lax' secure: true }

    Works for chrome but this did not work on safari

  2. Cookie { Same-site: 'None' secure: true }

    Works for chrome and on safari

this needs to be set on the express-session middleware (sorry Im not sure of the syntax thats needed)

user2296208
  • 83
  • 2
  • 6
0

One thought is if you decide to enable sessions, then you need to use express.session() before passport.session() to ensure that the user's login session is restored in the right order.

See this article

Tore Nestenius
  • 16,431
  • 5
  • 30
  • 40
  • Thanks, I've tried it either like : ` app.use( expressSesssion({ secret: 'kbrsession', resave: false, saveUninitialized: true, }), ); app.use(passport.initialize()); app.use(passport.session()); ` but still the same erorr : ( , I want to use this libarary `github.com/auth0/express-openid-connect` which doesnt needs passport as I see that the error come from passport, WDYT? – Beno Odr Aug 12 '20 at 09:21
-1

Use of waterfall async function may be helpful in this.Just replace app.get route function to below code. When we have to run tasks which depend on the output of the previous task, Waterfall can be helpful.

    app.get('/redirect', async (req, res, next) => {
        await passport.authenticate('oidc',
            async.waterfall([
                function (err,user) {
                    // print second log
                    console.log('2. ------------redirect Called!------------');
                    if (err) {
                        console.log(`Authentication failed: ${err}`);
                        return next(err);
                    }
                    if (!user) {
                        return res.send('no identity');
                    }

                    req.login(user, async (e) => {
                        if (e) {
                            console.log('not able to login', e);
                            return next(e);
                        }
                        try {
                            const url = await azpi.GetUsers(user.id_token);
                            // print last log
                            console.log('3. ------------user process finished successfully----');
                            return res.redirect(url);

                        } catch (er) {
                            res.send(er.message);
                        }
                    });
                }
            ], function (err) {
                if (err) return next(err);  //here you can check error
            })   
        );
    });
Daniel_Knights
  • 7,940
  • 4
  • 21
  • 49
-4

try use as this way

const key = crypto.randomBytes(16)
const mac = crypto.createMac('cmac', key, { cipher: 'aes-128-cbc' })
mac.update(crypto.randomBytes(30))
console.log(mac.final())
// => <Buffer b9>

  • 2
    Sorry I dont understand, to use what, how its related to my question ?how it solve the problem please explain – Beno Odr Aug 11 '20 at 18:23