چگونه Auth را با لوسیا به برنامه React/Next.js خود اضافه کنید – راهنمای گام به گام

اگرچه احراز هویت یکی از متداولترین ویژگیهای برنامه وب است، اما راههای مختلفی برای انجام آن وجود دارد که آن را به یک کار بسیار بیاهمیت تبدیل میکند. در این پست، من تجربه شخصی خود را از استفاده از لوسیا به اشتراک میگذارم – یک کتابخانه احراز هویت مدرن، چارچوب-آگنوستیک که در ماههای اخیر مورد علاقه زیادی از سوی جامعه قرار گرفته است.
ابتدا، من نشان خواهم داد که چگونه می توانید آن را در برنامه Next.js خود از طریق راهنمای گام به گامی که می توانید دنبال کنید، پیاده سازی کنید. این به مقدار مناسبی از کد و پیکربندی نیاز دارد، اما این فرآیند به خودی خود کاملاً ساده است.
ثانیاً، خواهیم دید که چگونه با Wasp تنها در چند خط کد به همین نتیجه برسیم. Wasp یک فریمورک کامل پشتهای با باتریها برای React & Node.js است که از Lucia در زیر هود برای اجرای احراز هویت استفاده میکند. این به طور کامل بر روی زیرساخت شما اجرا می شود و 100٪ منبع باز و رایگان است.
چرا لوسیا؟
هنگامی که نوبت به افزودن احراز هویت به برنامه های شما می رسد، چندین راه حل محبوب وجود دارد. به عنوان مثال، Clerk یک سرویس پولی ارائه می دهد، در حالی که NextAuth.js یک راه حل منبع باز در کنار لوسیا است که اخیراً بسیار محبوب شده است.
این ابزارها ویژگیهای قوی را ارائه میکنند، اما تعهد به خدمات شخص ثالث – که نه تنها لایه دیگری از پیچیدگی را اضافه میکند، بلکه دارای سطوح پولی است که باید مراقب آنها باشید – ممکن است برای یک پروژه کوچک هزینهای بیش از حد باشد. راه حل های داخلی همه چیز را متمرکز نگه می دارند، اما اجرای برخی از ویژگی های ذکر شده را به یک توسعه دهنده واگذار می کنند.
در مورد ما، لوسیا ثابت کرده است که میانه کاملی است – این یک سرویس شخص ثالث نیست و به زیرساخت اختصاصی نیاز ندارد، اما همچنین پایه بسیار محکمی را فراهم می کند که ساخت آن آسان است.
اکنون، بیایید به یک راهنمای گام به گام در مورد نحوه اجرای احراز هویت خود با Next.js و Lucia بپردازیم.
مرحله 1: راه اندازی Next.js
ابتدا یک پروژه Next.js جدید ایجاد کنید:
npx create-next-app@latest my-nextjs-app
cd my-nextjs-app
npm install
مرحله 2: لوسیا را نصب کنید
بعد، لوسیا را نصب کنید:
npm install lucia
مرحله 3: احراز هویت را تنظیم کنید
ایجاد کنید auth
در پروژه خود فایل کنید و فایل های لازم را اضافه کنید تا لوسیا وارد و مقداردهی اولیه شود. این یک دسته آداپتور برای پایگاه داده های مختلف دارد و می توانید همه آنها را اینجا بررسی کنید. در این مثال، ما از SQLite استفاده می کنیم:
// lib/auth.ts
import { Lucia } from "lucia";
import { BetterSqlite3Adapter } from "@lucia-auth/adapter-sqlite";
const adapter = new BetterSQLite3Adapter(db); // your adapter
export const lucia = new Lucia(adapter, {
sessionCookie: {
// this sets cookies with super long expiration
// since Next.js doesn't allow Lucia to extend cookie expiration when rendering pages
expires: false,
attributes: {
// set to `true` when using HTTPS
secure: process.env.NODE_ENV === "production"
}
}
});
// To get some good Typescript support, add this!
declare module "lucia" {
interface Register {
Lucia: typeof lucia;
}
}
مرحله 4: افزودن کاربر به DB
بیایید یک فایل پایگاه داده اضافه کنیم تا در حال حاضر شامل طرحواره های ما باشد:
// lib/db.ts
import sqlite from "better-sqlite3";
export const db = sqlite("main.db");
db.exec(`CREATE TABLE IF NOT EXISTS user (
id TEXT NOT NULL PRIMARY KEY,
github_id INTEGER UNIQUE,
username TEXT NOT NULL
)`);
db.exec(`CREATE TABLE IF NOT EXISTS session (
id TEXT NOT NULL PRIMARY KEY,
expires_at INTEGER NOT NULL,
user_id TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES user(id)
)`);
export interface DatabaseUser {
id: string;
username: string;
github_id: number;
}
مرحله 5: ورود و ثبت نام را پیاده سازی کنید
برای تحقق این امر، ابتدا باید یک برنامه GitHub OAuth ایجاد کنیم. این نسبتاً ساده است، شما آن را ایجاد میکنید، آدرسهای URL و ENV لازم را به برنامه خود اضافه میکنید و آماده هستید. برای بررسی نحوه انجام این کار می توانید اسناد GitHub را دنبال کنید.
//.env.local
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
پس از آن، اضافه کردن قابلیت های ورود و ثبت نام به صفحات شماست، بنابراین، بیایید این کار را سریع انجام دهیم:
// login/page.tsx
import { validateRequest } from "@/lib/auth";
import { redirect } from "next/navigation";
export default async function Page() {
const { user } = await validateRequest();
if (user) {
return redirect("/");
}
return (
h1>Sign inh1>
a href="/login/github">Sign in with GitHuba>
>
);
}
پس از افزودن صفحه، باید تغییر مسیر ورود به گیت هاب و تماسی که قرار است فراخوانی شود را نیز اضافه کنیم. اجازه دهید ابتدا تغییر مسیر ورود به سیستم را با URL مجوز اضافه کنیم:
// login/github/route.ts
import { generateState } from "arctic";
import { github } from "../../../lib/auth";
import { cookies } from "next/headers";
export async function GET(): PromiseResponse> {
const state = generateState();
const url = await github.createAuthorizationURL(state);
cookies().set("github_oauth_state", state, {
path: "/",
secure: process.env.NODE_ENV === "production",
httpOnly: true,
maxAge: 60 * 10,
sameSite: "lax"
});
return Response.redirect(url);
}
و در نهایت، callback (که در واقع همان چیزی است که ما در GitHub OAuth اضافه می کنیم):
// login/github/callback/route.ts
import { github, lucia } from "@/lib/auth";
import { db } from "@/lib/db";
import { cookies } from "next/headers";
import { OAuth2RequestError } from "arctic";
import { generateId } from "lucia";
import type { DatabaseUser } from "@/lib/db";
export async function GET(request: Request): PromiseResponse> {
const url = new URL(request.url);
const code = url.searchParams.get("code");
const state = url.searchParams.get("state");
const storedState = cookies().get("github_oauth_state")?.value ?? null;
if (!code || !state || !storedState || state !== storedState) {
return new Response(null, {
status: 400
});
}
try {
const tokens = await github.validateAuthorizationCode(code);
const githubUserResponse = await fetch("https://api.github.com/user", {
headers: {
Authorization: `Bearer ${tokens.accessToken}`
}
});
const githubUser: GitHubUser = await githubUserResponse.json();
const existingUser = db.prepare("SELECT * FROM user WHERE github_id = ?").get(githubUser.id) as
| DatabaseUser
| undefined;
if (existingUser) {
const session = await lucia.createSession(existingUser.id, {});
const sessionCookie = lucia.createSessionCookie(session.id);
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
return new Response(null, {
status: 302,
headers: {
Location: "/"
}
});
}
const userId = generateId(15);
db.prepare("INSERT INTO user (id, github_id, username) VALUES (?, ?, ?)").run(
userId,
githubUser.id,
githubUser.login
);
const session = await lucia.createSession(userId, {});
const sessionCookie = lucia.createSessionCookie(session.id);
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
return new Response(null, {
status: 302,
headers: {
Location: "/"
}
});
} catch (e) {
if (e instanceof OAuth2RequestError && e.message === "bad_verification_code") {
// invalid code
return new Response(null, {
status: 400
});
}
return new Response(null, {
status: 500
});
}
}
interface GitHubUser {
id: string;
login: string;
}
نکته مهم دیگر در اینجا این است که در حال حاضر، ما به GitHub OAuth می رویم، اما، به طور کلی، این کتابخانه ها حاوی دسته ای از ارائه دهندگان ورود به سیستم مختلف (از جمله نام کاربری و رمز عبور ساده) هستند، بنابراین معمولاً اگر می خواهید انتخاب کنید. ارائه دهندگان دیگر را اضافه کنید
// lib/auth.ts
import { Lucia } from "lucia";
import { BetterSqlite3Adapter } from "@lucia-auth/adapter-sqlite";
import { db } from "./db";
import { cookies } from "next/headers";
import { cache } from "react";
import { GitHub } from "arctic";
import type { Session, User } from "lucia";
import type { DatabaseUser } from "./db";
// these two lines here might be important if you have node.js 18 or lower.
// you can check Lucia's documentation in more detail if that's the case
// (https://lucia-auth.com/getting-started/nextjs-app#polyfill)
// import { webcrypto } from "crypto";
// globalThis.crypto = webcrypto as Crypto;
const adapter = new BetterSqlite3Adapter(db, {
user: "user",
session: "session"
});
export const lucia = new Lucia(adapter, {
sessionCookie: {
attributes: {
secure: process.env.NODE_ENV === "production"
}
},
getUserAttributes: (attributes) => {
return {
githubId: attributes.github_id,
username: attributes.username
};
}
});
declare module "lucia" {
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: OmitDatabaseUser, "id">;
}
}
export const validateRequest = cache(
async (): Promise{ user: User; session: Session } | { user: null; session: null }> => {
const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null;
if (!sessionId) {
return {
user: null,
session: null
};
}
const result = await lucia.validateSession(sessionId);
// next.js throws when you attempt to set cookie when rendering page
try {
if (result.session && result.session.fresh) {
const sessionCookie = lucia.createSessionCookie(result.session.id);
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}
if (!result.session) {
const sessionCookie = lucia.createBlankSessionCookie();
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}
} catch {}
return result;
}
);
export const github = new GitHub(process.env.GITHUB_CLIENT_ID!, process.env.GITHUB_CLIENT_SECRET!);
مرحله 6: محافظت از مسیرها
پس از افزودن همه این موارد برای اینکه ورود به سیستم به درستی کار کند، فقط باید مطمئن شویم که مسیرها با بررسی وضعیت احراز هویت محافظت می شوند – در این مورد، این صفحه ساده ای است که نام کاربری، شناسه و دکمه را در صورت ورود به سیستم نشان می دهد و تغییر مسیر می دهد. به /login که در آن کاربر از طریق فرمی ورود به سیستم بالا را تکمیل می کند.
import { lucia, validateRequest } from "@/lib/auth";
import { redirect } from "next/navigation";
import { cookies } from "next/headers";
export default async function Page() {
const { user } = await validateRequest();
if (!user) {
return redirect("/login");
}
return (
h1>Hi, {user.username}!h1>
p>Your user ID is {user.id}.p>
form action={logout}>
button>Sign outbutton>
form>
>
);
}
async function logout(): PromiseActionResult> {
"use server";
const { session } = await validateRequest();
if (!session) {
return {
error: "Unauthorized"
};
}
await lucia.invalidateSession(session.id);
const sessionCookie = lucia.createBlankSessionCookie();
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
return redirect("/login");
}
interface ActionResult {
error: string | null;
}
تکه کیک، اینطور نیست؟ خوب، نه واقعا.
بیایید مرور کنیم که چه مراحلی برای تحقق این امر ضروری بود:
- برنامه خود را تنظیم کنید
- لوسیا را اضافه کنید.
- احراز هویت را تنظیم کنید.
- افزودن کاربر به DB
- اعتبار GitHub OAuth را دریافت کنید و متغیرهای محیط خود را پیکربندی کنید.
- چند توابع کاربردی ایجاد کنید.
- مسیرهای ورود و ثبت نام را با اجزای سفارشی اضافه کنید.
- در نهایت یک مسیر محافظت شده ایجاد کنید.
صادقانه بگویم، زمانی که سعی می کنید چیز جالبی ایجاد کنید سریع، تکرار این مراحل و اشکال زدایی چند مشکل منطقی اینجا و آنجا که همیشه رخ می دهد می تواند کمی ناامید کننده باشد. به زودی، نگاهی به رویکرد Wasp برای حل همان مشکل خواهیم داشت و میتوانیم مقایسه کنیم که فرآیند اجرای تأیید اعتبار Wasp چقدر آسانتر است.
در صورتی که می خواهید کل کد این قسمت را بررسی کنید، لوسیا یک مخزن نمونه دارد (که منبع اکثر کدهای نشان داده شده است)، بنابراین، در صورت تمایل می توانید آن را بررسی کنید.
پیاده سازی Wasp
حالا بیایید به این موضوع بپردازیم که چگونه می توانیم با Wasp به همان چیزها برسیم. اگرچه هنوز از لوسیا در پسزمینه استفاده میکند، Wasp تمام کارهای سنگین را برای شما انجام میدهد و این روند را بسیار سریعتر و سادهتر میکند. بیایید تجربه توسعه دهنده را برای خودمان بررسی کنیم.
قبل از اینکه به آن بپردازیم، اگر بیشتر یک یادگیرنده بصری هستید، در اینجا یک ویدیوی 1 دقیقه ای وجود دارد که تأیید اعتبار با Wasp را به نمایش می گذارد.
https://www.youtube.com/watch?v=Qiro77q-ulI
همانطور که در ویدئو مشاهده می شود، Wasp چارچوبی برای ساخت برنامه ها با مزایای استفاده از یک فایل پیکربندی برای تسهیل توسعه است. بسیاری از کارهای تکراری را انجام می دهد و به شما امکان می دهد بر روی ایجاد ویژگی های منحصر به فرد تمرکز کنید. در این آموزش، ما همچنین درباره فایل پیکربندی Wasp بیشتر می آموزیم و خواهیم دید که چگونه تنظیم احراز هویت را ساده تر می کند.
مرحله 1: یک پروژه Wasp ایجاد کنید
curl -sSL https://get.wasp-lang.dev/installer.sh | sh
wasp new my-wasp-app
cd my-wasp-app
مرحله 2: موجودیت کاربر را به DB خود اضافه کنید
به همین سادگی تعریف کردن app.auth.userEntity
موجودیت در schema.prisma
فایل و اجرای برخی از مهاجرت ها:
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
// Add your own fields below
// ...
}
مرحله 3: احراز هویت را تعریف کنید
در پیکربندی اصلی Wasp، ارائهدهنده احراز هویتی را که میخواهید برای برنامه خود اضافه کنید
//main.wasp
app myApp {
wasp: {
version: "^0.14.0"
},
title: "My App",
auth: {
userEntity: User,
methods: {
// 2. Enable Github Auth
gitHub: {}
},
onAuthFailedRedirectTo: "/login"
},
}
و پس از آن، فقط در ترمینال خود اجرا کنید:
wasp db migrate-dev
مرحله 4: اعتبارنامه و برنامه GitHub OAuth خود را اجرا کنید
این بخش برای هر دو چارچوب مشابه است، برای انجام این کار می توانید مستنداتی را که GitHub در اینجا ارائه می دهد دنبال کنید: ایجاد یک برنامه OAuth – GitHub Docs. برای برنامه Wasp، URL های پاسخ به تماس عبارتند از:
- در حین توسعه:
http://localhost:3001/auth/github/callback
- پس از استقرار:
https://your-server-url.com/auth/github/callback
پس از آن، اسرار خود را دریافت کنید و آن را به فایل env اضافه کنید:
//.env.server
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
مرحله 5: مسیرها و صفحات را اضافه کنید
اکنون، اجازه دهید به سادگی مقداری مسیریابی و صفحه لازم برای ورود به سیستم را اضافه کنیم – این فرآیند بسیار ساده تر است زیرا Wasp فرم های ورود و ثبت نام را از پیش ساخته است، ما به سادگی می توانیم آنها را مستقیماً اضافه کنیم:
// main.wasp
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
component: import { SignupPage } from "@src/SignupPage"
}
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { LoginPage } from "@src/LoginPage"
}
// src/LoginPage.jsx
import { Link } from 'react-router-dom'
import { LoginForm } from 'wasp/client/auth'
export const LoginPage = () => {
return (
div style={{ maxWidth: '400px', margin: '0 auto' }}>
LoginForm />
br />
span>
I don't have an account yet (Link to="/signup">go to signupLink>).
span>
div>
)
}
// src/SignupPage.jsx
import { Link } from 'react-router-dom'
import { SignupForm } from 'wasp/client/auth'
export const SignupPage = () => {
return (
div style={{ maxWidth: '400px', margin: '0 auto' }}>
SignupForm />
br />
span>
I already have an account (Link to="/login">go to loginLink>).
span>
div>
)
}
و در نهایت، برای محافظت از مسیرها، به سادگی تغییر آن است main.wasp
اضافه کردن authRequired: true
، بنابراین، به سادگی می توانیم آن را به این صورت اضافه کنیم:
// main.wasp
page MainPage {
component: import Main from "@src/pages/Main",
authRequired: true
}
اگر میخواهید این مثال را عمیقتر بررسی کنید، به راحتی این مخزن را در اینجا بررسی کنید: wasp/examples/todo-typescript در زمان انتشار · wasp-lang/wasp (github.com).
مکان عالی دیگر برای بررسی اسناد آنها است که می توانید در اینجا پیدا کنید. این بیشتر مواردی را که در اینجا گفتم، و حتی بیشتر را پوشش میدهد (به عنوان مثال، قلابهای جدید و فوقالعادهای که با Wasp v0.14 ارائه شدهاند)
خیلی راحت تر، اینطور نیست؟ بیایید مراحلی را که برای رسیدن به اینجا انجام دادیم مرور کنیم:
- پروژه را راه اندازی کنید.
- موجودیت User را به پایگاه داده اضافه کنید.
- احراز هویت را در پیکربندی اصلی Wasp تعریف کنید.
- اعتبار GitHub OAuth را دریافت کنید و متغیرهای محیط خود را پیکربندی کنید.
- مسیرها و صفحات را برای ورود و ثبت نام با اجزای از پیش ساخته شده و آسان برای استفاده اضافه کنید.
- از مسیرها با مشخص کردن محافظت کنید
authRequired
در پیکربندی شما
سفارشی کردن Wasp Auth
اگر به کنترل و سفارشیسازی بیشتر روی جریان احراز هویت نیاز دارید، Wasp قلابهای Auth را ارائه میکند که به شما امکان میدهد تجربه را مطابق با نیازهای خاص برنامه خود تنظیم کنید. این قلابها شما را قادر میسازند تا کد سفارشی را در طول مراحل مختلف فرآیند احراز هویت اجرا کنید و اطمینان حاصل کنید که میتوانید هر رفتار سفارشی مورد نیاز را اجرا کنید.
برای اطلاعات دقیق تر در مورد استفاده از قلاب های Auth با Wasp، به مستندات Wasp مراجعه کنید.
بخش پاداش: افزودن ورود به ایمیل / رمز عبور با Wasp و سفارشی کردن Auth
حالا بیایید تصور کنیم که میخواهیم احراز هویت ایمیل و رمز عبور را اضافه کنیم – با تمام ویژگیهای معمولی که انتظار داریم از این روش ورود به سیستم (مانند بازنشانی رمز عبور، تأیید ایمیل، و غیره) پیروی کند.
با Wasp، تنها کاری که ما باید انجام دهیم این است که چند خط به فایل main.wasp خود اضافه کنیم، بنابراین، بهروزرسانی پیکربندی Wasp برای احراز هویت ایمیل/رمز عبور، باعث میشود که مستقیماً کار کند!
Wasp بقیه کارها را انجام می دهد، همچنین اجزای رابط کاربری را به روز می کند و جریان احراز هویت روان و ایمن را تضمین می کند.
//main.wasp
app myApp {
wasp: {
version: "^0.14.0"
},
title: "My App",
auth: {
// 1. Specify the User entity
userEntity: User,
methods: {
// 2. Enable Github Auth
gitHub: {},
email: {
// 3. Specify the email from field
fromField: {
name: "My App Postman",
email: "hello@itsme.com"
},
// 4. Specify the email verification and password reset options
emailVerification: {
clientRoute: EmailVerificationRoute, //this route/page should be created
},
passwordReset: {
clientRoute: PasswordResetRoute, //this route/page should be created
},
// Add an emailSender -- Dummy just logs to console for dev purposes
// but there are a ton of supported providers :D
emailSender: {
provider: Dummy,
},
},
},
onAuthFailedRedirectTo: "/login"
},
}
پیادهسازی این مورد در Next.js با لوسیا کار بسیار بیشتری را میطلبد، که شامل مجموعهای از موارد مختلف از ارسال ایمیلها، تولید نشانههای تأیید و موارد دیگر میشود. آنها به این موضوع در اینجا اشاره میکنند، اما دوباره، Wasp's Auth کل فرآیند را آسانتر میکند، و مجموعهای از پیچیدگیها را برای ما مدیریت میکند، در حالی که مجموعهای از مؤلفههای رابط کاربری دیگر، آماده استفاده، برای سهولت جزئیات UI (مثلاً VerifyEmailForm
، ForgotPasswordForm
و ResetPasswordForm
).
نکته اصلی در اینجا تفاوت در زمان و تجربه توسعه دهندگان به منظور اجرای سناریوهای مشابه است. برای پروژه Next.js با لوسیا، اگر به تنهایی پیش بروید، حداقل چند ساعت را صرف اجرای همه چیز خواهید کرد. همین تجربه با Wasp بیش از 1 ساعت طول نمی کشد. با بقیه زمان ها چه کنیم؟ موارد مهمی که کسب و کار خاص شما نیاز دارد را پیاده سازی کنید!
آیا می توانید حمایت خود را به ما نشان دهید؟
آیا به محتوای بیشتری مانند این علاقه دارید؟ برای خبرنامه ما ثبت نام کنید و در GitHub به ما یک ستاره بدهید! برای ادامه پروژه هایمان به حمایت شما نیاز داریم 😀
نتیجه گیری
فکر میکنم اگر توسعهدهندهای هستید که میخواهید کارها را انجام دهید، احتمالاً به تفاوت قابل توجهی در سطوح پیچیدگی هر دوی این پیادهسازیها اشاره کردهاید.
Wasp با کاهش دیگ بخار و انتزاع وظایف تکراری، به توسعه دهندگان این امکان را می دهد که به جای گرفتار شدن در جزئیات احراز هویت، بیشتر بر روی ساخت ویژگی های منحصر به فرد تمرکز کنند. این می تواند به ویژه برای تیم های کوچک یا توسعه دهندگان فردی که قصد دارند محصولات را به سرعت راه اندازی کنند مفید باشد.
البته، به طور کلی وقتی از انتزاع صحبت می کنیم، همیشه با از دست دادن ظرافت اجرای شخصی تر همراه است. در این مورد، Wasp مجموعهای از موارد را برای پیادهسازی در اطراف شما فراهم میکند و از لوسیا در پسزمینه استفاده میکند، بنابراین سناریویی که در آن عدم تطابق اجرای محتوا وجود دارد، بسیار غیرممکن است اتفاق بیفتد.
به طور خلاصه، در حالی که اجرای احراز هویت خود با Next.js و Lucia کنترل و سفارشی سازی کامل را فراهم می کند، می تواند پیچیده و وقت گیر باشد. از سوی دیگر، استفاده از راه حلی مانند Wasp فرآیند را ساده می کند، طول کد را کاهش می دهد و سرعت توسعه را افزایش می دهد.