Summary:
I'm trying to access some data written to a session on a previous route on another route. However, the session continuously comes back undefined despite it being visible in my Redis store. The route I am trying to access this session on is actually when an OAuth2.0 redirect takes place back to my app.
Workflow:
Technologies Used:
Worth Noting:
Code Snippets:
_app.js_
```const bodyParser = require('body-parser');
const config = require('config');
const cors = require('cors');
const express = require('express');
const session = require('express-session')
const helmet = require('helmet');
const morgan = require('morgan');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const redis = require('redis');
const RedisStore = require('connect-redis')(session)
const uuidv1 = require('uuid/v1');
const uuidv4 = require('uuid/v4');
const helpers = require('./helpers');
const log = require('./logger');
const routes = require('./routes');
const app = express();
const redisClient = redis.createClient();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors({ credentials: true, origin: true }));
app.use(helmet());
app.use(session({
genid: () => {
return uuidv4(); // use UUIDs for session IDs
},
name: 'CustomName',
resave: false,
saveUninitialized: false,
secret: config.get('session.secret'),
store: new RedisStore({
client: redisClient,
prefix: 'custom-name-session-'
})
}));
app.use(passport.initialize());
app.use(passport.session());
morgan.token('reqid', req => req.reqid);
app.use((req, res, next) => {
req.reqid = uuidv1();
res.set('X-Request-Id', req.reqid);
next();
});
app.use(
morgan(
":remote-addr - - ':method :url HTTP/:http-version' " +
":status :res[content-length] :response-time ':reqid'",
{ stream: log.stream }
)
);
passport.use(new LocalStrategy(async (username, password, done) => {
let user;
try {
user = await helpers.findUser(username);
const message = 'Incorrect Credentials!';
if (!user) {
return done(null, false, {message: message});
}
if (password !== user.password) {
return done(null, false, {message: message})
}
}
catch (err) {
return done(err);
};
return done(null, user);
}));
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
const user = await helpers.findUserById(id);
done(null, user);
});
app.use('/api', routes);
const port = process.env.PORT || 3001;
app.listen(port, () => log.info(Server started: http://localhost:${port}/));
_login.js_
const passport = require('passport');
const router = require('express').Router();
const log = require('../logger');
router.post('/login', (req, res, next) => {
console.log(req.headers);
passport.authenticate('local', (err, user, info) => {
if (err) {
log.error(err);
return next(err);
}
if (!user) {
log.info(info);
info.success = 0;
return res.send(info);
}
req.logIn(user, (err) => {
if (err) {
log.error(err);
return next(err);
}
return res.send({
success: 1,
message: 'Successfully logged in!',
user: req.user
});
});
})(req, res, next);
});
router.get('/current-user', (req, res) => {
if (req.user) {
res.send({ user: req.user });
}
else {
res.send({ user: null });
}
});
router.get('/logout', (req, res) => {
req.logOut();
res.send({ success: 1, message: 'Successfully logged out!' });
});
module.exports = router;
_thirdParty.js_
const config = require('config');
const router = require('express').Router();
const uuid = require('uuid/v1');
const thirdParty = require('../../thirdPartyAPI');
router.get('/oauth/login', (req, res) => {
const ouathState = uuid();
req.session.ouathState = ouathState;
const thirdPartyConfig = config.get('thirdParty');
const clientId = thirdPartyConfig.clientId;
const redirectUrl = thirdPartyConfig.redirectUrl;
const thirdPartyOauthUrl = thirdPartyConfig.oauthUrl;
const url = ${thirdPartyOauthUrl}client_id=${clientId}&response_type=code&state=${ouathState}&redirect_uri=${redirectUrl};
res.redirect(url);
});
router.get('/oauth/redirect', async (req, res) => {
// const state = req.query.state;
const code = req.query.code;
// TODO: FIX ME - Session no persisting
if (state && state !== req.session.ouathState) {
console.log(req.session.ouathState);
console.log('State did not match original state');
return res.status(400).send();
}
const accessToken = await thirdParty.getAccessToken(code);
req.session.accessToken = accessToken;
res.redirect('http://localhost:3000/');
});
module.exports = router;
```
Please let me know if there is any additional information I can provide to help debug this issue.
Does anyone look into these issues anymore?
Change your res.redirect(url) to
res.session.save((err)=>{
if(err){
console.log(err)
}
res.redirect(url);
})
Sessions are saved lazily to your store, so unless you wait for it to finish saving, a quick redirect may beat it.
See https://github.com/expressjs/session#sessionsavecallback for more info.
BTW, not the maintainer, just passing by.
@crisward - Unfortunately, this still hasn't done the trick. I've even tried to reload the session upon redirect back to my app. Here is the updated code:
router.get('/oauth/login', (req, res) => {
const ouathState = uuid();
req.session.ouathState = ouathState;
const starlingConfig = config.get('starling');
const clientId = starlingConfig.clientId;
const redirectUrl = starlingConfig.redirectUrl;
const starlingOauthUrl = starlingConfig.oauthUrl;
const url = `${starlingOauthUrl}client_id=${clientId}&response_type=code&state=${ouathState}&redirect_uri=${redirectUrl}`;
req.session.save(err => {
if (err) log.error(err);
res.redirect(url);
});
});
router.get('/oauth/redirect', (req, res) => {
const state = req.query.state;
const code = req.query.code;
req.session.reload(async err => {
if (err) log.error(err);
console.log(req.session);
// TODO: FIX ME - Session not persisting
if (state && state !== req.session.ouathState) {
return res.status(400).send();
}
const accessToken = await starling.getAccessToken(code);
req.session.accessToken = accessToken.data.access_token;
req.session.refreshToken = accessToken.data.refresh_token;
res.redirect('http://localhost:3000/');
});
});
I appreciate you giving it a try though. 馃憤
That's ok, the other thing I'd check is if the session cookie is being set in the browser. Also if you're using a database store, checking there to make sure the session is being saved. If you're going straight to your /oauth/login route and redirecting to another domain the browser may not have chance to write the cookie. Good luck finding the issue.
ping @n6rayan - were you able to find the solution for this?
@gireeshpunathil - Nope, unfortunately not. Sorry I couldn鈥檛 be more help.
I am also facing the same issue.
Do post if anyone gets anything on the same.
Hi,
this would be useful documentation to achieve what you want
https://auth0.com/docs/protocols/oauth2/redirect-users
Basically the problem is that the session registered when your user hits the route is different from the one created when you return from the OAuth callback.
So you need to save the redirect url and other info you want to persist in your session DB, use the key generated as a Nonce to be sent as the state parameter in the Oauth request.
Then on Oauth callback you need to get this state query params from the incoming requests, compare. Try to fetch it in the session DB (if not present, don't authorize the auth).
Then restore the info you wanted.
Hope it helped !
@deveshMantra @gireeshpunathil - I tested out @Yacine-A's theory and he is correct (which actually makes perfect sense). Basically, instead of using Ngrok, I added an entry into my /etc/hosts file that points to my local app and used that as the redirect URL in the OAuth transaction.
This, in conjunction with @crisward's suggestion of saving the session prior to going off to the third-party and reloading the session upon being redirected back to my app, worked a treat.
Hi,
this would be useful documentation to achieve what you want
https://auth0.com/docs/protocols/oauth2/redirect-usersBasically the problem is that the session registered when your user hits the route is different from the one created when you return from the OAuth callback.
So you need to save the redirect url and other info you want to persist in your session DB, use the key generated as a Nonce to be sent as the state parameter in the Oauth request.
Then on Oauth callback you need to get this state query params from the incoming requests, compare. Try to fetch it in the session DB (if not present, don't authorize the auth).
Then restore the info you wanted.
Hope it helped !
@Yacine-A This is the perfect solution. I will save the session with the state as the KEY on my REDIS (session store), before redirecting.Then once the server returns to the callbak, I will use the state on my REDIS as the KEY to retrieve the VALUE with store.get(sid).
When I finish I will post the example
Then once the server returns to the callbak, I will use the state on my REDIS as the KEY to retrieve the VALUE with store.get(sid).
Hey, do you managed to make it work? @AllanOricil
Then once the server returns to the callbak, I will use the state on my REDIS as the KEY to retrieve the VALUE with store.get(sid).
Hey, do you managed to make it work? @AllanOricil
I have the same question, @tambu22, albeit a little different. Here鈥檚 my implementation, I鈥檓 using a library I鈥檓 working on for authentication, and part of the code sets up the session ID to be sent as the state parameter:
params.response_type = 'code';
params.redirect_uri = callbackURL;
params.state = req.session.id;
And upon redirect, on the route, I have this:
router.get('/discordcb', (req, res, next) => {
let sessionID = req.query.state;
store.get(sessionID, function storeGetCallback(err, session) {
if (err) {
return console.error(err);
}
next();
});
}
So the state parameter calls the session from the store before next() is called, which continues the process of exchanging the auth code for an access token. And so far this works.
My question, and maybe someone can help me out with this, is once store.get(sid, cb) is called, does this load the requested session? That鈥檚 my next step in understanding how this works. Any help is appreciated.
Most helpful comment
Hi,
this would be useful documentation to achieve what you want
https://auth0.com/docs/protocols/oauth2/redirect-users
Basically the problem is that the session registered when your user hits the route is different from the one created when you return from the OAuth callback.
So you need to save the redirect url and other info you want to persist in your session DB, use the key generated as a Nonce to be sent as the state parameter in the Oauth request.
Then on Oauth callback you need to get this state query params from the incoming requests, compare. Try to fetch it in the session DB (if not present, don't authorize the auth).
Then restore the info you wanted.
Hope it helped !