데이터베이스 어댑터 만들기
Auth.js 어댑터를 사용하면 공식 패키지가 아직 없는 경우에도 어떤 데이터베이스나 백엔드 서비스와도 통합할 수 있습니다. (새로운 어댑터에 대한 PR을 환영합니다! 아래 가이드라인을 참고하세요.)
Auth.js 어댑터는 매우 유연합니다. 필요한 메서드만 구현하고 실제로 사용될 데이터베이스 테이블/컬럼만 생성하면 됩니다.
Auth.js 어댑터는 ORM/데이터베이스 클라이언트를 받아서 데이터베이스와 상호작용하는 메서드가 포함된 객체를 반환하는 함수입니다. (이 메서드들은 Adapter
인터페이스를 기반으로 합니다.) 동일한 데이터베이스 어댑터는 모든 Auth.js 라이브러리와 호환됩니다.
선택적으로, 여러분의 어댑터가 Auth.js와 호환되는지 확인하기 위해 어댑터 테스트를 실행할 수 있습니다.
사용자 관리
Auth.js는 사용자(user)와 계정(account)을 구분합니다. 한 사용자는 여러 계정을 가질 수 있습니다. 계정은 사용자가 처음 로그인한 프로바이더 타입마다 생성됩니다. 예를 들어, 사용자가 Google로 로그인한 후 Facebook으로 로그인하면, 각 프로바이더마다 하나씩 총 두 개의 계정이 생깁니다. 사용자가 처음 로그인한 프로바이더는 사용자 객체를 생성하는 데에도 사용됩니다. 자세한 내용은 profile()
프로바이더 메서드를 참고하세요.
메서드와 모델
Auth.js에서 아직 호출되지 않는 메서드:
Auth.js에서 필수는 아니지만, 기본적인 표시를 위해 User
테이블에 다음 컬럼을 추가하는 것을 권장합니다: name
, email
, image
. 이 컬럼들은 profile()
프로바이더 메서드를 통해 설정할 수 있습니다. 만약 이러한 속성을 저장할 필요가 없다면, 빈 profile() {}
메서드를 생성하세요.
Auth.js에서 필수는 아니지만, Account
테이블은 일반적으로 프로바이더로부터 받은 토큰을 저장합니다. 이 컬럼들은 account()
프로바이더 메서드를 통해 설정할 수 있습니다. 토큰을 저장할 필요가 없다면, 빈 account() {}
메서드를 생성하세요.
데이터베이스 세션 관리
Auth.js는 두 가지 방식으로 세션을 관리할 수 있습니다. 각 방식의 장단점은 개념: 세션 전략에서 확인할 수 있습니다.
Methods and models_dUpoHHVGrSu2WCNSzBaiuf
데이터베이스 세션을 사용하려면 다음 메서드를 구현해야 합니다:
데이터베이스 세션 관리를 추가하려면 다음과 같이 데이터베이스 테이블/컬럼을 확장해야 합니다:
참고: Session 모델.
검증 토큰
이메일/비밀번호 없는 로그인을 지원하려면 Auth.js는 사용자의 이메일 주소와 연결된 임시 검증 토큰을 데이터베이스에 저장합니다.
메서드와 모델_ZHohyJ6dYErG63KZq4uGoV
참고: Verification Token 모델.
공식 어댑터 가이드라인
아래 모든 단계를 완료하면, 저장소에 PR을 제출할 준비가 된 것입니다.
어댑터를 만들었고 이를 공식 패키지로 배포하고 싶다면, 다음 요구사항을 충족하는지 확인하세요. 기존 어댑터를 참고하여 패키지 구조, 필요한 파일, 테스트 설정, 구성 등을 확인할 수 있습니다.
-
어댑터는 반드시
Adapter
인터페이스의 모든 메서드를 구현해야 합니다. -
어댑터 테스트가 포함되어야 하며, 테스트는 반드시 통과해야 합니다. 네트워크 오류에 강하고 GitHub Action Secrets의 수를 줄이기 위해 Docker를 선호합니다. 이렇게 하면 포크 PR에서도 테스트를 실행할 수 있습니다.
-
어댑터는 다음 코딩 스타일을 따라야 합니다.
- TypeScript로 작성
- 모노레포의 린트 규칙 통과
- 폴리필 미포함
- ES 모듈(ESM)로 구성
- JSDoc 주석으로 문서화
- 메인 모듈에서 최소 하나의 명명된 내보내기가 있어야 함 (예:
export function MyAdapter(): Adapter {}
) - 컬렉션/테이블 이름은 기본 ORM/데이터베이스 문서/규칙에 따라 복수/단수, camelCase/snake_case 규칙을 따라야 함
-
모노레포를 구성하여 패키지 유지보수를 돕습니다.
- 이 디렉토리에 (가급적
.svg
형식의) 로고 추가 - 어댑터를 GitHub 워크플로 파일에 추가: 여기와 여기
- 생성된 파일이 있다면
.gitignore
에 추가
- 이 디렉토리에 (가급적
-
어댑터는 사용자로부터 오는 모든 속성을 처리할 수 있어야 합니다.
ORM/데이터베이스 클라이언트는 자체 데이터 타입을 가질 수 있지만, Auth.js는 일관성을 위해 이를 일반 JavaScript 객체로 정규화할 것을 기대합니다. ORM/데이터베이스 클라이언트가 자동으로 변환하지 않는다면, 데이터베이스에서 읽고 쓸 때 값을 변환해야 합니다.
속성 이름을 확인하고 이를 기반으로 변환하려는 유혹이 들 수 있지만, 이는 확장성이 떨어집니다 (예:
User
객체는emailVerified
외에도 여러Date
속성을 가질 수 있음).대신, 값을 변환하는 유틸리티 함수를 만드는 것을 권장합니다. 아래는 날짜를 변환하는 예제입니다 (ORM/데이터베이스 클라이언트가 다른 데이터 타입을 사용한다면, 날짜뿐만 아니라 해당 타입도 변환해야 합니다). 값이 날짜로 파싱될 수 있는지 확인하고, 가능하다면
Date
객체로 변환합니다. 그렇지 않으면 원래 값을 그대로 유지합니다.
// https://github.com/honeinc/is-iso-date/blob/8831e79b5b5ee615920dcb350a355ffc5cbf7aed/index.js#L5
const isoDateRE =
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/
const isDate = (val: any): val is ConstructorParameters<typeof Date>[0] =>
!!(val && isoDateRE.test(val) && !isNaN(Date.parse(val)))
export const format = {
/** 데이터베이스에서 오는 객체를 일반 JavaScript로 변환합니다. */
from<T>(object: Record<string, any> = {}): T {
const newObject: Record<string, unknown> = {}
for (const [key, value] of Object.entries(object))
if (isDate(value)) newObject[key] = new Date(value)
else newObject[key] = value
return newObject as T
},
/** Auth.js에서 오는 객체를 데이터베이스에 쓰기 위해 준비합니다. */
to<T>(object: Record<string, any>): T {
const newObject: Record<string, unknown> = {}
for (const [key, value] of Object.entries(object))
if (value instanceof Date) newObject[key] = value.toISOString()
else newObject[key] = value
return newObject as T
},
}
TypeScript
프레임워크 패키지(예: next-auth/adapters
, @auth/sveltekit/adapters
)와 함께 제공되는 타입을 활용할 수 있습니다.
import type { Adapter } from "next-auth/adapters"
function MyAdapter(): Adapter {
return {
// 어댑터 메서드 작성
}
}
JavaScript로 어댑터를 작성할 때도 JSDoc을 사용하면 편집기 힌트와 자동 완성을 활용할 수 있습니다.
/** @return { import("next-auth/adapters").Adapter } */
function MyAdapter() {
return {
// 어댑터 메서드 작성
}
}