Nếu các bạn chưa xem phần 1 thì đây là danh sách các phần của bài viết này
Ở phần 1, chúng ta đã kết thúc tại bước đăng nhập sử dụng Firebase Authentication. Đến đây, nếu bạn thấy như vậy đã đủ thì bạn có thể tự tùy chỉnh ứng dụng của mình và không cần xem tiếp phần 2 nữa
Còn nếu bạn đang tìm kiếm những chức năng nâng cao hơn thì hãy cùng tôi đi hết bài viết này (tham khảo các chức năng chỉ Firebase Admin cung cấp):
- Manage Users
- Import Users
- Create Custom Tokens
- Verify ID Tokens
- Manage User Sessions
- Manage Session Cookies => chúng ta sẽ sử dụng tính năng này
- Control Access with Custom Claims
- Generating Email Action Links
Sử dụng Firebase Admin SDK
Cách đơn giản nhất là các bạn truy cập vào Firebase Console và tạo private key
Thông tin serviceAccount
được download về sẽ có dạng như sau
// test12312-7c498-firebase-adminsdk-g5lvb.json
{
"type": "...",
"project_id": "<YOUR_PROJECT_ID>",
"private_key_id": "...",
"private_key": "<YOUR_PRIVATE_KEY>",
"client_email": "<YOUR_CLIENT_EMAIL>",
"client_id": "...",
"auth_uri": "...",
"token_uri": "...",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "..."
}
Chúng ta cần phải cài đặt firebase-admin
và cookie
$ yarn add firebase-admin
$ yarn add cookie
Tạo file firebase-admin.ts
rồi thêm cài đặt đã lấy được ở bước trên như sau
// ./libs/firebase-admin.ts
import { cert, initializeApp, getApps } from 'firebase-admin/app'
const serviceAccount = {
projectId: 'YOUR_PROJECT_ID',
clientEmail: '<YOUR_CLIENT_EMAIL>',
privateKey: `<YOUR_PRIVATE_KEY>`,
}
if (!getApps().length) {
initializeApp({
credential: cert(serviceAccount)
})
}
Tạo session cookie thông qua api
Đến thời điểm hiện tại, thì chúng ta mới chỉ đang cài đặt để có thể sử dụng Firebase Admin ở phía backend. Ở bước này chúng ta sẽ tạo session cookie sau khi user đã đăng nhập thành công thông qua Firebase Authentication
Tạo mới file api/create-session.ts
// ./pages/api/create-session.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { serialize } from "cookie";
import "../../libs/firebase-admin";
import { getAuth } from "firebase-admin/auth";
type Data = {
error: boolean;
};
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const { idToken } = req.body;
const expiresIn = 60 * 60 * 24 * 5 * 1000;
getAuth()
.createSessionCookie(idToken, { expiresIn })
.then(
(sessionCookie) => {
const options = {
path: "/",
maxAge: expiresIn,
httpOnly: true,
secure: true,
};
res.setHeader(
"Set-Cookie",
serialize("session", sessionCookie, options)
);
res.status(200).json({ error: false });
},
(error) => {
console.log("error", error);
res.status(401).json({ error: true });
}
);
}
Phía frontend, chúng ta cần gọi api vừa được tạo ra
// ./services/sign.ts
// ...
export const signInWithEmail = (email: string, password: string) => {
return signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
console.log(userCredential.user);
const user = userCredential.user;
user.getIdToken().then((idToken) => {
console.log("idToken", idToken);
// Gọi tới /api/create-session
fetch("/api/create-session", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ idToken }),
}).then((res) => {
console.log("res", res);
window.location.href = "/protected";
});
});
})
.catch((error) => {
console.log(error.code);
});
};
Kết quả nên được trả về từ api như sau. Chú ý dòng Set-Cookie
, do chúng ta đã tạo ra session cookie tại /api/create-session
Xác thực session cookie
Cài đặt cơ chế xác thực session cookie sử dụng getAuth().verifySessionCookie
// ./middlewares/authentication.ts
import '../libs/firebase-admin'
import { getAuth } from 'firebase-admin/auth'
import { GetServerSideProps } from 'next'
type GetServerHandlerParam = Parameters<GetServerSideProps>
type GetServerHandlerReturnType = ReturnType<GetServerSideProps>
export const _authentication = (
callback: (context: GetServerHandlerParam[0]) => GetServerHandlerReturnType
) => {
const getServerSideProps: GetServerSideProps = async (context) => {
const { req } = context
const url = req.url || ''
const session = req.cookies['session']
try {
const decoded = await getAuth().verifySessionCookie(session, true)
console.log('decoded', decoded)
if (url.includes('/login')) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
} catch (error) {
if (url.includes('/protected')) {
console.log('=>>', 'JWT session is invalid')
return {
redirect: {
destination: '/login',
permanent: false,
},
}
}
}
const returnData = callback(context)
return returnData
}
return getServerSideProps
}
Tạo trang protected để kiểm tra việc hoạt động
// ./pages/protected.tsx
import { GetServerSideProps } from "next";
import { _authentication } from "../middlewares/authentication";
export default function ProtectedPage() {
return (
<div className="h-screen">
<div className="bg-white min-h-full px-4 py-16 sm:px-6 sm:py-24 md:grid md:place-items-center lg:px-8">
<div className="max-w-max mx-auto">
<main className="sm:flex">
<p className="text-4xl font-extrabold text-indigo-600 sm:text-5xl">
200
</p>
<div className="sm:ml-6">
<div className="sm:border-l sm:border-gray-200 sm:pl-6">
<h1 className="text-4xl font-extrabold text-gray-900 tracking-tight sm:text-5xl">
Protected page
</h1>
<p className="mt-1 text-base text-gray-500">
Only verified user can access to this page
</p>
</div>
<div className="mt-10 flex space-x-3 sm:border-l sm:border-transparent sm:pl-6">
<a
href="/"
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Go back home
</a>
<a
href="#"
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Log out
</a>
</div>
</div>
</main>
</div>
</div>
</div>
);
}
export const getServerSideProps: GetServerSideProps = _authentication(
async () => {
return {
props: {
data: [1],
},
};
}
);
Cập nhật lại trang chủ, cho phép truy cập vào trang /protected
// ./pages/index.tsx
import type { NextPage } from "next";
const Home: NextPage = () => {
return (
<div className="m-6">
// ......
<a
href="/protected"
className="ml-4 inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
To protected page
</a>
</div>
);
};
export default Home;
Thành quả cuối cùng sẽ như sau
Kết luận
Cuối cùng chúng ta cũng cùng nhau đã hoàn thành chức năng xác thực user với Next.js sử dụng Firebase Admin. Hi vọng các bạn đã hiểu được cơ chế làm việc của Firebase qua bài viết này
Nếu có bất kì thắc mắc nào, hãy để lại feedback và tôi sẽ giải đáp. Hẹn gặp lại ở các bài viết tiếp theo