keertirajmalik / expenser

1 stars 0 forks source link

Security concern: Avoid storing tokens in localStorage #20

Open coderabbitai[bot] opened 3 days ago

coderabbitai[bot] commented 3 days ago

In PR #14 (https://github.com/keertirajmalik/expenser/pull/14), a discussion at https://github.com/keertirajmalik/expenser/pull/14#discussion_r1852259136 highlighted that storing tokens in localStorage can expose them to XSS attacks. It's recommended to consider more secure storage mechanisms, such as HTTP-only cookies or in-memory storage.\n\nOpened as per request by @keertirajmalik.

keertirajmalik commented 1 day ago

@coderabbitai do you have solution to this issue

coderabbitai[bot] commented 1 day ago

[!TIP] For best results, initiate chat on the files or code changes.

@keertirajmalik, yes, I can help address this issue. Storing tokens in localStorage can expose them to XSS attacks. To enhance security, you can consider using HTTP-only cookies to store your tokens. HTTP-only cookies are not accessible via JavaScript, which reduces the risk of token theft through XSS vulnerabilities.

Here's how you can implement this solution:

  1. Server-side: Set the token in an HTTP-only cookie upon authentication

    When a user logs in, generate a token and send it as an HTTP-only cookie in the response. Ensure you set the HttpOnly and Secure flags.

    // Example using Express.js
    app.post('/login', (req, res) => {
     // Authenticate user credentials
     // ...
    
     // Generate JWT or session token
     const token = generateToken(user);
    
     // Set the token in an HTTP-only cookie
     res.cookie('token', token, {
       httpOnly: true,
       secure: true,      // Set to true if using HTTPS
       sameSite: 'Strict' // Adjust based on your cross-site requirements
     });
    
     res.status(200).json({ message: 'Authentication successful' });
    });
  2. Client-side: Omit token handling in requests

    Since the token is stored in an HTTP-only cookie, it's automatically included in requests to the server's domain. You no longer need to attach the token manually in your client-side code.

    // Example API call without token attachment
    fetch('/api/protected-endpoint')
     .then(response => response.json())
     .then(data => {
       // Handle the protected data
     })
     .catch(error => {
       // Handle errors
     });
  3. Server-side: Verify the token from cookies on protected routes

    Protect your API endpoints by verifying the token from the incoming request's cookies.

    // Middleware to verify token
    function verifyToken(req, res, next) {
     const token = req.cookies.token;
    
     if (!token) {
       return res.status(401).json({ message: 'Unauthorized' });
     }
    
     try {
       // Verify the token (e.g., using JWT)
       const decoded = verifyToken(token);
       req.user = decoded;
       next();
     } catch (err) {
       res.status(401).json({ message: 'Invalid token' });
     }
    }
    
    // Protected route example
    app.get('/api/protected-endpoint', verifyToken, (req, res) => {
     // Handle the request knowing the user is authenticated
     res.json({ data: 'Protected data' });
    });
  4. Ensure Secure Deployment

    • Use HTTPS: The secure flag on cookies ensures they're only sent over HTTPS. Make sure your application is served over HTTPS in production.
    • SameSite Attribute: Set the SameSite attribute to prevent CSRF attacks. Strict is the most secure but may affect cross-site requests.
    • Cookie Lifetime: Set appropriate expiry times for your cookies based on your application's needs.

Alternative: If you prefer not to use cookies, you can store tokens in memory (e.g., React state or Redux store). However, tokens stored in memory are lost on page refresh, which may log out the user unexpectedly. This approach is more secure than localStorage but may impact user experience.

Let me know if you need further assistance implementing this solution or have any questions!