Authentification avec JWT et OAuth2

Guide d'implémentation de l'authentification sécurisée avec JWT et OAuth2.

JSON Web Tokens (JWT)

// Node.js avec jsonwebtoken
const jwt = require('jsonwebtoken');

// Création d'un token
const generateToken = (user) => {
  return jwt.sign(
    {
      id: user.id,
      email: user.email,
      role: user.role
    },
    process.env.JWT_SECRET,
    { expiresIn: '24h' }
  );
};

// Middleware de vérification
const verifyToken = (req, res, next) => {
  try {
    const token = req.headers.authorization.split(' ')[1];
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ message: 'Invalid token' });
  }
};

// Utilisation dans les routes
app.post('/login', async (req, res) => {
  const { email, password } = req.body;

  const user = await User.findOne({ email });
  if (!user || !user.comparePassword(password)) {
    return res.status(401).json({
      message: 'Invalid credentials'
    });
  }

  const token = generateToken(user);
  res.json({ token });
});

// Route protégée
app.get('/profile', verifyToken, (req, res) => {
  res.json({ user: req.user });
});

OAuth2 et Google Login

// Configuration Passport avec Google OAuth2
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 {
      let user = await User.findOne({ googleId: profile.id });

      if (!user) {
        user = await User.create({
          googleId: profile.id,
          email: profile.emails[0].value,
          name: profile.displayName
        });
      }

      return done(null, user);
    } catch (error) {
      return done(error, null);
    }
  }
));

// Routes OAuth2
app.get('/auth/google',
  passport.authenticate('google', {
    scope: ['profile', 'email']
  })
);

app.get('/auth/google/callback',
  passport.authenticate('google', {
    failureRedirect: '/login'
  }),
  (req, res) => {
    const token = generateToken(req.user);
    res.redirect(`/dashboard?token=${token}`);
  }
);

Hachage des mots de passe

// bcrypt
const bcrypt = require('bcrypt');

class User {
  async hashPassword(password) {
    const salt = await bcrypt.genSalt(12);
    return bcrypt.hash(password, salt);
  }

  async comparePassword(password) {
    return bcrypt.compare(password, this.password);
  }
}

// argon2
const argon2 = require('argon2');

class User {
  async hashPassword(password) {
    return argon2.hash(password, {
      type: argon2.argon2id,
      memoryCost: 2 ** 16,
      timeCost: 3,
      parallelism: 1
    });
  }

  async comparePassword(password) {
    return argon2.verify(this.password, password);
  }
}

Bonnes pratiques :

  • Utiliser un facteur de coût élevé pour bcrypt (> 12)
  • Préférer argon2id pour une sécurité optimale
  • Jamais stocker les mots de passe en clair
  • Utiliser des secrets différents pour JWT par environnement

Protection CORS/CSRF

// Configuration CORS
const cors = require('cors');

app.use(cors({
  origin: ['https://example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true,
  allowedHeaders: ['Content-Type', 'Authorization']
}));

// Protection CSRF
const csrf = require('csurf');
const cookieParser = require('cookie-parser');

app.use(cookieParser());
app.use(csrf({ cookie: true }));

app.get('/form', (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

// Frontend
<form action="/submit" method="POST">
  <input type="hidden"
         name="_csrf"
         value="{{csrfToken}}">
  <!-- autres champs -->
</form>

Sécurité HTTP avec Helmet

const helmet = require('helmet');

// Configuration de base
app.use(helmet());

// Configuration avancée
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "'unsafe-inline'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        imgSrc: ["'self'", "data:", "https:"],
        connectSrc: ["'self'", "https://api.example.com"]
      }
    },
    hsts: {
      maxAge: 31536000,
      includeSubDomains: true,
      preload: true
    },
    frameguard: {
      action: "deny"
    },
    referrerPolicy: {
      policy: "strict-origin-when-cross-origin"
    }
  })
);

// Rate limiting
const rateLimit = require('express-rate-limit');

app.use(
  rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100, // limite par IP
    message: 'Too many requests, please try again later.'
  })
);