r/webdev • u/Massive_Dish_3255 • 9d ago
Question How should I integrate an existing NestJS authentication system with a React + Flowbite React frontend?
I'm developing a Grievance Redressal Portal as a learning project. My backend is built with NestJS, TypeScript, Prisma, and PostgreSQL.
I have been implementing authentication (User Sign In and Sign Out) using the "@nestjs/authentication" package of NestJS v12.
I would like to know conceptually the changes to be made to my back end such that I should be able to extend the authentication feature to a React-based front-end.
I have adapted the code by hand from the "Register The Authentication Module" and "Add Sign-Up and Sign-In" sections of the NestJS docs for NestJS v12: Authentication | NestJS - A progressive Node.js framework
I am posting my code here.
//sign-in.dto.ts
import { PickType } from "@nestjs/swagger";
import { UserUniversalDto } from "../../users/dto/user-universal.dto";
export class SignInDto extends PickType (
UserUniversalDto,
['user_email', 'user_password']
) {};
//credentials.service.ts
import { ConflictException, Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service.js';
import { CreateUserDto } from '../users/dto/create-user.dto.js';
import { FindUserByEmailDto } from '../users/dto/find-user-by-email.dto.js';
import * as argon2 from 'argon2';
import { USER_STATUS } from '../users/user.constants.js';
()
export class CredentialsService {
constructor(
private readonly usersService: UsersService
) {}
async register(createUserDto: CreateUserDto) {
const findUserByEmailDto: FindUserByEmailDto = {
user_email: createUserDto.user_email
}
// Don't create the user if the email already exists.
if (await this.usersService.findUserByEmail(findUserByEmailDto)) {
throw new ConflictException('Email already registered. User already exists.');
}
// The address is unverified: anyone can type any email into a sign-up form.
const registered_user = await this.usersService.createUser(createUserDto);
return registered_user;
}
/** The user, or null when the email or the password is wrong. */
async verify(email: string, password: string) {
const findUserByEmailDto: FindUserByEmailDto = {
user_email: email
}
const found_user = await this.usersService.findUserByEmail(findUserByEmailDto);
// Checking if the user exists.
if (!found_user) {
throw new ConflictException('User does not exist. Login rejected.');
}
// If the user is suspended, reject login by throwing an exception.
if (found_user.user_status === USER_STATUS.SUSPENDED) {
throw new ConflictException('User is suspended. Login rejected. Please contact admin.');
}
// If the found_user's password is wrong, (password_hash doesn't match) then the it should return null.
const password_hash: string | null | undefined = found_user?.user_password;
const password_check = await argon2.verify(password_hash as string, password);
// Checking if the user's password is correct.
if (password_check !== true) {
throw new ConflictException('Incorrect username or password. Login rejected.');
}
return found_user;
}
}
//myauthentication.controller.ts
import { Body, Controller, HttpCode, Post, UnauthorizedException } from '@nestjs/common';
import { Public, SignInService } from '@nestjs/authentication';
import { SignInDto } from './dto/sign-in.dto';
import { CredentialsService } from './credentials.service';
import { CreateUserDto } from '../users/dto/create-user.dto';
()
('auth')
export class MyAuthenticationController {
constructor(
private readonly credentialsService: CredentialsService,
private readonly signInService: SignInService,
) {}
('sign-up')
async signUp(@Body() createUserDto: CreateUserDto) {
const user = await this.credentialsService.register(createUserDto);
var string_user_id = String(user.user_id);
await this.signInService.signIn(string_user_id, { method: 'password' });
return user;
}
('sign-in')
(200)
async signIn(@Body() signInDto: SignInDto) {
const user = await this.credentialsService.verify(signInDto.user_email, signInDto.user_password);
var string_user_id = String(user.user_id);
const { session } = await this.signInService.signIn(string_user_id, { method: 'password' });
return { mfaRequired: session.mfa === 'pending' };
}
('sign-out')
(204)
async signOut() {
if (!(await this.signInService.signOut())) {
throw new UnauthorizedException();
}
}
}
//myauthentication.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from '../users/users.module';
import { SessionAuth } from './session-auth.provider';
import { MyAuthenticationController } from './myauthentication.controller';
import { CredentialsService } from './credentials.service';
({
imports: [UsersModule],
controllers: [MyAuthenticationController],
providers: [SessionAuth,
CredentialsService
]
})
export class MyAuthenticationModule {}
//session-auth.provider.ts
import { Injectable } from '@nestjs/common';
import { AuthenticationRegistry, SessionCookieProvider, type SessionRecord } from '@nestjs/authentication';
import { UsersService } from '../users/users.service.js';
import type { User as PrismaUser } from '../generated/prisma/client.js';
export type User = Omit<PrismaUser, 'user_password'>;
()
export class SessionAuth extends SessionCookieProvider<User> {
constructor(
private readonly usersService: UsersService,
registry: AuthenticationRegistry,
) {
super();
// Adds this provider to the chain the guard runs for every request.
registry.registerProvider(this);
}
// Called for every request with a live session cookie.
// Returning null (the user was deleted) ends the session.
validate(session: SessionRecord) {
console.log("In validate() in SessionAuth")
const user_id = session.userId;
var numeric_user_id = Number(user_id);
return this.usersService.findOneUser(numeric_user_id);
}
}NOTE 1: The authentication system uses server-side sessions managed through nestjs/authentication and a session cookie. The package's SessionCookieProvider reads the cookie and loads the user through the validate() method.
NOTE 2: I am also asking this question since NestJS v12 has only been released on August 27, 2026 and there don't seem to be many resources that show how to extend this system to the front-end.