Backend-utveckling · Autentisering
Autentisering
Säker autentisering och auktorisering för webbapplikationer
01 · Översikt
Vad sidan tar upp
Exempel på säker autentisering: från lösenordshantering och JWT till OAuth och tvåfaktorsautentisering. Varje exempel visar kod du kan utgå från, följt av mönster för åtkomstkontroll, best practices och de vanligaste sårbarheterna.
- 4
- Autentiseringsexempel
- 3
- Säkerhetsmönster
- 4
- Best practices
- 4
- Sårbarheter
02 · Implementation
Autentiseringsexempel
JWT Token Generation
Generera och validera JWT tokens för säker autentisering.
const jwt = require('jsonwebtoken');
// Generera token
function generateToken(user) {
const payload = {
id: user.id,
email: user.email,
role: user.role
};
const token = jwt.sign(
payload,
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
return token;
}
// Verifiera token
function verifyToken(token) {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
return { valid: true, user: decoded };
} catch (error) {
return { valid: false, error: error.message };
}
}Password Hashing med Bcrypt
Säker lösenordshantering med bcrypt för hashning och salt.
const bcrypt = require('bcrypt');
// Hash lösenord
async function hashPassword(password) {
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);
return hashedPassword;
}
// Validera lösenord
async function validatePassword(plainPassword, hashedPassword) {
const isValid = await bcrypt.compare(plainPassword, hashedPassword);
return isValid;
}
// Exempel användning
const password = 'mySecurePassword123';
const hash = await hashPassword(password);
console.log('Hashed:', hash);
const isValid = await validatePassword(password, hash);
console.log('Valid:', isValid); // trueOAuth 2.0 Implementation
Implementera OAuth 2.0 för social login (Google exempel).
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/auth/google/callback"
},
async (accessToken, refreshToken, profile, done) => {
try {
// Kolla om användare finns
let user = await User.findOne({ googleId: profile.id });
if (!user) {
// Skapa ny användare
user = await User.create({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
avatar: profile.photos[0].value
});
}
return done(null, user);
} catch (error) {
return done(error, null);
}
}
));
// Routes
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => {
// Generera JWT token
const token = generateToken(req.user);
res.redirect(`/dashboard?token=${token}`);
}
);Session Management
Hantera sessioner med express-session och Redis.
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis');
// Skapa Redis client
const redisClient = redis.createClient({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
password: process.env.REDIS_PASSWORD
});
// Konfigurera sessions
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS i produktion
httpOnly: true, // Skydda mot XSS
maxAge: 1000 * 60 * 60 * 24 * 7, // 7 dagar
sameSite: 'strict' // CSRF-skydd
}
}));
// Login route
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await authenticateUser(email, password);
if (user) {
req.session.userId = user.id;
req.session.userRole = user.role;
res.json({ success: true, user });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
// Logout route
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
res.status(500).json({ error: 'Could not log out' });
} else {
res.clearCookie('connect.sid');
res.json({ success: true });
}
});
});03 · Mönster
Säkerhetsmönster
Role-Based Access Control (RBAC)
Implementera rollbaserad åtkomstkontroll för olika användarnivåer.
// Middleware för rollbaserad åtkomst
function authorize(roles = []) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (roles.length && !roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
// Använd i routes
app.get('/api/admin/users',
authenticate,
authorize(['admin']),
getUsers
);
app.get('/api/profile',
authenticate,
authorize(['user', 'admin']),
getProfile
);
app.delete('/api/posts/:id',
authenticate,
authorize(['admin', 'moderator']),
deletePost
);Two-Factor Authentication (2FA)
Implementera tvåfaktorsautentisering med TOTP.
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
// Aktivera 2FA
app.post('/api/2fa/enable', authenticate, async (req, res) => {
const secret = speakeasy.generateSecret({
name: `MyApp (${req.user.email})`
});
// Spara secret tillfälligt
await redis.setex(
`2fa_temp_${req.user.id}`,
300, // 5 minuter
JSON.stringify(secret)
);
// Generera QR-kod
const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
res.json({
secret: secret.base32,
qrCode: qrCodeUrl
});
});
// Verifiera och aktivera 2FA
app.post('/api/2fa/verify', authenticate, async (req, res) => {
const { token } = req.body;
// Hämta tillfällig secret
const tempSecret = await redis.get(`2fa_temp_${req.user.id}`);
if (!tempSecret) {
return res.status(400).json({ error: 'No pending 2FA activation' });
}
const secret = JSON.parse(tempSecret);
// Verifiera token
const verified = speakeasy.totp.verify({
secret: secret.base32,
encoding: 'base32',
token,
window: 1
});
if (verified) {
// Spara secret permanent
await User.update(req.user.id, {
twoFactorSecret: secret.base32,
twoFactorEnabled: true
});
await redis.del(`2fa_temp_${req.user.id}`);
res.json({ success: true });
} else {
res.status(400).json({ error: 'Invalid token' });
}
});Rate Limiting
Skydda API:et mot brute force-attacker med rate limiting.
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
// Global rate limiter
const apiLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rl:api:'
}),
windowMs: 15 * 60 * 1000, // 15 minuter
max: 100, // Max 100 requests per IP
message: 'Too many requests from this IP'
});
// Striktare limiter för login
const loginLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rl:login:'
}),
windowMs: 15 * 60 * 1000,
max: 5, // Max 5 login-försök
skipSuccessfulRequests: true, // Räkna bara misslyckade försök
keyGenerator: (req) => {
// Använd både IP och email för nyckeln
return `${req.ip}:${req.body.email || ''}`;
}
});
// Applicera limiters
app.use('/api/', apiLimiter);
app.post('/api/login', loginLimiter, loginHandler);04 · Praxis
Best practices
- Använd HTTPS ÖveralltAll autentiseringsdata måste skickas över krypterade anslutningarExempel: Tvinga HTTPS med middleware och sätt secure-flaggan på cookies
- Implementera Token RotationFörnya tokens regelbundet för att minimera risken vid kompromissExempel: Använd refresh tokens med kort livstid för access tokens
- Logga SäkerhetshändelserSpara alla autentiseringsförsök och säkerhetsrelaterade händelserExempel: Logga misslyckade inloggningar, lösenordsändringar och behörighetsfel
- Implementera Account LockoutLås konton efter upprepade misslyckade inloggningsförsökExempel: Lås efter 5 försök och kräv email-verifiering för att låsa upp
05 · Risker
Vanliga sårbarheter
- Session FixationÅteranvänd inte session-ID efter inloggningLösning: Regenerera session-ID vid varje inloggning med req.session.regenerate()
- Password Reset PoisoningValidera alltid host-header vid lösenordsåterställningLösning: Använd vit-lista för tillåtna domäner och validera redirect-URLs
- JWT Algorithm ConfusionSpecificera alltid algoritm vid JWT-verifieringLösning: Använd { algorithms: ['HS256'] } i verify() för att undvika 'none' algoritm
- Timing AttacksAnvänd konstant-tids jämförelser för känslig dataLösning: Använd crypto.timingSafeEqual() för token- och lösenordsjämförelser
06 · Tips
Säkerhetstips
- Aldrig spara lösenord i klartext
- Använd alltid bcrypt eller liknande för att hasha lösenord med salt.
- Implementera säkerhetshuvuden
- Använd
helmet.jsför att sätta viktiga HTTP-säkerhetshuvuden automatiskt. - Regelbundna säkerhetsgranskningar
- Kör
npm auditoch håll alla säkerhetsrelaterade paket uppdaterade.