الحفاظ على الجلسة مع بيانات الأعتماد (Credentials) و OAuth
Mohanad Alrwaihy
April 29, 2023
997
4
كيفية الحفاظ على جلسة المستخدم عند أستخدام بيانات الأعتماد و OAuth في نفس الوقت
7 دقائق للقراءة
إحدى الفوائد الرئيسية لاستخدام NextAuth هي المرونة للاختيار بين أنواع مختلفة من مقدمي الخدمات:
- موفرو OAuth - ( GitHub و Twitter و Google وما إلى ذلك … ).
- موفر OAuth مخصص.
- استخدام البريد الإلكتروني - الروابط السحرية
- استخدام بيانات أعتماد (Credentials) - اسم المستخدم وكلمة المرور أو أي بيانات اعتماد عشوائية.
ولكن هناك مشكلة في الحفاظ في الجلسة عند محاولة دمج بيانات الأعتماد و موفر OAuth أثناء استخدام قاعدة بيانات.
بشكل افتراضي ، تم الحد من أستخدام بيانات الأعتماد في NextAuth بسبب أستخدام كلمات المرور و مخاوفها الأمنية.
نصيحة
لا يمكن استخدام موفر بيانات الأعتماد إلا إذا تم تمكين رمز ويب JSON للجلسات ( لا يمكن الحفاظ على الجلسة في قاعدة البيانات 🥲 )
اقرأ المزيد حول موفر بيانات الأعتماد من الصفحة الرسمية NextAuth.
استراتيجيات الجلسة ( JWTs أو جلسة قاعدة البيانات ) 🤔
في منشور حديث تحدثت عن استراتيجيات الجلسة والاختلاف بين JWTs و جلسة قاعدة البيانات. انقر هنا لقراءة المزيد عن الموضوع.
الحل 😇
أثناء البحث عن حل لهذه المشكلة ، عثرت على هذا المشكلة في GitHub وكل الشكر ل@nneko للعثور على هذا الحل والحديث عنه مشكلة.
تحقق من هذا المنشور لحل هذه المشكلة.
سوف نقوم بأستخدام الأفكار المقترحة لحل المشكلة وتقديم أكبر قدر ممكن من التفسير في هذا المنشور 😉
الإعداد
سأضيف موفر بيانات الاعتماد إلى المنشور الأخير الذي طرحته حول كيفية إنشاء مشروع مدونة بأستخدام NextAuth و Prisma (PostgreSQL) . وحل مشكلة الحفاظ على الجلسة على المشروع.
المتطلبات
ستكون جميع التغييرات ضمن ملف [...nextauth].ts لكن هناك بعض الحزم علينا تحميلها:
- bcrypt -
npm install bcrypt - crypto - هذه الحزمة لا تحتاج الى تحميل
- cookie-next -
npm install cookies-next
جميع الImports التي نحتاجها من الحزم المذكورة والواردات المفيدة الأخرى:
TSXimport NextAuth, { NextAuthOptions, Session, getServerSession } from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
import CredentialsProvider from 'next-auth/providers/credentials'
import { PrismaClient, User } from '@prisma/client'
import { PrismaAdapter } from '@next-auth/prisma-adapter'
import { AdapterUser } from 'next-auth/adapters'
import {
NextApiRequest,
NextApiResponse,
} from 'next'
import bcrypt from 'bcrypt'
import { randomUUID } from 'crypto'
import { getCookie, setCookie } from 'cookies-next'
import { JWTDecodeParams, JWTEncodeParams, decode, encode } from 'next-auth/jwt'
type Credentials = {
username: string
email: string
password: string
confirm: string
}لنتحدث عن كل واحد ونشرح لماذا نحتاجها وكيفية استخدامها.
bcrypt
سنستخدم هذه الحزمة لتشفير كلمة مرور المستخدم وحفظها في قاعدة البيانات ومقارنة كلمة مرور المستخدم المدخلة مع النسخة المشفرة المحفوظة في قاعدة البيانات لتأكيد تسجيل دخول المستخدم.
دالة generateHash
تستخدم هذا الدالة لإنشاء كلمة مرور مشفرة.
TSXfunction generateHash(password: string): string {
const saltRounds = 10
return bcrypt.hashSync(password, saltRounds)
}compareHash
تُستخدم هذه الوظيفة لمقارنة كلمة المرور المدخلة مع كلمة السر المشفرة التي تم إنشاؤها وإرجاع true أو false.
TSXfunction compareHash(password: string): boolean {
return bcrypt.compareSync(password, generateHash(password))
}دالة randomUUID
تُستخدم لإنشاء Session Token لتسجيل الدخول إلى بيانات الاعتماد نظرًا لأن NextAuth لا ينشئ رمز Session عند استخدام موفر بيانات الأعتماد (Credentials Provider).
TSXconst sessionToken = randomUUID()
const sessionMaxAge = 60 * 60 * 24 * 30
const sessionExpiry = new Date(Date.now() + sessionMaxAge * 1000)دالة getCookie و setCookie
دالة سهلة الاستخدام لتعيين ملف تعريف الارتباط لـ next-auth.session-token ب الSession Token المنشى بأستخدام الفنكشن ranfromUUId.
TSXsetCookie(`next-auth.session-token`, sessionToken, {
expires: sessionExpiry,
req: req,
res: res,
})التعديلات على الNextAuth API
دعونا نلقي نظرة على ملف [...nextauth].ts الحالي:
pages/api/auth/[...nextauth].tsTSX/* pages/api/auth/[...nextauth].ts */
const prisma = new PrismaClient()
export const authOptions = {
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID || '',
clientSecret: process.env.GOOGLE_SECRET || '',
}),
],
callbacks: {
async session({ session, user }: { session: Session; user: AdapterUser }) {
session.user.id = user.id
return session
},
},
}
export default NextAuth(authOptions)من أجل الأستمرار ، علينا أولاً إجراء بعض التغييرات في المشروع واستخدام Advanced Initialization من NextAuth API Route من أجل الوصول إلى req و res في ال authOptions:
TSX/* pages/api/auth/[...nextauth].ts */
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
return await NextAuth(req, res, authOptions(req, res))
}
export const authOptions = (req: NextApiRequest, res: NextApiResponse): NextAuthOptions => {
const prisma = new PrismaClient()
return {
adapter: PrismaAdapter(prisma),
providers: [...],
callbacks: {...},
jwt: {}
}
}
export default NextAuth(authOptions)
نصيحة
بعد هذه الخطوة ، واجهت بعض المشاكل عند تسجيل الدخول إلى التطبيق ولكني قمت بمسح ملفات تعريف الارتباط وانحلت المشكلة.
إضافة موفر بيانات الاعتماد
سأضيف مزود بيانات اعتماد بسيط مع:
- اسم المستخدم
- البريد الإلكتروني
- كلمة المرور
- تأكيد كلمة المرور
تحقق من موفر بيانات اعتماد NextAuth الصفحة إذا كنت تريد معرفة كيفية تنفيذ خيارات بيانات الاعتماد أو التوسع فيها.
pages/api/auth/[...nextauth].tsTSX/* pages/api/auth/[...nextauth].ts */
import CredentialsProvider from 'next-auth/providers/credentials'
export const authOptions = (req: NextApiRequest, res: NextApiResponse) => {
...
return {
providers: [
...
CredentialsProvider({
name: 'credentials',
credentials: {
username: {
label: 'Username',
type: 'text',
placeholder: 'John Doe',
},
email: {
label: 'Email',
type: 'email',
placeholder: 'john@doe.com',
},
password: {
label: 'Password',
type: 'password',
placeholder: '**********',
},
confirm: {
label: 'Confirm',
type: 'password',
placeholder: '**********',
},
},
async authorize(credentials, req): Promise<any> {},
}),
],
...
}
}
export default NextAuth(authOptions)الدالة authorize غير المتزامنة بعد تعريفات موفر بيانات الاعتماد هي المكان الذي سيحدث فيه التنفيذ سواء:
- إنشاء مستخدم جديد.
- إرجاع معلومات المستخدم الموجودة.
- التحقق من كلمة المرور
- المزيد من التغيرات المنطقية المرادة!
الآن سأقوم بتغيير الزر signIn('google) في التطبيق الخاص بي إلى signIn() سيؤدي هذا بدلاً من ذلك إلى إظهار صفحة تسجيل دخول مخصصة من NextAuth 👇
دالة authorize
عند تسجيل الدخول ، يجب تعيين نوع الطلب على POST وإرسالها مع تفاصيل بيانات الاعتماد ولكن إذا لم يكن الأمر كذلك ، فيجب علينا رفض الreq نظرًا لأن نوع الطلب خاطئ:
TSXasync authorize(credentials, req): Promise<any> {
try {
if (req.method !== 'POST') {
res.setHeader('Allow', ['POST'])
return res.status(405).json({
statusText: `Method ${req.method} Not Allowed`,
})
}
} catch (error) {
console.error(error)
}
}, سأقوم بإنشاء دالة تأخذ حجتين الحالة ، الرسالة لتبسيط res وجعلها في سطر واحد داخل authOptions:
TSXfunction resMessage(status: number, message: string) {
return res.status(status).json({
statusText: message,
})
}دعنا نستخرج معلومات بيانات الاعتماد ونعيد معلومات المستخدم عندما تجتاز بيانات الاعتماد هذه الحالات الشرطية:
TSXasync authorize(credentials, req): Promise<any> {
try {
if (req.method !== 'POST') {...}
const { username, email, password, confirm } =
credentials as Credentials
if (!username || !email || !password || !confirm) {
return resMessage(400, 'Invalid user parameters')
}
if (password.length < 6) {
return resMessage(400, 'Password must be at least 6 characters')
}
if (password != confirm) {
return resMessage(400, 'Password mismatch')
}
return credentials
} catch (error) {...}
},الآن إذا حاولنا تسجيل الدخول باستخدام بيانات الاعتماد ، فسوف يعيد توجيهنا بدون حفظ الجلسة في ملفات تعريف الارتباط (Cookie)! ( نحتاج أيضًا إلى حفظ و التحقق من معلومات المستخدم في قاعدة البيانات يدويًا ).
الآن بدلاً من إرجاع بيانات الاعتماد ، سيتعين علينا البحث عن معلومات المستخدم في قاعدة البيانات ووفقًا للأجابة ، سيتعين علينا إما إنشاء مستخدم جديد في قاعدة البيانات أو تسجيل دخول المستخدم اذا كان موجود بالفعل.
TSXasync authorize(credentials, req): Promise<any> {
try {
...
// Search for user credentials in the database
const user = await prisma.user.findFirst({
where: {
email: email,
},
})
// User Exists
if (user) return signUser(user, credentials as Credentials)
// User not exist
return createNewUser(credentials as Credentials)
} catch (error) {...}
},دالة signUser
TSXasync function signUser(user: User, credentials: Credentials) {
// If user has signed in before using Google account it will create a new user in the database but without a username and password.
if (!user.username && !user.password) {
await prisma.user.update({
where: { id: user.id },
data: {
username: credentials.username,
password: generateHash(credentials.password),
},
})
// Create a Credential account for the user
const account = await prisma.account.create({
data: {
userId: user.id,
type: 'credentials',
provider: 'credentials',
providerAccountId: user.id,
},
})
if (user && account) return user
return resMessage(500, 'Unable to link account to created user profile')
}
const comparePassword = compareHash(
credentials.password,
user.password as string
)
if (comparePassword) return user
return resMessage(500, 'Wrong Password!')
}
دالة createNewUser
TSX async function createNewUser(credentials: Credentials) {
const { username, password, email } = credentials
const avatar = `https://ui-avatars.com/api/?background=random&name=${username}&length=1`
const user = await prisma.user.create({
data: {
username: username,
email: email,
password: generateHash(password),
image: avatar,
},
})
if (!user) return resMessage(500, 'Unable to create new user')
const account = await prisma.account.create({
data: {
userId: user.id,
type: 'credentials',
provider: 'credentials',
providerAccountId: user.id,
},
})
if (user && account) return user
return resMessage(500, 'Unable to link account to created user')
}دوال الCallback
اقرأ المزيد عن NextAuth Callback في موقع NextAuth.
دالة signIn
يتم استدعاء هذه الدالة بعد تنفيذ دالة authorize ولم يتم العثور على اي مشكلة.
هنا ماذا سنفعل في هذه الدالة:
- تحقق من نوع تسجيل الدخول - Credential أو OAuth:
req.query.nextauth.include ( 'credentials' )
- إذا كان النوع Credential سنقوم بإنشاء:
- رمز جلسة فريد مع عمر انتهاء الصلاحية.
- اضافة معلومات الجلسة في قاعدة البيانات.
- اضبط ملف تعريف الارتباط
next-auth.session-tokenباستخدام Session Token.
- إذا كان النوع OAuth فسوف نتحقق من:
- المستخدم موجود في قاعدة البيانات:
- إذا لم يكن موجودًا ، يمكننا فقط
return trueو NextAuth سينشئ مستخدمًا جديدًا بالمعلومات الصحيحة.
- إذا لم يكن موجودًا ، يمكننا فقط
- يوجد الحساب في قاعدة البيانات:
- إذا كان الحساب موجودًا ، يمكننا فقط
return true - اذا لا قم بإنشاء حساب جديد في قاعدة البيانات ثم قم بتحديث عمود المستخدم باستخدام صورة و اسم جديد.
- إذا كان الحساب موجودًا ، يمكننا فقط
- المستخدم موجود في قاعدة البيانات:
سأستخدم دالة أخرى للبساطة 👇
TSXfunction nextAuthInclude(include: string) {
return req.query.nextauth?.includes(include)
}الكود بالكامل:
TSXasync signIn({ user, account, email }: any) {
if (nextAuthInclude('callback') && nextAuthInclude('credentials')) {
if (!user) return true
// Generate Session Token when the user is signed with Credentials.
const sessionToken = randomUUID()
const sessionMaxAge = 60 * 60 * 24 * 30
const sessionExpiry = new Date(Date.now() + sessionMaxAge * 1000)
// Create Session Column in the database
await prisma.session.create({
data: {
sessionToken: sessionToken,
userId: user.id,
expires: sessionExpiry,
},
})
// Cookie is important to Persist the session in the browser
setCookie(`next-auth.session-token`, sessionToken, {
expires: sessionExpiry,
req: req,
res: res,
})
return true
}
// Check first if there is no user in the database. Then we can create new user with this OAuth credentials.
const profileExists = await prisma.user.findFirst({
where: {
email: user.email,
},
})
if (!profileExists) return true
// Check if there is an existing account in the database. Then we can log in with this account.
const accountExists = await prisma.account.findFirst({
where: {
AND: [{ provider: account.provider }, { userId: profileExists.id }],
},
})
if (accountExists) return true
// If there is no account in the database, we create a new account with this OAuth credentials.
await prisma.account.create({
data: {
userId: profileExists.id,
type: account.type,
provider: account.provider,
providerAccountId: account.providerAccountId,
access_token: account.access_token,
expires_at: account.expires_at,
token_type: account.token_type,
scope: account.scope,
id_token: account.id_token,
},
})
// Since a user is already exist in the database we can update user information.
await prisma.user.update({
where: { id: profileExists.id },
data: { name: user.name, image: user.image },
})
return user
},دالة jwt
سيتم استدعاء هذه الوظيفة فقط عندما نقوم بتسجيل الدخول باستخدام بيانات الاعتماد حيث يتم تعيين استراتيجية الجلسة على قاعدة البيانات لأننا نستخدم قاعدة بيانات و موفر بيانات الاعتماد لا يدعم قاعدة البيانات.
قم بتوسيع token بمعلومات المستخدم:
TSXasync jwt({ token, user }: any) {
if (user) token.user = user
return token
},دالة session
قم بتوسيع الجلسة بمعلومات المستخدم.
الرمز الكامل:
TSXasync session({
session,
user,
}: {
session: Session
token: any
user: AdapterUser
}) {
if (user) {
session.user = {
id: user.id,
name: user.name,
email: user.email,
image: user.image,
}
}
return session
},JWT
في خيار JWT ، سنتحقق مما إذا كان المستخدم سجل الدخول ببيانات اعتماد ، ثم سنقوم باستبدال سلوك encode و decode الافتراضي في NextAuth.
الرمز الكامل:
TSXjwt: {
encode: async ({
token,
secret,
maxAge,
}: JWTEncodeParams): Promise<any> => {
if (
nextAuthInclude('callback') &&
nextAuthInclude('credentials') &&
req.method === 'POST'
) {
const cookie = getCookie(`next-auth.session-token`, {
req: req,
})
if (cookie) return cookie
else return ''
}
return encode({ token, secret, maxAge })
},
decode: async ({ token, secret }: JWTDecodeParams) => {
if (
nextAuthInclude('callback') &&
nextAuthInclude('credentials') &&
req.method === 'POST'
) {
return null
}
return decode({ token, secret })
},
}دالة getServerAuthSession
دالة مساعدة إضافية للحصول على جلسة الخادم دون أضمان authOptions في كل مرة 👇
TSXexport const getServerAuthSession = (req: any, res: any) => {
return getServerSession(req, res, authOptions(req, res))
}يمكن استخدام الدالة في getServerSideProps أو داخل دالة API.
هنا مثال على كيفية استخدامه:
TSX // Before ❌
const session = await getServerSession(req, res, authOptions(req, res))
// After ✅
const session = await getServerAuthSession(req, res)التعديلات الكاملة على الNextAuth API
TSX/* pages/api/auth/[...nextauth].ts */
import NextAuth, { NextAuthOptions, Session, getServerSession } from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
import CredentialsProvider from 'next-auth/providers/credentials'
import { PrismaClient, User } from '@prisma/client'
import { PrismaAdapter } from '@next-auth/prisma-adapter'
import { AdapterUser } from 'next-auth/adapters'
import {
NextApiRequest,
NextApiResponse,
} from 'next'
import bcrypt from 'bcrypt'
import { randomUUID } from 'crypto'
import { getCookie, setCookie } from 'cookies-next'
import {
JWT,
JWTDecodeParams,
JWTEncodeParams,
decode,
encode,
} from 'next-auth/jwt'
type Credentials = {
username: string
email: string
password: string
confirm: string
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
return await NextAuth(req, res, authOptions(req, res))
}
export const authOptions = (
req: NextApiRequest,
res: NextApiResponse
): NextAuthOptions => {
const prisma = new PrismaClient()
function generateHash(password: string): string {
const saltRounds = 10
return bcrypt.hashSync(password, saltRounds)
}
function compareHash(plainPassword: string, hash: string): boolean {
return bcrypt.compareSync(plainPassword, hash)
}
function resMessage(status: number, message: string) {
return res.status(status).json({
statusText: message,
})
}
function nextAuthInclude(include: string) {
return req.query.nextauth?.includes(include)
}
async function signUser(user: User, credentials: Credentials) {
// If user has signed in before using Google account it will create a new user in the database but without a username and password.
if (!user.username && !user.password) {
await prisma.user.update({
where: { id: user.id },
data: {
username: credentials.username,
password: generateHash(credentials.password),
},
})
// Create a Credential account for the user
const account = await prisma.account.create({
data: {
userId: user.id,
type: 'credentials',
provider: 'credentials',
providerAccountId: user.id,
},
})
if (user && account) return user
return resMessage(500, 'Unable to link account to created user profile')
}
const comparePassword = compareHash(
credentials.password,
user.password as string
)
if (comparePassword) return user
return resMessage(500, 'Wrong Password!')
}
async function createNewUser(credentials: Credentials) {
const { username, password, email } = credentials
const avatar = `https://ui-avatars.com/api/?background=random&name=${username}&length=1`
const user = await prisma.user.create({
data: {
username: username,
email: email,
password: generateHash(password),
image: avatar,
},
})
if (!user) return resMessage(500, 'Unable to create new user')
const account = await prisma.account.create({
data: {
userId: user.id,
type: 'credentials',
provider: 'credentials',
providerAccountId: user.id,
},
})
if (user && account) return user
return resMessage(500, 'Unable to link account to created user')
}
return {
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID || '',
clientSecret: process.env.GOOGLE_SECRET || '',
}),
CredentialsProvider({
name: 'credentials',
credentials: {
username: {
label: 'Username',
type: 'text',
placeholder: 'John Doe',
},
email: {
label: 'Email',
type: 'email',
placeholder: 'john@doe.com',
},
password: {
label: 'Password',
type: 'password',
placeholder: '**********',
},
confirm: {
label: 'Confirm',
type: 'password',
placeholder: '**********',
},
},
async authorize(credentials, req): Promise<any> {
try {
if (req.method !== 'POST') {
res.setHeader('Allow', ['POST'])
return resMessage(405, `Method ${req.method} Not Allowed`)
}
const { username, email, password, confirm } =
credentials as Credentials
if (!username || !email || !password || !confirm) {
return resMessage(400, 'Invalid user parameters')
}
if (password.length < 6) {
return resMessage(400, 'Password must be at least 6 characters')
}
if (password != confirm) {
return resMessage(400, 'Password mismatch')
}
// Search for user credentials in the database
const user = await prisma.user.findFirst({
where: {
email: email,
},
})
// User Exists
if (user) return signUser(user, credentials as Credentials)
// User not exist
return createNewUser(credentials as Credentials)
} catch (error) {
console.error(error)
}
},
}),
],
callbacks: {
async signIn({ user, account, email }: any) {
if (nextAuthInclude('callback') && nextAuthInclude('credentials')) {
if (!user) return true
const sessionToken = randomUUID()
const sessionMaxAge = 60 * 60 * 24 * 30
const sessionExpiry = new Date(Date.now() + sessionMaxAge * 1000)
await prisma.session.create({
data: {
sessionToken: sessionToken,
userId: user.id,
expires: sessionExpiry,
},
})
setCookie(`next-auth.session-token`, sessionToken, {
expires: sessionExpiry,
req: req,
res: res,
})
return true
}
// Check first if there is no user in the database. Then we can create new user with this OAuth credentials.
const profileExists = await prisma.user.findFirst({
where: {
email: user.email,
},
})
if (!profileExists) return true
// Check if there is an existing account in the database. Then we can log in with this account.
const accountExists = await prisma.account.findFirst({
where: {
AND: [{ provider: account.provider }, { userId: profileExists.id }],
},
})
if (accountExists) return true
// If there is no account in the database, we create a new account with this OAuth credentials.
await prisma.account.create({
data: {
userId: profileExists.id,
type: account.type,
provider: account.provider,
providerAccountId: account.providerAccountId,
access_token: account.access_token,
expires_at: account.expires_at,
token_type: account.token_type,
scope: account.scope,
id_token: account.id_token,
},
})
// Since a user is already exist in the database we can update user information.
await prisma.user.update({
where: { id: profileExists.id },
data: { name: user.name, image: user.image },
})
return user
},
async jwt({ token, user }: any) {
if (user) token.user = user
return token
},
async session({
session,
user,
}: {
session: Session
token: any
user: AdapterUser
}) {
if (user) {
session.user = {
id: user.id,
name: user.name,
email: user.email,
image: user.image,
}
}
return session
},
},
jwt: {
encode: async ({
token,
secret,
maxAge,
}: JWTEncodeParams): Promise<any> => {
if (
nextAuthInclude('callback') &&
nextAuthInclude('credentials') &&
req.method === 'POST'
) {
const cookie = getCookie(`next-auth.session-token`, {
req: req,
})
if (cookie) return cookie
else return ''
}
return encode({ token, secret, maxAge })
},
decode: async ({ token, secret }: JWTDecodeParams) => {
if (
nextAuthInclude('callback') &&
nextAuthInclude('credentials') &&
req.method === 'POST'
) {
return null
}
return decode({ token, secret })
},
},
}
}
export const getServerAuthSession = (req: any, res: any) => {
return getServerSession(req, res, authOptions(req, res))
}
الخاتمة
إن عملية اكتشاف الحل لهذه المشكلة ومحاولة تنفيذها تعطي الكثير من المعرفة حول Session Tokens, JWT وكيفية التفاعل مع قاعدة بيانات عند استخدام موفر بيانات الأعتماد والمنطق لحفظ معلومات المستخدم والسماح للمستخدم بتسجيل الدخول أم لا.
العمل في مشروع حقيقي سيكون مختلفًا ويجب التعامل مع الأمان بجدية وقوة ولهذا السبب لا يوصي NextAuth لأستخدام موفر بيانات الأعتماد نظرًا لأن تعريف المستخدم والسماح له بالتسجيل بشكل أمن لا يمكن تنفيذه بسهولة من قبل أي شخص وهناك الكثير من الجوانب الأمنية التي يجب تغطيتها.
يعد تشفير كلمات المرور خطوة مهمة يجب اتخاذها وقد تعلمت عند إنشاء هذا المشروع على bcrypt ومدى سهولة استخدام حزمة للتعامل مع كلمة المرور وتشفير كلمات السر ومقارنتها بكلمة المرور المدخلة ومن الجميل انه كمطور او مدير المشروع والذي لديه حق الوصول مباشرة إلى قاعدة البيانات لعدم معرفة أي معلومات مهمة حول كلمات مرور المستخدمين او في حالة تسرب كلمات مرور المستخدمين يكون من الصعب لكلمة المرور ان يتم فك تشفيرها.
آمل أن يعمل كل شيء بشكل جيد بالنسبة لك وتأكد من عدم وجود أي رمز مفقود وأراك في منشوري القادم 😉
