(EN) Express+Passport with multiple user sessions
sample use case
Let's say you have one backend and multiple front ends and each front end has its own user type.
The backend is going to have one path for each user like this:
https://something/admin
https://something/developer
https://something/a-type-user
https://something/b-type-user
Each of them will need to have a different session, with different cookies, end even if you log in for /admin
it is necessary for you to be "logged out" of the other paths.
splitting the passport
The first issue you are going to face is the fact that when you import the passport you will be importing an instantiated Passport object and not a passport class.
// returns the same object every time
import passport from 'passport'
All you need to fix this is to import the class and create your own instance.
import { Passport } from 'passport'
const passport = new Passport()
you will also need to split the passport.use()
and give a different key for each one.
// src/admin/passport.ts
passport.use('admin', new Strategy(verifyUserFunction))
// src/developer/passport.ts
passport.use('developer', new Strategy(verifyUserFunction))
After you've done this your file will probably look something like this:
/src/admin/passport.ts
import { Passport } from 'passport'
import { Strategy, VerifyCallback } from 'passport-custom'
const passport = new Passport()
const verifyUser: VerifyCallback = async (req, done) => {
...
}
passport.serializeUser<UserInSession>((user, done) => {
done(null, user as UserInSession)
})
passport.deserializeUser<UserInSession>(async (userInSession, done) => {
done(null, userInSession)
})
passport.use('admin', new Strategy(verifyUser))
export passport
And you will finally be able to split your passport sessions on the app
src/index.ts
import express from 'express'
import { passport as adminPassport } from './admin/passport'
import { passport as developerPassport } from './developer/passport'
const app = express()
app.use('/admin', adminPassport.session())
app.use('/developer', developerPassport.session())
splitting the routers
The first thing to do is give each path its own router
/src/index.ts
import express from 'express'
import { Router as AdminRouter } from './admin/router'
import { Router as DeveloperRouter } from './developer/router'
const app = express()
app.use('/admin', AdminRouter)
app.use('/developer', DeveloperRouter)
Each router will have to then import its own passport, and use its authentication mehod
src/admin/router
import { Router } from 'express'
import { passport } from './passport'
const router = Router()
// the name 'admin' was set up with passport.use()
router.post('/login', passport.authenticate('admin'), (req, res) => {
return res.json(req.user)
})
...
export { router }
splitting up the middleware
After all this your index file will probably be something along these lines:
/src/index.ts
import express from 'express'
import { sessionMiddleware } from './middleware'
import { Router as AdminRouter } from './admin/router'
import { passport as adminPassport } from './admin/passport'
import { Router as DeveloperRouter } from './developer/router'
import { passport as developerPassport } from './developer/passport'
const app = express()
app.use(sessionMiddleware)
app.use('/admin', AdminRouter)
app.use('/admin', adminPassport.session())
app.use('/developer', DeveloperRouter)
app.use('/developer', developerPassport.session())
Because you are still using the same middleware if you are loggen in as an "admin" it will still think you are logged in when you access /developer
.
There is only two spots you have to change when splitting the middleware, and that is the session's key and name
export const sessionMiddleware = session({
// add the name parameter
store: new RedisStore({ client: redisClient }),
secret: 'some-random-session-key', // change here
...
})
In my case I decided to use the generate-api-key
library.
yarn add generate-api-key
// or
npm install generate-api-key
When you split the middleware it will change to something like this:
####/src/admin/middleware
import generateApiKey from 'generate-api-key';
export const sessionMiddleware = session({
name: 'admin-session',
store: new RedisStore({ client: redisClient }),
secret: generateApiKey({ prefix: 'admin' }),
...
})
You can also manually add a different api key for each file.
Finally
After splitting the middleware you should be good to go!
/src/index.ts
import express from 'express'
import { sessionMiddleware as AdminMiddleware } from './admin/middleware'
import { Router as AdminRouter } from './admin/router'
import { passport as adminPassport } from './admin/passport'
import { sessionMiddleware as DeveloperMiddleware } from './developer/middleware'
import { Router as DeveloperRouter } from './developer/router'
import { passport as developerPassport } from './developer/passport'
const app = express()
app.use('/admin', AdminMiddleware)
app.use('/admin', AdminRouter)
app.use('/admin', adminPassport.session())
app.use('/developer', DeveloperMiddleware)
app.use('/developer', DeveloperRouter)
app.use('/developer', developerPassport.session())
app.listen(port, () => {
console.log(`Server is listening at port ${port}.`)
})
And with this even if you log in as an admin, all the other paths will still return unauthenticated.
Discussion