🐺
NestJSでレスポンスのフィールド余剰公開を防ぐために
はじめに
こんにちは、calloc134です。
皆さんは、APIを作成するときにどのようなレスポンスを返しますか?
例えば、ユーザー情報を返すAPIを作成するとき、以下のようなレスポンスを返すことが多いと思います。
{
"id": 1,
"name": "calloc134",
}
このAPIは特に問題はないですね。
しかし、このようなレスポンスを返すAPIはどうでしょうか。
{
"id": 1,
"name": "calloc134",
"password": "$argon2i$v=19$m=16,t=2,p=1$MTIzNDU2Nzg$ZZ/eAcHRleU4ChG0EJ+2Mw",
"createdAt": "2021-06-03T14:00:00.000Z",
"updatedAt": "2021-06-03T14:00:00.000Z"
...
}
おそらく、このAPIはデータベースの情報をそのまま返しているのだと思います。
しかし、このAPIはセキュリティ上の問題があります。
特にpassword
フィールドはパスワードのハッシュであり、公開してはいけません。
このようになるのは避けたいですよね・・・。
NestJSでオブジェクトのフィールドをフィルタリングする
NestJSのServiceで、以下のようにユーザを返却していたとします。
import { Controller, Get } from '@nestjs/common';
import { PrismaService } from 'src/prisma.module';
@Injecable()
export class UserService {
constructor(private readonly prisma: PrismaService) {}
async findOne(id: number): Promise<User> {
return this.prisma.user.findUniqueOrThrow({
where: { id },
});
}
}
しかし、これだとpassword
フィールドが含まれてしまいます。
これをフィルタリングするために、class-transformer
というライブラリを使います。
npm install class-transformer
次に、ユーザレスポンスとしての型を定義します。
import { Expose } from 'class-transformer';
export class UserResponse {
@Expose()
id: number;
@Expose()
name: string;
constructor(partial: Partial<LimitedUserResDto>) {
Object.assign(this, partial);
}
}
@Expose()
デコレータをつけることで、そのフィールドが公開されるようになります。
また、UserServiceで以下のようにUserResponseをnewして返却します。
import { Injecable } from '@nestjs/common';
import { PrismaService } from 'src/prisma.module';
@Injecable()
export class UserService {
constructor(private readonly prisma: PrismaService) {}
async findOne(id: number): Promise<UserResponse> {
const user = await this.prisma.user.findUniqueOrThrow({
where: { id },
});
return new UserResponse(user);
}
}
最後に、main.tsにおいて、以下のようにしてシリアライザを設定します。
import { ClassSerializerInterceptor, ... } from '@nestjs/common';
async function bootstrap() {
// ...
const app = await NestFactory.create(AppModule);
(...)
// シリアライザを有効化
app.useGlobalInterceptors(
new ClassSerializerInterceptor(app.get(Reflector), {
excludeExtraneousValues: true,
})
)
// ...
await app.listen(3000);
}
bootstrap();
ここで、excludeExtraneousValues
をtrue
にすることで、@Expose()
デコレータがついていないフィールドを除外することができます。
これで、password
フィールドが含まれないレスポンスを返すことができました。
まとめ
お役に立てば幸いです。
Discussion