I'm building a Discord bot dashboard API using NestJS, Passport, and TypeORM for session management. When a user attempts to log in through Discord, I encounter two errors:
[Nest] 19524 - 09.08.2024 15:03:10 ERROR [ExceptionsHandler] Duplicate entry '[access_token]' for key 'sessions.PRIMARY'
QueryFailedError: Duplicate entry 'mBU1jFgl3Q8crGxFJbuYrImdz3tk3cj6' for key 'sessions.PRIMARY'
at Query.onResult (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\typeorm\driver\src\driver\mysql\MysqlQueryRunner.ts:246:33)
at Query.execute (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\mysql2\lib\commands\command.js:36:14)
at PoolConnection.handlePacket (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\mysql2\lib\connection.js:481:34)
at PacketParser.onPacket (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\mysql2\lib\connection.js:97:12)
at PacketParser.executeStart (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\mysql2\lib\packet_parser.js:75:16)
at Socket.<anonymous> (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\mysql2\lib\connection.js:104:25)
at Socket.emit (node:events:518:28)
at addChunk (node:internal/streams/readable:559:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:510:3)
at Readable.push (node:internal/streams/readable:390:5)
[Nest] 19524 - 09.08.2024 15:03:18 ERROR [ExceptionsHandler] Login sessions require session support. Did you forget to use `express-session` middleware?
Error: Login sessions require session support. Did you forget to use `express-session` middleware?
at SessionStrategy.authenticate (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\passport\lib\strategies\session.js:97:41)
at attempt (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\passport\lib\middleware\authenticate.js:378:16)
at authenticate (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\passport\lib\middleware\authenticate.js:379:7)
at Layer.handle [as handle_request] (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\express\lib\router\layer.js:95:5)
at trim_prefix (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\express\lib\router\index.js:328:13)
at C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\express\lib\router\index.js:286:9
at Function.process_params (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\express\lib\router\index.js:346:12)
at next (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\express\lib\router\index.js:280:10)
at initialize (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\passport\lib\middleware\initialize.js:98:5)
at Layer.handle [as handle_request] (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\express\lib\router\layer.js:95:5)
I've implemented a session entity using TypeORM:
import { ISession } from 'connect-typeorm';
import {
Column,
DeleteDateColumn,
Entity,
Index,
PrimaryColumn,
} from 'typeorm';
@Entity('sessions')
export class Session implements ISession {
@Index()
@Column('bigint')
expiredAt: number;
@PrimaryColumn('varchar', { length: 255 })
id: string;
@Column('text')
json: string;
@DeleteDateColumn()
deletedAt?: Date;
}
main.ts:
import 'reflect-metadata';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as session from 'express-session';
import * as passport from 'passport';
import { config } from './config';
import { TypeormStore } from 'connect-typeorm';
import { SessionService } from './session/session.service';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const sessionService = app.get(SessionService);
app.use(
session({
secret: config.sessionSecret,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1 * 60 * 60 * 1000,
},
store: new TypeormStore().connect(sessionService.getSessionRepository()),
}),
);
app.use(passport.initialize());
app.use(passport.session());
app.enableCors({
origin: 'http://localhost:3000',
credentials: true,
});
await app.listen(process.env.PORT);
}
bootstrap();
discord.strategy.ts:
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Profile, Strategy } from 'passport-discord';
import { AuthService } from '../auth.service';
import { config } from '../../config';
@Injectable()
export class DiscordStrategy extends PassportStrategy(Strategy, 'discord') {
constructor(private readonly authService: AuthService) {
super({
clientID: config.discordClientId,
clientSecret: config.discordClientSecret,
callbackURL: config.discordCallbackUrl,
scope: ['identify', 'email', 'guilds'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done: (error: any, user?: any) => void,
) {
const user = await this.authService.validateUser({
avatar: profile.avatar,
email: profile.email,
global_name: profile.global_name,
id: profile.id,
username: profile.username,
access_token: accessToken,
refresh_token: refreshToken,
});
if (user) {
done(null, user);
} else {
done(new Error('User not found'), null);
}
}
}
session.serializer.ts:
import { PassportSerializer } from '@nestjs/passport';
import { AuthService } from '../auth.service';
import { Inject } from '@nestjs/common';
import { SERVICES } from '../../utils/constants';
export class SessionSerializer extends PassportSerializer {
constructor(
@Inject(SERVICES.AUTH) private readonly authService: AuthService,
) {
super();
}
serializeUser(user: any, done: (error: any, user?: any) => void) {
console.log('serialize');
done(null, user);
}
async deserializeUser(payload: any, done: (error: any, user?: any) => void) {
console.log('deserialize');
const user = await this.authService.findUserById(payload.id);
return user ? done(null, user) : done(null, null);
}
}
auth.service.ts:
import { Injectable } from '@nestjs/common';
import { IUser } from './dtos/user.type';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '../utils/typeorm/entities/user.entity';
import { Repository } from 'typeorm';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(User) private readonly userRepository: Repository<User>,
) {}
async validateUser(profile: IUser): Promise<User> {
const user = await this.userRepository.findOne({
where: { id: profile.id },
});
if (user) {
return user;
}
// Create a new user if not found
const newUser = this.userRepository.create(profile);
return await this.userRepository.save(newUser);
}
async findUserById(id: string): Promise<any> {
return this.userRepository.findOne({ where: { id } });
}
}
auth.module.ts:
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { DiscordStrategy } from './utils/discord.strategy';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from '../utils/typeorm/entities/user.entity';
import { SessionSerializer } from './utils/session.serializer';
import { SERVICES } from '../utils/constants';
import { Session } from '../utils/typeorm/entities/session.entity';
@Module({
imports: [TypeOrmModule.forFeature([User, Session])],
providers: [
DiscordStrategy,
SessionSerializer,
{ provide: SERVICES.AUTH, useClass: AuthService },
],
controllers: [AuthController],
})
export class AuthModule {}
I've tried ensuring correct configuration of express-session and Passport middleware. I expected the session to be created successfully and the user to be authenticated. However, the duplicate session entry and missing session support errors prevent successful login.
Duplicate entryerror), and I can see the cookie on my browser. However when I try to access to session data, it throws the[Nest] 17548 - 10.08.2024 16:44:51 ERROR [ExceptionsHandler] Login sessions require session support. Did you forget to use express-session middleware?error.validateUserfunction inAuthService. Can you check every time is the user getting null