Next.js и SWR: невозможно удержать пользователя от успешного входа в систему через API Passport в ловушке SWR
Я перешел от экспресс-сервера, обрабатывающего мои API, в Next, к их встроенным API-маршрутам!
Люблю это!
В любом случае, я использую Passport.js для аутентификации и авторизации и успешно это реализовал.
Но я заметил несколько вещей, которые хочу рассказать в первую очередь, поскольку я почти уверен, что они связаны с проблемой с ловушкой SWR:
В моем маршруте входа:
/api/login
:
import nextConnect from 'next-connect'
import auth from '../../middleware/auth'
import passport from '../../lib/passport'
import connectDB from '../../lib/mongodb';
const handler = nextConnect()
handler
.use(auth)
.post(
async (req, res, next) => {
await connectDB();
passport.authenticate('local', (err, user, info) => {
if (err) { return errorHandler(err, res) }
if (user === false) {
return res.status(404).send({
msg: `We were unable to find this user. Please confirm with the "Forgot password" link or the "Register" link below!`
})
}
if (user) {
if (user.isVerified) {
req.user = user;
return res.status(200).send({
user: req.user,
msg: `Your have successfully logged in; Welcome to Hillfinder!`
});
}
return res.status(403).send({
msg: 'Your username has not been verified! Check your email for a confirmation link.'
});
}
})(req, res, next);
})
export default handler
Вы можете видеть, что я использую настраиваемый обратный вызов в Passport.js, поэтому, когда я получаю пользователя после успешного входа в систему, я просто назначаю пользователя req.user = user
Я думал, это должно позволить ловушке SWR всегда возвращать истину, что пользователь вошел в систему?
Это мой
hooks.js file
т.е. функциональность КСВ:
import useSWR from 'swr'
import axios from 'axios';
export const fetcher = async (url) => {
try {
const res = await axios.get(url);
console.log("res ", res);
return res.data;
} catch (err) {
console.log("err ", err);
throw err.response.data;
}
};
export function useUser() {
const { data, mutate } = useSWR('/api/user', fetcher)
// if data is not defined, the query has not completed
console.log("data ", data);
const loading = !data
const user = data?.user
return [user, { mutate, loading }]
}
В
fetcher
звонит
/api/user
:
import nextConnect from 'next-connect'
import auth from '../../middleware/auth'
const handler = nextConnect()
handler
.use(auth)
.get((req, res) => {
console.log("req.user ", req.user); // Shouldn't the req.user exist??
res.json({ user: req.user })
})
export default handler
Разве это не должно всегда возвращать пользователя после успешного входа в систему?
Напоследок вот мой
LoginSubmit
:
import axios from 'axios';
export default function loginSubmit(
email,
password,
router,
dispatch,
mutate
) {
const data = {
email,
password,
};
axios
.post(`/api/login`,
data, // request body as string
{ // options
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
}
)
.then(response => {
const { userId, user } = response.data
if (response.status === 200) {
setTimeout(() => {
router.push('/profile');
}, 3000);
dispatch({ type: 'userAccountIsVerified' })
mutate(user)
}
})
}
Любая помощь будет оценена по достоинству!
Обновлять
В вопрос добавлено промежуточное ПО аутентификации:
import nextConnect from 'next-connect'
import passport from '../lib/passport'
import session from '../lib/session'
const auth = nextConnect()
.use(
session({
name: 'sess',
secret: process.env.TOKEN_SECRET,
cookie: {
maxAge: 60 * 60 * 8, // 8 hours,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
},
})
)
.use((req, res, next) => {
req.session.users = req.session.users || []
next()
})
.use(passport.initialize())
.use(passport.session())
export default auth
Добавлен:
serializeUser && deserializeUser
функции;
import passport from 'passport'
import LocalStrategy from 'passport-local'
import User from '../models/User'
passport.serializeUser((user, done) => {
done(null, user._id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
passport.use(
new LocalStrategy(
{ usernameField: 'email', passwordField: 'password', passReqToCallback: true },
async (req, email, password, done) => {
try {
const user = await User.findOne({ email }).exec();
if (!user) {
return done(null, false, { message: 'Invalid username!' });
}
const passwordOk = await user.comparePassword(password);
if (!passwordOk) {
return done(null, false, {
message: 'Invalid password!'
});
}
return done(null, user);
} catch (err) {
return done(err);
}
}
)
);
export default passport
И это
session.js
файл:
import { parse, serialize } from 'cookie'
import { createLoginSession, getLoginSession } from './auth'
function parseCookies(req) {
// For API Routes we don't need to parse the cookies.
if (req.cookies) return req.cookies
// For pages we do need to parse the cookies.
const cookie = req.headers?.cookie
return parse(cookie || '')
}
export default function session({ name, secret, cookie: cookieOpts }) {
return async (req, res, next) => {
const cookies = parseCookies(req)
const token = cookies[name]
let unsealed = {}
if (token) {
try {
// the cookie needs to be unsealed using the password `secret`
unsealed = await getLoginSession(token, secret)
} catch (e) {
// The cookie is invalid
}
}
req.session = unsealed
// We are proxying res.end to commit the session cookie
const oldEnd = res.end
res.end = async function resEndProxy(...args) {
if (res.finished || res.writableEnded || res.headersSent) return
if (cookieOpts.maxAge) {
req.session.maxAge = cookieOpts.maxAge
}
const token = await createLoginSession(req.session, secret)
res.setHeader('Set-Cookie', serialize(name, token, cookieOpts))
oldEnd.apply(this, args)
}
next()
}
}