@cabin-id/nextjs
v1.2.7
Published
NextJS SDK for CabinID
Readme
CabinID SDK
CabinID SDK là một bộ công cụ xác thực và quản lý danh tính đa cấp, cung cấp giải pháp tích hợp dễ dàng cho các ứng dụng Next.js và React.
📦 Cài đặt
npm install @cabin-id/nextjs
# hoặc
pnpm add @cabin-id/nextjs
# hoặc
yarn add @cabin-id/nextjs🚀 Cấu hình 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 của bạ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
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=/auth2. Cấu hình CabinIdProvider
Bọc ứng dụng của bạn với CabinIdProvider:
// app/layout.tsx hoặc pages/_app.tsx
'use client';
import { CabinIdProvider } from '@cabin-id/nextjs';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="vi">
<body>
<CabinIdProvider>
{children}
</CabinIdProvider>
</body>
</html>
);
}3. Middleware (Tùy chọn)
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/server';
export default authMiddleware({
publicRoutes: ['/'],
afterAuth(auth, req) {
// Xử lý sau khi xác thực
if (!auth.userId && !auth.isPublicRoute) {
return Response.redirect(new URL('/auth', req.url));
}
},
});
export const config = {
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
};🔐 Tích hợp Authentication
SignInButton - Nút Đăng nhập
'use client';
import { SignInButton } from '@cabin-id/nextjs';
export default function LoginPage() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="text-center text-3xl font-extrabold text-gray-900">
Đăng nhập vào tài khoản
</h2>
</div>
<SignInButton />
</div>
</div>
);
}SignOutButton - Nút Đăng xuất
'use client';
import { SignOutButton } from '@cabin-id/nextjs';
export default function Dashboard() {
return (
<div className="min-h-screen bg-gray-50">
<nav className="bg-white shadow">
<div className="max-w-7xl mx-auto px-4">
<div className="flex justify-between h-16">
<div className="flex items-center">
<h1 className="text-xl font-semibold">Dashboard</h1>
</div>
<div className="flex items-center">
<SignOutButton
className="rounded bg-red-600 px-4 py-2 text-white hover:bg-red-700"
onSignOutSuccess={() => {
console.log('Đăng xuất thành công');
}}
onSignOutError={(error) => {
console.error('Lỗi đăng xuất:', error);
}}
>
Đăng xuất
</SignOutButton>
</div>
</div>
</div>
</nav>
{/* Nội dung dashboard */}
</div>
);
}useUser Hook - Quản lý trạng thái người dùng
'use client';
import { useUser } from '@cabin-id/nextjs';
export default function ProfilePage() {
const { user, isLoaded, isSignedIn } = 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="max-w-4xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-4">Thông tin cá nhân</h1>
<div className="bg-white shadow rounded-lg p-6">
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700">
ID người dùng
</label>
<p className="mt-1 text-sm text-gray-900">{user.id}</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Email
</label>
<p className="mt-1 text-sm text-gray-900">{user.primaryEmailAddress?.emailAddress}</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Số điện thoại
</label>
<p className="mt-1 text-sm text-gray-900">{user.primaryPhoneNumber?.phoneNumber}</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Ngày tạo
</label>
<p className="mt-1 text-sm text-gray-900">{user.createdAt}</p>
</div>
</div>
</div>
</div>
);
}🌐 Tích hợp cho Website Bên ngoài
Ví dụ: Tích hợp vào website hiện có
// components/AuthButton.tsx
'use client';
import { useUser, SignInButton, SignOutButton } from '@cabin-id/nextjs';
export default function AuthButton() {
const { isSignedIn, user, isLoaded } = useUser();
if (!isLoaded) {
return (
<div className="animate-pulse">
<div className="h-10 w-24 bg-gray-200 rounded"></div>
</div>
);
}
if (!isSignedIn) {
return <SignInButton />;
}
return (
<div className="flex items-center space-x-4">
<span className="text-sm text-gray-700">
Xin chào, {user.firstName || 'Người dùng'}
</span>
<SignOutButton
className="rounded bg-red-600 px-4 py-2 text-white hover:bg-red-700"
onSignOutSuccess={() => {
// Có thể thêm logic tùy chỉnh sau khi đăng xuất
window.location.reload();
}}
>
Đăng xuất
</SignOutButton>
</div>
);
}Bảo vệ các trang riêng tư
// components/ProtectedRoute.tsx
'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}</>;
}Sử dụng:
// app/dashboard/page.tsx
import ProtectedRoute from '@/components/ProtectedRoute';
export default function DashboardPage() {
return (
<ProtectedRoute>
<div className="p-6">
<h1 className="text-2xl font-bold">Dashboard</h1>
<p>Đây là trang chỉ dành cho người dùng đã đăng nhập.</p>
</div>
</ProtectedRoute>
);
}🛠️ Server-side Functions
getCurrentUser - Lấy thông tin người dùng từ server
// app/api/profile/route.ts
import { getCurrentUser } from '@cabin-id/nextjs/server';
import { NextResponse } from 'next/server';
export async function GET() {
try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json(
{ error: 'Chưa đăng nhập' },
{ status: 401 }
);
}
return NextResponse.json({ user });
} catch (error) {
return NextResponse.json(
{ error: 'Lỗi server' },
{ status: 500 }
);
}
}protect - Bảo vệ API routes
// app/api/admin/route.ts
import { protect } from '@cabin-id/nextjs/server';
import { NextResponse } from 'next/server';
export const GET = protect(async (req, { user }) => {
// Chỉ người dùng đã đăng nhập mới có thể truy cập
return NextResponse.json({
message: `Xin chào ${user.firstName}!`,
adminData: 'Dữ liệu quan trọng'
});
});🎨 Tùy chỉnh CSS
Tùy chỉnh SignInButton
import { SignInButton } from '@cabin-id/nextjs';
export default function CustomSignIn() {
return (
<SignInButton
className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
/>
);
}Tùy chỉnh SignOutButton
import { SignOutButton } from '@cabin-id/nextjs';
export default function CustomSignOut() {
return (
<SignOutButton
className="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
showLoading={true}
loadingText="Đang đăng xuất..."
onSignOutStart={() => console.log('Bắt đầu đăng xuất')}
onSignOutSuccess={() => console.log('Đăng xuất thành công')}
onSignOutError={(error) => console.error('Lỗi đăng xuất:', error)}
>
<svg className="mr-2 -ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
Đăng xuất
</SignOutButton>
);
}📝 Ví dụ hoàn chỉnh - Website Thương mại điện tử
// app/layout.tsx
'use client';
import { CabinIdProvider } from '@cabin-id/nextjs';
import './globals.css';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="vi">
<body>
<CabinIdProvider>
<Header />
<main>{children}</main>
<Footer />
</CabinIdProvider>
</body>
</html>
);
}
// components/Header.tsx
'use client';
import { useUser, SignInButton, SignOutButton } from '@cabin-id/nextjs';
import Link from 'next/link';
function Header() {
const { isSignedIn, user, isLoaded } = useUser();
return (
<header className="bg-white shadow">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex items-center">
<Link href="/" className="text-xl font-bold text-gray-900">
Cửa hàng của tôi
</Link>
</div>
<nav className="hidden md:flex items-center space-x-8">
<Link href="/products" className="text-gray-500 hover:text-gray-900">
Sản phẩm
</Link>
<Link href="/about" className="text-gray-500 hover:text-gray-900">
Về chúng tôi
</Link>
<Link href="/contact" className="text-gray-500 hover:text-gray-900">
Liên hệ
</Link>
</nav>
<div className="flex items-center space-x-4">
{!isLoaded ? (
<div className="animate-pulse h-10 w-24 bg-gray-200 rounded"></div>
) : isSignedIn ? (
<div className="flex items-center space-x-4">
<Link
href="/profile"
className="text-gray-700 hover:text-gray-900"
>
Xin chào, {user.firstName}
</Link>
<SignOutButton
className="rounded bg-red-600 px-4 py-2 text-white hover:bg-red-700"
>
Đăng xuất
</SignOutButton>
</div>
) : (
<SignInButton />
)}
</div>
</div>
</div>
</header>
);
}🔧 Cấu hình nâng cao
Tùy chỉnh Domain
// middleware.ts
import { authMiddleware } from '@cabin-id/nextjs/server';
export default authMiddleware({
// Domain tùy chỉnh
domain: 'myapp.cabinid.dev',
// Các route không cần xác thực
publicRoutes: ['/', '/products', '/about', '/contact'],
// Các route chỉ dành cho admin
adminRoutes: ['/admin'],
// Xử lý sau khi xác thực
afterAuth(auth, req) {
const { userId, isPublicRoute, isAdminRoute } = auth;
// Chuyển hướng người dùng chưa đăng nhập
if (!userId && !isPublicRoute) {
return Response.redirect(new URL('/auth', req.url));
}
// Kiểm tra quyền admin (cần implement logic kiểm tra)
if (isAdminRoute && !isAdmin(userId)) {
return Response.redirect(new URL('/unauthorized', req.url));
}
},
});
function isAdmin(userId: string): boolean {
// Logic kiểm tra quyền admin
return false;
}📚 API Reference
Components
SignInButton: Nút đăng nhập tự động chuyển hướng đến CabinIDSignOutButton: Nút đăng xuất với xử lý callbackCabinIdProvider: Provider chính để quản lý trạng thái xác thực
Hooks
useUser(): Hook để lấy thông tin người dùng hiện tại
Server Functions
getCurrentUser(): Lấy thông tin người dùng từ serverprotect(): Bảo vệ API routesauthMiddleware(): Middleware xác thực cho Next.js
🐛 Xử lý lỗi thường gặp
Lỗi: "CabinIdProvider not found"
Đảm bảo bạn đã bọc ứng dụng với CabinIdProvider:
// ✅ Đúng
<CabinIdProvider>
<YourApp />
</CabinIdProvider>
// ❌ Sai - thiếu provider
<YourApp />Lỗi: Biến môi trường không được tìm thấy
Kiểm tra file .env.local và đảm bảo tên biến chính xác:
# ✅ Đúng
NEXT_PUBLIC_CABIN_ID_PUBLISH_KEY=cabin_pk_...
CABIN_ID_SECRET_KEY=cabin_sk_...
# ❌ Sai - thiếu NEXT_PUBLIC_
CABIN_ID_PUBLISH_KEY=cabin_pk_...🔗 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.
