UNPKG

@point3/logto-module

Version:

포인트3 내부 logto Authentication 모듈입니다

484 lines (368 loc) 16.3 kB
# point3-logto-module NestJS 기반 Logto 인증/권한 통합 모듈 (서버/클라이언트, M2M, 사용자/역할 관리, OAuth, DI 기반 확장성 제공) --- ## 📦 개요 `@point3-logto-module`은 [Logto](https://logto.io/) 인증 시스템을 NestJS 환경에서 손쉽게 통합할 있도록 설계된 모듈 번들입니다. OAuth, M2M(Machine-to-Machine), 사용자/역할 관리, 토큰 검증, 인증 가드 인증/권한 관련 기능을 일관된 DI 패턴으로 제공합니다. ### 동작 모드 `enableClient` 옵션에 따라 가지 모드로 동작합니다: - **Stateless 모드 (`enableClient: false` 또는 미설정)**: - API 서버와 같이 토큰 검증만 필요한 경우 사용 - `@LogtoProtected()` 가드와 `LogtoTokenVerifier`만 활성화 - 필수 환경변수: `LOGTO_AUTH_ISSUER`만 필요 (LOGTO_JWKS_URI는 선택) - **Stateful 모드 (`enableClient: true`)**: - 로그인/로그아웃 처리가 필요한 애플리케이션이나 M2M 통신이 필요한 경우 사용 - `OAuthClient`, `LogtoM2MClient`, `LogtoLoginSession` 모든 클라이언트 기능 활성화 - 추가 환경변수 필요 (OAuth, M2M 관련) ### 주요 특징 - **유연한 로깅 연동**: 외부 로거 모듈/토큰을 DI로 주입받아, 다양한 로깅 시스템과 쉽게 통합 - **NestJS Dynamic Module 패턴**: 환경/구성에 따라 동적으로 모듈 생성 - **Global 모듈 지원**: 애플리케이션 전체에서 사용할 있는 글로벌 모듈로 설정 가능 - **실제 서비스에서 검증된 인증/권한 관리 기능**: OAuth, M2M, 사용자/역할 관리, 토큰 검증, 인증 가드 - **Fail-fast 환경변수 검증**: 필수 환경변수가 없으면 모듈 초기화 즉시 에러 발생 --- ## 🏗️ 주요 구성요소 ### 1. LogtoModule (Dynamic Module) - 외부 로거 모듈과 토큰을 주입받아, Logto 인증/권한 기능을 번들로 제공 - `LogtoModule.forRoot(options)` 또는 `LogtoModule.forRootAsync(options)` 패턴으로 사용 - `global` 옵션을 `true`로 설정하면 애플리케이션 전체에서 사용 가능한 글로벌 모듈로 등록 ### 2. 핵심 서비스/토큰 - **OAuthClient**: OAuth 인증(로그인/로그아웃 URI, 토큰 발급 등) - **LogtoM2MClient**: 서버 간(M2M) 인증 사용자/역할 관리 - **LogtoLoginSession**: 세션 기반 로그인 플로우 관리 - **LogtoTokenVerifier**: JWT 토큰 검증 권한 체크 - **LogtoTokenGuard**: NestJS 인증 가드(컨트롤러 보호) - **LogtoLoggerServiceToken**: DI로 주입받는 외부 로거 토큰 - **stateless**: 미들웨어/핸들러 기반의 Stateless 인증/인가 유틸리티 가드 제공 ### 3. Config 인터페이스 - **LogtoVerifierConfig**: 토큰 검증 설정 (jwksUri, issuer) - **LogtoOAuthConfig**: OAuth 클라이언트 설정 (endpoint, clientId, clientSecret, resources, scopes, prompt, redirectUri, signInUri, dashboardSignInUri) - **LogtoM2MConfig**: M2M 클라이언트 설정 (endpoint, clientId, clientSecret, resource, apiUrl, scopes) ### 4. 주요 타입/설정 - **LogtoConfig**: 인증/권한 기능을 위한 환경설정 객체 - **Prompt, GrantType**: OAuth 표준 파라미터 Enum - **LogtoUser, LogtoRole 등**: 사용자/역할 관리용 타입 --- ## ⚙️ 설치 ```bash npm install @point3/logto-module ``` --- ## 🚀 사용법 ### 1. 외부 로거 모듈 준비 ```ts // my-logger.module.ts import { Module } from '@nestjs/common'; import { WinstonLoggerService } from './winston-logger.service'; export const MY_LOGGER_TOKEN = Symbol.for('LOGGER'); @Module({ providers: [ { provide: MY_LOGGER_TOKEN, useClass: WinstonLoggerService, }, ], exports: [MY_LOGGER_TOKEN], }) export class MyLoggerModule {} ``` ### 2. AppModule에서 LogtoModule 설정 #### forRoot (동기 설정) ```ts import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as logto from '@point3/logto-module'; import { MyLoggerModule, MY_LOGGER_TOKEN } from './my-logger.module'; @Module({ imports: [ // 환경변수를 먼저 로드 ConfigModule.forRoot({ isGlobal: true }), // Stateless 모드: 토큰 검증만 필요한 경우 logto.module.LogtoModule.forRoot({ global: true, logger: { module: MyLoggerModule, token: MY_LOGGER_TOKEN, }, }), // Stateful 모드: 클라이언트 기능이 필요한 경우 // logto.module.LogtoModule.forRoot({ // global: true, // enableClient: true, // logger: { // module: MyLoggerModule, // token: MY_LOGGER_TOKEN, // }, // }), ], }) export class AppModule {} ``` #### forRootAsync (비동기 설정) ```ts import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import * as logto from '@point3/logto-module'; import { MyLoggerModule, MY_LOGGER_TOKEN } from './my-logger.module'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), logto.module.LogtoModule.forRootAsync({ global: true, imports: [MyLoggerModule], loggerToken: MY_LOGGER_TOKEN, useFactory: (configService: ConfigService) => ({ enableClient: configService.get('LOGTO_CLIENT') === 'true', }), inject: [ConfigService], }), ], }) export class AppModule {} ``` ### 3. 서비스 DI 및 사용 예시 (클래식 DI) ```ts import { Injectable, Inject } from '@nestjs/common'; import { client } from '@point3/logto-module'; @Injectable() export class AuthService { constructor( @Inject(client.OAuthClientToken) private readonly oauthClient: InstanceType<typeof client.OAuthClient>, @Inject(client.LogtoM2MClientToken) private readonly m2mClient: InstanceType<typeof client.LogtoM2MClient>, @Inject(client.LogtoLoginSessionToken) private readonly loginSession: InstanceType<typeof client.LogtoLoginSession>, ) {} // OAuth 로그인 URI 생성 async loginUri() { return this.oauthClient.getSignInURI('admin'); } // M2M 사용자 생성 async createUser(user) { return this.m2mClient.createUser(user); } // 세션 기반 로그인 플로우 예시 async sessionLoginFlow(username: string, password: string) { const session = await this.loginSession.createSignInSession(username); await this.loginSession.verifyPassword(session.sessionId, password); // ... 추가 플로우 } } ``` ### 4. stateless 네임스페이스 활용 예시 > ⚠️ **중요한 주의사항** > `requiredScopes`와 `requiredRoles`에 사용되는 모든 스코프와 역할은 **반드시 Logto 관리 콘솔에서 먼저 정의되어야 합니다**. > > - **스코프(Scopes)**: Logto 콘솔의 API Resources 해당 리소스 Scopes에서 정의 > - **역할(Roles)**: Logto 콘솔의 User Management Roles에서 정의 > - **역할-스코프 연결**: 역할에 필요한 스코프들을 Logto 콘솔에서 할당 > > 코드에서 사용하는 스코프/역할 이름이 Logto에 정의되지 않았다면 토큰 검증이 실패합니다. #### 4-1. LogtoProtected 데코레이터를 사용한 라우트 보호 ```ts // user.controller.ts import { Controller, Get } from '@nestjs/common'; import { stateless } from '@point3/logto-module'; @Controller('users') export class UserController { // 기본 토큰 인증만 필요한 경우 @Get('profile') @stateless.LogtoProtected() getProfile() { return { message: '인증된 사용자만 접근 가능' }; } // 특정 스코프가 필요한 경우 @Get('admin') @stateless.LogtoProtected({ requiredScopes: ['admin', 'user:read'] }) getAdminData() { return { message: '관리자 스코프가 필요한 데이터' }; } // 특정 역할이 필요한 경우 @Get('management') @stateless.LogtoProtected({ requiredRoles: ['superuser', 'management-point3'] }) getManagementData() { return { message: '관리자 역할이 필요한 데이터' }; } // 스코프와 역할을 모두 요구하는 경우 @Get('secure') @stateless.LogtoProtected({ requiredScopes: ['admin'], requiredRoles: ['superuser'] }) getSecureData() { return { message: '고급 권한이 필요한 데이터' }; } } ``` #### 4-2. 커스텀 데코레이터로 유저 정보 추출 ```ts // user.decorator.ts import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const CurrentUser = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); // LogtoTokenGuard가 request.user에 설정한 사용자 정보 반환 return request.user; }, ); // 컨트롤러에서 사용 @Controller('me') export class MeController { @Get() @stateless.LogtoProtected() getMe(@CurrentUser() user: any) { return { userId: user.userId, managerId: user.managerId, clientId: user.clientId }; } } ``` #### 4-3. 전역 가드로 LogtoTokenGuard 등록 ```ts // app.module.ts import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { stateless } from '@point3/logto-module'; @Module({ providers: [ { provide: APP_GUARD, useClass: stateless.LogtoTokenGuard, }, ], }) export class AppModule {} ``` #### 4-4. 커스텀 가드에서 LogtoTokenGuard 확장 ```ts // custom.guard.ts import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { stateless } from '@point3/logto-module'; @Injectable() export class CustomLogtoGuard extends stateless.LogtoTokenGuard { async canActivate(context: ExecutionContext): Promise<boolean> { // 기본 토큰 검증 수행 const isValid = await super.canActivate(context); if (!isValid) return false; // 추가 커스텀 로직 const request = context.switchToHttp().getRequest(); const user = request.user; // 예: 특정 시간대에만 접근 허용 const currentHour = new Date().getHours(); if (currentHour < 9 || currentHour > 18) { return false; } return true; } } ``` #### 4-5. 타입 안전성을 위한 역할 타입 정의 ```ts // types/roles.ts export type UserRole = 'admin' | 'user' | 'moderator'; // 컨트롤러에서 타입 안전하게 사용 @Controller('typed') export class TypedController { @Get('admin-only') @stateless.LogtoProtected<UserRole>({ requiredRoles: ['admin'] // 타입 체크됨 }) getAdminData() { return { message: '타입 안전한 역할 기반 접근 제어' }; } } ``` --- ## 🛠️ 주요 기능 및 역할 ### LogtoModule - 외부 로거 모듈/토큰을 받아, 인증/권한 관련 서비스와 가드를 번들로 제공 - DynamicModule 패턴으로 다양한 환경에 유연하게 적용 가능 ### OAuthClient - OAuth 로그인/로그아웃 URI 생성 - 인증 코드로 토큰 발급 - 토큰 해지(로그아웃) ### LogtoM2MClient - M2M 인증(서버 토큰 발급) - 사용자/역할 생성, 조회, 수정, 삭제, 할당 관리 - 인증코드 발송/검증, 비밀번호 변경 부가 기능 ### LogtoLoginSession - 세션 기반 로그인 플로우(단계별 API 호출) - 비밀번호 검증, 사용자 식별, 동의 처리 ### LogtoTokenVerifier - JWT 토큰 검증 권한 체크 - 인증 가드(LogtoTokenGuard)에서 활용 ### stateless - 미들웨어/핸들러 기반의 Stateless 인증/인가 유틸리티 가드 제공 - Express/Fastify 등에서 직접 활용 가능한 인증 미들웨어, 데코레이터, 커스텀 가드 포함 --- ## 🗂️ 환경변수 목록 및 설명 ### Stateless 모드 (토큰 검증만) | 환경변수명 | 필수 | 설명 | 예시 | |-----------|------|------|---------| | LOGTO_AUTH_ISSUER | | JWT 발급자(iss) | https://auth.example.com/oidc | | LOGTO_JWKS_URI | | JWT 검증용 JWKS 엔드포인트 (기본값: http://localhost:3001/oidc/jwks) | https://auth.example.com/oidc/jwks | ### Stateful 모드 (`enableClient: true`) #### OAuth 클라이언트 관련 (OAuthClient, LogtoLoginSession) | 환경변수명 | 필수 | 설명 | 예시 | |-----------|------|------|---------| | LOGTO_AUTH_ENDPOINT | | Logto 인증 서버 OIDC 엔드포인트 | https://auth.example.com/oidc | | LOGTO_CLIENT_ID | | OAuth 클라이언트 ID | my-client-id | | LOGTO_CLIENT_SECRET | | OAuth 클라이언트 시크릿 | my-client-secret | | LOGTO_RESOURCES | | 접근할 리소스 서버 | https://api.example.com | | LOGTO_SCOPES | | 요청할 OAuth 스코프 (쉼표로 구분) | openid,profile,email | | LOGTO_PROMPT | | OAuth prompt 파라미터 | login | | LOGTO_REDIRECT_URI | | 인증 리다이렉트될 URI | https://myapp.com/callback | | LOGTO_SIGN_IN_URI | | 기본 로그인 URI | https://auth.example.com | | LOGTO_DASHBOARD_SIGN_IN_URI | | 대시보드 로그인 URI (선택) | https://dashboard.example.com | #### M2M 클라이언트 관련 (LogtoM2MClient) | 환경변수명 | 필수 | 설명 | 예시 | |-----------|------|------|---------| | LOGTO_M2M_CLIENT_ID | | M2M 인증용 클라이언트 ID | my-m2m-client-id | | LOGTO_M2M_CLIENT_SECRET | | M2M 인증용 클라이언트 시크릿 | my-m2m-client-secret | | LOGTO_M2M_RESOURCE | | M2M 인증용 리소스 | https://api.example.com | | LOGTO_M2M_API_URL | | M2M API 서버의 base URL | https://api.example.com/api | > ⚠️ **중요**: > - 필수 환경변수(✅)가 설정되지 않으면 모듈 초기화 **즉시 에러가 발생**합니다. > - Stateless 모드에서는 `LOGTO_AUTH_ISSUER`만 필수입니다. > - Stateful 모드에서는 표의 모든 필수 환경변수가 설정되어야 합니다. --- ## 🧩 확장/커스터마이징 - 외부 로깅 시스템과의 연동이 필요할 경우, 원하는 로거 모듈/토큰을 DI로 주입 - 서비스별로 NestJS의 DI/Provider 패턴을 그대로 활용 가능 - 필요시 서비스/가드를 직접 DI 받아 커스텀 비즈니스 로직 구현 가능 --- ## ❓ FAQ - **Q. 로거 토큰이 다르면 어떻게 하나요?** `forRoot`의 `logger.token`에 원하는 토큰(Symbol)을 넘기면 됩니다. - **Q. 환경변수 기반 설정은 어떻게 하나요?** LogtoModule이 내부적으로 `ConfigService`를 사용하여 환경변수를 자동으로 읽습니다. `ConfigModule.forRoot({ isGlobal: true })`가 먼저 import되어 있어야 합니다. - **Q. 인증/권한 외에 사용자/역할 관리도 가능한가요?** 네, `LogtoM2MClient`를 통해 사용자/역할 생성, 조회, 수정, 삭제, 할당 모든 관리가 가능합니다. - **Q. Stateless 모드와 Stateful 모드의 차이는 무엇인가요?** Stateless 모드는 토큰 검증만 필요한 API 서버에 적합하며, `LOGTO_AUTH_ISSUER`만 필수입니다. Stateful 모드는 `enableClient: true`로 설정하여 로그인/로그아웃, M2M 통신 클라이언트 기능을 모두 사용할 있습니다. - **Q. global 옵션은 언제 사용하나요?** `global: true`로 설정하면 LogtoModule이 글로벌 모듈로 등록되어, 다른 모듈에서 별도의 import 없이 Logto 서비스들을 사용할 있습니다. - **Q. 토큰 검증만 필요한데 모든 환경변수를 설정해야 하나요?** 아니요. 토큰 검증만 필요하다면 `LOGTO_AUTH_ISSUER`만 설정하면 됩니다. `enableClient`를 설정하지 않거나 `false`로 설정하면 자동으로 Stateless 모드가 활성화됩니다. - **Q. 필수 환경변수가 없으면 어떻게 되나요?** 모듈 초기화 시점에 `ConfigService.getOrThrow()`를 사용하여 즉시 에러가 발생합니다. 이를 통해 런타임 오류를 사전에 방지할 있습니다. --- ## 🏷️ 내보내는 토큰/서비스 - `OAuthClientToken`, `LogtoM2MClientToken`, `LogtoTokenVerifierToken`, `LogtoLoginSessionToken`, `LogtoTokenGuard` - 서비스/가드는 NestJS DI로 바로 주입받아 사용 가능 --- ## 📚 참고 - [Logto 공식 문서](https://logto.io/) - [NestJS 공식 문서](https://docs.nestjs.com/) --- ## 📝 라이선스 MIT