Veritabanı yönetimi için Knex'i, verimli önbelleğe alma için Redis'i ve sorunsuz yönlendirme için Express'i kullanarak Node js sunucusu için güçlü bir kimlik doğrulama çözümü oluşturun. Node.js uygulamalarım için hızlı, sezgisel ve kolaylaştırılmış bir kimlik doğrulama çözümü arayışımda, işlevsellikten ödün vermeden hızlı uygulama gerektiren senaryolarla karşılaştım. Kullanıcı kaydı ve oturum açmadan, unutulan şifreleri yönetmeye, kullanıcı verilerini güncellemeye ve hatta hesap silmeye kadar, bu önemli kullanıcı etkileşimlerinde sorunsuz bir şekilde gezinen kapsamlı bir çözüm aradım. Bu nedenle, makalem tam olarak şunu sunmayı amaçlamaktadır: kimlik doğrulama ve önbelleğe alma uygulamak için net metodolojileri entegre eden, sağlam ve verimli bir kullanıcı akışı sağlayan uyumlu bir yaklaşım. Burada temel kurulum prosedürlerini ve model oluşturmayı atlayarak doğrudan kimlik doğrulamanın ve kullanıcı akışının inceliklerine odaklanacağız. Makale boyunca yapılandırma dosyalarını edinmek için gerekli tüm bağlantıları dahil edeceğiz ve kurulum için gereken kaynaklara sorunsuz erişim sağlayacağız. Aletler Bu uygulama için Knex, Express ve Redis'in yanı sıra Node.js 20.11.1 sürümünden de yararlanacağız. Ek olarak, sorunsuz yönetim için Docker kullanılarak kapsayıcıya alınacak ve düzenlenecek olan PostgreSQL'i veritabanımız olarak kullanacağız. Uygulamamızın adı olacaktır. Bu klasörü oluşturalım ve içinde temel oluşturmak için komutunu çalıştıralım. user-flow-boilerplate package.json npm init -y { "name": "user-flow-boilerplate", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" } Başlangıç package.json Bir sonraki adım gerekli bağımlılıkları eklemektir: : bağımlılıklar npm i -S bcrypt body-parser cors dotenv express jsonwebtoken knex pg redis validator : geliştirici bağımlılıkları npm i -D @babel/core @babel/eslint-parser @babel/plugin-transform-class-properties @babel/plugin-transform-runtime @babel/preset-env @babel/preset-typescript @faker-js/faker @types/bcrypt @types/body-parser @types/cors @types/express @types/jest @types/jsonwebtoken @types/node @types/node-cron @types/validator @typescript-eslint/eslint-plugin @typescript-eslint/parser babel-jest cross-env eslint eslint-config-prettier eslint-plugin-prettier jest nodemon npm-run-all prettier ts-jest ts-loader ts-node tsconfig-paths tslint typescript webpack webpack-cli webpack-node-externals ve uygulamamızı oluşturup çalıştıracak komut dosyalarını ekleyin: "scripts": { "start": "NODE_ENV=production node dist/bundle.js", "build": "NODE_ENV=production webpack --config webpack.config.js", "dev": "cross-env NODE_ENV=development && npm-run-all -p dev:*", "dev:build": "webpack --config webpack.config.js --watch", "dev:start": "nodemon --watch dist --exec node dist/bundle.js", "test": "NODE_ENV=test jest --config ./jest.config.js", "lint": "eslint ./src -c .eslintrc.json" }, Uygulamamızın sorunsuz bir şekilde başlatılmasını sağlamak için, bir klasörü oluşturmamız ve ilk giriş noktası dosyamız olan bu klasöre yerleştirmemiz önemlidir. src index.ts require('dotenv').config(); import process from 'process'; import express from 'express'; import bodyParser from 'body-parser'; import cors from 'cors'; const app = express(); const PORT = process.env.PORT || 9999; app.use(bodyParser.json()); app.use(cors()); app.get('/api/v1/health', (req, res) => res.status(200).json({ message: 'OK' })); (async () => { try { app.listen(PORT, async () => { console.log(`Server is running on port ${PORT}`); }); } catch (error) { console.error('Failed to start server:', error); process.exit(1); } })(); Giriş noktası dosyası Geliştirme için , , , , , ayarlarına sahip olmamız gerekiyor. Tüm bu dosyaları şu makalede anlattım: . typscript lint jest bable prettier nodemon Express'te Postgres ve Knex ile Node.js Sunucusu Oluşturma Tüm ayarları yapılandırdıktan ve giriş noktasını oluşturduktan sonra, çalıştırılması sunucuyu başlatmalı ve aşağıdakine benzer bir çıktı görmeyi beklemelisiniz: npm run dev ./src/index.ts 1.7 KiB [built] [code generated] external "dotenv" 42 bytes [built] [code generated] external "process" 42 bytes [built] [code generated] external "express" 42 bytes [built] [code generated] external "body-parser" 42 bytes [built] [code generated] external "cors" 42 bytes [built] [code generated] webpack 5.90.3 compiled successfully in 751 ms [nodemon] restarting due to changes... [nodemon] starting `node dist/bundle.js` Server is running on port 9999 Daha sonra şuraya gidin: uç noktalarımızı test etmeye adanmış bir koleksiyon oluşturacağımız yer. Yeni koleksiyonda yeni bir isteği ekleyin, tuşlarına basın (Mac'te ancak tuşlar işletim sisteminize bağlıdır) ve bunu olarak adlandırın. Postacı GET cmd + E health URL için enter ekleyin: . için koleksiyonda kullanacağınız yeni bir değişken ekleyin: {{BASE_URI}}/health BASE_URI http://localhost:9999/api/v1 Daha sonra 'Gönder' düğmesini tıklamanız yeterlidir; yanıt metnini gözlemlemelisiniz: { "message": "OK" } Veri tabanı İlerlemeden önce veritabanımızın çalışır durumda olması çok önemlidir. Bunu ile başlatarak başaracağız. Veritabanına erişmek ve yönetmek için aşağıdaki gibi çeşitli geliştirme platformlarını kullanabilirsiniz: . docker-compose pgAdmin Şahsen ben kullanmayı tercih ediyorum Etkin yönetim için PostgreSQL veritabanlarına kusursuz bağlantı sağlayan bir sürücüyle donatılmıştır. Yakut Madeni Gerekli anahtarları, şifreleri ve test adlarını içeren dosyasına ihtiyacımız var: .env PORT=9999 WEB_HOST="localhost" # DB DB_HOST="localhost" DB_PORT=5432 DB_NAME="user_flow_boilerplate" DB_USER="username_123" DB_PASSWORD="SomeParole999" # User DEFAULT_PASSWORD="SomeParole999" JWT_SECRET="6f1d7e9b9ba56476ae2f4bdebf667d88eeee6e6c98c68f392ed39f7cf6e51c5a" # Test User TEST_EMAIL="test_email@test.com" TEST_USERNAME="test_username" TEST_PASSWORD="SomeParole999" # Redis REDIS_HOST="localhost" REDIS_PORT=6379 REDIS_DB=0 REDIS_PASSWORD="SomeParole999" Veritabanına bağlantı için .env, Redis ve tohumlara ilişkin test değerleri Korkmayın, daha özgün bir şekilde göstermek için rastgele oluşturdum. Öyleyse projenin kökünde bir dosyası oluşturalım: JWT_SECRET docker-compose.yml version: '3.6' volumes: data: services: database: build: context: . dockerfile: postgres.dockerfile image: postgres:latest container_name: postgres environment: TZ: Europe/Madrid POSTGRES_DB: ${DB_NAME} POSTGRES_USER: ${DB_USER} POSTGRES_PASSWORD: ${DB_PASSWORD} networks: - default volumes: - data:/var/lib/postgresql/data ports: - "5432:5432" restart: unless-stopped redis: image: redis:latest container_name: redis command: redis-server --requirepass ${REDIS_PASSWORD} networks: - default ports: - "6379:6379" restart: unless-stopped hizmetleri içeren docker-compose dosyası Hızlı bağlantı için Docker'da iki hizmeti başlatacağız. Veritabanına veya Redis'e hızlı erişimi kolaylaştırmak ve verileri verimli bir şekilde almamızı sağlamak için bu süreci kolaylaştırdım. Öyleyse, hadi bu hizmetleri çalıştıralım ve aşağıdaki çıktıyı sonra görebilmemiz gerekiyor: docker-compose up docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e4bef95de1dd postgres:latest "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:5432->5432/tcp postgres 365e3a68351a redis:latest "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:6379->6379/tcp redis Şimdi uygulama için türlerimizi sakladığımız dosyasını oluşturmamız gerekiyor: src/@types/index.ts export enum Role { Admin = 'admin', Blogger = 'blogger', } export type UserSession = { id: number; }; export type DatabaseDate = { created_at: Date; updated_at: Date; }; export type DefaultUserData = { role: Role; }; export interface User extends DatabaseDate { id: number; email: string; username: string; password: string; role: Role; } Hizmet türleri Şu anda bağlantı, geçişler ve tohumlar için projenin kökünde ve veritabanı klasöründe bulunması gerekir. knexfile.ts Kullanıcıların bu env değişkenlerini kullandığımız veritabanına nasıl taşınacağı ve tohumlanacağı hakkında makalesinde oldukça ayrıntılı bir açıklama bıraktım. Postgres ve Knex ile Node.js Sunucusu Oluşturma Aynı sayfada olduğumuzdan emin olmak için geçişleri özellikle kontrol etmek istiyorum. Hizmetlerimizi zaten başlattık ve veritabanı bağlantısını kontrol edebilmemiz gerekiyor. docker exec -it postgres psql -U username_123 user_flow_boilerplate Bağlantı iyiyse konsolunda olacaksınız. Tamam, eğer bağlantıda sorun yoksa tablolarımızı oraya taşıyabiliriz. çalıştırın. Daha sonra veritabanı içerisindeki tablonuza yeni eklenen sütunları gözlemlemelisiniz. psql knex migrate:latest users Sahte veri ile tohumlayalım ve tabloyu tekrar kontrol edelim. knex seed:run Artık veritabanını yönetebilecek donanıma sahibiz ve gerektiğinde kullanıcıları eklememize, silmemize veya güncellememize olanak tanıyoruz. Yönlendirici Son olarak ayarları ve hazırlığı unutup özellikle kullanıcı akışına odaklanabiliriz. Bunun için bir yönlendirici oluşturmamız gerekiyor. Bu yönlendirici tarafından şu işlemleri gerçekleştirmemiz gerekir: , oturum , , , . login logout signup delete_user update_user Bunun için dosyasına aşağıdaki kodu ekleyin: src/routes/index.ts import { Router } from 'express'; import { authRouter } from 'src/routes/authRouter'; import { healthController } from 'src/controllers/healthController'; import { sessionController } from 'src/controllers/sessionController'; import { authMiddleware } from 'src/middlewares/authMiddleware'; import { userRouter } from 'src/routes/userRouter'; export const router = Router({ mergeParams: true }); router.get('/health', healthController); router.use('/auth', authRouter); router.get('/session', authMiddleware, sessionController); router.use('/user', authMiddleware, userRouter); router.use((_, res) => { return res.status(404).json({ message: 'Not Found' }); }); Rota dosyası Gördüğünüz gibi başlangıçta daha önce kontrol ettiğimiz rotasını ekledik. O halde, o rotaları orada uygulamak için giriş noktasını güncelleyelim. İlk önce önceki kaldırın. /health get -> REMOVE -> app.get('/api/v1/health', (req, res) => res.status(200).json({ message: 'OK' })); ve dosyanın en üstüne şunu ekleyin: import { router } from 'src/routes'; // ... app.use(cors()); app.use('/api/v1', router); ve şu kodla kontrolü için ilk denetleyiciyi oluşturun: health src/controllers/healthController.ts import { Request, Response } from 'express'; export const healthController = (_: Request, res: Response) => res.status(200).send('ok'); Sağlık Kontrolörü Şimdi yönlendiriciye geri dönelim ve rotalara daha fazla ne eklememiz gerektiğine bakalım. İki dosya daha eklememiz gerekiyor: ve authRouter.ts userRouter.ts import { Router } from 'express'; import { signUpController } from 'src/controllers/auth/signUpController'; import { loginController } from 'src/controllers/auth/loginController'; export const authRouter = Router(); authRouter.post('/signup', signUpController); authRouter.post('/login', loginController); Kimlik Doğrulama Yönlendiricisi import { Router } from 'express'; import { updateUserController } from 'src/controllers/user/updateUserController'; import { deleteUserController } from 'src/controllers/user/deleteUserController'; import { logoutController } from 'src/controllers/user/logoutController'; import { updatePasswordController } from 'src/controllers/user/updatePasswordController'; export const userRouter = Router(); userRouter.patch('/', updateUserController); userRouter.delete('/', deleteUserController); userRouter.post('/logout', logoutController); userRouter.post('/update-password', updatePasswordController); Kullanıcı Yönlendiricisi Okunabilirlik ve yalıtılmış işlevselliği sürdürme sorumluluğu açısından bu mantığı böldüm. Bu rotaların tümü, mantığı yöneteceğimiz kontrolörlere ihtiyaç duyar. Kimlik doğrulama ve sistem durumu rotaları, kimlik doğrulama ara yazılımına ihtiyaç duymaz, dolayısıyla bu rotalar korunmaz, ancak eşleşme yoksa durum 404'ü alırız. router.get('/health', healthController); router.use('/auth', authRouter); Artık tüm rotaları belirlediğimize göre kullanıcı modelini ayarlamamız gerekiyor. Kullanıcı Modeli Kullanıcı modeli için CRUD yöntemlerini yeniden kullanacağım temel modeli kullanacağım. Daha önce model oluşturmayı başka bir konuda ele almış olsam da , Daha iyi görünürlük ve anlayış için temel modeli buraya ekleyeceğim. oluşturun madde src/models/Model.ts import { database } from 'root/database'; export abstract class Model { protected static tableName?: string; protected static get table() { if (!this.tableName) { throw new Error('The table name must be defined for the model.'); } return database(this.tableName); } public static async insert<Payload>(data: Payload): Promise<{ id: number; }> { const [result] = await this.table.insert(data).returning('id'); return result; } public static async updateOneById<Payload>( id: number, data: Payload ): Promise<{ id: number; }> { const [result] = await this.table.where({ id }).update(data).returning('id'); return result; } public static async delete(id: number): Promise<number> { return this.table.where({ id }).del(); } public static async findOneById<Result>(id: number): Promise<Result> { return this.table.where('id', id).first(); } public static async findOneBy<Payload, Result>(data: Payload): Promise<Result> { return this.table.where(data as string).first(); } } Temel model Temel modelle aynı klasörde oluşturabilmemiz gerekiyor: UserModel.ts import { Model } from 'src/models/Model'; import { Role, User, DefaultUserData } from 'src/@types'; export class UserModel extends Model { static tableName = 'users'; public static async create<Payload>(data: Payload) { return super.insert<Payload & DefaultUserData>({ ...data, role: data.role || Role.Blogger, }); } public static findByEmail(email: string): Promise<User | null> { return this.findOneBy< { email: string; }, User >({ email }); } public static findByUsername(username: string): Promise<User | null> { return this.findOneBy< { username: string; }, User >({ username }); } } Kullanıcı Modeli Kullanıcı modelinde, yükten sağlanmadığı takdirde yalnızca varsayılan olarak ayarlıyorum. Artık modellerimiz hazır olduğuna göre bunları denetleyicilerimiz ve ara katman yazılımlarımızda kullanmaya devam edebiliriz. role Kimlik Doğrulama Ara Yazılımı Node.js uygulamasındaki kimlik doğrulama ara yazılımı, gelen isteklerin kimliğini doğrulamaktan ve bu isteklerin geçerli ve yetkili kullanıcılardan geldiğinden emin olmaktan sorumludur. Genellikle gelen istekleri yakalar, kimlik doğrulama belirteçlerini veya kimlik bilgilerini çıkarır ve bu durumda bunların geçerliliğini JWT (JSON Web Belirteçleri) gibi önceden tanımlanmış bir kimlik doğrulama mekanizmasına göre doğrular. Kimlik doğrulama işlemi başarılı olursa, ara yazılım, isteğin istek-yanıt döngüsündeki bir sonraki işleyiciye ilerlemesine izin verir. Ancak kimlik doğrulama başarısız olursa, uygun bir HTTP durum koduyla (örn. 401 Yetkisiz) yanıt verir ve isteğe bağlı olarak bir hata mesajı verir. klasörünü oluşturun ve buraya aşağıdaki kodu içeren dosyasını ekleyin: src/middlewares authMiddleware.ts import { jwt } from 'src/utils/jwt'; import { Redis } from 'src/redis'; import type { Request, Response, NextFunction } from 'express'; import type { UserSession } from 'src/@types'; export async function authMiddleware(req: Request, res: Response, next: NextFunction) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; const JWT_SECRET = process.env.JWT_SECRET; if (!token) return res.sendStatus(401); if (!JWT_SECRET) { console.error('JWT_SECRET Not Found'); return res.sendStatus(500); } if (!token) return res.status(401).json({ error: 'Token not provided' }); try { const userSession = await jwt.verify<UserSession>(token); if (!userSession) { return res.sendStatus(401); } const storedToken = await Redis.getSession(userSession.id); if (!storedToken || storedToken !== token) { return res.sendStatus(401); } req.user = userSession; next(); } catch (error) { console.error('JWT_ERROR', error); return res.sendStatus(401); } } Kimlik doğrulama ara yazılım dosyası Kimlik doğrulama ara yazılımı, JWT belirtecini istek başlığından çıkarır, JWT kitaplığını kullanarak geçerliliğini doğrular ve belirtecin Redis'te depolanan belirteçle eşleşip eşleşmediğini kontrol eder. Belirteç geçerliyse ve depolanan belirteçle eşleşiyorsa, ara yazılım, kimlik doğrulaması yapılmış kullanıcı oturumunu istek nesnesinde ( ) ayarlar ve kontrolü bir sonraki ara yazılıma veya rota işleyicisine geçirmek için işlevini çağırır. Aksi takdirde, kimlik doğrulama başarısızlığını belirten 401 durum koduyla yanıt verir. req.user next() JSON Web Belirteçleri Jwt'nin kullanımını gözden geçirelim. Aşağıdaki kodla dosyasında oluşturun: src/utils/jwt.ts require('dotenv').config(); import jsonwebtoken from 'jsonwebtoken'; const JWT_SECRET = process.env.JWT_SECRET as string; export const jwt = { verify: <Result>(token: string): Promise<Result> => { if (!JWT_SECRET) { throw new Error('JWT_SECRET not found in environment variables!'); } return new Promise((resolve, reject) => { jsonwebtoken.verify(token, JWT_SECRET, (error, decoded) => { if (error) { reject(error); } else { resolve(decoded as Result); } }); }); }, sign: (payload: string | object | Buffer): Promise<string> => { if (!JWT_SECRET) { throw new Error('JWT_SECRET not found in environment variables!'); } return new Promise((resolve, reject) => { try { resolve(jsonwebtoken.sign(payload, JWT_SECRET)); } catch (error) { reject(error); } }); }, }; JWT yardımcı program dosyası Bu yardımcı program, Node.js uygulaması içindeki JSON Web Belirteçlerinin işlenmesinde kritik bir rol oynar. nesnesi, kitaplığından yararlanarak JWT'leri imzalamak ve doğrulamak için işlevleri dışa aktarır. Bu işlevler, uygulamada kimlik doğrulama mekanizmalarının uygulanması için gerekli olan JWT'lerin oluşturulmasını ve doğrulanmasını kolaylaştırır. jwt jsonwebtoken Yardımcı program, JWT'leri işlemeye yönelik işlevselliği kapsar ve Node.js uygulaması içinde güvenli kimlik doğrulama mekanizmaları sağlarken ortam değişkeni yönetimine yönelik en iyi uygulamalara bağlı kalır. Redis Veritabanı, önbellek ve mesaj aracısı olarak kullanılır. Önbelleğe alma, oturum yönetimi, gerçek zamanlı analizler, mesajlaşma kuyrukları, skor tabloları ve daha fazlasını içeren çeşitli kullanım durumlarında yaygın olarak kullanılır. Jetonun Redis'ten kontrol edilmesi, JWT jetonu için ek bir güvenlik ve doğrulama katmanı görevi görür. Ayarlara dalalım. Bunun için dosyasını aşağıdaki kodla oluşturun: src/redis/index.ts require('dotenv').config({ path: '../../.env', }); import process from 'process'; import * as redis from 'redis'; const client = redis.createClient({ url: `redis://:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, }); client.on('error', error => console.error('Redis Client Error', error)); const connect = async () => { try { await client.connect(); console.log('Connected to Redis'); } catch (err) { console.error(`Could not connect to Redis: ${err}`); process.exit(1); } }; class Redis { public static setSession(userId: number, token: string) { if (!userId) throw new Error('userId is required'); if (!token) throw new Error('token is required'); try { return client.set(`session:${userId}`, token); } catch (error) { console.error(error); } } public static getSession(userId: number) { if (!userId) throw new Error('userId is required'); return client.get(`session:${userId}`); } public static deleteSession(userId: string) { if (!userId) throw new Error('userId is required'); try { return client.del(`session:${userId}`); } catch (error) { console.error(error); } } } export { client, connect, Redis }; Redis Oturum Mağazası Redis ile kullanıcı oturumu belirteçlerini saklayıp yöneteceğiz. Kimlik doğrulama ara yazılımında, JWT belirtecinin orijinalliği doğrulandıktan sonra ara yazılım, belirtecin mevcut olup olmadığını ve karşılık gelen kullanıcı oturumu için Redis'te depolanan belirteçle eşleşip eşleşmediğini kontrol eder. Bu, yalnızca geçerli ve yetkili kullanıcıların korumalı rotalara erişebilmesini sağlamaya yardımcı olur. Redis, kullanıcı oturumu belirteçlerini korumak için anahtar/değer deposu olarak kullanılır. Bir kullanıcı oturum açtığında veya kimlik doğrulaması yaptığında oturum belirteci Redis'te depolanır. Bu, sonraki kimlik doğrulama kontrolleri sırasında oturum belirteçlerinin verimli ve hızlı bir şekilde alınmasına olanak tanır. Redis, verimli oturum yönetimi için kimlik doğrulama ara yazılımında kullanılırken Redis ile ilgili dosya, yapılandırmayı ve Redis sunucusuna bağlantıyı yönetir ve uygulamanın diğer bölümlerinde Redis ile etkileşim kurmaya yönelik işlevler sağlar. Bu kurulum, Redis'te saklanan ve yönetilen kullanıcı oturumu belirteçleriyle güvenli ve güvenilir kimlik doğrulama mekanizmaları sağlar. Son kısım ise giriş noktamızda Redis'e bağlanmamız gerektiği: // all imports import * as Redis from 'src/redis'; const app = express(); const PORT = process.env.PORT || 9999; // middlewares (async () => { try { await Redis.connect(); app.listen(PORT, async () => { console.log(`Server is running on port ${PORT}`); }); } catch (error) { console.error('Failed to start server:', error); process.exit(1); } })(); Redis'e bağlanın Kimlik doğrulama hazırlığını tamamladıktan sonra artık odağımızı denetleyicilere kaydırabiliriz. Kontrolörler Rotalardaki denetleyiciler, endişeleri ayırarak ve kodun sürdürülebilirliğini teşvik ederek uygulamanın mantığını düzenlemeye yardımcı olur. Durum kontrolü için denetleyiciyi zaten oluşturduk. Daha sonra, kullanıcıyla işlemleri yürütmek için denetleyiciler oluşturmaya devam edeceğiz. Alacağımız ilk denetleyici, aşağıdaki kodla içinde olması gereken : src/controllers sessionController.ts import { Request, Response } from 'express'; import { UserModel } from 'src/models/UserModel'; import type { User } from 'src/@types'; export const sessionController = async (req: Request, res: Response) => { if (!req.user) return res.sendStatus(401); try { const user = await UserModel.findOneById<User>(req.user.id); if (user) { return res.status(200).json(user); } else { return res.sendStatus(401); } } catch (error) { return res.sendStatus(500); } }; Oturum Denetleyicisi Bu denetleyici, muhtemelen şu anda kimliği doğrulanmış kullanıcı hakkında bilgi almaktan sorumlu olan, oturumla ilgili bir uç noktayı işleme amacına hizmet eder. Bu denetleyiciye aşağıdaki nedenlerden dolayı ihtiyacımız var: Bu denetleyici, uygulamanın kullanıcı profili veya diğer ilgili veriler gibi kullanıcının oturumu hakkındaki bilgileri almasına olanak tanır. Bu bilgiler, kullanıcı deneyimini özelleştirmek veya kullanıcının profiline göre kişiselleştirilmiş içerik sağlamak için yararlı olabilir. Kullanıcı Oturumu Bilgileri: mevcut olup olmadığını kontrol eden denetleyici, yalnızca kimliği doğrulanmış kullanıcıların uç noktaya erişebilmesini sağlar. Bu, kimlik doğrulama ve yetkilendirme kurallarının uygulanmasına yardımcı olarak hassas kullanıcı verilerinin yalnızca yetkili kullanıcılar tarafından erişilebilir olmasını sağlar. Kimlik Doğrulama ve Yetkilendirme: req.user Denetleyici, oturum kimliklerine göre kullanıcının bilgilerini almak için veritabanını ( kullanarak) sorgular. Bu, uygulamanın kullanıcıya özel verileri dinamik olarak almasına olanak tanıyarak her kullanıcıya özel bir deneyim sunar. Bu bölüm kesinlikle Redis önbelleğiyle geliştirilebilir: Kullanıcı Profilinin Alınması: UserModel import { Request, Response } from 'express'; import { UserModel } from 'src/models/UserModel'; import { Redis } from 'src/redis'; import type { User } from 'src/@types'; export const sessionController = async (req: Request, res: Response) => { if (!req.user) return res.sendStatus(401); try { const cachedProfile = await Redis.getSession(req.user.id); if (cachedProfile) { return res.status(200).json(JSON.parse(cachedProfile)); } else { const user = await UserModel.findOneById<User>(req.user.id); if (user) { await Redis.setSession(req.user.id, JSON.stringify(user), CACHE_EXPIRATION); return res.status(200).json(user); } else { return res.sendStatus(401); } } } catch (error) { console.error('Error retrieving user profile:', error); return res.sendStatus(500); } }; Redis'in ayarlanmış oturumunu içeren Oturum Denetleyicisi dosyası Önbellek son kullanma süresini saniye cinsinden belirtmek için bir sabiti tanımlarız. Bu örnekte 3600 saniyeye (1 saat) ayarlanmıştır. Önbelleğe alınan veriler düzenli aralıklarla yenilenerek eski verilerin kullanıcılara sunulması önlenir ve önbellekteki veri bütünlüğü korunur. CACHE_EXPIRATION Uygulamamızda yeni kullanıcıların kayıt olma sürecini oluşturmaya geçmeden önce şemayı inceleyelim: signUpController Bizim durumumuzda, veritabanında mevcut bir e-postayla kaydolmaya çalışırken, kullanıcının var olup olmadığını açıkça açıklamayarak kullanıcı gizliliğine öncelik veriyoruz. Bunun yerine, müşteriyi belirten genel bir mesajla bilgilendiririz. Invalid email or password Bu yaklaşım, istemciyi mevcut kullanıcılar hakkındaki gereksiz bilgileri ifşa etmeden geçerli kimlik bilgileri göndermeye teşvik eder. Şimdi oluşturalım ve aşağıdaki kodu ekleyelim: src/controllers/auth/signUpController.ts import bcrypt from 'bcrypt'; import { jwt } from 'src/utils/jwt'; import { Request, Response } from 'express'; import { validate } from 'src/helpers/validation/validate'; import { userSchema } from 'src/helpers/validation/schemas/userSchema'; import { UserModel } from 'src/models/UserModel'; import { Redis } from 'src/redis'; import type { User } from 'src/@types'; import { getRandomString } from 'src/utils/getRandomString'; type Payload = Omit<User, 'id' | 'created_at' | 'updated_at' | 'role'>; export async function signUpController(req: Request, res: Response) { const { email, password }: Payload = req.body; const validation = validate<Payload>(req.body, userSchema); if (!validation.isValid) { return res.status(400).send(`Invalid ${validation.invalidKey}`); } try { const user = await UserModel.findOneBy({ email }); if (user) { return res.status(400).json({ message: 'Invalid email or password' }); } const hashedPassword = (await bcrypt.hash(password, 10)) as string; const username = `${email.split('@')[0]}${getRandomString(5)}`; const createdUser = await UserModel.create<Payload>({ email, password: hashedPassword, username, }); const token = await jwt.sign({ id: createdUser.id, }); await Redis.setSession(createdUser.id, token); res.status(200).json({ token, }); } catch (error) { return res.sendStatus(500); } } Denetleyiciye Kaydolun Denetleyici, genellikle bir kayıt formundan kullanıcının e-postasını ve şifresini içeren bir talep alır. Gerekli formatı karşıladığından emin olmak için gelen verileri önceden tanımlanmış bir göre doğrular. userSchema Doğrulama başarılı bir şekilde geçerse, mevcut bir kullanıcı ve geçerli alan bulunmadığını belirtirse, denetleyici kullanarak parolayı karma işlemine tabi tutar, bir oluşturur ve kullanarak kullanıcıyı oluşturur. bcrypt.hash username UserModel.create Son olarak kullanarak bir oluşturur, verilerini ayarlar ve kullanıcıya geri gönderir. jwt token session Redis token Şimdi bir oturum açma denetleyicisinin oluşturulmasına odaklanalım. dosyasını oluşturun: src/controllers/auth/loginController.ts require('dotenv').config({ path: '../../.env', }); import bcrypt from 'bcrypt'; import { Request, Response } from 'express'; import { jwt } from 'src/utils/jwt'; import { UserModel } from 'src/models/UserModel'; import { Redis } from 'src/redis'; export async function loginController(req: Request, res: Response) { const { email, password } = req.body; if (!email || !password) { return res.status(400).json({ message: 'Invalid email or password' }); } try { const user = await UserModel.findByEmail(email); if (user) { const isValidPassword = await bcrypt.compare(password, user.password); if (!isValidPassword) { return res.status(400).json({ message: 'Invalid email or password' }); } const token: string = await jwt.sign({ id: user.id, }); await Redis.setSession(user.id, token); res.status(200).json({ token }); } else { return res.status(400).json({ message: 'Invalid email or password' }); } } catch (error) { console.error(error); return res.sendStatus(500); } } Oturum Açma Denetleyicisi Temel olarak, sağlanan alanları doğrulayarak ve ardından bir kullanıcının varlığını kontrol ederek başlıyoruz. Hiçbir kullanıcı bulunamazsa, davranışa benzer şekilde mesajıyla birlikte 400 durum koduyla yanıt veririz. signupController Invalid email or password Bir kullanıcı varsa, sağlanan parolayı kullanarak veritabanında saklanan karma parolayla karşılaştırmaya devam ederiz. bcrypt.compare Şifreler eşleşmiyorsa tanıdık 'Geçersiz e-posta veya şifre' mesajıyla yanıt veririz. Son olarak, başarılı kimlik doğrulamanın ardından bir token oluşturuyoruz, Redis'te oturumu ayarlıyoruz ve tokenı istemciye geri gönderiyoruz. Ara katman yazılımından elde edilen user_id'nin varlığına bağlı olan korumalı denetleyicilerimizi gözden geçirelim. Bu denetleyiciler içindeki işlemler için sürekli olarak bu user_id'ye güveniyoruz. İsteğin başlığının eksik olduğu durumlarda durum koduyla yanıt vermemiz gerekir. authorization 401 const authHeader = req.headers['authorization']; Aşağıdaki kodla dosyasını oluşturun: src/controllers/user/logoutController.ts import type { Request, Response } from 'express'; import { Redis } from 'src/redis'; export async function logoutController(req: Request, res: Response) { try { await Redis.deleteSession(req.user.id); return res.sendStatus(200); } catch (error) { return res.sendStatus(500); } } Oturum Kapatma Denetleyicisi Bu , bir kullanıcının sistemden çıkış yapmasından sorumludur. Bir istek alındığında, ile ilişkili oturumu silmek için Redis istemcisiyle etkileşime girer. İşlem başarılı olursa, başarılı oturum kapatmayı belirtmek için durum koduyla yanıt verir. logoutController user.id 200 Ancak işlem sırasında bir hata meydana gelirse, dahili sunucu hatası sinyali vermek için durum koduyla yanıt verir. 500 Şimdi kullanıcı verilerinin silinmesi konusunu ele alalım. oluşturun ve şu kodu ekleyin: src/controllers/user/deleteUserController.ts import { Request, Response } from 'express'; import { UserModel } from 'src/models/UserModel'; import { Redis } from 'src/redis'; export const deleteUserController = async (req: Request, res: Response) => { const user_id = req.user.id; try { await Redis.deleteSession(user_id); await UserModel.delete(user_id); return res.sendStatus(200); } catch (error) { return res.sendStatus(500); } }; Kullanıcı denetleyicisini sil Bir istek alındığında, genellikle kimlik doğrulama ara yazılımından elde edilen istek nesnesinden kullanıcı kimliğini çıkarır. Daha sonra Redis istemcisini kullanarak bu ile ilişkili oturumu Redis'ten silmeye devam eder. Daha sonra kullanıcının verilerini veritabanından kaldırmak için yöntemini çağırır. user_id UserModel delete Hem oturumun hem de kullanıcı verilerinin başarıyla silinmesi üzerine, başarılı silme işlemini belirtmek için durum koduyla yanıt verir. Silme işlemi sırasında bir hata oluşması durumunda, dahili sunucu hatasını belirtmek için durum koduyla yanıt verir. 200 500 Sistemdeki kullanıcı verilerini güncellemek için oluşturun ve dosyaya aşağıdaki kodu ekleyin: src/controllers/user/updateUserController.ts import { Request, Response } from 'express'; import { UserModel } from 'src/models/UserModel'; import { filterObject } from 'src/utils/filterObject'; type Payload = { first_name?: string; last_name?: string; username?: string; }; export const updateUserController = async (req: Request, res: Response) => { const { first_name, last_name, username } = req.body; const payload: Payload = filterObject({ first_name, last_name, username, }); try { const existingUserName = await UserModel.findByUsername(username); if (existingUserName) { return res.status(400).json({ error: 'Invalid username', }); } const updatedUser = await UserModel.updateOneById<typeof payload>(req.user.id, payload); res.status(200).json(updatedUser); } catch (error) { res.sendStatus(500); } }; Kullanıcı Denetleyicisini Güncelle Bir istek alındığında, istek gövdesinden , ve alanlarını çıkarır. Daha sonra, veriye yalnızca geçerli alanların dahil edildiğinden emin olmak için yardımcı program işlevini kullanarak bu alanları filtreler. first_name last_name username filterObject Daha sonra, sağlanan veritabanında zaten mevcut olup olmadığını kontrol eder. Böyle bir durumda denetleyici, durum koduyla ve geçersiz belirten bir hata mesajıyla yanıt verir. benzersizse denetleyici, yöntemini kullanarak veritabanındaki kullanıcı verilerini güncellemeye devam eder. username 400 username username UserModel updateOneById Başarılı güncelleme sonrasında durum kodu ve güncellenmiş kullanıcı verileriyle yanıt verir. Güncelleme işlemi sırasında herhangi bir hata olması durumunda denetleyici, dahili sunucu hatasını belirtmek için durum koduyla yanıt verir. 200 500 Sonuncusu, kullanıcı verilerinin güncellenmesiyle hemen hemen aynı fikir olan, ancak yeni parolanın hashlenmesiyle parolayı güncellemek olacaktır. listemizden son denetleyiciyi oluşturun ve kodu ekleyin: src/controllers/user/updatePasswordController.ts import { Request, Response } from 'express'; import { UserModel } from 'src/models/UserModel'; import bcrypt from 'bcrypt'; export const updatePasswordController = async (req: Request, res: Response) => { try { const { password } = req.body; if (!password) return res.sendStatus(400); const hashedPassword = (await bcrypt.hash(password, 10)) as string; const user = await UserModel.updateOneById(req.user.id, { password: hashedPassword }); return res.status(200).json({ id: user.id }); } catch (error) { return res.sendStatus(500); } }; Şifre denetleyicisini güncelle Bir istek alındığında, istek gövdesinden yeni parolayı çıkarır. Daha sonra istek gövdesinde bir parola sağlanıp sağlanmadığını kontrol eder. Değilse, hatalı bir isteği belirten durum koduyla yanıt verir. Daha sonra, kütüphanesini kullanarak 10 tuz faktörüyle yeni şifreyi hashler. 400 bcrypt Karma hale getirilmiş parola daha sonra yöntemi kullanılarak ile ilişkilendirilerek veritabanında güvenli bir şekilde saklanır. Başarılı parola güncellemesi üzerine denetleyici, durum koduyla ve kullanıcının kimliğini içeren bir JSON nesnesiyle yanıt verir. UserModel updateOneById user.id 200 Şifre güncelleme işlemi sırasında herhangi bir hata oluşması durumunda kontrol cihazı diğer kontrol cihazlarında olduğu gibi dahili sunucu hatasını belirtmek için durum koduyla yanıt verir. 500 Doğrulama yardımcısını ve yardımcı programları gözden geçirip kurduğunuzdan emin olun. . Yapılandırıldıktan sonra uç noktaları test etmeye hazır olmalısınız. GitHub deposu Kayıt uç noktasını kontrol edelim: Açıkça görüldüğü gibi, oturumu geri almak için başlıkta kullanılacak bir jeton elde ettik. Başlıktaki yetkilendirme jetonunu sunucuya gönderdik ve yanıt olarak sunucu bize veritabanından alınan kullanıcı verilerini sağladı. Güvenlik özelliklerini ve Redis önbelleğe almayı keşfetmekten ve denemekten çekinmeyin. Temel model uygulandığında, şifrelerini unutan kullanıcılar için hesap kurtarma gibi ek işlevlere girebilirsiniz. Ancak bu konu daha sonraki bir yazıya bırakılacaktır. Çözüm Yönlendirme ve kullanıcı kimlik doğrulama akışını ölçeklenebilir bir şekilde yönetmek zor olabilir. Rotaları korumak için ara katman yazılımı uygularken, hizmetin performansını ve güvenilirliğini artırmak için ek stratejiler de mevcuttur. Hata yönetimi, daha kapsamlı kapsam gerektiren önemli bir husus olmaya devam ettiğinden, daha net hata mesajları sağlanarak daha da geliştirilmiş kullanıcı deneyimi sağlanır. Ancak, kullanıcıların kaydolmasına, hesaplarına erişmesine, oturum verilerini almasına, kullanıcı bilgilerini güncellemesine ve hesapları silmesine olanak tanıyarak birincil kimlik doğrulama akışını başarıyla uyguladık. Umarım bu yolculuğu anlayışlı bulmuşsunuzdur ve kullanıcı kimlik doğrulaması konusunda değerli bilgiler edinmişsinizdir. Kaynaklar GitHub Deposu Knex.js İfade etmek Düğüm Uygulaması Oluştur Postacı da yayınlandı Burada