![](/img/trans.png)
[英]Best way Make Posts Pages and their URLs in Next.js with MongoDB?
[英]Building a dashboard in Next.js : best practices to make pages private with roles, without "flickering", using JWT authentication?
考虑到我们有:
用户应该:
/login
而不会闪烁/profile
而不会闪烁)我现在处理 JWT 的逻辑:
// lib/axios.js
import Axios from 'axios';
import { getCookie, removeCookies } from 'cookies-next';
import qs from 'qs';
export const axios = Axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
paramsSerializer: (params) =>
qs.stringify(params, { arrayFormat: 'brackets' }),
withCredentials: true,
});
axios.interceptors.request.use(
(config) => {
const token = getCookie('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
config.headers.Accept = 'application/json';
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response.status === 401) {
// we need to implement refresh pattern
// const refresh = await refreshToken();
removeCookies('access_token');
}
return Promise.reject(error);
}
);
// For SWR
export const fetcher = async (...args) => await axios.get(...args).data;
我一直在积累这方面的研究,我发现了很多不同的答案。 到目前为止,我发现:
_app.js
放入数组中withPrivate
或withPublic
getServerSideProps
在每个页面内登录nextAuth
但我不确定,因为它似乎正在构建一个后端,而我们已经有了一个_middleware
显然可以进行重定向SWR
, Suspense
和Error Boundaries
但我不确定它是否适用于这种情况......关于我应该怎么做的任何线索?
如果您不使用静态生成并且从不打算getServerSideProps
工作。
如果您打算使用 Vercel 进行托管, _middleware
也可能是一个不错的选择,但是,第 3 方托管支持滞后。
由于您已经拥有自己的身份验证, nextAuth
似乎不是一个好的选择。
我建议使用 Auth 上下文 - 不再需要用 HOC 包装每个页面。
您可以使用加载屏幕或微调器来防止闪烁。
这是一个身份验证上下文的示例。
import { useEffect, useState, useCallback, createContext } from "react";
import { useRouter } from "next/router";
const AuthContext = createContext(null);
export const AuthProvider = ({ children }) => {
const { asPath, push } = useRouter();
const [user, setUser] = useState(null);
// if you use trailing slash you'd need to add them to each route
const isAuthRoute = ["/login", "/signup", "/forgot", "/reset"].includes(asPath);
const redirectToLogin = useCallback(async () => {
try {
setUser(null);
await push('/login');
} catch (e) {
console.error("Could not redirect to login");
}
}, [push]);
const signOut = async () => {
try {
await authServiceSignout();
await redirectToLogin();
} catch {
window.location.reload();
}
};
const goHome = useCallback(() => push('/'), [push]);
// check if user is logged in on every route changes and
// redirect accordingly
useEffect(() => {
const getUser = async () => {
const user = await getUserOrJWT();
if (user) {
setUser(user); // user details
if (isAuthRoute) await goHome();
} else if (!isAuthRoute) {
await redirectToLogin();
}
};
getUser();
}, [asPath, goHome, isAuthRoute, redirectToLogin]);
if (!user && isAuthRoute) return <>{children}</>;
if (!user) return <>Loading or loading spinner</>;
return (
<AuthContext.Provider value={{ user, signOut }}>
{children}
</AuthContext.Provider>
);
};
我删除了一堆代码,所以它可能无法直接复制和粘贴,但你可以明白。
isAuthRoute
只需要返回 true/false,因此如果您的仪表板路由都以/dashboard
开头,则另一个不错的选择可以使用asPath.startsWith('/dashboard')
代替。
我们有条件地渲染了 auth 上下文,因为它在公共页面上不需要,它还会在注销时清除用户上下文,因此我们不必担心通过上下文泄露旧的用户数据。
我们还使用本地存储和窗口的广播通道来监听注销调用。 这允许我们在每个选项卡和窗口中注销用户。
经过大量不同技术的测试后,我决定使用 Next.js 新的中间件令人难以置信的功能。
如果有人像我一样在这个话题上苦苦挣扎,这是我的代码:
// _middleware.js in /pages, works also with Typescript .ts
import { NextResponse } from 'next/server';
import { isAuthValid } from '@/lib/auth'
export function middleware(req) {
if (
req.nextUrl.pathname.startsWith('/login') ||
req.nextUrl.pathname.startsWith('/signup') ||
req.nextUrl.pathname.startsWith('/forgot') ||
req.nextUrl.pathname.startsWith('/reset')
) {
if (isAuthValid(req)) {
return NextResponse.redirect(new URL('/profile', req.url));
}
return NextResponse.next();
}
// All other routes
if (isAuthValid(req)) {
return NextResponse.next();
}
return NextResponse.redirect(
new URL(`/login?from=${req.nextUrl.pathname}`, req.url)
);
}
请注意,因为此文件名和位置将在新的 Next.js 12.2
版本中更改,在您的根文件夹中的middleware.js
(或.ts
)的名称下,无论是根目录还是src
取决于您的配置
感谢您围绕该主题展开讨论。 我的用例几乎与 OP 的示例完全相同。 我也在使用 SSR(主要是)。
在让它工作时,我也经历了许多陷阱和挑战,但由于 'jsonwebtoken' lib 在中间件中存在 eval 错误,构建失败。 从那里我使用了“cookies-next”库,但是 Next.js v12.2.0 在 NextRequest 中集成了 cookies.get()(cookies:NextCookies),我设法使用它。
我用:
cookie
lib 来“解析”和“序列化”,但很快可能会使用 Next.js 内部函数将其切换出来。jose
for 'SignJWT', 'jwtVerify' - 最新版本不使用 Native Node.js API(适用于中间件)。 在我的_app
> getInitialProps
:
// _app.tsx
MyApp.getInitialProps = async (appContext: AppContext) => {
const appProps = await App.getInitialProps(appContext);
let customerData = {};
if (!!appContext.ctx.req) {
/*
* Expose the cookie to the page props of all the pages
* for client-side auth datafetching
*/
const cookie = parse(appContext.ctx.req.headers.cookie || '');
appProps.pageProps[JWT_COOKIE_NAME] = cookie[JWT_COOKIE_NAME];
/*
* If the user is logged in, we'll verify the JWT and get the user data
*/
const userJWT = await verifyAuthCookieOnReload(appContext.ctx);
if (userJWT) {
const { id, token } = userJWT;
customerData = await UserService.getCustomer(id, token);
}
}
const props = { ...appProps, customerData };
return props;
};
我有一个帮助文件:
// eslint-disable-next-line @next/next/no-server-import-in-page
import type { NextRequest } from 'next/server';
import type { NextPageContext } from 'next';
import { nanoid } from 'nanoid';
import { parse, serialize } from 'cookie';
import { SignJWT, jwtVerify, JWTPayload } from 'jose';
import { JWT_SECRET, JWT_COOKIE_NAME } from '@constants/env';
export type UserJWTPayload = JWTPayload & JwtUserData & {
jti: string;
iat: number;
};
export type JwtUserData = {
id: string;
token: string;
};
export const setUserJWTCookie = async (userData: JwtUserData | {}, logoutTime?: number) => {
const time = Math.floor(Date.now() / 1000);
let expireTime = 60 * 60 * 24 * 14; // 14 days
if (logoutTime) {
expireTime = logoutTime; // override for logout "-1"
}
const token = await new SignJWT(userData)
.setProtectedHeader({ alg: 'HS256' })
.setJti(nanoid())
.setIssuedAt()
.setExpirationTime(time + expireTime)
.sign(new TextEncoder().encode(JWT_SECRET));
return serialize(JWT_COOKIE_NAME, token, {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
sameSite: 'strict',
maxAge: expireTime,
path: '/'
});
};
export const verifyAuthCookie = async (request: NextRequest): Promise<UserJWTPayload | undefined> => {
const token = request.cookies.get(JWT_COOKIE_NAME);
return await verify(token);
}
export const verifyAuthCookieOnReload = async (context: NextPageContext): Promise<UserJWTPayload | undefined> => {
const cookie = parse(context?.req?.headers.cookie || '');
const token = cookie[JWT_COOKIE_NAME];
return await verify(token);
};
export const verify = async (token: string | undefined) => {
if (!token) {
return undefined;
}
try {
const { payload } = await jwtVerify(token, new TextEncoder().encode(JWT_SECRET));
return payload as UserJWTPayload;
} catch (err) {
return undefined;
}
}
我的用例是在能够浏览网站之前“强制”用户登录。 我只是在努力管理我的中间件中的逻辑,即使使用本文中给出的示例也是如此。 我认为我仍然没有正确处理数据流,因为它不会阻止任何导航或重定向。
我希望这个示例对您当前的用例以及如何处理它有一些信息:)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.