Skip to content

Bug: FirebaseUser decorator returns nested user object instead of direct DecodedIdToken #11

@xandru314

Description

@xandru314

Description

When using the @FirebaseUser() decorator in version 1.6.0, the returned user object is nested within another user property, causing the structure to be { user: DecodedIdToken } instead of the expected DecodedIdToken directly.

Environment

  • Package version: @alpha018/[email protected]
  • Firebase Admin SDK: [email protected]
  • Node.js version: 20.x
  • NestJS version: Latest
  • Environment: Firebase Emulator Suite (Auth Emulator)

Expected Behavior

The @FirebaseUser() decorator should return the Firebase DecodedIdToken directly:

@UseGuards(FirebaseGuard)
@Get('protected')
getProtectedRoute(@FirebaseUser() user: auth.DecodedIdToken) {
  console.log(user.uid); // Should work directly
  return { user };
}

Expected response:

{
  "user": {
    "uid": "user123",
    "email": "[email protected]",
    "iat": 1234567890,
    // ... other DecodedIdToken properties
  }
}

Actual Behavior

The decorator returns a nested object structure:

@UseGuards(FirebaseGuard)
@Get('protected')
getProtectedRoute(@FirebaseUser() user: any) {
  console.log(user.user.uid); // Have to access nested property
  return { user };
}

Actual response:

{
  "user": {
    "user": {
      "uid": "user123",
      "email": "[email protected]",
      "iat": 1234567890,
      // ... other DecodedIdToken properties
    }
  }
}

Steps to Reproduce

  1. Set up NestJS project with Firebase Emulator Suite
  2. Configure @alpha018/[email protected] with the following config:
FirebaseAdminModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => {
    const isEmulator = configService.get('NODE_ENV') === 'development';

    if (isEmulator) {
      process.env.FIREBASE_AUTH_EMULATOR_HOST = 'localhost:9099';
      process.env.FIRESTORE_EMULATOR_HOST = 'localhost:8080';
      process.env.FIREBASE_STORAGE_EMULATOR_HOST = 'localhost:9199';
    }

    return {
      options: {
        projectId: configService.get('FIREBASE_PROJECT_ID') || 'demo-project',
      },
      auth: {
        config: {
          extractor: ExtractJwt.fromAuthHeaderAsBearerToken(),
          checkRevoked: false,
          validateRole: false,
          useLocalRoles: false,
        },
      },
    };
  },
  inject: [ConfigService],
}),
  1. Create a protected endpoint:
@UseGuards(FirebaseGuard)
@Get('protected')
getProtectedRoute(@FirebaseUser() user: auth.DecodedIdToken) {
  return { user };
}
  1. Start Firebase emulators: firebase emulators:start
  2. Make authenticated request to the endpoint
  3. Observe the nested user structure in the response

Root Cause Analysis

Looking at the source code in src/firebase/decorator/user.decorator.ts, the decorator extracts user data from request.metadata[FIREBASE_TOKEN_USER_METADATA]. The issue appears to be that somewhere in the authentication pipeline (likely in the guard or strategy), the user data is being stored as:

request.metadata[FIREBASE_TOKEN_USER_METADATA] = {
  user: decodedToken,
  // possibly other properties
};

Instead of:

request.metadata[FIREBASE_TOKEN_USER_METADATA] = decodedToken;

Workaround

Currently using a monkey patch to fix the issue:

// firebase-user.decorator.patch.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const UserFactory = (data: unknown, ctx: ExecutionContext) => {
  const context = ctx.switchToHttp();
  const request = context.getRequest();
  let user = request.metadata?.firebase_user; // or whatever the constant is
  
  // Fix nested user issue
  if (user && user.user && typeof user.user === 'object') {
    user = user.user;
  }
  
  return user;
};

export const FirebaseUser = createParamDecorator(UserFactory);

Possible Fix

The issue likely needs to be fixed in the guard or strategy where the user data is stored in the request metadata. The code should store the DecodedIdToken directly rather than wrapping it in an additional object.

Additional Context

  • This issue might be specific to Firebase Emulator usage
  • The issue was not present in earlier versions (needs verification)
  • TypeScript expects auth.DecodedIdToken but receives { user: auth.DecodedIdToken }

Is this related to Firebase Emulator?

This might be emulator-specific behavior. Could you confirm if this also happens in production Firebase environments?

Thanks for this excellent library! This is likely a simple fix but causes confusion for developers expecting the documented behavior.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingenhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions