← Back to TypeScript Course | Chapter 28: Real World Projects | Lesson 9 of 14

Authentication System

TypeScript can model authentication requests, user identities, sessions, and authorization decisions. Strong types help separate public user data from sensitive credentials.
Syntax
typescript
interface JwtPayload {
    userId: string;
    role: "admin" | "user";
}

const payload = jwt.verify(token, secret) as JwtPayload;

Core Concept

A typed authentication system models its core entities — a User, a Session, a JwtPayload — as explicit interfaces, so every layer that touches auth data (login handler, middleware, protected route) agrees on exactly what fields are present.

Example: Core Concept

typescript
interface User { id: string; email: string; }
interface Session { userId: string; expiresAt: Date; }
interface JwtPayload { userId: string; role: "admin" | "user"; }
const payload: JwtPayload = { userId: "1", role: "user" };
console.log(payload);

Basic Setup

A basic setup types the decoded JWT payload (interface JwtPayload { userId: string; role: admin | user }) so jwt.verify()'s return value is cast to a known, specific shape instead of any.

Example: Basic Setup

typescript
interface JwtPayload {
  userId: string;
  role: "admin" | "user";
}
// const decoded = jwt.verify(token, secret) as JwtPayload;
const decoded: JwtPayload = { userId: "1", role: "admin" };
console.log(decoded);

Typed Example

A typed example: an Express middleware function requireAuth(req: Request, res: Response, next: NextFunction) that attaches req.user: JwtPayload after verifying a token, with a custom Request type augmentation so req.user is recognized everywhere downstream.

Example: Typed Example

typescript
interface JwtPayload { userId: string; role: "admin" | "user"; }
function requireAuth(token: string): JwtPayload {
  // In a real app: jwt.verify(token, SECRET) as JwtPayload
  return { userId: "1", role: "user" };
}
console.log(requireAuth("fake-token"));

Project Usage

In a real project, typing the role field as a literal union (admin | user) rather than a plain string means an authorization check like if (req.user.role === admni) (a typo) is caught at compile time instead of silently always failing.

Example: Project Usage

typescript
type Role = "admin" | "user";
function isAdmin(role: Role): boolean {
  return role === "admin"; // typo like "admni" would be a compile error
}
console.log(isAdmin("admin"));

Best Practices

Never store sensitive data like a password hash in the typed JWT payload interface — type the payload to hold only what's needed for authorization decisions, keeping the token itself small and non-sensitive.

Example: Best Practices

typescript
interface JwtPayload {
  userId: string;
  role: "admin" | "user";
  // No passwordHash here -- keep the token small and non-sensitive.
}
const payload: JwtPayload = { userId: "1", role: "user" };
console.log(payload);
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Typing role as string instead of "admin" | "user", so a typo like "admn" compiles.
  2. Casting jwt.verify(...) as JwtPayload and trusting it, when the token content is not validated at runtime.
  3. Attaching req.user without augmenting Express's Request type, which gives a compile error.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.