七、Authentication
大约 5 分钟约 1508 字
(一)Authentication in Next.js
- 任何项目都会涉及到用户,需要考虑身份和权限
Identity
身份:验证用户是谁 ——Authentication
认证Access
权限:验证用户有什么许可 ——Authorization
授权
1.分类
- Client-side authentication
- Server-side authentication
- API routes authentication
2. 如何从以上三方面保护用户数据?
- 不需要持久化:使用 GitHub、Facebook 等认证服务,以确保用户的身份验证
- 需要持久化:使用数据库
(二)NextAuth Setup
components/Navbar.js
1.import Link from "next/link";
function Navbar() {
return (
<nav className="header">
<h1 className="logo">
<a href="#">NextAuth</a>
</h1>
<ul className={`main-nav`}>
<li>
<Link href="/">
<a>Home</a>
</Link>
</li>
<li>
<Link href="/dashboard">
<a>Dashboard</a>
</Link>
</li>
<li>
<Link href="/blog">
<a>Blog</a>
</Link>
</li>
<li>
<Link href="#">
<a>Sign In</a>
</Link>
</li>
<li>
<Link href="#">
<a>Sign Out</a>
</Link>
</li>
</ul>
</nav>
);
}
export default Navbar;
components/Navbar.css
2.ul {
margin: 0;
padding: 0;
list-style: none;
}
h2,
h3,
a {
color: #34495a;
}
a {
text-decoration: none;
}
.logo {
margin: 0;
font-size: 1.45em;
}
.main-nav {
display: flex;
margin-top: 5px;
}
.logo a,
.main-nav a {
padding: 10px 15px;
text-transform: uppercase;
text-align: center;
display: block;
}
.main-nav a {
color: #34495a;
font-size: 0.99em;
}
.main-nav a:hover {
color: #718daa;
}
.header {
display: flex;
justify-content: space-between;
padding-top: 0.5em;
padding-bottom: 0.5em;
background-color: #f4f4f4;
-webkit-box-shadow: 0px 0px 14px 0px rgba(0, 0, 0, 0.75);
-moz-box-shadow: 0px 0px 14px 0px rgba(0, 0, 0, 0.75);
box-shadow: 0px 0px 14px 0px rgba(0, 0, 0, 0.75);
}
pages/_app.js
3.import Navbar from "../components/Navbar";
import "../styles/gloabals.css";
import "../components/Navbar.css";
function MyApp({ Component, pageProps }) {
return (
<>
<Navbar />
<Component {...pageProps} />
</>
);
}
export default MyApp;
pages/dashboard.js
4.function Dashboard() {
return <h1>Dashboard page</h1>;
}
export default Dashboard;
pages/blog.js
5.function Blog() {
return <h1>Blog page</h1>;
}
export default Blog;
6.setup
yarn add next-auth
pages/api/auth/[...nextauth].js
1)import NextAuth from "next-auth";
import Providers from "next-auth/providers";
export default NextAuth({
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
});
.env.local
2)GITHUB_ID=XXX
GITHUB_SECRET=XXXXX
(三)Sign In and Sign Out
components/Navbar.js
1.import Link from "next/link";
import { signIn, signOut } from "next-auth/client";
function Navbar() {
return (
<nav className="header">
<h1 className="logo">
<a href="#">NextAuth</a>
</h1>
<ul className={`main-nav`}>
<li>
<Link href="/">
<a>Home</a>
</Link>
</li>
<li>
<Link href="/dashboard">
<a>Dashboard</a>
</Link>
</li>
<li>
<Link href="/blog">
<a>Blog</a>
</Link>
</li>
<li>
<Link href="/api/auth/signin">
<a
onClick={(e) => {
e.preventDefault();
{
/* signIn(); */
}
signIn("github");
}}
>
Sign In
</a>
</Link>
</li>
<li>
<Link href="/api/auth/signout">
<a
onClick={(e) => {
e.preventDefault();
signOut();
}}
>
Sign Out
</a>
</Link>
</li>
</ul>
</nav>
);
}
export default Navbar;
(四)Client-side Authentication
components/Navbar.js
1.import Link from "next/link";
import { signIn, signOut, useSession } from "next-auth/client";
function Navbar() {
const [session, loading] = useSession();
return (
<nav className="header">
<h1 className="logo">
<a href="#">NextAuth</a>
</h1>
<ul className={`main-nav ${!session && loading ? "loading" : "loaded"}`}>
<li>
<Link href="/">
<a>Home</a>
</Link>
</li>
<li>
<Link href="/dashboard">
<a>Dashboard</a>
</Link>
</li>
<li>
<Link href="/blog">
<a>Blog</a>
</Link>
</li>
{!loading && !session && (
<li>
<Link href="/api/auth/signin">
<a
onClick={(e) => {
e.preventDefault();
signIn("github");
}}
>
Sign In
</a>
</Link>
</li>
)}
{session && (
<li>
<Link href="/api/auth/signout">
<a
onClick={(e) => {
e.preventDefault();
signOut();
}}
>
Sign Out
</a>
</Link>
</li>
)}
</ul>
</nav>
);
}
export default Navbar;
components/Navbar.css
2.ul {
margin: 0;
padding: 0;
list-style: none;
}
h2,
h3,
a {
color: #34495a;
}
a {
text-decoration: none;
}
.logo {
margin: 0;
font-size: 1.45em;
}
.main-nav {
display: flex;
margin-top: 5px;
}
.logo a,
.main-nav a {
padding: 10px 15px;
text-transform: uppercase;
text-align: center;
display: block;
}
.main-nav a {
color: #34495a;
font-size: 0.99em;
}
.main-nav a:hover {
color: #718daa;
}
.header {
display: flex;
justify-content: space-between;
padding-top: 0.5em;
padding-bottom: 0.5em;
background-color: #f4f4f4;
-webkit-box-shadow: 0px 0px 14px 0px rgba(0, 0, 0, 0.75);
-moz-box-shadow: 0px 0px 14px 0px rgba(0, 0, 0, 0.75);
box-shadow: 0px 0px 14px 0px rgba(0, 0, 0, 0.75);
}
.loading {
opacity: 0;
transition: all 0.2s ease-in;
}
.loaded {
opacity: 1;
transition: all 0.2s ease-in;
}
(五)Securing Pages Client-side
pages/dashboard.js
1.useSession
返回的loading
从false => true
,但是session
从null => undefined
- 无法利用此数据判断用户是否有权限访问当前页面
- 使用
getSession
判断用户是否登录
// import { useSession } from "next-auth/client";
import { useState, useEffect } from "react";
import { getSession, signIn } from "next-auth/client";
function Dashboard() {
// const [session, loading] = useSession();
// console.log({ session, loading });
const [loading, setLoading] = useState(true);
useEffect(() => {
const securePage = async () => {
const session = await getSession();
if (!session) {
signIn();
} else {
setLoading(false);
}
};
securePage();
}, []);
if (loading) {
return <h2>Loading...</h2>;
}
return <h1>Dashboard page</h1>;
}
export default Dashboard;
(六)NextAuth Provider
Provider
允许通过useSession
跨组件使用session
- 推荐,因为页面刷新时
useSession
会自动检查用户是否有session
pages/index.js
1.import { useSession } from "next-auth/client";
export default function Home() {
const [session, loading] = useSession();
console.log({ session, loading });
<main>{session ? `${session.user.name}, ` : ""} Welcome to NextJS</main>;
}
pages/_app.js
2.import { Provider } from "next-auth/client";
import Navbar from "../components/Navbar";
import "../styles/gloabals.css";
import "../components/Navbar.css";
function MyApp({ Component, pageProps }) {
return (
<Provider>
<Navbar />
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
(七)Server-side Authentication
- 无需从
props
中解构session
,也无需判断loading
- 因为 SSR 的 session 一定会被加载,且可以全局访问
pages/blog.js
1.import { getSession, useSession } from "next-auth/client";
function Blog({ data }) {
const [session] = useSession();
console.log({ session });
return <h1>Blog page - {data}</h1>;
}
export async function getServerSideProps(context) {
const session = await getSession(context);
return {
props: {
session,
data: session ? "List of 100 personalized blogs" : "List of free blogs",
},
};
}
export default Blog;
pages/_app.js
2.import { Provider } from "next-auth/client";
import Navbar from "../components/Navbar";
import "../styles/gloabals.css";
import "../components/Navbar.css";
function MyApp({ Component, pageProps }) {
return (
<Provider session={pageProps.session}>
<Navbar />
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
(八) Securing Pages Server-side
- 用户通过
url
直接访问无权限访问的页面时,跳转回首页
pages/blog.js
1.import { getSession, useSession } from "next-auth/client";
function Blog({ data }) {
const [session] = useSession();
console.log({ session });
return <h1>Blog page - {data}</h1>;
}
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: "/api/auth/signin?calbackUrl=http://localhost:3000/blog",
permanent: false,
},
};
}
return {
props: {
session,
data: session ? "List of 100 personalized blogs" : "List of free blogs",
},
};
}
export default Blog;
(九) Securing API Routes
pages/api/test-session.js
1.import { getSession } from "next-auth/client";
const handler = async (req, res) => {
const session = await getSession({ req });
if (!session) {
res.status(401).json({ error: "Unauthenticated user" });
} else {
res.status(200).json({ message: "Success", session });
}
};
export default handler;
(十) Connecting to a Database
yarn add mongodb
.env.local
1.GITHUB_ID=XXX
GITHUB_SECRET=XXXXX
DB_USER=XXX
DB_PASSWORD=XXXXX
DB_URL=XXXXXXX
pages/api/auth/[...nextauth].js
2.import NextAuth from "next-auth";
import Providers from "next-auth/providers";
export default NextAuth({
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
database: process.env.DB_URL,
session: {
jwt: true,
},
jwt: {
secret: "xxxxx",
},
});
(十一)Callbacks
pages/api/auth/[...nextauth].js
1.import NextAuth from "next-auth";
import Providers from "next-auth/providers";
export default NextAuth({
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
database: process.env.DB_URL,
session: {
jwt: true,
},
jwt: {
secret: "xxxxx",
},
callbacks: {
// token创建和更新时自动调用,可在此自定义格式
async jwt(token, user) {
if (user) {
token.id = user.id;
}
return token;
},
async session(session, token) {
session.user.id = token.id;
return session;
},
},
});
(十二)Summary
- 使用
useSession
钩子函数进行客户端身份验证 - 使用
getSession
函数从客户端层面限制访问页面 - 当在
getServerSideProps
中使用getSession
函数进行服务端身份验证时,next-auth
的Provider
可以提高性能,减少网络调用并避免页面闪烁 - 通过从
getServerSideProps
中重定向来从服务端层面限制访问页面 - 使用
getSession
函数可用于验证API
路由权限 Callbacks
允许自定义session
对象