Backend Architecture Series — Jocoso.cl eCommerce · #02
JWT, Refresh Tokens and 2FA: Real Security from Day 1
Short-lived access tokens, refresh token rotation, and TOTP for a multichannel ecommerce
Security Cannot Be an Afterthought
In an ecommerce the consequences of a breach are direct: compromised accounts, fraudulent orders, card data at risk. Designing security from day 1 not only protects users — it protects the business reputation and the regulatory compliance required by payment gateways like MercadoPago.
Dual Token Architecture
■ Technical Decision
Access Token (JWT, 15 min) + rotated Refresh Token (7 days, SHA-256 hashed in DB). The 15 minutes minimize the exposure window if the token is intercepted. The refresh token is never stored in plain text — only its SHA-256 hash.
Refresh Token Rotation — Theft Detection
Each use of the refresh token invalidates the previous token and generates a new one. If an attacker steals the token and uses it before the legitimate user, the second use attempt detects the collision and revokes the entire session:
// application/auth/use-cases/refresh.usecase.ts
async execute(rawToken: string): Promise<TokenPair> {
const hash = this.tokenSvc.hashToken(rawToken); // SHA-256
const record = await this.refreshRepo.findByHash(hash);
if (!record || record.isRevoked || record.expiresAt < new Date()) {
throw new UnauthorizedException('Invalid or expired token');
}
// Rotation: revoke old, issue new
await this.refreshRepo.revoke(record.id);
const newRaw = this.tokenSvc.generateRefreshToken();
const newHash = this.tokenSvc.hashToken(newRaw);
await this.refreshRepo.save({ userId: record.userId, hash: newHash, ... });
return { accessToken: this.tokenSvc.generateAccessToken(user), refreshToken: newRaw };
}RBAC — Role-Based Access Control
The system handles three roles: ADMIN, SELLER, and CUSTOMER. Authorization is implemented with a declarative decorator and a guard that reads the JWT payload:
// infrastructure/security/guards/roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(ctx: ExecutionContext): boolean {
const roles = this.reflector.get<Role[]>('roles', ctx.getHandler());
if (!roles?.length) return true;
const { user } = ctx.switchToHttp().getRequest();
return roles.includes(user.role);
}
}
// Usage in controller:
@Get('admin/users')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMIN)
listUsers() { ... }2FA with TOTP — Google Authenticator
Two-factor authentication uses TOTP (Time-based One-Time Password) compatible with Google Authenticator and Authy. The flow has three steps:
- Setup: the server generates a secret with speakeasy and returns a QR code URI
- Verify: the user enters the 6-digit code to confirm setup
- Login: if 2FA is enabled, login requires the TOTP code in addition to password
// infrastructure/security/totp.service.ts
@Injectable()
export class TotpService {
generateSecret(email: string) {
const secret = speakeasy.generateSecret({
name: `Jocoso.cl (${email})`,
issuer: 'Jocoso.cl',
});
return { secret: secret.base32, otpauthUrl: secret.otpauth_url };
}
verify(secret: string, token: string): boolean {
return speakeasy.totp.verify({
secret, encoding: 'base32', token, window: 1,
});
}
}bcrypt with 12 Rounds
Passwords are hashed with bcrypt using 12 rounds. On modern hardware, 12 rounds takes ~250ms — enough to make brute-force attacks prohibitively slow, without degrading the user login experience.
API Hardening
- Helmet: configures HTTP security headers (CSP, HSTS, X-Frame-Options)
- Restrictive CORS: only whitelisted origins allowed
- Global ValidationPipe: whitelist + transform — rejects fields not declared in DTOs
- class-validator on all DTOs:
IsEmail,IsStrongPassword,IsJWT
■ Trade-offs
Stateless JWT vs stateful sessions. JWT allows horizontal scaling without shared store — key for preparing microservices. The downside is that immediate revocation requires a Redis blacklist. For Jocoso.cl the solution is short access tokens (15 min): the impact of a stolen token is bounded by the expiration time. Refresh tokens are revoked in DB on logout and rotation.
Conclusion
Security in an ecommerce is not an additional feature — it is the foundation that allows operation. This design covers three dimensions: robust authentication (JWT + refresh rotation), granular authorization (RBAC), and second factor (TOTP). All implemented in the infrastructure layer, invisible to the domain.