11
11

More than 3 years have passed since last update.

【まじで注意】NestJSをAPIサーバとして使ってユーザの認証管理をする時の注意事項

Posted at

昨日公開したサーバの挙動がどうもおかしかったのです。ガチガチにValidationかけてたので不具合とはならなかったのですが、想定してるよりバリデーションにひっかかる可能性が高い。具体的には、Controllerの上のほうの処理と下のほうの処理で、ユーザが異なるような挙動が確認される。

具体的なコードで紹介すると

@Injectable()
export class AuthService {
  public authId: number;

  get userId() {
    return authId;
  }

  // ...認証やらいろいろして、認証できたらauthIdにユーザIDを入れる処理とか
}

といったサービスをDIした以下のようなControllerがあったとします。

export class StatusController {
  constructor(
    private auth: AuthService,
  ) {}

  @Get()
  async userStatus(): Promise<IResponseGetUserStatus> {
    console.log(this.auth.userId);  // 【1】
    // ...時間のかかる非同期処理
    console.log(this.auth.userId);  // 【2】
  }
}

みたいな処理をしていると、【1】と【2】でそのリクエスト内では再ログインや上書きをしていないのに、なぜか値が異なる!!

ちょっと各位に相談したりドキュメントみたりしてたのですが、どうもNestJSではデフォルトではDIはHTTP Request単位ではなく、サーバ全体で行われるようです。簡単にいうと、ControllerもServiceもユーザ間で共有します。なので、プロパティでユーザIDをいれようものなら、取り合いがはじまって容赦なく書き換わります。

Issuesはこちらですね。

Http Request based DI #1376
https://github.com/nestjs/nest/issues/1376

いやー、焦った。テストでまったくでてこないし、当然ローカルテストで発見できるようなものではないので。確認したところ、ちょうど1年前にこれを解決するための機能が実装されていました。

Injection scopes
https://docs.nestjs.com/fundamentals/injection-scopes

この機能を使うことによって、DIをHTTP単位で生成するようにすることができます(代わりにコストが高くなることを注意する旨書かれていたりしますので、何でもすればいいわけじゃないです。認証といったクリティカルなものはそうしないとだめなだけです)。

先程の例だと、以下のようにすれば治ります。

@Injectable(
  { scope: Scope.REQUEST }  // 追記
)
export class AuthService {
  public authId: number;

  get userId() {
    return authId;
  }
}

実環境で動かすまで気づけない的にはかなりハマりポイントだと思いますので、ぜひ設計段階でNodeのDIをどの範囲で行うかは考えるようにすることをおすすめします。ブラウザと違って、DIは他のユーザと共有するリスクがあることにご注意ください。

それではまた。

11
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11