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.

JavaScript
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.

JavaScript
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); // true

OAuth 2.0 Implementation

Implementera OAuth 2.0 för social login (Google exempel).

JavaScript
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.

JavaScript
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.

JavaScript
// 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.

JavaScript
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.

JavaScript
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.js för att sätta viktiga HTTP-säkerhetshuvuden automatiskt.
Regelbundna säkerhetsgranskningar
Kör npm audit och håll alla säkerhetsrelaterade paket uppdaterade.