이중 인증 (2FA)

OTP TOTP 백업 코드 신뢰 기기

이중 인증(2FA)은 사용자가 로그인할 때 추가적인 보안 단계를 제공합니다. 단순히 비밀번호만 사용하는 대신, 두 번째 형태의 인증을 제공해야 합니다. 이를 통해 비밀번호를 알아낸 경우에도 무단으로 계정에 접근하는 것을 훨씬 어렵게 만듭니다.

이 플러그인은 두 가지 주요 2차 인증 방법을 제공합니다:

  1. OTP (일회용 비밀번호): 사용자의 이메일이나 전화번호로 전송되는 임시 코드입니다.
  2. TOTP (시간 기반 일회용 비밀번호): 사용자 기기의 앱에서 생성되는 코드입니다.

추가 기능:

  • 계정 복구를 위한 백업 코드 생성
  • 2FA 활성화/비활성화
  • 신뢰 기기 관리

설치

auth 설정에 플러그인 추가

auth 설정에 이중 인증 플러그인을 추가하고 발급자(issuer)로 앱 이름을 지정합니다.

auth.ts
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"

export const auth = betterAuth({
    // ... 기타 설정 옵션
    appName: "My App", // 앱 이름을 제공합니다. 발급자로 사용됩니다.
    plugins: [
        twoFactor() 
    ]
})

데이터베이스 마이그레이션

마이그레이션을 실행하거나 스키마를 생성하여 필요한 필드와 테이블을 데이터베이스에 추가합니다.

npx @better-auth/cli migrate
npx @better-auth/cli generate

필드를 수동으로 추가하려면 스키마 섹션을 참조하세요.

클라이언트 플러그인 추가

클라이언트 플러그인을 추가하고 사용자가 2차 인증을 확인해야 하는 경우 리디렉션될 위치를 지정합니다.

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
    plugins: [
        twoFactorClient()
    ]
})

사용법

2FA 활성화

이중 인증을 활성화하려면 사용자의 비밀번호와 발급자(선택 사항)와 함께 twoFactor.enable을 호출합니다:

POST
/two-factor/enable
const { data, error } = await authClient.twoFactor.enable({    password: "secure-password", // required    issuer: "my-app-name",});
PropDescriptionType
password
사용자의 비밀번호
string
issuer?
TOTP URI의 사용자 정의 발급자(선택 사항). 기본값은 auth 설정에 정의된 앱 이름입니다.
string

2FA가 활성화되면:

  • 암호화된 secretbackupCodes가 생성됩니다.
  • enabletotpURIbackupCodes를 반환합니다.

참고: twoFactorEnabled는 사용자가 TOTP 코드를 확인할 때까지 true로 설정되지 않습니다. TOTP 확인에 대한 자세한 내용은 여기를 참조하세요. 플러그인 설정에서 skipVerificationOnEnable을 true로 설정하여 확인을 건너뛸 수 있습니다.

현재 이중 인증은 자격 증명 계정에서만 활성화할 수 있습니다. 소셜 계정의 경우 제공업체가 이미 2FA를 처리한다고 가정합니다.

2FA로 로그인

2FA가 활성화된 사용자가 이메일을 통해 로그인을 시도하면, 응답 객체에 twoFactorRedirecttrue로 설정됩니다. 이는 사용자가 2FA 코드를 확인해야 함을 나타냅니다.

onSuccess 콜백에서 이를 처리하거나 플러그인 설정에서 onTwoFactorRedirect 콜백을 제공할 수 있습니다.

sign-in.tsx
await authClient.signIn.email({
        email: "user@example.com",
        password: "password123",
    },
    {
        async onSuccess(context) {
            if (context.data.twoFactorRedirect) {
                // 2FA 확인을 제자리에서 처리
            }
        },
    }
)

onTwoFactorRedirect 설정 사용:

sign-in.ts
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";

const authClient = createAuthClient({
    plugins: [
        twoFactorClient({
            onTwoFactorRedirect(){
                // 2FA 확인을 전역적으로 처리
            },
        }),
    ],
});

auth.api와 함께 사용

서버에서 auth.api.signInEmail을 호출하고 사용자가 2FA를 활성화한 경우, twoFactorRedirecttrue로 설정된 객체를 반환합니다. 이 동작은 TypeScript에서 추론되지 않아 오해의 소지가 있을 수 있습니다. 대신 in을 사용하여 twoFactorRedirecttrue로 설정되었는지 확인할 수 있습니다.

const response = await auth.api.signInEmail({
	body: {
		email: "test@test.com",
		password: "test",
	},
});

if ("twoFactorRedirect" in response) {
	// 2FA 확인을 제자리에서 처리
}

2FA 비활성화

이중 인증을 비활성화하려면 사용자의 비밀번호와 함께 twoFactor.disable을 호출합니다:

POST
/two-factor/disable
const { data, error } = await authClient.twoFactor.disable({    password, // required});
PropDescriptionType
password
사용자의 비밀번호
string

TOTP

TOTP(시간 기반 일회용 비밀번호)는 시간을 카운터로 사용하여 각 로그인 시도마다 고유한 비밀번호를 생성하는 알고리즘입니다. 고정된 간격(Better Auth는 기본적으로 30초)마다 새로운 비밀번호가 생성됩니다. 이는 기존 비밀번호의 여러 문제를 해결합니다: 잊어버릴 수 있고, 도난당하거나, 추측될 수 있습니다. OTP는 이러한 문제 중 일부를 해결하지만, SMS나 이메일을 통한 전달은 신뢰할 수 없거나(새로운 공격 벡터를 열어주기 때문에 위험할 수도 있습니다) 위험할 수 있습니다.

그러나 TOTP는 코드를 오프라인으로 생성하여 안전하고 편리합니다. 휴대폰의 인증 앱만 있으면 됩니다.

TOTP URI 가져오기

2FA를 활성화한 후, 사용자에게 표시할 TOTP URI를 가져올 수 있습니다. 이 URI는 서버에서 secretissuer를 사용하여 생성되며, 사용자가 인증 앱으로 스캔할 QR 코드를 생성하는 데 사용할 수 있습니다.

POST
/two-factor/get-totp-uri
const { data, error } = await authClient.twoFactor.getTotpUri({    password, // required});
PropDescriptionType
password
사용자의 비밀번호
string

예제: React 사용

TOTP URI를 받으면 사용자가 인증 앱으로 스캔할 QR 코드를 생성하는 데 사용할 수 있습니다.

user-card.tsx
import QRCode from "react-qr-code";

export default function UserCard({ password }: { password: string }){
    const { data: session } = client.useSession();
	const { data: qr } = useQuery({
		queryKey: ["two-factor-qr"],
		queryFn: async () => {
			const res = await authClient.twoFactor.getTotpUri({ password });
			return res.data;
		},
		enabled: !!session?.user.twoFactorEnabled,
	});
    return (
        <QRCode value={qr?.totpURI || ""} />
   )
}

기본적으로 TOTP의 발급자는 auth 설정에 제공된 앱 이름으로 설정되며, 제공되지 않은 경우 Better Auth로 설정됩니다. 플러그인 설정에 issuer를 전달하여 이를 재정의할 수 있습니다.

TOTP 확인

사용자가 2FA 코드를 입력한 후, twoFactor.verifyTotp 메서드를 사용하여 확인할 수 있습니다. Better Auth는 현재 코드보다 한 기간 이전과 한 기간 이후의 TOTP 코드를 수락하는 표준 관행을 따라, 사용자 측에서 약간의 시간 지연이 있어도 인증할 수 있도록 합니다.

POST
/two-factor/verify-totp
const { data, error } = await authClient.twoFactor.verifyTotp({    code: "012345", // required    trustDevice: true,});
PropDescriptionType
code
확인할 OTP 코드
string
trustDevice?
true인 경우 기기를 30일 동안 신뢰합니다. 이 시간 내에 모든 로그인 요청에서 갱신됩니다.
boolean

OTP

OTP(일회용 비밀번호)는 TOTP와 유사하지만 무작위 코드가 생성되어 사용자의 이메일이나 전화번호로 전송됩니다.

2차 인증을 확인하기 위해 OTP를 사용하기 전에 Better Auth 인스턴스에서 sendOTP를 구성해야 합니다. 이 함수는 애플리케이션에서 지원하는 사용자의 이메일, 전화번호 또는 기타 방법으로 OTP를 전송하는 역할을 합니다.

auth.ts
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [
        twoFactor({
          	otpOptions: {
				async sendOTP({ user, otp }, request) {
                    // 사용자에게 OTP 전송
				},
			},
        })
    ]
})

OTP 전송

OTP를 전송하려면 twoFactor.sendOtp 함수를 호출합니다. 이 함수는 Better Auth 설정에서 제공한 sendOTP 구현을 트리거합니다.

POST
/two-factor/send-otp
const { data, error } = await authClient.twoFactor.sendOtp({    trustDevice: true,});if (data) {    // 사용자에게 코드 입력을 리디렉션하거나 표시}
PropDescriptionType
trustDevice?
true인 경우 기기를 30일 동안 신뢰합니다. 이 시간 내에 모든 로그인 요청에서 갱신됩니다.
boolean

OTP 확인

사용자가 OTP 코드를 입력한 후 확인할 수 있습니다

POST
/two-factor/verify-otp
const { data, error } = await authClient.twoFactor.verifyOtp({    code: "012345", // required    trustDevice: true,});
PropDescriptionType
code
확인할 OTP 코드
string
trustDevice?
true인 경우 기기를 30일 동안 신뢰합니다. 이 시간 내에 모든 로그인 요청에서 갱신됩니다.
boolean

백업 코드

백업 코드는 생성되어 데이터베이스에 저장됩니다. 사용자가 휴대폰이나 이메일에 대한 접근 권한을 잃은 경우 계정에 대한 접근을 복구하는 데 사용할 수 있습니다.

백업 코드 생성

계정 복구를 위한 백업 코드를 생성합니다:

POST
/two-factor/generate-backup-codes
const { data, error } = await authClient.twoFactor.generateBackupCodes({    password, // required});if (data) {    // 사용자에게 백업 코드 표시}
PropDescriptionType
password
사용자의 비밀번호
string

백업 코드를 생성하면 이전 백업 코드는 삭제되고 새 코드가 생성됩니다.

백업 코드 사용

이제 사용자가 백업 코드를 계정 복구 방법으로 제공할 수 있습니다.

POST
/two-factor/verify-backup-code
const { data, error } = await authClient.twoFactor.verifyBackupCode({    code: "123456", // required    disableSession: false,    trustDevice: true,});
PropDescriptionType
code
확인할 백업 코드
string
disableSession?
true인 경우 세션 쿠키가 설정되지 않습니다.
boolean
trustDevice?
true인 경우 기기를 30일 동안 신뢰합니다. 이 시간 내에 모든 로그인 요청에서 갱신됩니다.
boolean

백업 코드를 사용하면 데이터베이스에서 제거되며 다시 사용할 수 없습니다.

백업 코드 보기

사용자에게 백업 코드를 표시하려면 서버에서 viewBackupCodes를 호출할 수 있습니다. 이는 응답에 백업 코드를 반환합니다. 사용자가 새로운 세션(방금 생성된 세션)을 가지고 있는 경우에만 이를 사용해야 합니다.

GET
/two-factor/view-backup-codes
const data = await auth.api.viewBackupCodes({    body: {        userId: "user-id",    },});
PropDescriptionType
userId?
모든 백업 코드를 볼 사용자 ID
string | null

신뢰 기기

verifyTotp 또는 verifyOtptrustDevice를 전달하여 기기를 신뢰할 수 있는 것으로 표시할 수 있습니다.

const verify2FA = async (code: string) => {
    const { data, error } = await authClient.twoFactor.verifyTotp({
        code,
        callbackURL: "/dashboard",
        trustDevice: true // 이 기기를 신뢰할 수 있는 것으로 표시
    })
    if (data) {
        // 2FA 확인 및 기기 신뢰
    }
}

trustDevicetrue로 설정되면, 현재 기기는 60일 동안 기억됩니다. 이 기간 동안 사용자는 이 기기에서 후속 로그인 시 2FA를 요구받지 않습니다. 신뢰 기간은 사용자가 성공적으로 로그인할 때마다 갱신됩니다.

발급자

issuer를 추가하여 2FA 애플리케이션에 애플리케이션 이름을 설정할 수 있습니다.

예를 들어, 사용자가 Google Auth를 사용하는 경우 기본 appName은 Better Auth로 표시됩니다. 그러나 다음 코드를 사용하면 my-app-name으로 표시됩니다.

twoFactor({
    issuer: "my-app-name"
})

스키마

플러그인은 user 테이블에 1개의 추가 필드와 이중 인증 데이터를 저장할 1개의 추가 테이블이 필요합니다.

테이블: user

Field NameTypeKeyDescription
twoFactorEnabledboolean사용자의 이중 인증 활성화 여부

테이블: twoFactor

Field NameTypeKeyDescription
idstring이중 인증의 ID
userIdstring사용자의 ID
secretstringTOTP 코드를 생성하는 데 사용되는 비밀
backupCodesstring사용자가 휴대폰이나 이메일에 대한 접근 권한을 잃은 경우 계정에 대한 접근을 복구하는 데 사용되는 백업 코드

옵션

서버

twoFactorTable: 이중 인증 데이터를 저장하는 테이블의 이름. 기본값: twoFactor.

skipVerificationOnEnable: 사용자의 이중 인증을 활성화하기 전에 확인 프로세스를 건너뜁니다.

Issuer: 발급자는 애플리케이션의 이름입니다. TOTP 코드를 생성하는 데 사용됩니다. 인증 앱에 표시됩니다.

TOTP options

TOTP에 대한 옵션입니다.

Prop

Type

OTP options

OTP에 대한 옵션입니다.

Prop

Type

백업 코드 옵션

백업 코드는 사용자가 이중 인증을 활성화할 때 생성되어 데이터베이스에 저장됩니다. 사용자가 휴대폰이나 이메일에 대한 접근 권한을 잃은 경우 계정에 대한 접근을 복구하는 데 사용할 수 있습니다.

Prop

Type

클라이언트

클라이언트에서 이중 인증 플러그인을 사용하려면 플러그인 목록에 추가해야 합니다.

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"

const authClient =  createAuthClient({
    plugins: [
        twoFactorClient({ 
            onTwoFactorRedirect(){ 
                window.location.href = "/2fa" // 2FA 확인 리디렉션 처리
            } 
        }) 
    ]
})

옵션

onTwoFactorRedirect: 사용자가 2FA 코드를 확인해야 할 때 호출되는 콜백. 사용자를 2FA 페이지로 리디렉션하는 데 사용할 수 있습니다.