Authentication

Harpia provides a robust, built-in Authentication ecosystem via its CLI generator, allowing you to implement a complete JSON Web Token (JWT) Bearer or Cookie-based Session authentication system in mere seconds.

Generating the Auth Module

To scaffold the authentication system within your application, utilise the Harpia CLI generator from the root of your project:

bun harpia generate setup

When you run this command, the CLI will prompt you to choose your preferred authentication strategy:

  • Session: Generates a complete Session-based authentication system (Cookie-based), ideal for traditional monolithic Fullstack web applications.
  • Bearer: Generates a JWT (JSON Web Token) authentication system, ideal for APIs and Single Page Applications.

If you select the Bearer strategy, you will be prompted to choose a token storage provider:

  • Database (Prisma): Stores token sessions in your relational database, automatically generating a Token model in your schema.prisma.
  • Redis: Stores token sessions in memory via Redis for high-performance retrieval and expiration.

What gets generated?

The generator scaffolds an entire auth (or session) module under app/modules/, along with necessary configuration files:

  1. app/config/auth.ts or app/config/jwt.ts: Central configuration files defining secrets, expiration times, and storage behavior.
  2. app/modules/.../controllers/: Contains the store (login), show (me/profile), and destroy (logout) controllers.
  3. app/modules/.../services/: The business logic for verifying credentials and handling tokens or sessions.
  4. app/middlewares/auth.ts: A robust middleware providing the isAuthenticated guard to protect your routes.

Protecting Routes

Once generated, you can seamlessly protect any route in your application by importing and attaching the isAuthenticated middleware to your router definitions.

import { Router } from "@harpiats/core";
import { userController } from "./controllers";
import { isAuthenticated } from "app/middlewares/auth";

const userRoutes = new Router("/users");

// This route is public
userRoutes.get("/", userController.index);

// This route requires authentication
userRoutes.get("/profile", isAuthenticated, userController.show);

export { userRoutes };

Accessing the Authenticated User

When a request passes through the isAuthenticated middleware, you can retrieve the current user’s session or token data using the provided utilities within your controllers.

Using Bearer (JWT)

// app/modules/users/controllers/show.ts
import type { Request, Response } from "@harpiats/core";
import { ApiResponse, AppError } from "@harpiats/common";
import { jwt } from "app/config/jwt"; 

export const show = async (request: Request, response: Response) => {
  try {
    // Extract user from the JWT Bearer token in the request header
    const user = await jwt.fromRequest(request);
    
    if (!user) {
      throw AppError.E_UNAUTHORIZED();
    }

    return ApiResponse.success(response, user);
  } catch (error: any) {
    return ApiResponse.error(response, error);
  }
};

Using Session

// app/modules/users/controllers/show.ts
import type { Request, Response } from "@harpiats/core";
import { ApiResponse, AppError } from "@harpiats/common";
import { session } from "app/config/session"; 

export const show = async (request: Request, response: Response) => {
  try {
    // Extract user from the HttpOnly Session cookie
    const user = await session.fromRequest(request);
    
    if (!user) {
      throw AppError.E_UNAUTHORIZED();
    }

    return ApiResponse.success(response, user);
  } catch (error: any) {
    return ApiResponse.error(response, error);
  }
};

Inside WebSockets

If you are building real-time features, you can also easily authenticate WebSocket connections. The Session and JWT utilities provide a fromWebSocket method specifically for this purpose.

import harpia from "@harpiats/core";
import { session } from "app/config/session"; // or import { jwt } from "app/config/jwt";

const app = harpia();

app.ws("/chat", {
  async open(ws) {
    // Retrieve the authenticated user from the WebSocket connection
    const user = await session.fromWebSocket(ws); // or await jwt.fromWebSocket(ws)
    
    if (!user) {
      // Reject unauthorized connections immediately
      return ws.close(1008, "Unauthorized");
    }
    
    // Store user data in the typed ws.data for use in other handlers
    ws.data.userId = user.id;
    ws.send(`Welcome to the chat!`);
  },
  message(ws, message) {
    console.log(`User ${ws.data.userId} sent: ${message}`);
  }
});

Configuration

You can tweak the authentication behaviour by editing the generated files in app/config/. Ensure you define strong environment variables (e.g., JWT_SECRET) in your .env file before deploying your application to production.

JWT_SECRET=your_super_secret_string_here