Effect CommunityEC
Effect Community13mo ago
9 replies
Jambudipa

Auth middleware in HttpRpcRouter.toHttpApp

Morning all

I would like to add some middleware to my RPC server to decode Firebase tokens and somehow make them available to the services that eventually handle the requests.

I have this:
const HttpLive = HttpRouter.empty.pipe(
  HttpRouter.use(decodeAuthTokenMiddleware),
  HttpRouter.post('/rpc', toHttpApp(appRouter)),
  HttpServer.serve(HttpMiddleware.cors({
    allowedHeaders: ['*'],
    allowedMethods: ['*']
  })),
  HttpServer.withLogAddress,
  Layer.provide(BunHttpServer.layer({ port: 8080 })),
  Layer.provide(searchServiceLayer)
);

BunRuntime.runMain(Layer.launch(HttpLive));

and some AI generated code for the middleware itself:
export const firebaseAuthMiddleware = HttpMiddleware.make(
  (app) =>
    Effect.gen(function* () {
      // Get the incoming request
      const req = yield* HttpServerRequest.HttpServerRequest;

      // Extract the Authorization header
      const authorization = req.headers["authorization"];

      // Check if the Authorization header exists and starts with "Bearer"
      if (!authorization || !authorization.startsWith("Bearer ")) {
        // If the token is missing or invalid, we fail the request
        return yield* Effect.fail(new Error("Invalid or missing Authorization header"))
      }

      const token = authorization.split(" ")[1];

      try {
        // Verify the token using Firebase Admin SDK
        const decodedToken = yield* Effect.promise(() => admin.auth().verifyIdToken(token));

        // Attach decoded user data to the request context (or locals)
        req.locals = {
          ...req.locals,
          authData: decodedToken, // Add the decoded token info here
        };

        // Proceed with the next app/middleware in the pipeline
        return yield* app;
      } catch (error) {
        // Authentication failed or token is invalid
        return yield* _(
          Effect.fail(new Error("Invalid or expired Firebase token"))
        );
      }
    })
);

(which does not compile, req.locals is not a thing)

But I am unsure of the pattern. I am not able to find anything resembling a request context, so how might my services, invoked by the following router, obtain the decoded token?
export const appRouter = RpcRouter.make(
  Rpc.effect(
    Search, (request) => SearchService.pipe(Effect.andThen((search) => search.search(request)))
  ),
  Rpc.effect(
    Books, () => SearchService.pipe(Effect.andThen((search) => search.books()))
  ),
);
Was this page helpful?