Skip to content

Reset password

Let users reset their password after verifying account ownership.

This guide adds the reset-password feature.

The reset-password feature lets users set a new password after proving account ownership. It uses features such as otp or magic-link as verification strategies. These features can be used not only for authentication, but also by other flows that need to verify account ownership.

Add the feature

Run the CLI command:

Terminal window
brkpt auth add reset-password

The CLI adds a new features/reset-password/ folder.

Validate the DTOs

The reset password feature uses a fixed request shape. This guide has already enabled ValidationPipe, so add validation decorators to the generated DTOs.

src/brkpt-auth/features/reset-password/dto/send.dto.ts
import { IsString } from 'class-validator';
export class SendDto {
@IsString()
target!: string;
@IsString()
strategy!: string;
@IsString()
method!: string;
}
src/brkpt-auth/features/reset-password/dto/reset.dto.ts
import { IsOptional, IsString, MinLength } from 'class-validator';
export class ResetDto {
@IsOptional()
@IsString()
target?: string;
@IsString()
strategy!: string;
@IsString()
proof!: string;
@IsString()
@MinLength(6)
newPassword!: string;
}

strategy selects the verification strategy used to prove account ownership. This guide has enabled both otp and magic-link.

proof contains the verification proof produced by that strategy: an OTP code for otp, or a token for magic-link.

target is optional during the reset step because different strategies resolve the verified target differently. OTP requires the target to locate and verify the code, while a magic link can recover the target directly from its token.

Implement the adapter

Create an adapter that finds users by a verified target and updates their password:

  • Directorysrc/
    • Directorybrkpt-auth/
      • Directoryadapters/
        • reset-password.adapter.ts
src/brkpt-auth/adapters/reset-password.adapter.ts
import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { User } from '../../../generated/prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import { ResetPasswordPort } from '../features/reset-password/reset-password.port';
@Injectable()
export class ResetPasswordAdapter implements ResetPasswordPort<User> {
constructor(private readonly prisma: PrismaService) {}
async findUserByTarget(method: string, target: string): Promise<User | null> {
switch (method) {
case 'email':
return this.prisma.user.findUnique({
where: { email: target },
});
}
return null;
}
async updatePassword(user: User, newPassword: string): Promise<void> {
const password = await bcrypt.hash(newPassword, 10);
await this.prisma.user.update({
where: { id: user.id },
data: { password },
});
}
extractUserIdFromUser(user: User): number {
return user.id;
}
}

Register the feature

Update features.ts and pass ResetPasswordAdapter to resetPasswordFeature:

src/brkpt-auth/features.ts
import { BlacklistAdapter } from './adapters/blacklist.adapter';
import { ChangePasswordAdapter } from './adapters/change-password.adapter';
import { CoreAdapter } from './adapters/core.adapter';
import { CredentialsAdapter } from './adapters/credentials.adapter';
import { MagicLinkAdapter } from './adapters/magic-link.adapter';
import { OAuthAdapter } from './adapters/oauth.adapter';
import { OtpAdapter } from './adapters/otp.adapter';
import { ResetPasswordAdapter } from './adapters/reset-password.adapter';
import { SessionAdapter } from './adapters/session.adapter';
import { FeatureConfig } from './common/interfaces';
import { blacklistFeature } from './features/blacklist/blacklist.feature';
import { changePasswordFeature } from './features/change-password/change-password.feature';
import { coreFeature } from './features/core/core.feature';
import { credentialsFeature } from './features/credentials/credentials.feature';
import { EmailMagicLinkDriver } from './features/magic-link/drivers/email.driver';
import { magicLinkFeature } from './features/magic-link/magic-link.feature';
import { GithubOAuthDriver } from './features/oauth/drivers/github.driver';
import { GoogleOAuthDriver } from './features/oauth/drivers/google.driver';
import { oauthFeature } from './features/oauth/oauth.feature';
import { EmailOtpDriver } from './features/otp/drivers/email.driver';
import { otpFeature } from './features/otp/otp.feature';
import { resetPasswordFeature } from './features/reset-password/reset-password.feature';
import { sessionFeature } from './features/session/session.feature';
export const features: FeatureConfig[] = [
coreFeature(CoreAdapter),
blacklistFeature(BlacklistAdapter),
credentialsFeature(CredentialsAdapter),
sessionFeature(SessionAdapter),
oauthFeature(OAuthAdapter, GoogleOAuthDriver, GithubOAuthDriver),
otpFeature(OtpAdapter, EmailOtpDriver),
magicLinkFeature(MagicLinkAdapter, EmailMagicLinkDriver),
changePasswordFeature(ChangePasswordAdapter),
resetPasswordFeature(ResetPasswordAdapter),
];

OTP can be used immediately with the configuration from the previous guides. To also use magic link for password reset, add a resetPassword callback URL:

src/app.module.ts
magicLink: {
expiresIn: '5m',
callbackUrls: {
authenticate: 'http://localhost:3000/auth/magic-link/authenticate',
resetPassword: 'http://localhost:3000/auth/reset-password/reset',
},
},

Start the application:

Terminal window
pnpm start:dev

The reset-password feature adds two endpoints:

Method Path Description
POST /auth/reset-password/send Send password reset verification
POST /auth/reset-password/reset Reset the password

Reset password with OTP

Use an OTP verification strategy, then send the verification target and proof with the new password:

POST /auth/reset-password/send
Content-Type: application/json
{
"target": "kevin@example.com",
"strategy": "otp",
"method": "email"
}

The email contains an OTP for the password reset flow:

Subject: Your OTP code to reset your password
Your OTP code is: 127419

Submit the target, code, and new password:

POST /auth/reset-password/reset
Content-Type: application/json
{
"target": "kevin@example.com",
"strategy": "otp",
"proof": "127419",
"newPassword": "new-password"
}

OTP requires target during verification. If it is omitted, the request fails with Target is required for OTP verification.

Reset password with magic link

Use a magic link verification strategy. The token from the link is used as the proof:

POST /auth/reset-password/send
Content-Type: application/json
{
"target": "kevin@example.com",
"strategy": "magic-link",
"method": "email"
}

The email contains a link like:

Subject: Your magic link to reset your password
Click the link to continue: http://localhost:3000/auth/reset-password/reset?token=<magic-link-token>

For this backend-only example, copy the token from the link and submit it with the new password:

POST /auth/reset-password/reset
Content-Type: application/json
{
"strategy": "magic-link",
"proof": "<magic-link-token>",
"newPassword": "new-password"
}

Magic link verification does not require target during the reset request because the verified target is recovered from the token.

After a successful password reset, existing sessions for the user are revoked.