왜 Auth0 email_verified가 내 액세스 토큰에 없었는가
I’m happy to translate the article for you, but I’ll need the full text of the post (excluding the source line you already provided). Could you please paste the content you’d like translated? Once I have it, I’ll keep the source link at the top and translate the rest into Korean while preserving the original formatting, markdown, and technical terms.
The Problem
Spring Boot 필터가 Auth0 대시보드에서 Verified(인증됨)로 표시된 사용자들을 거부하고 있었습니다.
Boolean emailVerified = jwt.getClaimAsBoolean("email_verified");
if (emailVerified == null || !emailVerified) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("{\"error\":\"Email not verified\"}");
return;
}
로그 출력:
User email not verified: sub=auth0|67c6a695657d0f4f7ac8736f
사용자들은 인증된 상태였습니다. 이유는: Auth0는 기본적으로 email_verified를 액세스 토큰에 포함시키지 않고 ID 토큰에만 포함하기 때문입니다.
액세스 토큰을 디코딩하면 다음과 같이 보일 수 있습니다:
{
"iss": "https://your-tenant.auth0.com/",
"sub": "auth0|67c6a695657d0f4f7ac8736f",
"aud": ["https://your-api/"],
"iat": 1767421775,
"exp": 1767508175,
"scope": "openid profile email"
}
email 스코프를 요청했음에도 불구하고 email_verified 클레임이 누락된 것을 확인할 수 있습니다.
email_verified를 Access Token에 추가하기
Post‑Login 액션 사용하기
- Auth0 대시보드에서 Actions → Flows → Login 으로 이동합니다.
- 새 액션을 추가하고 다음 코드를 붙여넣습니다:
exports.onExecutePostLogin = async (event, api) => {
// API 검증을 위해 access token에 email_verified 추가
api.accessToken.setCustomClaim('email_verified', event.user.email_verified);
};
- 액션을 배포하고 Login 흐름에 끌어다 놓습니다.
배포가 완료되면 access token에 다음과 같은 내용이 포함됩니다:
{
"email_verified": true,
"iss": "https://your-tenant.auth0.com/",
"sub": "auth0|67c6a695657d0f4f7ac8736f",
...
}
왜 이렇게 동작하는가
- ID 토큰은 클라이언트(프론트엔드)에게 신원 정보를 전달하기 위한 것입니다.
- Access 토큰은 API(백엔드)가 요청을 인증하기 위해 사용합니다.
- Auth0의 기본 철학은 신원 관련 클레임을 access 토큰에 포함하지 않는 것이지만, 많은 API가 이러한 클레임을 필요로 합니다. 액션을 통해 클레임을 추가하는 것이 권장되는 방법입니다.
대안: UserInfo 엔드포인트 조회
액션을 사용할 수 없는 경우, 해당 클레임이 없을 때 /userinfo 엔드포인트에서 가져올 수 있습니다:
if (emailVerified == null) {
String userinfoUrl = auth0Issuer + "userinfo";
// Perform GET with Bearer token
// Parse JSON response for "email_verified"
}
참고: 이는 모든 요청에 추가 지연을 발생시키므로, 일반적으로 액션 방식을 선호합니다.
소셜 로그인
소셜 연결(Google, GitHub 등)의 경우 Auth0는 제공자가 이미 이메일을 확인했기 때문에 email_verified: true를 자동으로 설정합니다. 데이터베이스 연결이 아닌 경우에는 이 검사를 건너뛸 수 있습니다:
String subject = jwt.getSubject();
boolean isDatabaseConnection = subject != null && subject.startsWith("auth0|");
if (isDatabaseConnection) {
// Only check email_verified for username/password users
}
전체 액션 예시 (옵션 이메일 재전송 포함)
exports.onExecutePostLogin = async (event, api) => {
// Always include email_verified in access token
api.accessToken.setCustomClaim('email_verified', event.user.email_verified);
// Auto‑resend verification email for unverified users (rate‑limited)
if (!event.user.email_verified) {
const lastSent = event.user.user_metadata?.verification_email_last_sent;
const now = Date.now();
const ONE_HOUR = 60 * 60 * 1000;
if (!lastSent || (now - lastSent) > ONE_HOUR) {
// Trigger verification email via Management API
// (see Auth0 docs for Management API setup)
api.user.setUserMetadata('verification_email_last_sent', now);
}
}
};
주요 내용
- ID 토큰 ≠ 액세스 토큰 – 두 토큰은 목적이 다르고 포함하는 클레임도 다릅니다.
- Auth0 Actions를 사용하여 액세스 토큰에 필요한 사용자 정의 클레임을 추가하세요.
- 대시보드 UI에만 의존하지 말고 실제 토큰 내용을 (예: jwt.io) 확인하세요.