Skip to content

Instantly share code, notes, and snippets.

@remojansen
Last active December 10, 2025 22:05
Show Gist options
  • Select an option

  • Save remojansen/1398c891affe7e90c93f9e7edbaf36d7 to your computer and use it in GitHub Desktop.

Select an option

Save remojansen/1398c891affe7e90c93f9e7edbaf36d7 to your computer and use it in GitHub Desktop.
temp.md

This middleware is used by all microservices. When the microservice is the auth service, it essentially invokes itself. When the microservice is not the auth service, it makes a remote call to the auth service.

Is this a good approach, or should I change the logic within the middleware so that the auth service avoids making a remote call to itself? Alternatively, should I use two different middlewares?

import type { ExpressMiddleware } from "@inversifyjs/http-express";
import type * as express from "express";
import { inject, injectable } from "inversify";
import {
	type RemoteServiceClient,
	RemoteServiceClientSymbol,
} from "../../../../app-services/shared/remote-service-client";
import { Principal } from "./auth-principal";

export interface AuthenticatedRequest extends express.Request {
	user: Principal | null;
}

export interface AuthortizedRequest extends express.Request {
	user: Principal;
}

@injectable()
export class AuthenticateMiddleware implements ExpressMiddleware {
	public constructor(
		@inject(RemoteServiceClientSymbol)
		private readonly _remoteServiceClient: RemoteServiceClient,
	) {}

	public async execute(
		request: AuthenticatedRequest,
		_response: express.Response,
		next: express.NextFunction,
	): Promise<void> {
		request.user = null;

		const token = request.headers["x-auth-token"];
		if (token && typeof token === "string") {
			const userId = await this._remoteServiceClient.auth.verifyToken(token);
			if (userId) {
				request.user = new Principal({ userId });
			}
		}
		next();
	}
}

Authentication Middleware Architecture Decision

Current Situation

The AuthenticateMiddleware always uses RemoteServiceClient to verify tokens via HTTP call:

  • When running in the auth service, this creates a self-call (HTTP roundtrip to itself) - wasteful and adds latency
  • When running in cms/dam services, this is the correct behavior (calling auth service remotely)

Recommendation: Use a Token Verification Abstraction

I recommend neither two separate middlewares nor conditional logic in the middleware. Instead, use Dependency Injection properly by creating a TokenVerifier abstraction that has two implementations:

  1. LocalTokenVerifier - Uses AuthTokenService directly (for auth service)
  2. RemoteTokenVerifier - Uses HTTP client (for other services)

This is cleaner because:

  • The middleware stays simple and testable
  • Each microservice binds the appropriate implementation via IoC
  • Follows the Open/Closed principle

Implementation

1. Token Verifier Interface

// filepath: /api/src/app-services/shared/token-verifier/index.ts
export const TokenVerifierSymbol = Symbol("TokenVerifier");

export interface TokenVerifier {
    verifyToken(token: string): Promise<string | null>;
}

2. Local Token Verifier (for Auth Service)

// filepath: /api/src/infrastructure/auth/local-token-verifier.ts
import { inject, injectable } from "inversify";
import {
    type AuthTokenService,
    AuthTokenServiceSymbol,
} from "../../app-services/auth/auth-token-service";
import type { TokenVerifier } from "../../app-services/shared/token-verifier";

@injectable()
export class LocalTokenVerifier implements TokenVerifier {
    constructor(
        @inject(AuthTokenServiceSymbol)
        private readonly _authTokenService: AuthTokenService,
    ) {}

    async verifyToken(token: string): Promise<string | null> {
        return this._authTokenService.verifyToken(token, false);
    }
}

3. Remote Token Verifier (for CMS/DAM Services)

// filepath: /api/src/infrastructure/http/client/remote-token-verifier.ts
import { inject, injectable } from "inversify";
import type { TokenVerifier } from "../../../app-services/shared/token-verifier";
import {
    type RemoteServiceClient,
    RemoteServiceClientSymbol,
} from "../../../app-services/shared/remote-service-client";

@injectable()
export class RemoteTokenVerifier implements TokenVerifier {
    constructor(
        @inject(RemoteServiceClientSymbol)
        private readonly _remoteServiceClient: RemoteServiceClient,
    ) {}

    async verifyToken(token: string): Promise<string | null> {
        return this._remoteServiceClient.auth.verifyToken(token);
    }
}

4. Updated Authenticate Middleware

// filepath: /api/src/infrastructure/http/controllers/shared/authenticate-middleware.ts
import type { ExpressMiddleware } from "@inversifyjs/http-express/lib/cjs/models/ExpressMiddleware";
import type * as express from "express";
import { inject, injectable } from "inversify";
import {
    type TokenVerifier,
    TokenVerifierSymbol,
} from "../../../../app-services/shared/token-verifier";

// ...existing Principal class and interfaces...

@injectable()
export class AuthenticateMiddleware implements ExpressMiddleware {
    public constructor(
        @inject(TokenVerifierSymbol)
        private readonly _tokenVerifier: TokenVerifier,
    ) {}

    public async execute(
        request: AuthenticatedRequest,
        _response: express.Response,
        next: express.NextFunction,
    ): Promise<void> {
        request.user = null;

        const token = request.headers["x-auth-token"];
        if (token && typeof token === "string") {
            const userId = await this._tokenVerifier.verifyToken(token);
            if (userId) {
                request.user = new Principal({ userId });
            }
        }
        next();
    }
}

5. IoC Binding for Auth Service

// filepath: /api/src/infrastructure/ioc/modules/auth-ioc-module.ts
// ...existing imports...
import { TokenVerifierSymbol } from "../../../app-services/shared/token-verifier";
import { LocalTokenVerifier } from "../../auth/local-token-verifier";

export const authIocModule = new ContainerModule((options) => {
    const { bind } = options;
    // ...existing code...
    
    // Auth service verifies tokens locally
    bind<TokenVerifier>(TokenVerifierSymbol)
        .to(LocalTokenVerifier)
        .inRequestScope();
});

6. IoC Binding for CMS/DAM Services

// filepath: /api/src/infrastructure/ioc/modules/template-management-ioc-module.ts (or similar)
// ...existing imports...
import { TokenVerifierSymbol } from "../../../app-services/shared/token-verifier";
import { RemoteTokenVerifier } from "../../http/client/remote-token-verifier";

// For CMS/DAM services:
bind<TokenVerifier>(TokenVerifierSymbol)
    .to(RemoteTokenVerifier)
    .inRequestScope();

Benefits

  • No conditional logic - cleaner, more testable code
  • Single middleware - no duplication
  • Proper separation of concerns - the middleware doesn't know how tokens are verified
  • Easy to test - just mock TokenVerifier
  • Performance - auth service avoids unnecessary HTTP roundtrip
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment