前回の記事に保続する形で、今回の記事を書いていきます。
NestJS 側ではsessionが保存できており、ログイン成功のレスポンスも返っている。
→ しかしNext(Vite)側でaxios.get(adminSessionUrl, { withCredentials: true })を送っても
セッション情報が読み取れず「未ログインです」が返ってしまう、という現象が起きていました。
(原因)
CORS(および Cookie の SameSite/secure 設定)でした。
つまり、
・ログイン時に Set-Cookie がブラウザに保存されていない
・または 保存されても cross-origin だと送信されていない
ので、auth/check にアクセスしてもsessionが読み込めず
req.session.user が undefined → 「未ログインです」と返されます。
Backendのmain.ts修正
express-session のCookie設定を見直しました。
app.use(
session({
secret: 'your-secret',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: false, // ← ローカルなら false にする
sameSite: 'lax', // ← 重要! none は https でしか動かない
},
}),
);
全体のコードはこちら⇩
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import session from "express-session";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// ★ CORS(セッション対応)
app.enableCors({
origin: 'http://localhost:5173', // ← React の URL
credentials: true, // ← Cookie を送受信
});
// ★ session ミドルウェア
app.use(
session({
secret: 'nest-app',
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 30 * 1000, // ← 30秒
httpOnly: true,
sameSite: 'lax',
secure:false,
},
rolling: true,
}),
);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
❗もし sameSite: 'none' + secure: true にしていたら Cookie は ローカルでは絶対送信されません
→ Chrome が完全に拒否します。
関連コード
修正する箇所ないですが、上記のクラスファイルとやり取りするクラスを載せておきます。
Nest.js
import { Controller, Post, Body, UseInterceptors,Req,Get } from '@nestjs/common';
import { AnyFilesInterceptor } from '@nestjs/platform-express';
import { User } from 'src/models/user.entity';
import { UserService } from 'src/models/user.service';
import type { Request } from 'express';//セッションライブラリを追加
import { MailService } from 'src/mail/mail.service';//追加
import * as crypto from "crypto"//追加
//画面側に返却するレスポンス型の定義
interface UserResponse{
status:number,
message:string,
error?:string
}
interface TypeUserLoginFormData{
userName:string;
userEmail:string;
userPassword:string
}
interface UserResponseWithEmail{
status:number,
message:string,
error?:string,
email:string,
}
@Controller('adminuser')
export class AdminuserController {
//コンストラクタ
constructor(private readonly userService:UserService,private readonly mailService: MailService){}
/**
* ユーザ登録する処理
* @param body(any) リクエストパラメータ
* @returns response(UserResponse)
*/
@Post('register')
@UseInterceptors(AnyFilesInterceptor())
async registerAdminUser(@Body() body: any):Promise<UserResponse>{
console.log("ブラウザから受け取ったデータは:", body);
// User Entityのインスタンスをnewする
const adminUser = new User();
adminUser.setName(body.user_name);
adminUser.setEmail(body.user_email);
adminUser.setPassword(body.user_password); // ← 絶対にこの形式
adminUser.setAdminCheck(body.admin_user === "true");
// userserviceクラスのcreateOrUpdate関数の返却値(型:UserResponse)を代入
const response:UserResponse = await this.userService.createOrUpdate(adminUser);
return response;
}
/**
* メールアドレスから一意のユーザ情報を取得する処理
* @param body:string
* @returns response:USer
*/
@Post('getoneuser')
@UseInterceptors(AnyFilesInterceptor())
async getOneUserFromEmail(@Body('email') email:string):Promise<User | UserResponse>{
console.log("Post送信で受け取ったEmailアドレスは:",email);
return this.userService.findOneUserFromEmail(email);
}
/**
* (セッション対応)ログイン画面でメールアドレスから一意のユーザ情報を取得する処理
* @param body
* @returns
*/
@Post('login')
//@UseInterceptors(AnyFilesInterceptor())
async loginUser(@Req() req: Request,@Body() body: TypeUserLoginFormData):Promise<User | UserResponse | UserResponseWithEmail>{
console.log("ログイン画面から受けとったデータは:",body);
const userOrError = await this.userService.findOneUserUsingPassword(body.userEmail,body.userPassword);
if(!isUser(userOrError)){
return {
status:userOrError.status || 404,
message: userOrError.message || 'ユーザーが取得できません / パスワード不一致',
}
}
// ★ セッションにログイン情報を保存する
req.session.user = {
id:userOrError.user_id,
email:userOrError.user_email,
name:userOrError.user_name,
}
console.log("セッション保存:", req.session.user);
return {
status:201,
message:"ログイン成功",
email:userOrError.user_email
};
}
/**
* セッションチェック API
* ログイン状態かどうか確認する
*/
@Get('auth/check')
async checkSession(@Req() req: Request){
if(req.session.user){
console.log("auth/check ユーザ名は:", req.session.user);
return {
status:200,
message: 'ログイン中',
user:req.session.user,
}
}else{
console.log("未ログイン")
return {
status:401,
message:'未ログインです',
}
}
}
/**
* ログアウト API
* セッション破棄してログイン状態を解除
*/
@Post('logout')
async logout(@Req() req: Request){
return new Promise((resolve)=>{
req.session.destroy((err)=>{
if(err){
console.log('セッション破棄エラー:', err);
resolve({
status:500,
message: 'ログアウトできませんでした',
});
}else{
resolve({
status:200,
message: 'ログアウト成功',
})
}
})
});
}
@Post('resetpassword')
async resetPassword(@Body('email') email: string){
console.log("パスワード再設定リクエスト:", email);
// 対象ユーザを取得
const user = await this.userService.findOneUserFromEmail(email);
console.log("サービスクラスから受け取ったユーザ情報は:",user);
if(!isUser(user)){
return {
status:404,
message:"該当するユーザが存在しません。",
};
}
// 仮パスワード生成
const tempPassword = crypto.randomBytes(4).toString('hex'); // 8桁程度の仮PW
console.log("生成された仮パスワード:", tempPassword);
// 仮パスワードをハッシュ化して保存
(user as any).user_password = tempPassword; // ★ UserService 側でハッシュ化される想定
await this.userService.updateUserInfoUsingEmail(email,tempPassword);
console.log("162行目");
// メール送信
await this.mailService.sendTemporaryPassword(email,tempPassword);
return {
status:201,
message:"仮パスワードを送信しました。",
}
}
}
function isUser(obj: User | UserResponse): obj is User {
return (obj as User).user_id !== undefined;
}