Я пытаюсь создать функцию аутентификации для своего веб-приложения, следуя этому онлайн-руководству.
Я создал middleware.ts для своего веб-приложения и увидел, что там есть const isLoggedIn
, поэтому подумал, что могу использовать это логическое значение на всех остальных страницах для постоянной аутентификации, если пользователь вошел в систему.
поэтому я абстрагировал это вот так
export const checkIsLoggedIn = (req: any): boolean => {
return !!req.auth;
};
поскольку это промежуточное программное обеспечение, которое представляет собой действие сервера, а все мои компоненты — это действия клиента, я решил создать API, чтобы checkisloggedin передавался из middleware.ts -> check-auth.ts (api) -> main-nav .tsx (компонент, который я хочу использовать, чтобы проверить, вошел ли пользователь в систему).
но когда я console.info, чтобы проверить, работает ли мой API, консоль элемента проверки моего браузера вернула эту ошибку
[main-nav-tsx][checkAuthStatus]: Error fetching auth status
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
window.console.error @ app-index.js:33
Видимо, это означало, что мой маршрут API не найден.
Проблема: я не уверен, как еще я могу передать логическое значение checkisloggedin своим компонентам, поскольку каждый метод, который я пытался проверить, вошел ли пользователь в систему, не работает. все мои каталоги тоже казались правильными.
Ожидаемый результат: мне просто нужен способ проверить, вошел ли пользователь в систему, и при необходимости отобразить кнопку выхода.
Я также открыт для новых решений, связанных с сеансами и токенами, но понятия не имею, как их использовать.
Это мои коды. Поскольку NextJS уделяет особое внимание файловым каталогам, я прокомментировал, где хранится каждый файл, в самом верху.
// src/middleware.ts
// import { auth } from "./auth"
import authConfig from "./auth.config"
import NextAuth from "next-auth"
import {
DEFAULT_LOGIN_REDIRECT,
apiAuthPrefix,
authRoutes,
publicRoutes,
} from "@/routes"
import { NextResponse } from "next/server";
const { auth } = NextAuth(authConfig);
export const checkIsLoggedIn = (req: any): boolean => {
return !!req.auth;
};
export default auth((req) => {
const { nextUrl } = req;
const isLoggedIn = !!req.auth;
console.info('[middleware-ts][checkisloggedin]: ', checkIsLoggedIn(req));
console.info('[middleware-ts][isloggedin]: ', isLoggedIn);
const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix);
const isPublicRoute = publicRoutes.includes(nextUrl.pathname);
const isAuthRoute = authRoutes.includes(nextUrl.pathname);
if (isApiAuthRoute) {
return NextResponse.next();
}
if (isAuthRoute) {
if (isLoggedIn) {
return NextResponse.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl))
}
return NextResponse.next();
}
if (!isLoggedIn && !isPublicRoute) {
return NextResponse.redirect(new URL("/auth/login", nextUrl));
}
return NextResponse.next();
})
export const config = {
// matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
// matcher: ["/auth/login", "/auth/register"],
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
}
// src/components/main-nav.tsx
"use client"
import * as React from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { docsConfig } from "@/config/docs"
import { siteConfig } from "@/config/site"
import { cn } from "@/lib/utils"
import { Icons } from "@/components/icons"
import { signOut } from "@/auth"
export function MainNav() {
const pathname = usePathname()
const [isLoggedIn, setIsLoggedIn] = React.useState(false);
React.useEffect(() => {
const checkAuthStatus = async () => {
console.info('[main-nav-tsx][checkAuthStatus]: Checking auth status...');
try {
const response = await fetch('/app/api/authentication/check-auth');
if (response.ok) {
const data = await response.json();
console.info('[main-nav-tsx][checkAuthStatus]: Auth status response:', data);
setIsLoggedIn(data.isLoggedIn);
} else {
console.error('[main-nav-tsx][checkAuthStatus]: Error fetching auth status', response.status);
}
} catch (error) {
console.error('[main-nav-tsx][checkAuthStatus]: Error fetching auth status', error);
}
}
checkAuthStatus();
}, []);
/**
* handleSignOut is a server side action to check for log in
* main-nav is a client side action
* even though the method is post, we cannot use server action for checking if logged in is true or false
* hence a POST api is needed
*/
const handleSignOut = async () => {
const response = await fetch('/app/api/authentication/signout', {
method: 'POST',
});
if (response.ok) {
// Handle successful sign-out, e.g., redirect or update state
setIsLoggedIn(false);
} else {
// Handle error
console.error('Error signing out');
}
};
return (
<div className = "mr-4 hidden md:flex">
<Link href = "/" className = "mr-6 flex items-center space-x-2">
<Icons.logo className = "h-6 w-6" />
<span className = "hidden font-bold sm:inline-block">
{siteConfig.name}
</span>
</Link>
<nav className = "flex items-center gap-4 text-sm lg:gap-6">
{docsConfig.mainNav?.map(item => (
item.href && (
<Link
key = {item.href}
href = {item.href}
className = {cn(
"transition-colors hover:text-foreground/80",
pathname === item.href ? "text-foreground" : "text-foreground/60"
)}
>
{item.title}
</Link>
)
))}
{/* change this button else where */}
{isLoggedIn && (
<button onClick = {handleSignOut}>Sign Out</button>
)}
</nav>
</div>
)
}
// src/app/api/authentication/check-auth.ts
import { NextApiRequest, NextApiResponse } from 'next'
import { checkIsLoggedIn } from '../../../middleware'
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const isLoggedIn = checkIsLoggedIn(req);
// console.info("[check-auth-ts]: ", isLoggedIn)
res.status(200).json({ isLoggedIn });
}
В предоставленном вами руководстве для аутентификации используются сторонние библиотеки, в частности следующая аутентификация. Итак, если вы хотите получить доступ к данным сеанса, все, что вам нужно, это просто использовать крючок useSession. Он предоставляет поле статуса, которое может иметь одно из следующих значений: "loading" | "authenticated" | "unauthenticated"
.
Однако вы также спрашиваете, как передавать данные из промежуточного программного обеспечения на страницы, что немного сложнее. Что касается текущей версии (14.2.4), Next.js использует Edge runtime для промежуточного программного обеспечения и маршрутов API, при этом по умолчанию используется Node для рендеринга. Поэтому я сомневаюсь, что это вообще возможно. Что ж, вы можете подписаться на Edge Runtime , но Next.js по-прежнему не предоставляет никакого прямого способа доступа req
к объекту, если вы используете App Router. Обсуждения смотрите здесь.
Что касается возможных решений передачи данных из промежуточного программного обеспечения, могу предложить несколько решений:
Переместите логику промежуточного программного обеспечения в компонент template.tsx
или layout.tsx
. Там вы можете получить доступ к заголовкам и файлам cookie и получить из них необходимые данные. Затем вы можете поместить его в удобное вам решение для управления состоянием (Redux, React Context и т. д.). Что-то вроде этого:
export const Layout = ({ children }: LayoutProps) => {
const userData = getUserData();
return (
<StateProvider userData = {userData}>
{children}
</StateProvider>
)
}
Таким образом, при необходимости будет удобнее передавать больше пользовательских данных — информацию о пользователе, роли и т. д. Кроме того, это хорошее место для выполнения сетевого запроса, если полученные из него данные актуальны для всего приложения.
Если это не вариант для вас из-за некоторых ограничений, вы можете сделать довольно хакерское решение, но, тем не менее, работающее. Вы можете создать собственный заголовок, установить его в промежуточном программном обеспечении и получить его со своей страницы.
// middleware.ts
const myMiddleware = (req: Request, res: Response, next: NextFunction) => {
const userIsAuthenticated = checkAuthentication(req);
req.headers['x-user-authenticated'] = userIsAuthenticated;
return next();
};
// page.ts
const MyPage = () => {
const userIsAuthenticated = headers().get('x-user-authenticated');
// rest of the logic
};
Однако я бы воздержался от этого решения, потому что обычно в контексте аутентификации вам нужно больше, чем одно поле. Да, передача каждого отдельного поля в отдельный заголовок затруднительна.
next-auth
(или что-то подобное), она предоставляет API для получения данных сеанса на страницах — например, крючок useSession
.К сожалению нет. Я считаю, что в более старых версиях объект req использовался совместно разными контекстами (пользовательский _document и страница), возможно, даже промежуточным программным обеспечением. В любом случае, для вашего случая аутентификации библиотеки next-auth должно быть достаточно.
о, так никогда не было способа «естественно» передавать значения из промежуточного программного обеспечения на мои страницы? я никогда этого не знал. Спасибо.