JWT
JWT 플러그인은 JWT 토큰을 검색하는 엔드포인트와 토큰을 확인하는 JWKS 엔드포인트를 제공합니다.
이 플러그인은 세션을 대체하기 위한 것이 아닙니다. JWT 토큰이 필요한 서비스에 사용하기 위한 것입니다. 인증에 JWT 토큰을 사용하려는 경우 Bearer 플러그인을 확인하세요.
설치
auth 설정에 플러그인 추가
import { betterAuth } from "better-auth"
import { jwt } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
jwt(),
]
})데이터베이스 마이그레이션
마이그레이션을 실행하거나 스키마를 생성하여 필요한 필드와 테이블을 데이터베이스에 추가합니다.
npx @better-auth/cli migratenpx @better-auth/cli generate수동으로 필드를 추가하려면 스키마 섹션을 참조하세요.
사용법
플러그인을 설치하면 JWT & JWKS 플러그인을 사용하여 각각의 엔드포인트를 통해 토큰과 JWKS를 가져올 수 있습니다.
JWT
토큰 검색
JWT 토큰을 검색하는 방법에는 여러 가지가 있습니다:
- 클라이언트 플러그인 사용 (권장)
auth 클라이언트 구성에 jwtClient 플러그인을 추가합니다:
import { createAuthClient } from "better-auth/client"
import { jwtClient } from "better-auth/client/plugins"
export const authClient = createAuthClient({
plugins: [
jwtClient()
]
})그런 다음 클라이언트를 사용하여 JWT 토큰을 가져옵니다:
const { data, error } = await authClient.token()
if (error) {
// 오류 처리
}
if (data) {
const jwtToken = data.token
// 외부 서비스에 대한 인증된 요청에 이 토큰을 사용합니다
}이것은 외부 API 인증을 위해 JWT 토큰이 필요한 클라이언트 애플리케이션에 권장되는 방법입니다.
- 세션 토큰 사용
토큰을 가져오려면 /token 엔드포인트를 호출합니다. 다음을 반환합니다:
{
"token": "ey..."
}auth 설정에 bearer 플러그인이 추가된 경우 요청의 Authorization 헤더에 토큰을 포함해야 합니다.
await fetch("/api/auth/token", {
headers: {
"Authorization": `Bearer ${token}`
},
})set-auth-jwt헤더에서
getSession 메서드를 호출하면 set-auth-jwt 헤더에 JWT가 반환되며, 이를 사용하여 서비스에 직접 보낼 수 있습니다.
await authClient.getSession({
fetchOptions: {
onSuccess: (ctx)=>{
const jwt = ctx.response.headers.get("set-auth-jwt")
}
}
})토큰 확인
토큰은 추가 확인 호출이나 데이터베이스 확인 없이 자체 서비스에서 확인할 수 있습니다.
이를 위해 JWKS가 사용됩니다. 공개 키는 /api/auth/jwks 엔드포인트에서 가져올 수 있습니다.
이 키는 자주 변경되지 않으므로 무기한 캐시할 수 있습니다.
JWT에 서명하는 데 사용된 키 ID(kid)는 토큰의 헤더에 포함됩니다.
다른 kid를 가진 JWT를 받은 경우 JWKS를 다시 가져오는 것이 좋습니다.
{
"keys": [
{
"crv": "Ed25519",
"x": "bDHiLTt7u-VIU7rfmcltcFhaHKLVvWFy-_csKZARUEU",
"kty": "OKP",
"kid": "c5c7995d-0037-4553-8aee-b5b620b89b23"
}
]
}OAuth 제공자 모드
시스템을 oAuth 규격으로 만드는 경우(OIDC 또는 MCP 플러그인을 사용하는 경우) /token 엔드포인트(oAuth 동등물 /oauth2/token)를 비활성화하고 jwt 헤더 설정(oAuth 동등물 /oauth2/userinfo)을 비활성화해야 합니다.
betterAuth({
disabledPaths: [
"/token",
],
plugins: [jwt({
disableSettingJwtHeader: true,
})]
})원격 JWKS와 함께 jose 사용 예제
import { jwtVerify, createRemoteJWKSet } from 'jose'
async function validateToken(token: string) {
try {
const JWKS = createRemoteJWKSet(
new URL('http://localhost:3000/api/auth/jwks')
)
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'http://localhost:3000', // JWT 발급자와 일치해야 하며, BASE_URL입니다
audience: 'http://localhost:3000', // JWT 대상과 일치해야 하며, 기본적으로 BASE_URL입니다
})
return payload
} catch (error) {
console.error('Token validation failed:', error)
throw error
}
}
// 사용 예제
const token = 'your.jwt.token' // /api/auth/token 엔드포인트에서 가져온 토큰입니다
const payload = await validateToken(token)로컬 JWKS를 사용한 예제
import { jwtVerify, createLocalJWKSet } from 'jose'
async function validateToken(token: string) {
try {
/**
* 이것은 /api/auth/jwks 엔드포인트에서 가져온 JWKS입니다
*/
const storedJWKS = {
keys: [{
//...
}]
};
const JWKS = createLocalJWKSet({
keys: storedJWKS.data?.keys!,
})
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'http://localhost:3000', // JWT 발급자와 일치해야 하며, BASE_URL입니다
audience: 'http://localhost:3000', // JWT 대상과 일치해야 하며, 기본적으로 BASE_URL입니다
})
return payload
} catch (error) {
console.error('Token validation failed:', error)
throw error
}
}
// 사용 예제
const token = 'your.jwt.token' // /api/auth/token 엔드포인트에서 가져온 토큰입니다
const payload = await validateToken(token)원격 JWKS URL
/jwks 엔드포인트를 비활성화하고 OIDC와 같은 모든 검색에서 이 엔드포인트를 사용합니다.
JWKS가 /jwks에서 관리되지 않거나 jwks가 인증서로 서명되어 CDN에 배치된 경우 유용합니다.
참고: 서명에 사용되는 비대칭 알고리즘을 반드시 지정해야 합니다.
jwt({
jwks: {
remoteUrl: "https://example.com/.well-known/jwks.json",
keyPairConfig: {
alg: 'ES256',
},
}
})사용자 정의 서명
이것은 고급 기능입니다. 이 플러그인 외부의 구성을 반드시 제공해야 합니다.
구현자:
sign함수를 사용하는 경우remoteUrl을 정의해야 합니다. 현재 키뿐만 아니라 모든 활성 키를 저장해야 합니다.- 로컬 접근 방식을 사용하는 경우 순환 시 서버가 최신 개인 키를 사용하도록 합니다. 배포에 따라 서버를 다시 시작해야 할 수 있습니다.
- 원격 접근 방식을 사용하는 경우 전송 후 페이로드가 변경되지 않았는지 확인합니다. 사용 가능한 경우 CRC32 또는 SHA256 검사와 같은 무결성 검증을 사용합니다.
로컬 서명
jwt({
jwks: {
remoteUrl: "https://example.com/.well-known/jwks.json",
keyPairConfig: {
alg: 'EdDSA',
},
},
jwt: {
sign: async (jwtPayload: JWTPayload) => {
// 이것은 의사 코드입니다
return await new SignJWT(jwtPayload)
.setProtectedHeader({
alg: "EdDSA",
kid: process.env.currentKid,
typ: "JWT",
})
.sign(process.env.clientPrivateKey);
},
},
})원격 서명
Google KMS, Amazon KMS 또는 Azure Key Vault와 같은 원격 키 관리 서비스를 사용하는 경우 유용합니다.
jwt({
jwks: {
remoteUrl: "https://example.com/.well-known/jwks.json",
keyPairConfig: {
alg: 'ES256',
},
},
jwt: {
sign: async (jwtPayload: JWTPayload) => {
// 이것은 의사 코드입니다
const headers = JSON.stringify({ kid: '123', alg: 'ES256', typ: 'JWT' })
const payload = JSON.stringify(jwtPayload)
const encodedHeaders = Buffer.from(headers).toString('base64url')
const encodedPayload = Buffer.from(payload).toString('base64url')
const hash = createHash('sha256')
const data = `${encodedHeaders}.${encodedPayload}`
hash.update(Buffer.from(data))
const digest = hash.digest()
const sig = await remoteSign(digest)
// integrityCheck(sig)
const jwt = `${data}.${sig}`
// verifyJwt(jwt)
return jwt
},
},
})스키마
JWT 플러그인은 데이터베이스에 다음 테이블을 추가합니다:
JWKS
테이블 이름: jwks
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | 각 웹 키의 고유 식별자 | |
| publicKey | string | - | 웹 키의 공개 부분 |
| privateKey | string | - | 웹 키의 개인 부분 |
| createdAt | Date | - | 웹 키가 생성된 타임스탬프 |
jwks 테이블의 테이블 이름과 필드를 사용자 정의할 수 있습니다. 플러그인 스키마를 사용자 정의하는 방법에 대한 자세한 내용은 데이터베이스 개념 문서를 참조하세요.
옵션
키 쌍의 알고리즘
키 쌍 생성에 사용되는 알고리즘입니다. 기본값은 Ed25519 곡선을 사용하는 EdDSA입니다. 사용 가능한 옵션은 다음과 같습니다:
jwt({
jwks: {
keyPairConfig: {
alg: "EdDSA",
crv: "Ed25519"
}
}
})EdDSA
- 기본 곡선:
Ed25519 - 선택적 속성:
crv- 사용 가능한 옵션:
Ed25519,Ed448 - 기본값:
Ed25519
- 사용 가능한 옵션:
ES256
- 추가 속성 없음
RSA256
- 선택적 속성:
modulusLength- 숫자를 예상함
- 기본값:
2048
PS256
- 선택적 속성:
modulusLength- 숫자를 예상함
- 기본값:
2048
ECDH-ES
- 선택적 속성:
crv- 사용 가능한 옵션:
P-256,P-384,P-521 - 기본값:
P-256
- 사용 가능한 옵션:
ES512
- 추가 속성 없음
개인 키 암호화 비활성화
기본적으로 개인 키는 AES256 GCM을 사용하여 암호화됩니다. disablePrivateKeyEncryption 옵션을 true로 설정하여 이를 비활성화할 수 있습니다.
보안상의 이유로 개인 키를 암호화된 상태로 유지하는 것이 좋습니다.
jwt({
jwks: {
disablePrivateKeyEncryption: true
}
})JWT 페이로드 수정
기본적으로 전체 사용자 객체가 JWT 페이로드에 추가됩니다. definePayload 옵션에 함수를 제공하여 페이로드를 수정할 수 있습니다.
jwt({
jwt: {
definePayload: ({user}) => {
return {
id: user.id,
email: user.email,
role: user.role
}
}
}
})발급자, 대상, 주체 또는 만료 시간 수정
제공되지 않으면 BASE_URL이 발급자로 사용되고 대상은 BASE_URL로 설정됩니다. 만료 시간은 15분으로 설정됩니다.
jwt({
jwt: {
issuer: "https://example.com",
audience: "https://example.com",
expirationTime: "1h",
getSubject: (session) => {
// 기본적으로 주체는 사용자 ID입니다
return session.user.email
}
}
})