DEV Community

Cover image for Securing Web Applications: Stateful vs. Stateless Systems, Authentication, and Authorization in Node.js
Sushant Gaurav
Sushant Gaurav

Posted on

Securing Web Applications: Stateful vs. Stateless Systems, Authentication, and Authorization in Node.js

In our previous articles, we've laid the foundation of NodeJS, exploring its core concepts, including how NodeJS works, building servers, managing URLs, HTTP methods, utilizing the power of the Express framework, REST APIs, middleware, HTTP headers, MongoDB, Models, Views, Controllers, etc.

Link to the previous articles:

  1. Getting Started with NodeJS
  2. Deepening NodeJS Knowledge: URLs, HTTP Methods, Express Framework, and Versioning
  3. Mastering NodeJS: REST APIs, Middleware, and HTTP Headers
  4. Mastering Backend Development with NodeJS: MongoDB Integration, Mongoose, CRUD Operations, and MVC Architecture

Continuing our journey into NodeJS, this article explores the differences between stateful and stateless systems, session management, authentication and authorization in NodeJS, JWT tokens, cookies, and a real-world use case that ties these concepts together.

Understanding Stateful vs Stateless Systems, Authentication, and Authorization in NodeJS

Stateful vs Stateless Systems

What is a Stateful System?

A stateful system is one where the server maintains information (state) about the client across multiple requests. This means that the server can remember previous interactions with the client and provide responses based on that context.

  • Example: A shopping cart on an e-commerce site where the server remembers the items added to the cart across different page visits.

What is a Stateless System?

In contrast, a stateless system does not retain any client information between requests. Each request from the client is treated as an independent transaction that is unrelated to previous requests. The server does not store any session information.

  • Example: RESTful APIs, where each request contains all the information needed for the server to fulfil it.

How Does Client-Server Interaction Work in Both Cases?

  • Stateful Interaction: The server stores session data, such as user login status or shopping cart contents. Each client request is associated with a session identifier, allowing the server to maintain continuity across multiple requests.

    • Example: A client logs in, and the server generates a session ID. This ID is used for subsequent requests to identify the client.
  • Stateless Interaction: Each request from the client must contain all the necessary information for the server to process it. The server does not retain any knowledge of prior interactions with the client.

    • Example: Each API request includes authentication tokens and other relevant data since the server doesn't retain any session data.

Session ID: Managing Client Sessions

A session ID is a unique identifier that the server assigns to a client to maintain session data. It allows the server to identify requests from the same client across multiple interactions.

Ways the Server Provides a Session ID:

  • Cookies: The session ID is stored in a cookie on the clientside and sent with each request.
    • Pros: Widely supported, easy to implement.
    • Cons: Vulnerable to cross-site scripting (XSS) attacks if not properly secured.
  • URL Parameters: The session ID is included in the URL of each request.
    • Pros: Simple to implement.
    • Cons: Exposes session ID in the URL, making it vulnerable to attacks if intercepted.
  • Hidden Form Fields: The session ID is included as a hidden field in forms submitted by the client.
    • Pros: Useful for form submissions.
    • Cons: Limited to form-based interactions. Which One is Better?
  • Cookies are generally considered the best option for session management because they are automatically sent with each HTTP request, reducing the complexity of session handling. However, they must be properly secured (e.g., using the HttpOnly and Secure flags) to prevent attacks.

Authentication and Authorization in NodeJS

What is Authentication?

Authentication is the process of verifying the identity of a user or system. In a typical web application, authentication involves verifying a user's credentials (e.g., username and password) to ensure they are who they claim to be.

What is Authorization?

Authorization determines what an authenticated user is allowed to do. It defines the permissions and access levels within the system.

  • Example: After authentication, a user may be authorized to access certain resources or perform specific actions based on their role (e.g., admin, user).

Implementing Authentication and Authorization in NodeJS

Example: Using Passport.js for Authentication
Passport.js is a popular middleware for authentication in NodeJS applications. It supports various strategies (e.g., local, OAuth) for authenticating users.

  • Installation:
  npm install passport passport-local express-session
Enter fullscreen mode Exit fullscreen mode
  • Setting Up:
  const express = require('express');
  const passport = require('passport');
  const LocalStrategy = require('passport-local').Strategy;
  const session = require('express-session');

  const app = express();

  // Session setup
  app.use(session({
    secret: 'secret',
    resave: false,
    saveUninitialized: false
  }));

  // Passport setup
  app.use(passport.initialize());
  app.use(passport.session());

  // Define the local strategy
  passport.use(new LocalStrategy((username, password, done) => {
    // Here, you would verify the user's credentials with your database
    if (username === 'user' && password === 'password') {
      return done(null, { id: 1, username: 'user' });
    } else {
      return done(null, false, { message: 'Incorrect credentials.' });
    }
  }));

  // Serialize and deserialize user instances
  passport.serializeUser((user, done) => done(null, user.id));
  passport.deserializeUser((id, done) => {
    // Find user by ID in database
    done(null, { id: 1, username: 'user' });
  });

  app.post('/login', passport.authenticate('local', {
    successRedirect: '/dashboard',
    failureRedirect: '/login'
  }));

  app.get('/dashboard', (req, res) => {
    if (req.isAuthenticated()) {
      res.send('Welcome to your dashboard, ' + req.user.username);
    } else {
      res.redirect('/login');
    }
  });

  app.listen(3000, () => console.log('Server started on port 3000'));
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • Authentication: The server verifies the user's credentials (username and password) using the passport-local strategy.
    • Authorization: Once authenticated, the user can access protected routes like /dashboard.

JWT Token: What It Is and Why It Is Used?

What is a JWT Token?

JWT (JSON Web Token) is a compact, URL-safe token format used for securely transmitting information between parties as a JSON object. JWTs are often used for stateless authentication.

Why Use JWT Tokens?

  • Stateless Authentication: JWTs allow for stateless authentication, meaning the server does not need to store session information. The token itself contains all the necessary data.
  • Security: JWTs can be signed and optionally encrypted, ensuring the integrity and confidentiality of the data.
  • Scalability: Stateless authentication makes it easier to scale applications horizontally, as there is no need to manage session state across servers.

Example: Using JWT in NodeJS

  • Installation:
  npm install jsonwebtoken
Enter fullscreen mode Exit fullscreen mode
  • Implementation:
  const jwt = require('jsonwebtoken');

  // Creating a JWT
  const token = jwt.sign({ username: 'user' }, 'secretKey', { expiresIn: '1h' });

  // Verifying a JWT
  jwt.verify(token, 'secretKey', (err, decoded) => {
    if (err) {
      console.log('Token verification failed');
    } else {
      console.log('Token is valid', decoded);
    }
  });
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • Creating a JWT: The jwt.sign() function creates a token containing the user's information (e.g., username) and signs it with a secret key.
    • Verifying a JWT: The jwt.verify() function checks the validity of the token using the same secret key.

Cookies: What They Are and Why They Are Used

What are Cookies?

Cookies are small pieces of data stored on the client-side (browser) and sent with every HTTP request to the server. They are commonly used to maintain session information, store user preferences, and track user behaviour.

Why Use Cookies?

  • Session Management: Cookies can store session IDs, allowing the server to identify the user across multiple requests.
  • Personalization: Cookies can store user preferences, such as language settings or themes.
  • Tracking and Analytics: Cookies can track user behaviour across different sessions, providing valuable data for analytics.

Example: Using Cookies in NodeJS

  • Installation:
  npm install cookie-parser
Enter fullscreen mode Exit fullscreen mode
  • Implementation:
  const express = require('express');
  const cookieParser = require('cookie-parser');
  const app = express();

  app.use(cookieParser());

  app.get('/set-cookie', (req, res) => {
    res.cookie('user', 'Aadyaa', { maxAge: 900000, httpOnly: true });
    res.send('Cookie has been set');
  });

  app.get('/get-cookie', (req, res) => {
    const user = req.cookies['user'];
    res.send('User cookie value: ' + user);
  });

  app.listen(3000, () => console.log('Server started on port 3000'));
Enter fullscreen mode Exit fullscreen mode
  • Explanation:
    • Setting a Cookie: The server sets a cookie named user with the value 'Aadyaa'. The cookie is set to expire in 15 minutes (maxAge: 900000 milliseconds).
    • Retrieving a Cookie: The server reads the value of the user cookie and sends it back in the response.

Real-World Use Case: Implementing Secure User Authentication and Authorization

Example: Building a Secure User Dashboard

Imagine you're building a secure user dashboard for an application where users can log in and access personalized content. The requirements include:

  • User Authentication: Verify users' identities using their credentials.
  • Authorization: Restrict access to certain parts of the application based on user roles.
  • Session Management: Maintain user sessions securely across multiple requests.
  • Token-Based Authentication: Implement stateless authentication for API endpoints.

Implementation Overview:

  1. Authentication:

    • Use Passport.js with the local strategy to authenticate users. Upon successful login, create a session ID and store it in a cookie.
  2. Authorization:

    • Implement role-based access control (RBAC) to restrict access to certain routes. For example, only admin users can access the /admin route.
  3. Session Management:

    • Use cookies to store session IDs securely. Ensure cookies are marked as HttpOnly and Secure to prevent XSS attacks.
  4. Token-Based Authentication:

    • For API endpoints, use JWT tokens for stateless authentication. Generate a JWT upon login and include it in the Authorization header of subsequent API requests.
  5. Cookie Usage:

    • Store user preferences (e.g., theme, language) in cookies to personalize the user experience.
  6. Real-World Example:

    • A user logs in to the application. The server authenticates the user using Passport.js, generates a session ID, and stores it in a cookie. The user can now access their dashboard, and the server recognizes their session across multiple requests.
    • If the user interacts with an API endpoint, a JWT is generated and used for stateless authentication. The server verifies the JWT with each request, ensuring the user is authorized to access the resource.

Conclusion

Understanding the differences between stateful and stateless systems, session management, authentication, authorization, JWT tokens, and cookies is crucial for building secure and scalable web applications. By applying these concepts in real-world scenarios, you can enhance the security and user experience of your applications.

Top comments (0)