Đăng nhập, Đăng ký với JWT – Vue + NodeJS + MongoDB giúp ứng dụng của bạn bảo vệ dữ liệu cá nhân, cho phép mỗi người dùng chỉ xem và quản lý todo của riêng họ.
Mục tiêu:
- Tạo API đăng ký và đăng nhập bằng NodeJS + MongoDB
- Sử dụng JWT để xác thực người dùng
- Vue lưu
token
tronglocalStorage
- Bảo vệ API: mỗi người chỉ thấy todo của mình
Phần 1: Cập nhật Backend
1.1 Cài thêm thư viện:
npm install bcryptjs jsonwebtoken
1.2 Thêm User
schema
// models/User.js
const mongoose = require('mongoose')
const bcrypt = require('bcryptjs')
const userSchema = new mongoose.Schema({
username: { type: String, unique: true },
password: String
})
// Mã hóa mật khẩu trước khi lưu
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next()
this.password = await bcrypt.hash(this.password, 10)
next()
})
userSchema.methods.comparePassword = function (pass) {
return bcrypt.compare(pass, this.password)
}
module.exports = mongoose.model('User', userSchema)
1.3 Cập nhật index.js
– tạo routes auth
const User = require('./models/User')
const jwt = require('jsonwebtoken')
const SECRET = 'vue-app-secret-key' // => nên dùng .env
// Đăng ký
app.post('/auth/register', async (req, res) => {
const { username, password } = req.body
try {
const user = new User({ username, password })
await user.save()
res.json({ message: 'Đăng ký thành công' })
} catch (e) {
res.status(400).json({ error: 'Username đã tồn tại' })
}
})
// Đăng nhập
app.post('/auth/login', async (req, res) => {
const { username, password } = req.body
const user = await User.findOne({ username })
if (!user || !(await user.comparePassword(password))) {
return res.status(401).json({ error: '❌ Sai tài khoản hoặc mật khẩu' })
}
const token = jwt.sign({ userId: user._id }, SECRET, { expiresIn: '7d' })
res.json({ token })
})
1.4 Middleware xác thực JWT
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.split(' ')[1]
if (!token) return res.status(401).json({ error: 'Unauthorized' })
try {
const decoded = jwt.verify(token, SECRET)
req.userId = decoded.userId
next()
} catch (e) {
return res.status(403).json({ error: 'Token không hợp lệ' })
}
}
1.5 Gắn vào route /todos
const Todo = mongoose.model('Todo', {
title: String,
completed: Boolean,
createdAt: { type: Date, default: Date.now },
userId: String
})
// Đổi toàn bộ route /todos:
app.get('/todos', authMiddleware, async (req, res) => {
const todos = await Todo.find({ userId: req.userId }).sort({ createdAt: -1 })
res.json(todos)
})
app.post('/todos', authMiddleware, async (req, res) => {
const todo = new Todo({ ...req.body, userId: req.userId })
await todo.save()
res.json(todo)
})
app.put('/todos/:id', authMiddleware, async (req, res) => {
const todo = await Todo.findOneAndUpdate(
{ _id: req.params.id, userId: req.userId },
req.body,
{ new: true }
)
res.json(todo)
})
app.delete('/todos/:id', authMiddleware, async (req, res) => {
await Todo.findOneAndDelete({ _id: req.params.id, userId: req.userId })
res.json({ message: 'Deleted' })
})
Phần 2: Cập nhật Vue Frontend
2.1 Tạo store authStore.js
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
token: localStorage.getItem('token') || '',
username: '',
error: ''
}),
actions: {
async login(username, password) {
const res = await fetch('http://localhost:5000/auth/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: { 'Content-Type': 'application/json' }
})
const data = await res.json()
if (res.ok) {
this.token = data.token
localStorage.setItem('token', data.token)
this.error = ''
} else {
this.error = data.error || 'Lỗi đăng nhập'
}
},
async register(username, password) {
const res = await fetch('http://localhost:5000/auth/register', {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: { 'Content-Type': 'application/json' }
})
const data = await res.json()
if (!res.ok) {
this.error = data.error || 'Lỗi đăng ký'
}
},
logout() {
this.token = ''
localStorage.removeItem('token')
}
}
})
2.2 Dùng token khi gọi todo API
Trong
todoStore.js
, thay dòngfetch(API_URL)
bằng:
fetch(API_URL, {
headers: {
Authorization: 'Bearer ' + localStorage.getItem('token')
}
})
Áp dụng tương tự cho POST
, PUT
, DELETE
.
2.3 Tạo component login/register đơn giản
Ví dụ:
LoginView.vue
– bạn có thể gọiauthStore.login(...)
Và sau khi login thành công → gọitodoStore.fetchTodos()
lại.
Bạn đã học được:
Kỹ năng | Vai trò |
---|---|
JWT Auth | Bảo mật API, phân quyền người dùng |
Pinia authStore | Quản lý đăng nhập toàn cục |
Middleware | Chặn truy cập nếu chưa login |
Token lưu trong localStorage | Ghi nhớ đăng nhập |
Bài tập mở rộng:
- Làm giao diện đẹp hơn cho login / register
- Hiển thị “Xin chào [username]” sau khi đăng nhập
- Tự động logout khi token hết hạn (dùng
jwt-decode
để kiểm tra)
Thảo luận