< Back

Industry Standard Tools/Libraries/Ways to Add Authorization and Authentication To Modern Application

Author: Emmanuel

Date: 2024-12-05

At the bottom of this article, you will find recommendations and implementation basics, what is the authorization and authentication approach to choose in each scenario, and why. We will also look at the pros and cons of various authorization and authentication types and how to make decisions that's best for your application.


Table of Contents:

The issue of security in the web/cyber security is an enormous and broad topic, I will be going straight to the points I consider useful at all levels.

Adding authentication and authorization for example, to a REST API with user and product inventory routes is crucial for security. The choice of tools and libraries depends on your stack and requirements. But here are industry-standard ways to implement these features:

Authentication:

What is authentication:

Authentication refers to a process or situation where a server needs to know or verify who is accessing its content. Authentication would usually go both ways the client also wants to be sure the server provider is really who they say they are, so while the server is trying to verify that the client is who they say they are so does a legitimate client the server. In a single sentence, Authentication verifies the identity of a user.

Industry-Standard Tools and Libraries

1.JWT (JSON Web Tokens):

Basic Example

* TypeScript:

import jwt from 'jsonwebtoken';

const generateToken = (userId: string) => {
  return jwt.sign({ id: userId }, process.env.JWT_SECRET!, { expiresIn: '1h' });
};

const verifyToken = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ message: 'Unauthorized' });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!);
    req.user = decoded;
    next();
  } catch (err) {
    res.status(403).json({ message: 'Forbidden' });
  }
};

2.OAuth2:

3.Session-Based Authentication:

Authorization

On the other hand, authorization simply means controlling access to resources based on user roles and permissions.

Industry-Standard Tools and Libraries

1.Role-Based Access Control (RBAC):

Example with CASL:

* TypeScript:

import { AbilityBuilder, Ability } from '@casl/ability';

const defineAbilitiesFor = (user) => {
  const { can, cannot, build } = new AbilityBuilder(Ability);

  if (user.role === 'admin') {
    can('manage', 'all'); // Admins can do everything
  } else {
    can('read', 'Product'); // Regular users can only read products
    cannot('delete', 'Product'); // Regular users cannot delete products
  }

  return build();
};

const checkPermission = (action, resource) => (req, res, next) => {
  const ability = defineAbilitiesFor(req.user);
  if (ability.can(action, resource)) {
    return next();
  }
  res.status(403).json({ message: 'Forbidden' });
};

// Use in routes
app.delete('/products/:id', checkPermission('delete', 'Product'), (req, res) => {
  // Product deletion logic
});

2.Attribute-Based Access Control (ABAC):

Full-Service Authentication and Authorization Providers

For projects where you don’t want to build these systems from scratch, use managed services:

1.Auth0:

2 Firebase Authentication:

3.AWS Cognito:

4.Okta:

Middleware for Protection

In simple terms, Middleware ensures that only authorized users can access protected routes.

Example Middleware for Authentication:

* TypeScript:

import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';

const authenticate = (req: Request, res: Response, next: NextFunction) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ message: 'Unauthorized' });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!);
    req.user = decoded;
    next();
  } catch (err) {
    res.status(403).json({ message: 'Forbidden' });
  }
};

export default authenticate;

5.Best Practices

Example Workflow

1.Authentication:

2.Authorization:

3.Protected Routes:

Comparison of session-based authentication and stateless authentication (e.g., JWT), along with recommendations for security-conscious use cases.

Session-Based Authentication

Session-based authentication involves storing session data on the server and using cookies to maintain user sessions.

How It Works:

  1. User logs in, and the server creates a session (a unique ID linked to the user) and stores it in a database or in-memory store (e.g., Redis).

  2. The session ID is sent to the client as a cookie.

  3. On subsequent requests, the client includes the cookie, and the server retrieves the session data to authenticate the user.

Pros:

Cons:

Stateless Authentication (JWT)

Stateless authentication uses self-contained tokens, typically in the form of JSON Web Tokens (JWTs), to authenticate users.

How It Works:

  1. User logs in, and the server generates a signed JWT containing claims (e.g., user ID, roles).

  2. The token is sent to the client (usually stored in Authorization headers or localStorage).

  3. On subsequent requests, the server validates the token's signature without storing any state.

Pros:

Cons:

Security Considerations

Session-Based Authentication:

Stateless Authentication (JWT):

Best Choice for Security

The best choice depends on your architecture and specific requirements. Here’s a recommendation based on security concerns:

Use Session-Based Authentication When:

Use Stateless Authentication (JWT) When:

Hybrid Approach (Best Practice for Security and Scalability)

You can combine the strengths of both approaches:

Example:

  1. JWT for access: Short-lived token (e.g., 15 minutes) stored in memory or headers.

  2. Refresh token: Long-lived, securely stored (e.g., in an HttpOnly cookie).

  3. Backend logic

    • Validate access tokens for normal requests.
    • Use refresh tokens only when access tokens expire.
    • Maintain a blacklist of revoked refresh tokens.

Summary Table

Feature Session-Based Authentication Stateless Authentication (JWT)
Scalability Limited, requires session store Highly scalable
Ease of Implementation Simpler for monoliths Simpler for distributed systems
Security Stronger control, revocable Harder to revoke
Storage Server-side Client-side
Use Case Banking, enterprise systems Distributed systems, APIs, microservices

Conclusion

For security-sensitive use cases, session-based authentication with well-configured cookies is generally safer. For distributed systems, consider a hybrid approach to balance security and scalability.



Recomendation and Example

When combining Passport.js with JWT (stateless authentication), the industry-standard strategy is the passport-jwt strategy. This approach leverages JSON Web Tokens for authentication while benefiting from Passport's modularity and middleware-based integration.

Why passport-jwt?

1.Stateless Authentication:

2.Compatibility:

3.Security:

4.Flexibility:

Typical Setup for passport-jwt

1.Install Required Packages

bash
npm install passport passport-jwt jsonwebtoken

2.Configure the JWT Strategy

import passport from 'passport';
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';

// Your secret or private key
const JWT_SECRET = process.env.JWT_SECRET || 'your_secret_key';

// JWT options
const opts = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: JWT_SECRET,
};

// Define the strategy
passport.use(
  new JwtStrategy(opts, async (jwtPayload, done) => {
    try {
      // Example: Validate the user based on the payload
      const user = await findUserById(jwtPayload.id); // Replace with your DB call
      if (user) {
        return done(null, user); // Pass user to req.user
      }
      return done(null, false); // No user found
    } catch (error) {
      return done(error, false);
    }
  })
);

3.Issue a JWT on Login
Use a library like jsonwebtoken to generate a token when the user logs in.

import jwt from 'jsonwebtoken';

const JWT_SECRET = process.env.JWT_SECRET || 'your_secret_key';

function generateToken(user) {
  return jwt.sign(
    { id: user.id, username: user.username, role: user.role }, // Payload
    JWT_SECRET,
    { expiresIn: '1h' } // Expiration time
  );
}

4.Protect Routes Using Middleware

import express from 'express';

const app = express();

// Protect an endpoint
app.get(
  '/protected',
  passport.authenticate('jwt', { session: false }),
  (req, res) => {
    res.json({ message: 'You are authorized!', user: req.user });
  }
);

Best Practices When Using passport-jwt

1.Use Short-Lived Tokens:

2.Secure Token Storage:

3.Validate Tokens on Each Request:

4.Token Revocation:

5.Combine with Other Strategies:

Example Hybrid Flow: Combining passport-local with passport-jwt

1.Install passport-local

bash
npm install passport-local

2.Configure passport-local

TypeScript:

import { Strategy as LocalStrategy } from 'passport-local';

// Example local strategy
passport.use(
  new LocalStrategy(async (username, password, done) => {
    try {
      const user = await findUserByUsername(username); // Replace with your DB call
      if (!user || !validatePassword(user, password)) {
        return done(null, false, { message: 'Invalid credentials' });
      }
      return done(null, user);
    } catch (error) {
      return done(error);
    }
  })
);

3.Login Route (Issue JWT)

TypeScript:

app.post('/login', (req, res, next) => {
  passport.authenticate('local', { session: false }, (err, user, info) => {
    if (err || !user) {
      return res.status(401).json({ message: info?.message || 'Unauthorized' });
    }

    // Generate token
    const token = generateToken(user);
    return res.json({ token });
  })(req, res, next);
});

Summary

v
Feature Session-Based (passport-local) JWT-Based (passport-jwt))
Statefulness Requires session store Stateless
Scalability Limited Highly scalable
Security Centralized control Decentralized, token revocation needed
Best Use Case Monolithic appsDistributed apps, APIs

Was this page helpful?

If you find this article helpful, please feel free to share the link.

Yes👍 No👎