Next.jsでsessionとPassport.jsを使う
Next.jsでsessionとかPassport.js(認証)を使う方法を書いてみる
next.js session
とか next.js 認証
とかでググるとNextにexpressを入れる感じでのサンプルは結構あったけど、デフォルトで用意されているAPI Routeを利用したやつの実装のサンプルがあまり見つけれなかったので書いてみる
Next.js では connectというmiddlewareレイヤーに対応していて、これに対応しているライブラリなどであれば利用することが出来ます
サンプルは下記の配置で書いていきます
- middleweres
- index.ts
- connect.ts
- passport.ts
- session.ts
- page
- api
- userInfo.ts
- login.ts
- logout.ts
- api
利用しているライブラリ
- express-session (expressとか書いてるけど問題なくつかえる
- connect-redis (今回のサンプルではsessionをredisで管理するようにしているので
- passport (認証
- passport-local (認証を独自ロジックで行いたいので
yarn add -D express-session connect-redis passport passport-local
Middlewareの前準備
connectするための関数を定義
connect.ts
import{NextApiRequest,NextApiResponse}from'next'exportconstconnectMiddleware=(req:NextApiRequest,res:NextApiResponse,middleware:Function)=>{returnnewPromise((resolve,reject)=>{middleware(req,res,(result:any)=>{if(resultinstanceofError){returnreject(result)}returnresolve(result)})})}
https://nextjs.org/docs/api-routes/api-middlewares#connectexpress-middleware-support
これを関数化しただけ
sessionを定義
session.ts
import{NextApiRequest,NextApiResponse}from'next'importsessionfrom'express-session'import{createClient}from'redis'import{connectMiddleware}from'./connect'constRedisStore=require('connect-redis')(session)exporttypeSession={session:{[key:string]:any}}// このへんよしなに。開発用の設定になってるconstconfig={saveUninitialized:true,secret:'keyboard cat',resave:false,store:newRedisStore({client:createClient({host:'0.0.0.0',port:6380,prefix:'backend:',}),}),cookie:{httpOnly:true,sameSite:true,secure:false,},}exportconstwithSession=async(req:NextApiRequest,res:NextApiResponse)=>{awaitconnectMiddleware(req,res,session(config))}
Passport.jsを定義
passport.ts
import{NextApiRequest,NextApiResponse}from'next'import{connectMiddleware}from'./connect'constpassport=require('passport')constLocalStrategy=require('passport-local').Strategy// passport.d.ts の上書きがめんどいのでこれで(さぼり)exporttypePassportFunctions={authInfo?:anyuser?:Userlogin(user:{id:number,name:string},done:(err:any)=>void):voidlogin(user:{id:number,name:string},options:any,done:(err:any)=>void):voidlogIn(user:{id:number,name:string},done:(err:any)=>void):voidlogIn(user:{id:number,name:string},options:any,done:(err:any)=>void):voidlogout():voidlogOut():voidisAuthenticated():booleanisUnauthenticated():boolean}passport.use(newLocalStrategy({usernameField:'id',passwordField:'passport',},async(username:string,password:string,done:Function)=>{// なんか認証してユーザーを取得するやつconstuser:{id:number,user:string}|null=awaitauthUser({username,password})// 取得エラーif(!result){done(null,false)return}// IDと名前done(null,user)}))passport.serializeUser((user:User,done:Function)=>{done(null,user)})passport.deserializeUser((user:User,done:Function)=>{done(null,user)})exportconstwithPassport=async(req:NextApiRequest,res:NextApiResponse)=>{awaitconnectMiddleware(req,res,passport.initialize())// Passport.jsでセッションを使いたいのでawaitconnectMiddleware(req,res,passport.session())}
Passport.js(認証) & sessionを使うための高階関数を定義
index.ts
import{NextApiRequest,NextApiResponse}from'next'import{Session,withSession}from'./session'import{PassportFunctions,withPassport}from'./passport'// デフォルトのNextApiRequestとライブラリで拡張されたのをくっつけて再定義typeRequest=NextApiRequest&Session&PassportFunctionstypeResponse=NextApiResponse&SessiontypeOptions={requiredAuth:boolean// 認証がされているかどうか、されていなければ403を返す}exportconstwithApiMiddlewares=(options:Options)=>(fn:(req:Request,res:Response)=>void)=>{returnasync(req:Request,res:Response)=>{awaitwithSession(req,res)awaitwithPassport(req,res)// 認証していなければ403を返すif(options.requiredAuth&&!req.isAuthenticated()){res.status(403).json({message:'Forbidden'})return}fn(req,res)}}
あとは withApiMiddlewares を使うことでAPIの中でsessionやPassport.js(認証)を使うことが可能になった
withApiMiddlewares の第一引数はライブラリのオプションなどで利用。第2引数はメインの処理を書く感じ
下記が例
exportdefaultwithApiMiddlewares({requiredAuth:true,methods:['GET','POST']})(async(req,res)=>{res.status(200).json({message:'ok'})})
こんな感じで使える。第一引数のmethodsみたいな感じで利用できるHttpMethodを限定するみたいな拡張をしてもいいかも
高階関数にすることで、req resの型定義も勝手にされるのでサボれる
ミドルウェアを使ったAPIの例
ユーザーの情報を返すAPI
page/api/userInfo.ts
import{withApiMiddlewares}from'../../middlewares'exportdefaultwithApiMiddlewares({requiredAuth:true})(async(req,res)=>{res.status(200).json({user:req.user,})})
ログインのやつ
page/api/loggin.ts
importpassportfrom'passport'import{withApiMiddlewares}from'/middlewares'import{connectMiddleware}from'/middlewares/connect'exportdefaultwithApiMiddlewares({requiredAuth:false})(async(req,res)=>{awaitconnectMiddleware(req,res,passport.authenticate('local',(err:any,user:{id:number,name:string}|null)=>{if(err||!user){res.writeHead(302,{Location:'/'}).end()return}req.logIn(user,(error:any)=>{if(error){res.writeHead(302,{Location:'/'}).end()return}res.writeHead(302,{Location:'/loggedInPage'}).end()})}))})
ログアウトのやつ
page/api/logout.ts
import{withApiMiddlewares}from'../../middlewares'exportdefaultwithApiMiddlewares({requiredAuth:false})(async(req,res)=>{req.logout()res.status(200).end()})
結構コードがメインの記事になりましたが、これで利用可能です
next-connectというライブラリもあり、これを使うのもありかも。
Next.jsのサンプルでも使われてたりはします https://github.com/zeit/next.js/tree/canary/examples
以上。ご査収のほどよろしくお願い致します