
이메일은 대부분의 웹 서비스와 앱에서 사용자 식별 및 인증의 핵심 요소입니다.
회원가입 과정에서 이메일 주소가 유효하고 중복되지 않는지 확인하는 것은 매우 중요합니다.
하지만 많은 애플리케이션에서 이메일 검증 과정에 허점이 있어 사용자 경험을 저하시키고 보안 위험을 초래할 수 있습니다.
기존의 많은 이메일 검증 시스템은 다음과 같은 흐름으로 동작합니다:
하지만 여기에는 중요한 보안 허점이 존재합니다:
사용자가 중복 확인 후 이메일을 변경하는 경우, 시스템은 이를 감지하지 못하고 이미 검증된 것으로 간주할 수 있습니다. 이로 인해:
이러한 문제를 해결하기 위해, 개선된 이메일 검증 시스템은 다음 기능을 포함해야 합니다:
이제 이러한 개선 사항을 구현한 React Native 컴포넌트를 살펴보겠습니다:
const EmailVerificationInput = ({
email,
onEmailChange,
onVerificationStatusChange,
}: EmailVerificationInputProps) => {
const [emailCheckMessage, setEmailCheckMessage] = useState<string>("");
const [isEmailVerified, setIsEmailVerified] = useState(false);
const [verifiedEmail, setVerifiedEmail] = useState("");
const handleEmailChange = (text: string) => {
onEmailChange(text);
// 이메일이 변경되면 검증 상태 초기화
if (text !== verifiedEmail) {
setIsEmailVerified(false);
setEmailCheckMessage("");
onVerificationStatusChange(false, "");
}
};
const handleCheckEmailDuplicate = async () => {
// 이메일 형식 유효성 검사
const formatResult = validateEmailFormat(email);
if (!formatResult.isValid) {
setEmailCheckMessage(formatResult.message);
return;
}
// 이메일 중복 검사
const result = await checkEmailDuplicate(email);
setEmailCheckMessage(result.message);
if (result.isValid) {
setIsEmailVerified(true);
setVerifiedEmail(email);
onVerificationStatusChange(true, email);
} else {
setIsEmailVerified(false);
onVerificationStatusChange(false, "");
}
};
return (
<>
<View style={styles.emailInputContainer}>
<TextInput
style={[
styles.emailInput,
isEmailVerified && { borderColor: "green", borderWidth: 1 },
]}
value={email}
onChangeText={handleEmailChange}
placeholder="example@gmail.com"
placeholderTextColor="#666"
/>
<TouchableOpacity
style={[
styles.button,
isEmailVerified && email === verifiedEmail
? { backgroundColor: "green" }
: { backgroundColor: colors.primary },
]}
onPress={handleCheckEmailDuplicate}
>
<Text style={styles.buttonText}>
{isEmailVerified && email === verifiedEmail
? "✓ 확인됨"
: "중복 확인"}
</Text>
</TouchableOpacity>
</View>
{emailCheckMessage ? (
<Text style={styles.errorText}>{emailCheckMessage}</Text>
) : null}
</>
);
};
이중 상태 관리:
isEmailVerified: 검증 완료 여부 (boolean)verifiedEmail: 검증에 성공한 이메일 값 (string)이메일 변경 감지 로직:
// 이메일이 변경되면 검증 상태 초기화
if (text !== verifiedEmail) {
setIsEmailVerified(false);
setEmailCheckMessage("");
onVerificationStatusChange(false, "");
}
이메일 값이 변경되면 즉시 검증 상태를 초기화합니다.
검증 성공 시 이메일 저장:
if (result.isValid) {
setIsEmailVerified(true);
setVerifiedEmail(email);
onVerificationStatusChange(true, email);
}
검증에 성공한 경우에만 해당 이메일을 verifiedEmail 상태에 저장합니다.
부모 컴포넌트 상태 업데이트:
onVerificationStatusChange(true, email);
부모 컴포넌트에 검증 상태와 검증된 이메일을 함께 전달합니다.
시각적 피드백:
style={[
styles.emailInput,
isEmailVerified && { borderColor: "green", borderWidth: 1 },
]}
검증 완료 시 입력 필드 테두리를 녹색으로 변경하여 사용자에게 시각적 피드백을 제공합니다.
이 컴포넌트는 두 가지 유틸리티 함수에 의존합니다:
// 이메일 형식 검증 함수
export const validateEmailFormat = (email: string): EmailVerificationResult => {
if (!email) {
return {
isValid: false,
message: "이메일을 입력해주세요.",
};
}
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(email)) {
return {
isValid: false,
message: "유효한 이메일 형식이 아닙니다.",
};
}
return {
isValid: true,
message: "",
};
};
// 이메일 중복 검사 함수
export const checkEmailDuplicate = async (
email: string
): Promise<EmailVerificationResult> => {
try {
const response = await axiosInstance.post("/member/check-email", {
email: email,
});
if (response.data === false) {
return {
isValid: false,
message: "이미 사용 중인 이메일입니다.",
};
} else {
return {
isValid: true,
message: "사용 가능한 이메일입니다.",
};
}
} catch (error) {
// 오류 처리 로직
// ...
}
};
이 개선된 이메일 검증 시스템을 구현할 때 고려해야 할 추가 사항들:
디바운스(Debounce) 적용:
이메일 입력 시 매 타이핑마다 상태를 변경하기보다는 약간의 지연을 두어 성능 최적화
토큰 기반 검증:
서버에서 검증된 이메일에 대한 임시 토큰을 발급하고, 회원가입 완료 시 함께 전송하여 추가적인 보안 강화
이메일 자동 완성 처리:
브라우저/기기의 자동 완성 기능이 작동할 때도 검증 상태가 올바르게 초기화되도록 처리
네트워크 오류 처리:
네트워크 불안정으로 인한 중복 검사 실패 시 적절한 오류 메시지와 재시도 옵션 제공
접근성 고려:
시각적 피드백 외에도 스크린 리더 사용자를 위한 접근성 메시지 제공
이러한 개선된 이메일 검증 시스템을 구현함으로써, 사용자가 중복 확인 후 이메일을 변경하는 실수를 방지하고, 더 안전하고 신뢰할 수 있는 회원가입 프로세스를 제공할 수 있습니다.