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.
Prerequisites
Section titled “Prerequisites”- A project completed from Change password
Add the reset password feature
Section titled “Add the reset password feature”Add the feature
Run the CLI command:
brkpt auth add reset-passwordThe 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.
import { IsString } from 'class-validator';
export class SendDto { @IsString() target!: string;
@IsString() strategy!: string;
@IsString() method!: string;}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
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:
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),];Configure the magic link callback
Section titled “Configure the magic link callback”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:
magicLink: { expiresIn: '5m', callbackUrls: { authenticate: 'http://localhost:3000/auth/magic-link/authenticate', resetPassword: 'http://localhost:3000/auth/reset-password/reset', },},Run and verify
Section titled “Run and verify”Start the application:
pnpm start:devnpm run start:devyarn start:devThe 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/sendContent-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: 127419Submit the target, code, and new password:
POST /auth/reset-password/resetContent-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/sendContent-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/resetContent-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.