在 web 应用中经常需要验证使用者的权限,例如登入与未登入能看到的页面可能会不同,需要透过一些方式验证使用者是否能够进入该页面。在 Next.js 中对於 SSG 与 SSR 有不同的验证方式,以下将会以 next-auth
这个套件为主轴,介绍在 SSG 与 SSR 的页面中如何做验证。
在官方文件中还有提到 with-iron-session
这一个套件,先从下载量来看, npm trend 中可以看见 next-auth
的下载量是高於 next-iron-session
的。虽然 next-iron-session
被开源的时间较晚,但如果从 2020 年来看,下载量在当时并没有差距太多,都落在每周几千的次的下载量,但是随着时间推进,到了 2021 年 next-auth
的下载量是高过很多的。
而笔者认为 next-iron-session
的下载量较低的原因是它的写法可能较不容易被接受,如果使用这个套件,将需要大幅度更动 getServerSideProps
的写法:
import withSession from '../lib/session'
export const getServerSideProps = withSession(async function ({ req, res }) {
const user = req.session.get('user')
// ...
}
如上面的例子,想要取得 session 的讯息,必须在 function 外用 HOC 的方式包一层 withSession
,才能透过 req.session
取得验证权限的讯息,这种方法肯定很难让人被接受。
而 next-auth
的写法更像似遵照 hook 的思维,可以使用 useSession
或 getSession
取得验证权限的讯息,而不用破坏原本 Next.js 的程序码结构。
在 Next.js 由於可以混用 SSR、SSG 与 CSR 三种不同的页面,所以针对不同的页面,也会有不同的验证方式。 SSG 与 CSR 的验证方式很接近,都是在用户端验证,如果验证失败则可以使用 next/router
转址到其他页面;而 SSR 则是在服务器端验证,验证失败则可以直接在 getServerSideProps
转址到其他页面。
从 web vitals 的角度来看,由於 SSR 会在服务器端做验证,所以验证阶段必须越快越好,否则会影响 TTI (Time to Interactive) 与 TTFB (Time to First Byte)。果验证较慢,不如可以考虑将页面转换成 SSG 的形式,有利於提升使用者体验。
next-auth
是一个提供完整解决方案的套件,包括支援 Google、 Facebook、 Twitter 等第三方登入的方法,也可以使用自定义的验证方式,支援 OAuth 1.0、1.0A 与 2.0 的验证,也同时支援 JSON Web Token 与 database session 等的验证模式,是完整性很高的套件。
next-auth
的 API 与前面提到的 next-iron-session
相较起来更容易阅读,不仅提供 hook 的 API,而且还有提供在用户端与服务器端端都能使用的 getSession
,让使用者验证更容易实作。
如果你需要的是用户端的验证解决方案, next-auth
可能无法满足你的需求,因为 next-auth
使用的是服务器端验证,在一个页面中必须使用 next-auth
的 client API - signIn
进行登入验证,而登入验证会导入到 next-auth
所属的 API routes - api/auth/[...nextauth].ts
,然後再根据不同的情况使用第三方登入验或自定义的验证。
NextAuth 的官方 GitHub 有 next-auth-example ,如果需要一个模板,可以从跟着 README 玩玩看。
以下我们来看看在 Next.js 中几个 next-auth
基本的使用方法,包括设定 API routes、在 component 中使用 hook API ,以及使用 context 让 Next.js 全域都能取得使用者的 session。
next-auth
是一个在服务器端的验证套件,首先,我们必须在定义一个 API routes 於 pages/api/auth/[...nextauth].ts
,这个 API routes 与常见的写法不太一样,使用的是 next-auth
的 API ,其中包含验证的 providers
,例如 Google、Facebook、Twitter 等等,如果需要将使用者的资料储存在资料库中,可以在 database 这个参数加上 url。
实际上 NextAuth
这个 API 的能够传入参数有很多,如果需要知道更多资讯的读者,可以直接到官方文件中浏览。
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
export default NextAuth({
// 使用 Google 验证
providers: [
Providers.Google({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
// 如果需要将使用者的资料储存在资料库中,可以在 database 这个参数加上 url
database: process.env.DATABASE_URL,
});
pages/login/index.ts
NextAuth 有提供 hooks API 让我们使用,在使用上非常方便,只要从 next-auth/client
引入 useSession
後不用做任何的设定就可以使用。
从下面中的范例中,我们还可以看到 NextAuth 也提供了 signIn
与 signOut
的 function,登入时可以呼叫 signIn
,NextAuth 会帮我们打 API routes ,并经过一连串验证权限的流程,如果验证成功,我们就可已透过 useSession
拿到验证後的资料,例如 accessToken
。
而登出的流程也可以透过 NextAuth 的 signOut
触发,如此一来,session 中资料就会被清空,使用者便会回到登入前的状态。
import { signIn, signOut, useSession } from "next-auth/client";
function Login() {
const [session, loading] = useSession();
return (
<>
{!session && <button onClick={() => signIn()}>登入</button>}
{session && (
<>
<p>使用者 email: {session.user.email}</p>
<button onClick={() => signOut()}>登出</button>
</>
)}
</>
);
}
export default Login;
page/_app.ts
由於 NextAuth 是透过 Next.js 的 API routes 验证使用者,透过上述的 signIn
触发後, session 会被记录下来,当我们使用到 Next.js 的 SSR 功能时,会透过 getSession
取得验证後的一些讯息,例如 accessToken
,我们在一些情况需要在打 API 时带入 accessToken
才能够成功获取资料。
在获取资料,并且成功由服务器渲染完页面後,使用者後续还会跟页面互动,而在切换页面时,如果需要不断地跟服务器交换使用者是否已认证的讯息,难免会让一些互动会有些延迟,所以为了解决这个情况,NextAuth 提供了 Context API,让我们可以在 getServerSideProps
中传入 session
,如此一来,在页面切换时,取得使用者的验证讯息就会更有效率,像是在 accessToken
过期之前,不必再次透过服务器验证使用者,而是直接从 Context 取得 accessToken
,藉此提升使用者体验。
import { AppProps } from "next/app";
import { Provider } from "next-auth/client";
function MyApp({ Component, pageProps }: AppProps) {
return (
<Provider session={pageProps.session}>
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
>>: Day 17手势识别GestureDectector
昨天介绍了在 Function Component 中该如何操作 state 的方法(附上传送门)...
写在前面 相比起BI这些当红炸子鸡概念,报表工具大家可能不太熟悉,希望这篇文章能够给大家提供一些新的...
取号机制是专案中很常会使用到的项目。在我们的生活中小到饮料店的取餐单、银行的号码牌, 大到公文系统的...
您的订阅是我制作影片的动力 订阅点这里~ 若内容有误,还请留言指正,谢谢您的指教 ...
OAuth 2.0 指定了存取资源的存取令牌,但它没有提供提供身份信息(ID Token)的标准方...