我的 NextJs 应用程序中有 HTTP 服务,用于管理对外部后端服务器的 API 调用,就像 API 端点一样(NodeJs 应用程序与 NextJs 没有任何联系)
问题是: 我必须登录用户并取回 JWT。 我必须将 JWT 保存在某处,以便我的 NextJs HTTP 服务在每个请求之前将令牌注入标头,并且它可以从客户端和服务器端组件运行,因为现在,从其中一个组件发送它是未定义的,我相信这是因为它已保存饼干中只包含一个而不是两者
这是我的 HTTP 服务:
const headers: Record<string, string> = {
language: "en",
Accept: "application/json",
"Content-Type": "application/json",
};
type CustomRequestInit = RequestInit & {};
abstract class HttpFactory {
abstract createHttp(url: string): Http;
}
abstract class Http {
abstract builtUrl: string;
abstract updateToken(_?: string): void;
abstract get<T>(url: string, config?: CustomRequestInit): Promise<T>;
abstract delete<T>(url: string, config?: CustomRequestInit): Promise<T>;
abstract put<T>(
url: string,
data?: any,
config?: CustomRequestInit,
): Promise<T>;
abstract post<T>(
url: string,
data?: any,
config?: CustomRequestInit,
): Promise<T>;
abstract patch<T>(
url: string,
data?: any,
config?: CustomRequestInit,
): Promise<T>;
protected buildPath(path: string, trailingPath: string) {
return `${path}${trailingPath}`;
}
}
// FetchHttpFactory class implementing HttpFactory
class FetchHttpFactory extends HttpFactory {
createHttp(url?: string): Http {
return new FetchHttp(url);
}
}
// FetchHttp class extending Http and implementing fetch methods
class FetchHttp extends Http {
builtUrl: string;
headers = headers || {};
baseUrl = "http://localhost";
token?: string = undefined;
constructor(mainUrl?: string) {
super();
this.builtUrl = `${this.baseUrl}/api${mainUrl}`;
}
updateToken(token: string) {
this.token = token;
this.headers = {
authorization: token,
};
}
private async handleResponse<T>(response: Response): Promise<T> {
const data = await response.json();
return data;
}
async get<T>(url: string, config?: CustomRequestInit): Promise<T> {
const { ...requestConfig } = config ?? ({} as CustomRequestInit);
const response = await fetch(`${this.builtUrl}/${url}`, {
method: "GET",
headers: { ...this.headers, ...(requestConfig.headers || {}) },
...requestConfig,
});
return this.handleResponse<T>(response);
}
async post<T>(
url: string,
data?: any,
config?: CustomRequestInit,
): Promise<T> {
const { ...requestConfig } = config ?? ({} as CustomRequestInit);
const response = await fetch(`${this.builtUrl}${url}`, {
method: "POST",
headers: { ...this.headers, ...(requestConfig.headers || {}) },
body: JSON.stringify(data),
...requestConfig,
});
return this.handleResponse<T>(response);
}
}
export { HttpFactory, FetchHttpFactory };
这是我的登录页面:
"use client";
import { useState } from "react";
import { useDispatch } from "react-redux";
import { useRouter } from "next/navigation";
import { Button, Label, TextInput } from "flowbite-react";
import API from "@/api";
import { AuthActions } from "@/store/reducers/auth";
import { navPageWrapper } from "@/hoc/navPageWrapper";
const SignIn = () => {
const router = useRouter();
const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e: any) => {
e.preventDefault();
const form = new FormData(e.target);
const email = form.get("email")?.toString();
const password = form.get("password")?.toString();
if (!email || !password) return;
setIsLoading(true);
try {
const response = await API.DB.Auth.signIn({ email, password });
if ("token" in response) {
const token = response.token;
// where to save the token so that I can retrieve it in the HTTP service
dispatch(AuthActions.setToken(token));
router.push("/home");
}
} catch (err: any) {
console.error("SignIn error:", err.message);
}
setIsLoading(false);
};
return (
<div className="flex flex-1 items-center justify-center">
<form
onSubmit={handleSubmit}
className="mx-2 w-11/12 rounded p-10 shadow-md sm:w-1/2 md:w-2/5 lg:w-3/12"
>
<div>
<Label value="Email" />
<TextInput
value={"[email protected]"}
name="email"
placeholder="Email"
type="text"
required
/>
</div>
<div className="mt-4">
<Label value="Password" />
<TextInput
value={"12345678@a"}
name="password"
placeholder="Password"
required
/>
</div>
<div className="mt-4 flex flex-1 justify-end">
<Button
color="dark"
type="submit"
className="w-1/2 shadow"
isProcessing={isLoading}
>
Submit!
</Button>
</div>
</form>
</div>
);
};
export default navPageWrapper(SignIn);
根据评论,我可以向您展示我的解决方案。
您可以使用适用于 OAuth2 或 JWT 的 Next-Auth 库。 您需要像这样创建凭据提供程序:
import Credentials from "next-auth/providers/credentials";
export const authOptions: NextAuthOptions = {
providers: [
Credentials({
name: 'Credentials',
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
const res = await fetch(<Backend authorization endpoint, {
method: 'POST',
body: JSON.stringify({ username: credentials?.username, password: credentials?.password }),
headers: { "Content-Type": "application/json" }
})
const user = await res.json()
if (res.ok && user) {
return user
}
return null
}
})
],
callbacks: {
<callbacks>
},
session: {
strategy: 'jwt',
maxAge: <session_valid_time>,
},
pages: {
signIn: '/login',
error: '/<some_err_page>'
}
}
在布局中,您需要使用 import NextAuthSessionProvider from "./SessionProvider" 来包装所有 {children};
然后您可以从登录组件调用此提供程序,如下所示:
import { signIn } from "next-auth/react";
const result = await signIn("credentials", {
username: <username value>,
password: <password value>,
redirect: <true or false for enabling redirecting after login>,
callbackUrl: "<URL for callback>",
});
您还可以指定中间件。