(JP) Express+Passport複数のユーザーSessionで起動
ユーズケース
ひとつのバックエンドと複数のフロントがあり、各フロントが独自のユーザーを持っているとします
バックエンドにはユーザーごとに別々ののPATHが設定されます
https://something/admin
https://something/developer
https://something/a-type-user
https://something/b-type-user
それぞれのセッションは異なるCookieと異なるSessionを持つ必要があり、/admin
にログインしてたとしても、他のパスからは「ログアウト」されてる必要があります。
パスポートを分ける
最初に直面するべき問題はPassportをインポートするときにクラスではなく、インスタンス化されたPassportオブジェクトをインポートされてることです
// returns the same object every time
import passport from 'passport'
修正するには、クラスをインポートして独自のインスタンスを作成することです
import { Passport } from 'passport'
const passport = new Passport()
またpassport.use()
を分割し、それぞれに異なるキーを与える必要があります。
// src/admin/passport.ts
passport.use('admin', new Strategy(verifyUserFunction))
// src/developer/passport.ts
passport.use('developer', new Strategy(verifyUserFunction))
完了したらファイルがこのようになります:
/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
そしてついにアプリ上でPassportのSessionを分割できるようになります
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())
ルーターを分ける
最初に必要なのは、各パスに独自のルーターを与えることです
/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)
各ルーターは独自のパスポートをインポートし、その認証方法を使用する必要があります。
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 }
ミドルウエアを分ける
ここまでくると、index.tsがこのようなものになってるはずです
/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())
Adminとしてログインしていても同じMiddlewareを使用しているため、/developer
のpathにアクセスしようとしてもログインしていると認識されます
Middlewareを分割するに変更するのは二箇所のセッションキーと名前だけです
export const sessionMiddleware = session({
// add the name parameter
store: new RedisStore({ client: redisClient }),
secret: 'some-random-session-key', // change here
...
})
僕はこの場合generate-api-key
のライブラリを使うことにしました.
yarn add generate-api-key
// or
npm install generate-api-key
Middlewareを分割するとこのようになります
####/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' }),
...
})
ファイルごとに手入力でセッションキーを手動で追加することも可能です
Finally
Middlewareを分割し終わったら、準備完了のはずです!
/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}.`)
})
これで管理者としてログインしてても、他のすべてのPATHはUnauthenticatedと返します。
Discussion