終於說了那麼多next-auth
篇幅現在可以談主角trpc
了,有了next-auth
的 session
內容,我們就可以把它放到 trpc
的 context
拉~
跟 express
概念一樣 middleware
就是讓你來做身份驗證的,但我們要怎麼拿到 user
身份內容呢?不知道讀者還記不記得我們可以在 client
端透過 useSession
中查看 user session
內容,但其實next-auth
有提供 server-side
的 function
讓你拿到 session
他可以用在 SSR
或是 api route
甚至是 server enver environment
。
import { authOptions } from 'pages/api/auth/[...nextauth]'
import { getServerSession } from "next-auth/next"
export async function getServerSideProps(context) {
const session = await getServerSession(context.req, context.res, authOptions)
if (!session) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
return {
props: {
session,
},
}
}
import { authOptions } from 'pages/api/auth/[...nextauth]'
import { getServerSession } from "next-auth/next"
export default async function handler(req, res) {
const session = await getServerSession(req, res, authOptions)
if (!session) {
res.status(401).json({ message: "You must be logged in." });
return;
}
return res.json({
message: 'Success',
})
}
但如果是在 trpc
你需要先定義 getServerAuthSession
//~src/server/auth
import { getServerSession } from 'next-auth'
export const getServerAuthSession = (ctx: {
req: GetServerSidePropsContext["req"];
res: GetServerSidePropsContext["res"];
}) => {
return getServerSession(ctx.req, ctx.res, authOptions);
};
之後 getServerAuthSession
會放到 createTRPCContext
中把 session
傳到 trpc context
。
//~src/server/api/trpc
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts
const session = await getServerAuthSession({ req, res });
return {
session,
prisma
};
};
那 createTRPCContext
的 opts params
則是因為在 ~src/api/trpc/[trpc].ts
create 一個 api route
,所以才會有 next
的 req
跟 res
參數。
export default createNextApiHandler({
router: appRouter,
createContext: createTRPCContext,
});
因為我們有添加 session
到 trpc
的 contect
,所以實作 middleware
時,我們就可以判斷 ctx
中是否有 user info
,接著就可以訂一個 protectProcedure
包含enforceUserIsAuthed middleware
。
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' })
}
return next({
ctx: {
session: { ...ctx.session, user: ctx.session.user },
}
})
})
export const publicProcedure = t.procedure;
export const protectProcedure = t.procedure.use(enforceUserIsAuthed);
接著我們把 protectProcedure
替換 publicProcedure
export const postsRouter = router({
infinitePosts: protectProcedure
.input(getInfinitePostSchema)
//..
addPost
部分也要。
addPost: protectProcedure
.input(createPostSchema)
.mutation(async ({ input, ctx }) => {
const { prisma, session } = ctx
const { title } = input
const duplicatePost = await prisma.post.findFirst({
where: {
title: title
}
})
//..
return { message: 'success create post' }
}),
然後到 PostForm
中我們加個 toast error
export const PostForm = ({ updateSuccessCallBack }: PostFormProps) => {
const utils = api.useContext()
const { mutateAsync: createPost } = api.posts.addPost.useMutation({
onSuccess: () => {
// utils.posts.getPosts.invalidate()
utils.posts.infinitePosts.refetch()
if (updateSuccessCallBack) {
updateSuccessCallBack()
}
},
onError: (e) => {
if (e instanceof TRPCClientError) {
toast.error(e.message)
}
}
})
然後我們到 http://localhost:3000/posts 隨便新增 post
,這時你就會發現有 error
摟~日後假如你的 router
希望有身份限制就用 protectProcedure
~
那現在 addPost
改成 protectProcedure
,所以我們可以透過 ctx
拿到 session
。
addPost: protectProcedure
.input(createPostSchema)
.mutation(async ({ input, ctx }) => {
const { prisma, session } = ctx
接著我們將 session
的 user.id
關聯到我們的 post
中,如此一來只要user
是登入狀態,create post
後就會自動關聯到 user.id
addPost: protectProcedure
.input(createPostSchema)
.mutation(async ({ input, ctx }) => {
const { prisma, session } = ctx
const { title } = input
const duplicatePost = await prisma.post.findFirst({
where: {
title: title
}
})
if (duplicatePost) {
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Title already exists' })
}
const newPost = await prisma.post.create({
data: {
title: input.title,
content: input.content,
author: {
connect: {
id: session?.user.id
}
}
},
})
//..
接著我們重新登入後新增post
如此一來不會有 UNAUTHORIZED error
了。
從 prisma studio
來看你會發現 post
就關聯到對應的 user
了~
( 這邊圖很小讀者可以自行放大XD )
最後一步就是在 PostForm
加上 signup
功能。
export const PostForm = ({ updateSuccessCallBack }: PostFormProps) => {
//..
return (
//..
<Button type='submit' disabled={false} fullWidth>submit</Button>
//..
)
}
但你會發現登出後還是停留在 http://localhost:3000/posts
,那是因為我們還沒有做轉址,明天會繼續教讀者完善這功能~
以上大概是簡單說明 middleware
用法,明天我們就來優化一下 auth
其他功能期待一下吧~
https://github.com/Danny101201/next_demo/tree/main
✅ 前端社群 :
https://lihi3.cc/kBe0Y