NestJSでのGoogle OAuth実装で、stateパラメータを受け取る方法
NestJS で Google OAuth を実装する際、認証フローの一環として redirect URI に state パラメータを渡したかったのですが、うまく渡せず困ったため、解決方法を共有します。
state パラメータは CSRF 攻撃を防止するために使用され、認証リクエストとレスポンスを関連付ける役割を果たします。(アプリ側の任意の情報を保持するために使用できます。)
環境
- Node.js: v22.16.0
- passport: v0.7.0,
- passport-google-oauth20: v2.0.0,
- passport-jwt: v4.0.1,
発生した現象
以下のように、Google OAuth の認証エンドポイントに state パラメータを付与してリクエストを送信しましたが、リダイレクト先のエンドポイントで state パラメータが undefined
となり、正常に受け取れませんでした。
//フロントエンドからこのエンドポイントにアクセス
@Get('/google')
@UseGuards(AuthGuard('google'))
async googleAuth(@Req() _req: any): Promise<void> {}
// Googleからのリダイレクトを受け取るエンドポイント
@Get('/google/redirect')
@UseGuards(GoogleOauthGuard)
async googleAuthRedirect(
@Req() req: any,
@Res({ passthrough: true }) res: Response,
): Promise<void> {
console.log('state:', req.query.state); // ここで state が undefined になっていた
//省略
}
解決方法
カスタムガードの作成
NestJS 標準のAuthGuard('google')
が Google リダイレクト時に リクエスト毎に異なる state パラメータを設定できなかったため、
AuthGuard('google') を拡張し、state を明示的に Google に渡すカスタムガードを作成し、リクエストのクエリパラメータから state を動的に取得して渡すようにしました。
//NOTE: このコード例はAI生成です
// google-auth.guard.ts
import { Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
@Injectable()
export class GoogleAuthGuard extends AuthGuard("google") {
getAuthenticateOptions(context) {
const request = context.switchToHttp().getRequest();
const state = request.query.state;
return {
scope: ["email", "profile", "openid"],
accessType: "offline",
state, // ← ここでGoogleへ明示的に渡す
};
}
}
// auth.controller.tsで上記Guardを使用
@Get('/google/redirect')
@UseGuards(GoogleAuthGuard)
async googleAuthRedirect(
@Req() req: any,
@Res({ passthrough: true }) res: Response,
): Promise<void> {
// req.user に GoogleUser オブジェクトが入っており、
// その中に state が含まれている
console.log('state:', req.user.state);
// 以降の処理
}
GoogleStrategy の設定変更
また、AuthGuard('google')が呼び出す GoogleStrategy のvalidate
メソッドで state パラメータを受け取れるようにしました。
元々 GoogleStrategy のコンストラクタでpassReqToCallback
を設定していなかったため HTTP リクエストオブジェクトを受け取れていませんでした。
passReqToCallback: true
を設定したことでvalidate
メソッドの第一引数に HTTP リクエストオブジェクトが渡されるようになったため、そこから state パラメータを取得できるようになりました。
// google.strategy.ts
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, "google") {
constructor(private readonly configService: ConfigService) {
super({
// ... 他のプロパティ
passReqToCallback: true, // trueに設定するとvalidateメソッドの第一引数にHTTPリクエストオブジェクトを渡す
});
}
async validate(
req: any,
accessToken: string,
refreshToken: string,
profile: Profile,
_done: VerifyCallback
): Promise<GoogleUser> {
const state = req.query.state; // ← ここで state を取得
const user: GoogleUser = {
id: GoogleId(id),
email: GoogleEmail(emails[0].value),
// ... 他のプロパティ
state: state || null, // ← state を含める
};
return user;
}
}
以上の対応により、Google OAuth のリダイレクト時に state パラメータを正常に受け取れるようになりました。
まとめ
NestJS で Google OAuth を実装する際、state パラメータを正しく渡すためには、AuthGuard を拡張して state を明示的に渡し、GoogleStrategy の validate メソッドで HTTP リクエストオブジェクトを受け取る設定をする必要がありました。
同じ現象で悩んでいる方の参考になれば幸いです。
何か間違いやお気づきの点がございましたら、コメントいただければと思います。
Discussion