-
Notifications
You must be signed in to change notification settings - Fork 2
Description
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
- Set up NestJS project with Firebase Emulator Suite
- 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],
}),
- Create a protected endpoint:
@UseGuards(FirebaseGuard)
@Get('protected')
getProtectedRoute(@FirebaseUser() user: auth.DecodedIdToken) {
return { user };
}
- Start Firebase emulators:
firebase emulators:start
- Make authenticated request to the endpoint
- 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.