Skip to main content
The MarsAI platform uses JWT (JSON Web Tokens) for stateless authentication with bcrypt password hashing to securely store user credentials.

Architecture Overview

The authentication system consists of three main components:
  1. Password Hashing - Bcrypt with 10 salt rounds
  2. JWT Token Generation - Signed tokens with configurable expiration
  3. Middleware Authentication - Bearer token validation on protected routes

Password Security

Bcrypt Implementation

Passwords are hashed using bcrypt with 10 salt rounds before storage:
// /back/src/utils/password.js
import * as bcrypt from "bcrypt";

const SALT_ROUNDS = 10;

async function hashPassword(password) {
  return bcrypt.hash(password, SALT_ROUNDS);
}

async function comparePassword(password, hashedPassword) {
  return bcrypt.compare(password, hashedPassword);
}
Bcrypt automatically handles salt generation and storage within the hash string. The resulting hash format is $2b$10$... where 10 indicates the cost factor (2^10 rounds).

Authentication Flow

1
User Registration
2
New users submit credentials through the registration endpoint:
3
// POST /auth/register
const newUser = await User.create({
  first_name: userFirstName,
  last_name: userLastName,
  email,
  password: hashedPassword, // bcrypt hash
  role: userRole || "PRODUCER"
});
4
User Login
5
Authentication happens in two stages:
6
1. Credential Verification
7
// /back/src/controllers/AuthController.js:20-60
function login(req, res) {
  const { email, password } = req.body;

  // Find user by email
  User.findOne({ where: { email } }).then((user) => {
    if (!user) {
      return res.status(401).json({ error: "Identifiants invalides" });
    }

    // Compare password with stored hash
    comparePassword(password, user.password).then((isMatch) => {
      if (!isMatch) {
        return res.status(401).json({ error: "Identifiants invalides" });
      }

      // Create JWT token
      const token = jwt.sign(
        { id: user.id_user, role: user.role },
        process.env.JWT_SECRET,
        { expiresIn: process.env.JWT_EXPIRES_IN || "1h" }
      );

      return res.status(200).json({
        message: "Connexion réussie",
        data: {
          email: user.email,
          first_name: user.first_name,
          role: user.role,
          token
        }
      });
    });
  });
}
8
2. JWT Token Generation
9
The token payload includes:
10
  • id: User ID (id_user)
  • role: User role (ADMIN, JURY, or PRODUCER)
  • Expiration time (default: 1 hour)
  • 11
    Request Authentication
    12
    Protected routes validate the JWT token:
    13
    // /back/src/middlewares/AuthMiddleware.js:5-63
    export default function AuthMiddleware(roles = []) {
      return async function(req, res, next) {
        // Extract Bearer token from Authorization header
        const authHeader = req.header("Authorization");
        const [prefix, token] = authHeader?.split(" ") || [null, undefined];
    
        if (prefix !== "Bearer") {
          return res.status(401).json({ error: "No Bearer token" });
        }
    
        if (!token) {
          return res.status(401).json({ 
            error: "You must be authenticated to access this resource" 
          });
        }
    
        try {
          // Verify JWT signature and expiration
          const decoded = jwt.verify(token, process.env.JWT_SECRET);
    
          if (!decoded?.id) {
            return res.status(401).json({ error: "Invalid Payload" });
          }
    
          // Fetch user from database
          const user = await User.findOne({
            where: { id_user: decoded.id }
          });
    
          // Check role-based authorization
          if (!user || (roles.length && !roles.includes(user.role))) {
            return res.status(401).json({
              error: "User not found or unauthorized"
            });
          }
    
          // Attach user to request object
          req.user = user;
          return next();
        } catch (error) {
          return res.status(401).json({ error: error.message });
        }
      }
    }
    

    Frontend Integration

    Login API Call

    // /front/src/api/auth.js
    import instance from "./config.js";
    
    async function login(data) {
      return await instance.post("auth/login", data);
    }
    
    export { login };
    

    Login Component

    // /front/src/pages/auth/Login.jsx:62-102
    const loginMutation = useMutation({
      mutationFn: async (data) => {
        return await login(data);
      },
      onSuccess: (response) => {
        const userData = response.data?.data || response.data;
        
        // Store JWT and user data in localStorage
        localStorage.setItem("email", userData?.email);
        localStorage.setItem("firstName", userData?.first_name || "");
        localStorage.setItem("role", userData?.role);
        localStorage.setItem("token", userData?.token);
    
        // Redirect based on role
        switch (userData?.role) {
          case "ADMIN":
            navigate("/admin");
            break;
          case "JURY":
            navigate("/jury");
            break;
          case "PRODUCER":
            navigate("/producer");
            break;
          default:
            navigate("/");
            break;
        }
      },
      onError: (error) => {
        alert(error.response?.data?.error || "Erreur de connexion");
      }
    });
    

    Making Authenticated Requests

    The frontend includes the JWT token in the Authorization header:
    import axios from "axios";
    
    const instance = axios.create({
      baseURL: import.meta.env.VITE_API_URL
    });
    
    // Add token to all requests
    instance.interceptors.request.use((config) => {
      const token = localStorage.getItem("token");
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    });
    

    Environment Configuration

    Required environment variables:
    # JWT secret key (use a strong random string in production)
    JWT_SECRET=your-secret-key-here
    
    # Token expiration time (default: 1h)
    JWT_EXPIRES_IN=1h
    
    Always use a strong, randomly generated JWT_SECRET in production. Never commit secrets to version control.

    Security Best Practices

    • Minimum length enforced at application level
    • Bcrypt salt rounds: 10 (balance between security and performance)
    • Passwords never stored in plain text
    • Password comparison uses constant-time algorithm
    • Tokens expire after 1 hour by default
    • Tokens stored in localStorage (consider httpOnly cookies for enhanced security)
    • Each token includes user ID and role for authorization
    • Invalid tokens return 401 Unauthorized
    • All sensitive endpoints protected with AuthMiddleware
    • Role-based access control enforced at middleware level
    • Bearer token format required
    • Token verification happens on every request

    Error Handling

    Status CodeError MessageCause
    401”No Bearer token”Missing or malformed Authorization header
    401”Invalid Payload”Token missing user ID
    401”User not found or unauthorized”User doesn’t exist or lacks required role
    401”Identifiants invalides”Wrong email or password
    403”Permission denied”User authenticated but lacks required role

    Special Registration Flow

    Producers can register with a film submission simultaneously:
    // POST /auth/register-film
    async function registerWithFilm(req, res) {
      const transaction = await db.sequelize.transaction();
      try {
        // Create user
        const hashedPassword = await hashPassword(password);
        const newUser = await User.create({
          first_name: userFirstName,
          last_name: userLastName,
          email,
          password: hashedPassword,
          role: "PRODUCER"
        }, { transaction });
    
        // Create associated film
        const newMovie = await Movie.create({
          title: filmTitleOriginal,
          description: synopsisOriginal,
          id_user: newUser.id_user
        }, { transaction });
    
        await transaction.commit();
        return res.status(201).json({ user, movie });
      } catch (error) {
        await transaction.rollback();
        return res.status(500).json({ error: error.message });
      }
    }
    
    This endpoint uses database transactions to ensure atomicity - if film creation fails, user creation is rolled back.