本文将指导你使用Strapi CMS和Next.js来创建一个可以注册和验证用户并持续会话的应用程序。
设置Next.js应用程序
让我们从设置Next.js应用程序开始。设置的最简单方法是使用create-next-app 包。
在你的终端中,运行npx create-next-app next-app ,其中next-app 是你喜欢的任何项目名称。
这将生成一个名为next-app 的文件夹。然后,在你的终端屏幕上导航到该文件夹,运行npm run dev 。你将看到应用程序在本地主机3000端口运行。
设置Strapi
就像Next.js一样,Strapi也有一个npm包,可以方便地启动一个项目。
运行npx create-strapi-app strapi-app ,创建一个Strapi项目。当提示时,选择Quickstart 选项。
一旦安装完成,导航到strapi-app 文件夹并运行npm run dev 。当你访问localhost:1337 ,你将被要求创建一个管理员帐户。这样做之后,你应该被引导到Strapi控制面板。
在Strapi中注册一个用户
在Strapi的侧边栏,在集合类型下,你应该看到Users ,这是默认创建的。在这里你可以手动创建用户,并看到所有的现有用户。
默认情况下,Strapi的API提供了创建、获取和认证用户的端点。
/auth/local/register 是我们需要发送用户名、电子邮件和密码的端点,以便注册一个用户。
对于这个例子的应用程序,我们将有/register 和/profile 路径,在这里你可以分别注册一个用户,然后看到一个用户的资料。登录界面将出现在主页上。
让我们从<RegisterComponent/> 组件开始。
这个组件返回一个非常基本的表单,用户在其中输入他们的用户名、电子邮件和密码。
然后userData 对象持有这些信息并将其发送到/api/register 端点,这是我们将在Next应用程序的后台部分创建的东西。请注意,为了进行API调用,我使用了axios 包,所以请确保你把它作为一个依赖项来安装。
npm install --save axios
一旦对/api/register 的请求成功,我们就将应用程序路由到/profile ,以便显示用户信息。我们将在后面创建/profile 页面。
下面的文件是/components/registerComponent.jsx ,我们将在/pages/register.jsx 中使用它。
import { useState } from 'react';
import { useRouter } from 'next/router';
import axios from 'axios';
const RegisterComponent = () => {
const router = useRouter();
const [userData, setUserData] = useState({
username: '',
email: '',
password: '',
})
const handleSubmit = async (e) => {
e.preventDefault();
try {
await axios.post('/api/register', userData);
router.replace('/profile');
} catch (err) {
console.log(err.response.data);
}
}
const handleChange = (e) => {
const { name, value } = e.target;
setUserData({...userData, [name]: value });
}
return (
<form onSubmit={handleSubmit}>
<label>
Username:
<input type="text" name="username" onChange={e => handleChange(e)} />
</label>
<br />
<label>
Email:
<input type="text" name="email" onChange={e => handleChange(e)} />
</label>
<br />
<label>
Password:
<input type="password" name="password" onChange={e => handleChange(e)} />
</label>
<br />
<button>Register</button>
</form>
)
}
export default RegisterComponent;
现在在pages 文件夹下创建register.jsx ,只需返回<RegisterComponent/> 。
import RegisterComponent from "../components/registerComponent";
const Register = () => (
<RegisterComponent />
)
export default Register;
我们需要做的下一件事是创建/api/register 端点。
在/pages ,你会发现/api 文件夹。在该文件夹中,创建register.js 。这样,当我们在Next.js客户端向/api/register 发出请求时,将由这个文件来处理。
import axios from 'axios';
import { setCookie } from 'nookies'
export default async (req, res) => {
const { username, password, email } = req.body;
try {
const response = await axios.post('http://localhost:1337/auth/local/register', {
username,
email,
password,
})
setCookie({ res }, 'jwt', response.data.jwt, {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
maxAge: 30 * 24 * 60 * 60,
path: '/',
});
res.status(200).end();
} catch (e) {
res.status(400).send(e.response.data.message[0].messages[0]);
}
}
如前所述,注册用户的端点是/auth/local/register 。由于Strapi运行在localhost:1337 ,因此我们将一个请求发送到 [http://localhost:1337/auth/local/register](http://localhost:1337/auth/local/register)并附上我们从请求正文中检索到的用户名、电子邮件和密码数据。
请注意,如果你将你的Strapi应用程序部署到远程服务器上,那么你需要相应地替换基础URL。好的做法是将基本URL存储在环境变量中,然后使用它而不是硬编码URL。然而,为了简单起见,我将在本文中只使用localhost:1337 。
从POST请求返回的响应包含一个JWT令牌。这个令牌是特定于该用户的,应该被安全地存储,以便自动验证用户。
nookies 包是一个辅助函数的集合,用于处理Next.js应用程序中的cookie。
setCookie 函数将响应对象作为第一个参数。第二个参数是cookie的名称,它可以是你选择的任何东西。
第三个参数是cookie的值。在我们的例子中,这是从请求响应中返回的JWT token,而作为最后一个参数,我们可以传递一个带有选项的对象。
httpOnly 标志可以防止客户端--在浏览器上运行的javascript--访问cookie,这样你就可以保护cookie免受可能的跨站脚本(XSS)攻击。
secure 标志确保cookie只通过安全的https 连接传输。因为localhost不是一个https 的连接,如果我们运行应用程序的环境是development ,我们就把它设置为false 。
maxAge 决定cookie的有效期是多少秒。在这个例子中,它被设置为30天。
path 决定cookie应在哪个路径上有效。它被设置为/ ,以便使cookie对所有的路径都有效。
请注意,在请求失败的情况下,我们正在发送一条消息。从e.response.data.message[0].messages[0] ,这个消息包含关于请求失败原因的有用信息。这可能是一个无效的电子邮件,或一个已经在使用的用户名,等等。
因此,这些信息可以用来在注册失败的情况下显示适当的错误信息。
为了简单起见,我们不在客户端中处理这个错误信息。我们只是把它记录下来,显示在浏览器的控制台中。
在Strapi中创建用户配置文件页面
这是/profile 路径,用户数据,如用户名和电子邮件,在这里显示。这个路由可以直接访问,或者,用户在登录或注册时可以被引导到这里。
因为这个页面需要使用cookie来验证用户,而且我们已经在服务器端用httpsOnly 标志设置了cookie,所以我们也需要在服务器上读取cookie。
getServerSideProps 是一个Next.js函数,在每次请求时在服务器上运行。在这个函数中,我们可以解析cookie,以便访问JWT令牌,然后向Strapi发出请求,获取用户数据。然后我们可以从函数中返回这些数据,将其作为道具暴露在页面中。
/pages/profile.jsx 的内容如下。
import { useRouter } from 'next/router';
import axios from 'axios';
import nookies from 'nookies';
const Profile = (props) => {
const router = useRouter();
const { user: { email, username } } = props;
const logout = async () => {
try {
await axios.get('/api/logout');
router.push('/');
} catch (e) {
console.log(e);
}
}
return (
<div>
<div>Username: {username}</div>
<div>Email: {email}</div>
<button onClick={logout}>Logout</button>
</div>
)
}
export const getServerSideProps = async (ctx) => {
const cookies = nookies.get(ctx)
let user = null;
if (cookies?.jwt) {
try {
const { data } = await axios.get('http://localhost:1337/users/me', {
headers: {
Authorization:
`Bearer ${cookies.jwt}`,
},
});
user = data;
} catch (e) {
console.log(e);
}
}
if (!user) {
return {
redirect: {
permanent: false,
destination: '/'
}
}
}
return {
props: {
user
}
}
}
export default Profile;
让我们先关注一下getServerSideProps 这个函数。我们再次使用nookies 包,从上下文对象(ctx)中获取cookie。我们设置的cookie的名称是jwt ,因此要检查是否存在cookies.jwt 。
如果这个特定的cookie存在,那么我们就向本地Strapi服务器的/users/me 端点发送一个请求,其中包含JWT令牌的Authorization 头,以获取用户信息。
如果cookie不存在或者JWT令牌无效,user 变量将保持在null ,我们将页面重定向到/ ,也就是带有登录界面的主页。否则,我们在props 对象中返回user ,然后在我们导出的Profile 函数中作为一个道具可用。
Profile 函数返回一个非常基本的标记,其中显示了用户名和电子邮件,还有一个注销按钮,它调用logout 函数。
这个函数向/api/logout 端点发送请求,以删除cookie,然后将页面路由到/ 。
下面是/pages/api/logout.js 。
import { destroyCookie } from 'nookies'
export default async (req, res) => {
destroyCookie({ res }, 'jwt', {
path: '/',
});
res.status(200).end();
}
登录用户
下面是/pages/index.jsx 的内容,这是主页和<LoginComponent> 的位置,还有一个注册按钮,它将页面路由到/register 路由。
import { useRouter } from 'next/router';
import axios from 'axios';
import nookies from 'nookies';
import LoginComponent from '../components/loginComponent';
const Home = () => {
const router = useRouter();
const goToRegister = () => {
router.push('/register');
}
return (
<div>
<LoginComponent />
<button onClick={goToRegister}>Register</button>
</div>
)
}
export const getServerSideProps = async (ctx) => {
const cookies = nookies.get(ctx)
let user = null;
if (cookies?.jwt) {
try {
const { data } = await axios.get('http://localhost:1337/users/me', {
headers: {
Authorization:
`Bearer ${cookies.jwt}`,
},
});
user = data;
} catch (e) {
console.log(e);
}
}
if (user) {
return {
redirect: {
permanent: false,
destination: '/profile'
}
}
}
return {
props: {}
}
}
export default Home;
正如我们在/pages/profile.jsx ,我们再次使用getServerSideProps 函数来获取cookie,然后检查用户的存在。
如果用户确实存在,那么我们就重定向到/profile 路由。如果不存在,我们返回一个空的props 对象,并留在同一路径上渲染<LoginComponent/> ,其内容如下。
import { useState } from 'react';
import { useRouter } from 'next/router';
import axios from 'axios';
const LoginComponent = () => {
const router = useRouter();
const [userData, setUserData] = useState({
identifier: '',
password: '',
});
const handleSubmit = async (e) => {
e.preventDefault();
try {
await axios.post('/api/login', { ...userData });
router.push('/profile');
} catch (err) {
console.log(err.response.data);
}
}
const handleChange = (e) => {
const { name, value } = e.target;
setUserData({...userData, [name]: value })
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>
Email:
<input type="text" name="identifier" onChange={e => handleChange(e)} />
</label>
<br />
<label>
Password:
<input type="password" name="password" onChange={e => handleChange(e)} />
</label>
<br />
<button>Login</button>
</form>
</div>
)
}
export default LoginComponent;
这个组件渲染了一个表单,以获得电子邮件和密码。当表单被提交后,向/api/login 发出POST请求,页面被重定向到/profile 路由。
下面你可以看到的内容是/api/login.js
import axios from 'axios';
import { setCookie } from 'nookies'
export default async (req, res) => {
const { password, identifier } = req.body;
try {
const postRes = await axios.post('http://localhost:1337/auth/local', {
identifier,
password,
})
setCookie({ res }, 'jwt', postRes.data.jwt, {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
maxAge: 30 * 24 * 60 * 60,
path: '/',
});
res.status(200).end();
} catch (e) {
res.status(400).send(e.response.data.message[0].messages[0]);
}
}
/auth/local 是一个用于登录用户的Strapi端点。发送到这个端点的对象应该有identifier 和password 键。
正如我们在/api/register.js 中所做的那样,我们也设置了一个具有相同名称和选项的cookie来保持用户会话。
总结
在这篇文章中,我们演示了如何将Next.js作为一个全栈应用程序来创建一个具有多个路由的用户界面,并创建API端点来与Strapi的API通信,以便注册和获取用户信息。还演示了在Next.js的服务器端设置和获取cookie,以保持用户会话。
The postImplementing user registration and authentication with Strapi and Next.jsappeared first onLogRocket Blog.