![](/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.