@ace-common/auth
v3.0.2
Published
Reusable, type-safe NestJS authentication library
Maintainers
Readme
@ace-common/auth
Reusable, type-safe NestJS authentication library with JWT, OAuth (Google, Facebook, Apple), and phone OTP support.
Installation
pnpm add @ace-common/authPeer dependencies: @nestjs/common, @nestjs/core, reflect-metadata, rxjs.
Quick Start
Synchronous config
import { configureAuth } from '@ace-common/auth';
@Module({
imports: [
configureAuth<User>({
jwtSecret: 'your-secret',
jwtExpiresIn: '7d', // optional, defaults to "1h"
userAdapter: MyUserAdapter,
oauth: {
google: { clientId: '...' },
facebook: { appId: '...', appSecret: '...' },
apple: { clientId: '...' },
},
phone: {
verifyCode: (phone, code) => twilioVerify(phone, code),
normalizePhone: (p) => p.replace(/\s/g, ''),
},
}),
],
})
export class AppModule {}Async config (e.g. with ConfigService)
import { configureAuthAsync } from '@ace-common/auth';
import { ConfigService } from '@nestjs/config';
@Module({
imports: [
configureAuthAsync<User>({
userAdapter: MyUserAdapter,
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
jwtSecret: config.getOrThrow('JWT_SECRET'),
jwtExpiresIn: config.get('JWT_EXPIRES_IN', '1h'),
oauth: {
google: { clientId: config.getOrThrow('GOOGLE_CLIENT_ID') },
},
}),
}),
],
})
export class AppModule {}UserAdapter
Implement the UserAdapter interface to connect the library to your data layer:
@Injectable()
export class MyUserAdapter implements UserAdapter<User> {
findById(id: string): Observable<User | null> { /* ... */ }
findOneBy(criteria: Partial<User>): Observable<User | null> { /* ... */ }
findOrCreate?(criteria: Partial<User>, data: Partial<User>): Observable<User> { /* ... */ }
create?(data: Partial<User>): Observable<User> { /* ... */ }
}Required: findById, findOneBy.
For OAuth/phone flows: implement findOrCreate or create.
Controller Usage
@Controller('auth')
@UseFilters(AuthExceptionFilter)
export class AuthController {
constructor(private readonly authService: AuthService<User>) {}
@Post('google')
login(@Body() dto: OAuthLoginDto) {
return firstValueFrom(this.authService.authenticateOAuth('google', dto.token));
}
@Post('phone')
loginPhone(@Body() dto: PhoneLoginDto) {
return firstValueFrom(this.authService.authenticatePhone(dto.phone, dto.code));
}
@Get('profile')
@UseGuards(AuthGuard)
getProfile(@CurrentUser() jwt: JwtPayload) {
return firstValueFrom(this.authService.findUserById(jwt.sub));
}
}Error Handling
Apply AuthExceptionFilter at the controller level (not globally) so it only handles auth routes:
@Controller('auth')
@UseFilters(AuthExceptionFilter)
export class AuthController { /* ... */ }All auth errors follow a consistent shape:
{
"statusCode": 401,
"errorCode": "AUTH_INVALID_TOKEN",
"message": "Token has expired",
"timestamp": "2026-04-03T12:00:00.000Z"
}Exports
| Export | Description |
|---|---|
| configureAuth / configureAuthAsync | Module configuration helpers |
| AuthModule | NestJS dynamic module (forRoot / forRootAsync) |
| AuthService | JWT signing/verification, OAuth, phone auth |
| AuthGuard | Route guard — validates Bearer token |
| CurrentUser | Parameter decorator — extracts JWT payload |
| AuthException | Base exception class for all auth errors |
| AuthExceptionFilter | Exception filter scoped to AuthException |
| AuthErrorCode | Enum of all error codes |
| UserAdapter | Interface for your data layer |
License
MIT
