Home Projects Portfolio Dashboard Export PDF Log in

Strengthening Authentication with Strongly-Typed JWT Payloads

Introduction

In the crm-saas-backend project, we are dedicated to building a robust and secure foundation for our customer relationship management platform. As we evolve our authentication layer, ensuring that our security tokens are handled with strict type safety is critical for preventing runtime errors and maintaining clear contracts between services.

The Problem

Previously, our JWT strategy relied on loose object structures when decoding user session tokens. While convenient, this approach often led to "string-typing" issues where developers had to guess or manually verify the existence of claims like userId or roles within the payload. This lack of explicit structure made it difficult to refactor auth-related logic and increased the risk of bugs when extending our identity providers.

The Solution: Strongly-Typed Payloads

To resolve this, we introduced explicit interfaces to define the shape of our JWT payloads. By leveraging TypeScript's interface system alongside our existing NestJS authentication strategy, we can ensure that every part of the application consuming the token understands exactly what data is available.

Here is how we moved to a more structured approach:

interface JwtPayload {
  sub: string;
  email: string;
  roles: string[];
}

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET,
    });
  }

  async validate(payload: JwtPayload): Promise<JwtPayload> {
    // The payload is now type-safe, ensuring 'sub' and 'roles' exist
    return {
      sub: payload.sub,
      email: payload.email,
      roles: payload.roles,
    };
  }
}

Why This Matters

By enforcing these types, we treated our authentication data like a physical map—you know exactly which paths exist and what you will find at the destination. This change has several benefits:

  1. Developer Experience: IDEs now provide autocomplete for claims, reducing typos.
  2. Runtime Safety: We catch missing claims at compile time rather than during user requests.
  3. Refactorability: Changing a claim name now triggers compiler errors wherever that claim is used, allowing us to update the entire codebase with confidence.

Actionable Takeaway

If you are using JWTs in a TypeScript project, stop relying on any or loose objects for your decoded tokens. Create a shared JwtPayload interface today and use it in your strategy validation methods. You will immediately notice a reduction in "missing property" errors and a smoother development flow.


Generated with Gitvlg.com

Strengthening Authentication with Strongly-Typed JWT Payloads
SOFIA DESIREE BARTOLI

SOFIA DESIREE BARTOLI

Author

Share: