Authentication and Authorization
Authentication and authorization are core mechanisms for building secure applications. Authentication verifies user identity, while authorization determines which resources a user can access. In Node.js, these functionalities are typically implemented using middleware and libraries such as Passport, JWT, and OAuth2.0.
Basic Concepts of Authentication
Authentication is the process of verifying a user's identity. Common methods include username/password, social login, and API keys. In Node.js, bcrypt
can be used to hash passwords for security.
const bcrypt = require('bcrypt');
const saltRounds = 10;
async function hashPassword(password) {
const salt = await bcrypt.genSalt(saltRounds);
return await bcrypt.hash(password, salt);
}
async function comparePassword(password, hash) {
return await bcrypt.compare(password, hash);
}
Session Management and Cookies
HTTP is stateless, so session management is needed to track users. In Express, the express-session
middleware can be used:
const session = require('express-session');
app.use(session({
secret: 'your_secret_key',
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}));
JWT Authentication
JSON Web Token (JWT) is a stateless authentication method. It consists of three parts: header, payload, and signature. The jsonwebtoken
library simplifies JWT generation and verification:
const jwt = require('jsonwebtoken');
const token = jwt.sign({ userId: 123 }, 'secret_key', { expiresIn: '1h' });
jwt.verify(token, 'secret_key', (err, decoded) => {
if (err) throw err;
console.log(decoded); // { userId: 123, iat: ..., exp: ... }
});
OAuth2.0 and Third-Party Login
OAuth2.0 allows users to log in via third-party services like Google or GitHub. Passport.js provides multiple strategies:
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: 'your_client_id',
clientSecret: 'your_client_secret',
callbackURL: '/auth/google/callback'
},
(accessToken, refreshToken, profile, done) => {
// Handle user data
done(null, profile);
}
));
Authorization Implementation
Authorization determines what a user can do. Common models include RBAC (Role-Based Access Control) and ABAC (Attribute-Based Access Control). Here's an RBAC example:
function checkRole(role) {
return (req, res, next) => {
if (req.user.role !== role) {
return res.status(403).send('Forbidden');
}
next();
};
}
app.get('/admin', checkRole('admin'), (req, res) => {
res.send('Admin Dashboard');
});
Fine-Grained Permission Control
For more granular control, ACL (Access Control List) or policy patterns can be used:
const permissions = {
admin: ['read', 'write', 'delete'],
user: ['read']
};
function checkPermission(action) {
return (req, res, next) => {
if (!permissions[req.user.role].includes(action)) {
return res.status(403).send('Forbidden');
}
next();
};
}
Security Best Practices
- HTTPS: Always use HTTPS for sensitive data transmission.
- CSRF Protection: Use the
csurf
middleware to prevent cross-site request forgery. - Rate Limiting: Use
express-rate-limit
to prevent brute-force attacks.
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // Limit requests
});
app.use(limiter);
Common Vulnerabilities and Mitigations
- SQL Injection: Use parameterized queries or ORM libraries.
- XSS Attacks: Escape user input and use the
helmet
middleware. - JWT Security: Avoid storing sensitive data in JWTs and set reasonable expiration times.
const helmet = require('helmet');
app.use(helmet());
Practical Case Study
Consider a blog platform where users can post articles, but only admins can delete them. Here's the implementation:
app.post('/articles', authenticateUser, (req, res) => {
// User posts an article
});
app.delete('/articles/:id', authenticateUser, checkRole('admin'), (req, res) => {
// Admin deletes an article
});
Performance Optimization
Authentication and authorization can become performance bottlenecks. Optimize by:
- Caching: Cache user permission data.
- Async Validation: Validate JWTs asynchronously.
- Minimize JWT Size: Avoid storing excessive data in JWTs.
const redis = require('redis');
const client = redis.createClient();
function cacheUserPermissions(userId, permissions) {
client.setex(`perms:${userId}`, 3600, JSON.stringify(permissions));
}
Testing and Debugging
Write test cases to ensure authentication and authorization logic is correct. Use supertest
for HTTP testing:
const request = require('supertest');
describe('Auth API', () => {
it('should deny access without token', async () => {
const res = await request(app).get('/protected');
expect(res.statusCode).toEqual(401);
});
});
Logging and Monitoring
Log authentication and authorization events for troubleshooting:
const winston = require('winston');
const logger = winston.createLogger({
transports: [new winston.transports.File({ filename: 'auth.log' })]
});
app.use((req, res, next) => {
logger.info(`${req.method} ${req.path} - ${req.user?.id || 'anonymous'}`);
next();
});
Multi-Factor Authentication (MFA)
Enhance security with MFA, such as SMS codes or TOTP:
const speakeasy = require('speakeasy');
const secret = speakeasy.generateSecret();
const token = speakeasy.totp({
secret: secret.base32,
encoding: 'base32'
});
// Verify token
const verified = speakeasy.totp.verify({
secret: secret.base32,
encoding: 'base32',
token: userToken,
window: 1
});
Authentication in Microservices
In a microservices architecture, use an API gateway for centralized authentication or pass JWTs:
// Pass JWT between services
axios.get('http://service-b/data', {
headers: { 'Authorization': `Bearer ${jwtToken}` }
});
Serverless Authentication
In serverless environments like AWS Lambda, use Cognito or Auth0:
exports.handler = async (event) => {
const user = event.requestContext.authorizer.claims;
return { statusCode: 200, body: JSON.stringify(user) };
};
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn