@cabin-id/nextjs
v2.2.1
Published
NextJS SDK for CabinID
Readme
CabinID SDK for Next.js
CabinID SDK là bộ công cụ xác thực và quản lý danh tính, cung cấp giải pháp tích hợp dễ dàng cho các ứng dụng Next.js (App Router).
📦 Cài đặt
npm install @cabin-id/nextjs
# hoặc
pnpm add @cabin-id/nextjs
# hoặc
yarn add @cabin-id/nextjs🚀 Bắt đầu nhanh
1. Thiết lập biến môi trường
Tạo file .env.local trong thư mục gốc dự án:
# CabinID Configuration
NEXT_PUBLIC_CABIN_ID_PUBLISH_KEY=cabin_pk_your_publishable_key_here
CABIN_ID_SECRET_KEY=cabin_sk_your_secret_key_here
# URL Redirects (tùy chọn)
NEXT_PUBLIC_CABIN_ID_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CABIN_ID_AFTER_SIGN_UP_URL=/welcome
NEXT_PUBLIC_CABIN_ID_SIGN_IN_URL=/auth
NEXT_PUBLIC_CABIN_ID_SIGN_UP_URL=/auth/register
# API URL (tùy chọn, mặc định: https://api.cabinid.dev/)
NEXT_PUBLIC_CABIN_ID_API_URL=https://api.cabinid.dev/2. Cấu hình Middleware
Tạo file middleware.ts trong thư mục gốc để bảo vệ các route:
// middleware.ts
import { authMiddleware } from '@cabin-id/nextjs';
import { NextResponse } from 'next/server';
const publicRoutes = ['/', '/auth', '/terms', '/api/public/(.*)'];
const isPublicRoute = (path: string) => {
return publicRoutes.some(route => {
if (route.includes('(.*)')) {
const regex = new RegExp('^' + route);
return regex.test(path);
}
return route === path;
});
};
export default authMiddleware(async (auth, req) => {
const { userId } = auth();
const path = req.nextUrl.pathname;
// 1. Cho phép truy cập các routes công khai
if (isPublicRoute(path)) {
return NextResponse.next();
}
// 2. Nếu chưa đăng nhập và route cần bảo vệ -> Chuyển hướng đến trang đăng nhập
if (!userId) {
const returnUrl = new URL(req.nextUrl.pathname + req.nextUrl.search, req.url);
return auth().redirectToSignIn({ returnBackUrl: returnUrl.toString() });
}
// 3. Đã đăng nhập -> Cho phép truy cập
return NextResponse.next();
});
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};3. Cấu hình Provider
Bọc ứng dụng với CabinIDProvider trong root layout. Lưu ý: CabinIDProvider là một async Server Component.
// app/layout.tsx
import { CabinIDProvider } from '@cabin-id/nextjs';
import './globals.css';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="vi">
<body>
<CabinIDProvider>
{children}
</CabinIDProvider>
</body>
</html>
);
}� Client ID và Redirect URLs
Khi người dùng được chuyển hướng đến trang đăng nhập/đăng ký của CabinID, SDK tự động thêm client_id vào URL. Điều này giúp CabinID xác định ứng dụng nào đang yêu cầu xác thực.
Cách hoạt động
- Publishable Key: Giá trị
NEXT_PUBLIC_CABIN_ID_PUBLISH_KEYđược sử dụng làmclient_id - Redirect URL: URL được xây dựng tự động với format:
https://{domain}.cabinid.dev/sign-in?client_id={publishable_key}&redirect_url={return_url}
Ví dụ URL được tạo
https://myapp.cabinid.dev/sign-in?client_id=cabin_pk_xxx&redirect_url=https://myapp.com/dashboardTham số URL
| Tham số | Mô tả |
|---------|-------|
| client_id | Publishable key của project, giúp CabinID nhận diện ứng dụng |
| redirect_url | URL để quay lại ứng dụng sau khi xác thực thành công |
Lưu ý:
client_idđược tự động thêm bởi SDK. Bạn không cần xử lý thủ công.
�🔐 Components
SignInButton
Component nút đăng nhập, tự động chuyển hướng đến CabinID.
'use client';
import { SignInButton } from '@cabin-id/nextjs';
export default function LoginPage() {
return (
<div className="flex items-center justify-center min-h-screen">
<SignInButton />
</div>
);
}Props:
| Prop | Type | Default | Mô tả |
|------|------|---------|-------|
| children | React.ReactNode | "Continue to CabinID" | Nội dung hiển thị |
| className | string | Styled button | CSS class tùy chỉnh |
| afterSignInUrl | string | NEXT_PUBLIC_CABIN_ID_AFTER_SIGN_IN_URL | URL redirect sau khi đăng nhập |
| showLogo | boolean | true | Hiển thị logo CabinID |
Ví dụ tùy chỉnh:
<SignInButton
afterSignInUrl="/dashboard"
showLogo={false}
className="bg-indigo-600 text-white px-6 py-3 rounded-lg hover:bg-indigo-700"
>
Đăng nhập với CabinID
</SignInButton>SignOutButton
Component nút đăng xuất với xử lý callback.
'use client';
import { SignOutButton } from '@cabin-id/nextjs';
export default function Header() {
return (
<SignOutButton
onSignOutStart={() => console.log('Đang đăng xuất...')}
onSignOutSuccess={() => console.log('Đăng xuất thành công!')}
onSignOutError={(error) => console.error('Lỗi:', error)}
>
Đăng xuất
</SignOutButton>
);
}Props:
| Prop | Type | Default | Mô tả |
|------|------|---------|-------|
| children | React.ReactNode | "Sign Out" | Nội dung hiển thị |
| className | string | Styled button | CSS class tùy chỉnh |
| onSignOutStart | () => void | - | Callback trước khi đăng xuất |
| onSignOutSuccess | () => void | - | Callback khi đăng xuất thành công |
| onSignOutError | (error: Error) => void | - | Callback khi có lỗi |
| showLoading | boolean | true | Hiển thị trạng thái loading |
| loadingText | string | "Signing out..." | Text khi đang loading |
| afterSignOutUrl | string | NEXT_PUBLIC_CABIN_ID_AFTER_SIGN_IN_URL | URL redirect sau khi đăng xuất |
🪝 Hooks
useUser
Hook để lấy thông tin người dùng hiện tại trong Client Components.
'use client';
import { useUser } from '@cabin-id/nextjs';
export default function ProfilePage() {
const { user, isLoaded, isSignedIn, signOut } = useUser();
if (!isLoaded) {
return <div>Đang tải...</div>;
}
if (!isSignedIn) {
return <div>Vui lòng đăng nhập để tiếp tục</div>;
}
return (
<div className="p-6">
<h1 className="text-2xl font-bold">Xin chào, {user.firstName}!</h1>
<p>Email: {user.email}</p>
<p>ID: {user.id}</p>
<button onClick={() => signOut()}>
Đăng xuất
</button>
</div>
);
}Return type:
type UseUserReturn =
| { isLoaded: false; isSignedIn: undefined; user: undefined; signOut: SignOut }
| { isLoaded: true; isSignedIn: false; user: null; signOut: SignOut }
| { isLoaded: true; isSignedIn: true; user: User; signOut: SignOut };🖥️ Server Functions
auth()
Lấy thông tin xác thực trong Server Components hoặc Server Actions.
// app/dashboard/page.tsx
import { auth } from '@cabin-id/nextjs';
export default async function DashboardPage() {
const { userId, protect, redirectToSignIn } = await auth();
// Nếu chưa đăng nhập, redirect đến trang sign-in
if (!userId) {
redirectToSignIn();
}
return (
<div>
<h1>Dashboard</h1>
<p>User ID: {userId}</p>
</div>
);
}Return type:
type Auth = {
userId: string | null;
protect: AuthProtect;
redirectToSignIn: (opts?: { returnBackUrl?: string }) => never;
};currentUser()
Lấy thông tin chi tiết người dùng từ server.
// app/profile/page.tsx
import { currentUser } from '@cabin-id/nextjs';
export default async function ProfilePage() {
const user = await currentUser();
if (!user) {
return <div>Vui lòng đăng nhập</div>;
}
return (
<div>
<h1>Hồ sơ cá nhân</h1>
<p>Tên: {user.firstName} {user.lastName}</p>
<p>Email: {user.email}</p>
</div>
);
}🛡️ Bảo vệ API Routes
Sử dụng auth()
// app/api/profile/route.ts
import { auth } from '@cabin-id/nextjs';
import { NextResponse } from 'next/server';
export async function GET() {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Lấy dữ liệu người dùng từ database
const userData = await getUserFromDatabase(userId);
return NextResponse.json({ user: userData });
}🎨 Tùy chỉnh giao diện
AuthButton Component
AuthButton là component tiện ích đã được tích hợp sẵn trong SDK, tự động chuyển đổi giữa SignInButton và SignOutButton dựa trên trạng thái đăng nhập.
'use client';
import { AuthButton } from '@cabin-id/nextjs';
export default function Header() {
return (
<nav className="flex justify-between items-center p-4">
<h1>My App</h1>
<AuthButton
signInLabel="Đăng nhập"
signOutLabel="Đăng xuất"
afterSignInUrl="/dashboard"
afterSignOutUrl="/"
/>
</nav>
);
}Props:
| Prop | Type | Default | Mô tả |
|------|------|---------|-------|
| signInLabel | React.ReactNode | "Sign In" | Text hiển thị trên nút đăng nhập |
| signOutLabel | React.ReactNode | "Sign Out" | Text hiển thị trên nút đăng xuất |
| className | string | - | CSS class tùy chỉnh |
| afterSignInUrl | string | NEXT_PUBLIC_CABIN_ID_AFTER_SIGN_IN_URL | URL redirect sau khi đăng nhập |
| afterSignOutUrl | string | NEXT_PUBLIC_CABIN_ID_AFTER_SIGN_IN_URL | URL redirect sau khi đăng xuất |
| showLogo | boolean | true | Hiển thị logo CabinID (chỉ áp dụng cho nút đăng nhập) |
Ví dụ nâng cao:
<AuthButton
signInLabel={
<span className="flex items-center gap-2">
<UserIcon className="w-4 h-4" />
Đăng nhập
</span>
}
signOutLabel="Thoát"
className="bg-blue-600 text-white px-4 py-2 rounded-lg"
/>Protected Route Component
Component bảo vệ các trang yêu cầu đăng nhập:
'use client';
import { useUser } from '@cabin-id/nextjs';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
interface ProtectedRouteProps {
children: React.ReactNode;
fallback?: React.ReactNode;
}
export default function ProtectedRoute({
children,
fallback = <div>Đang chuyển hướng...</div>
}: ProtectedRouteProps) {
const { isSignedIn, isLoaded } = useUser();
const router = useRouter();
useEffect(() => {
if (isLoaded && !isSignedIn) {
router.push('/auth');
}
}, [isLoaded, isSignedIn, router]);
if (!isLoaded) {
return <div>Đang tải...</div>;
}
if (!isSignedIn) {
return fallback;
}
return <>{children}</>;
}📚 API Reference
Exports từ @cabin-id/nextjs
| Export | Type | Mô tả |
|--------|------|-------|
| CabinIDProvider | Component (async) | Provider chính, bọc ứng dụng |
| SignInButton | Component | Nút đăng nhập |
| SignOutButton | Component | Nút đăng xuất |
| AuthButton | Component | Nút đăng nhập/đăng xuất tự động |
| useUser | Hook | Lấy thông tin người dùng (client) |
| auth | Function | Lấy thông tin xác thực (server) |
| currentUser | Function | Lấy chi tiết người dùng (server) |
| authMiddleware | Function | Middleware xác thực |
Exports từ @cabin-id/nextjs/server
| Export | Type | Mô tả |
|--------|------|-------|
| authMiddleware | Function | Middleware xác thực cho Next.js |
Subdomain Utilities
import {
detectCabinIdSubdomain,
storeCabinIdSubdomain,
clearCabinIdSubdomain
} from '@cabin-id/nextjs';🔧 Cấu hình nâng cao
Middleware với Custom Logic
// middleware.ts
import { authMiddleware } from '@cabin-id/nextjs';
import { NextResponse } from 'next/server';
const adminRoutes = ['/admin', '/admin/(.*)'];
const publicRoutes = ['/', '/about', '/contact'];
export default authMiddleware(async (auth, req) => {
const { userId } = auth();
const path = req.nextUrl.pathname;
// Public routes
if (publicRoutes.some(r => path === r || path.match(new RegExp(`^${r}$`)))) {
return NextResponse.next();
}
// Require authentication
if (!userId) {
return auth().redirectToSignIn({ returnBackUrl: req.url });
}
// Admin routes - thêm logic kiểm tra quyền admin
if (adminRoutes.some(r => path.match(new RegExp(`^${r}$`)))) {
// TODO: Kiểm tra quyền admin từ database
// const isAdmin = await checkAdminRole(userId);
// if (!isAdmin) {
// return NextResponse.redirect(new URL('/unauthorized', req.url));
// }
}
return NextResponse.next();
});🔔 Webhook Integration
CabinID hỗ trợ webhook để thông báo cho ứng dụng của bạn khi có các sự kiện xác thực xảy ra.
Cấu hình Webhook
- Đăng nhập vào CabinID Dashboard
- Chọn project của bạn
- Vào Settings → Webhooks
- Thêm URL endpoint của bạn (ví dụ:
https://yourapp.com/api/webhooks/cabinid)
Các loại sự kiện
| Event Type | Mô tả |
|------------|-------|
| sign-in | Người dùng đăng nhập thành công |
| sign-up | Người dùng đăng ký tài khoản mới |
| sign-out | Người dùng đăng xuất |
Webhook Payload
Khi một sự kiện xảy ra, CabinID sẽ gửi POST request đến webhook URL của bạn với payload sau:
interface WebhookPayload {
eventType: 'sign-in' | 'sign-up' | 'sign-out';
date: number; // Unix timestamp (milliseconds)
payload: {
user: {
id: string;
avatar: string;
email: string;
phoneNumber: string;
firstName: string;
lastName: string;
createdAt: number;
updatedAt: number;
};
};
}Xử lý Webhook trong Next.js
// app/api/webhooks/cabinid/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface CabinIDWebhookPayload {
eventType: 'sign-in' | 'sign-up' | 'sign-out';
date: number;
payload: {
user: {
id: string;
email: string;
firstName: string;
lastName: string;
};
};
}
export async function POST(request: NextRequest) {
try {
const body: CabinIDWebhookPayload = await request.json();
const { eventType, payload } = body;
const { user } = payload;
switch (eventType) {
case 'sign-up':
// Tạo user mới trong database của bạn
await createUserInDatabase(user);
console.log(`New user registered: ${user.email}`);
break;
case 'sign-in':
// Cập nhật last login
await updateLastLogin(user.id);
console.log(`User signed in: ${user.email}`);
break;
case 'sign-out':
// Xử lý đăng xuất (nếu cần)
console.log(`User signed out: ${user.email}`);
break;
}
return NextResponse.json({ received: true }, { status: 200 });
} catch (error) {
console.error('Webhook error:', error);
return NextResponse.json(
{ error: 'Webhook processing failed' },
{ status: 500 }
);
}
}Best Practices cho Webhook
- Respond nhanh: Trả về response 200 ngay lập tức, sau đó xử lý async
- Idempotency: Xử lý trường hợp webhook được gửi nhiều lần
- Logging: Log tất cả webhook events để debug
- Error handling: Xử lý lỗi gracefully, không để crash server
// Ví dụ với background processing
export async function POST(request: NextRequest) {
const body = await request.json();
// Respond ngay lập tức
// Xử lý trong background (ví dụ: queue job)
processWebhookAsync(body).catch(console.error);
return NextResponse.json({ received: true });
}🐛 Xử lý lỗi thường gặp
Lỗi: "useUser must be used within CabinIDProvider"
Đảm bảo bạn đã bọc ứng dụng với CabinIDProvider trong root layout:
// ✅ Đúng
<CabinIDProvider>
<YourApp />
</CabinIDProvider>
// ❌ Sai - thiếu provider
<YourApp />Lỗi: Environment variables không được tìm thấy
Kiểm tra file .env.local:
# ✅ Đúng - có prefix NEXT_PUBLIC_ cho client-side
NEXT_PUBLIC_CABIN_ID_PUBLISH_KEY=cabin_pk_...
CABIN_ID_SECRET_KEY=cabin_sk_...
# ❌ Sai - thiếu NEXT_PUBLIC_ prefix
CABIN_ID_PUBLISH_KEY=cabin_pk_...Lỗi: Middleware không hoạt động
Đảm bảo file middleware.ts nằm ở thư mục gốc (cùng cấp với app/ hoặc src/):
project-root/
├── app/
├── middleware.ts ✅
└── .env.local🔗 Liên kết hữu ích
📄 Giấy phép
MIT License - xem file LICENSE để biết thêm chi tiết.
🚀 Bắt đầu ngay hôm nay! Tích hợp CabinID vào ứng dụng của bạn chỉ trong vài phút.
