Careeroza
Real World Example
OTP Login for Indian Mobile Users
Most Indian consumer apps (Swiggy, Zepto, PhonePe) log users in with a mobile OTP instead of a password. The OTP is hashed, short-lived, single-use and rate limited; a verified OTP returns the same JWT pair as a password login.
src/auth/otp.service.ts
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import type { Cache } from 'cache-manager';
import { createHash, randomInt } from 'node:crypto';
import { SMS_SENDER, SmsSender } from '../notifications/sms.module';
import { UsersService } from '../users/users.service';
import { AuthService } from './auth.service';
const hash = (s: string) => createHash('sha256').update(s).digest('hex');
@Injectable()
export class OtpService {
constructor(
@Inject(CACHE_MANAGER) private readonly cache: Cache,
@Inject(SMS_SENDER) private readonly sms: SmsSender,
private readonly users: UsersService,
private readonly auth: AuthService,
) {}
async send(phone: string) {
const otp = String(randomInt(100000, 1000000));
await this.cache.set(`otp:${phone}`, { hash: hash(otp), attempts: 0 }, 5 * 60_000);
await this.sms.send(phone, `${otp} is your ShopEase login OTP. Do not share it.`);
return { sent: true, expiresIn: 300 };
}
async verify(phone: string, otp: string) {
const entry = await this.cache.get<{ hash: string; attempts: number }>(`otp:${phone}`);
if (!entry || entry.attempts >= 5) throw new BadRequestException('OTP expired, request a new one');
if (entry.hash !== hash(otp)) {
await this.cache.set(`otp:${phone}`, { ...entry, attempts: entry.attempts + 1 }, 5 * 60_000);
throw new BadRequestException('Incorrect OTP');
}
await this.cache.del(`otp:${phone}`); // single use
const user = await this.users.findOrCreateByPhone(phone);
return this.auth.issueTokens(user);
}
}Example
curl -X POST http://localhost:3000/auth/otp/send -H "Content-Type: application/json" -d '{"phone":"+919876543210"}'
curl -X POST http://localhost:3000/auth/otp/verify -H "Content-Type: application/json" -d '{"phone":"+919876543210","otp":"482913"}'Example output
{"sent":true,"expiresIn":300}
{"accessToken":"eyJhbGciOiJIUzI1NiIs...","refreshToken":"eyJhbGciOiJIUzI1NiIs..."}