Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8833

Next.jsでsessionとPassport.js(認証)を使う

$
0
0

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

利用しているライブラリ

  • 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

以上。ご査収のほどよろしくお願い致します


Viewing all articles
Browse latest Browse all 8833

Trending Articles