Open3
Blitz.js + passport.js + firebase authenticationで認証
Blitz.jsでのOAuth認証事始め
Blitz.jsのOAuth認証はBlitz.jsがラップしているpassport.jsの passportAuth
を /api/auth/[...auth].ts
という感じでAPI生やして、任意Strategyを追加してやるように公式ドキュメントに記載がある。
Facebookの場合はこんな感じ。
import { passportAuth } from "blitz"
import db from "db"
import { Strategy as FacebookStrategy } from "passport-facebook"
export default passportAuth({
successRedirectUrl: "/",
errorRedirectUrl: "/",
secureProxy: true,
strategies: [
{
authenticateOptions: { scope: "email" },
strategy: new FacebookStrategy(
{
clientID: process.env.FACEBOOK_APP_ID as string,
clientSecret: process.env.FACEBOOK_APP_SECRET as string,
callbackURL: "http://localhost:3000/api/auth/facebook/callback",
},
async function (_token, _tokenSecret, profile, done) {
const email = profile.emails && profile.emails[0]?.value
if (!email) {
return done(new Error("Facebook OAuth response doesn't have email."))
}
const user = await db.user.upsert({
where: { email },
create: {
email,
name: profile.displayName,
},
update: { email },
})
const publicData = {
userId: user.id,
roles: [user.role],
source: "facebook",
}
done(null, { publicData })
}
),
},
]
})
ユーザ情報をあまり持ちたくないのでFirebase Authenticationを使う
ユーザ情報をFirebase Authenticatonに集約したいのでFacebookやGoogleなど個別のStrategyは使わず、Custom Strategyを使ってFirebase Authとの連携を試みる。
Client
※このfirebaseインスタンスはclient向けのもの。
-
firebase.auth().signInWithFacebook()
でFirebase経由でFacebook認証 - 非同期で結果を受け取って
firebase.auth().currentUser!.getIdToken(true)
でidTokenを取得 - idTokenをつけてfetchで
/api/auth/custom/callback?idToken=${idToken}
としてたたく
Server
- passport-customのverifiedCallback内でidTokenを
firebase.auth().verifyIdToken(idToken)
でFacebookのUser情報(email, name辺り)を取得 - emailとnameを
db.user.upsert()
で追加 or 生成する
- emailが取得出来ない場合は
done(Error)
- emailがある場合も
done(null, { ...publicData })
import { passportAuth } from "blitz"
import db from "db"
import { Strategy as FacebookStrategy } from "passport-facebook"
import { Strategy as CustomStrategy } from "passport-custom"
import firebase from "../../firebase/admin"
export default passportAuth({
successRedirectUrl: "/",
errorRedirectUrl: "/",
secureProxy: true,
strategies: [
{
strategy: new CustomStrategy((req, done) => {
if (!req.query.idToken) return done(new Error("Request doesn't have idToken."))
const idToken = req.query.idToken as string
try {
const { email, name } = await firebase.auth().verifyIdToken(token)
if (!email) {
return done(new Error("Facebook OAuth response doesn't have email."))
}
const user = await db.user.upsert({
where: { email },
create: {
email,
name,
},
update: { email },
})
const publicData = { userId: user.id, roles: [user.role], source: "facebook" }
done(null, { publicData })
} catch (error) {
done(new Error(`Facebook OAuth failed...`))
}
}),
}
],
})
他のOAuth認証を追加する場合はqueryで分岐するなどすればよいかと。 client側だけでこっちはここまでで充分。
で、Firebase Authentcatonが使えるということなのでメールリンク認証も使えるわけです。
Firebaseを使わない場合はMagic Link Strategyがあったのでそちらを使うと良いと思います。
client側には以下レスポンスがある。
{
...
redirected: true
status: 200
statusText: "OK"
type: "basic"
url: "http://localhost:3000/"
}
他のStrategyを指定すると passportAuth()
で指定した successRedirectUrl
と errorRedirectUrl
で指定したいずれかにリダイレクトするが、Custom Strategyにするとclient fetchするためリダイレクト処理はクライアント側で行うことになる。はず。