fastapi_mail은 FastAPI에서 이메일을 보내기 쉽게 만든 라이브러리이다.
이를 사용하여 이메일 인증, 사용자 알람 등등을 구현할 수 있다.
해당 게시글에서는 fastapi_mail을 이용하여 이메일 인증을 구현해볼 것이다.
이메일 인증의 전체적인 흐름은 다음과 같다.
- 유저가 회원가입을 진행 → FastAPI가 인증 토큰 생성
- FastAPI가 Redis에 토큰 저장 (만료시간 포함)
key: email_verify:{token}, value: user_id- FastAPI가 인증 링크을 포함한 메일 보내기
인증 링크에는 get parameter로 token 값을 저장한다.- 유저가 메일 링크 클릭 → FastAPI가 토큰 검증 → 인증 완료 처리
필자는 사용자 편의성을 고려하여 "인증 링크"를 발송하도록 하였지만, "인증 번호" 방식도 이와 비슷하게 진행하면 된다.
아래는 이해를 돕기 위한 코드로 redis value에 user 정보를 모두 넣어 작성하였다.
실제 프로젝트에서는 DB에 유저 정보를 저장 (email_verified=false)하고, redis value에 user_id를 넣어 메일 인증이 완료되었을 때 email_verified=true를 해주면 된다.
(로그인 과정에서는 email_verified=false면, 로그인 실패 + 메일 인증 요구를 해주면 된다)
import json
import secrets
import uvicorn
from typing import Optional
from pydantic import BaseModel, EmailStr
import redis
from fastapi import FastAPI, HTTPException
from fastapi_mail import FastMail, MessageSchema, ConnectionConfig
APP_BASE_URL = "http://127.0.0.1:5000"
VERIFY_TTL_SEC = 60 * 10 # 10분
conf = ConnectionConfig(
MAIL_USERNAME="<USERNAME>",
MAIL_PASSWORD="<APP_PASSWORD>", # Gamil의 경우, App 패스워드를 발급받아서 사용하면 된다.
MAIL_FROM="<FROM_EMAIL>",
MAIL_SERVER="smtp.gmail.com",
MAIL_PORT=587,
MAIL_STARTTLS=True,
MAIL_SSL_TLS=False,
)
app = FastAPI()
r = redis.Redis(host="127.0.0.1", port=6379, decode_responses=True)
class User(BaseModel):
name: Optional[str] = None
email: EmailStr
password: str
def redis_verify_key(token: str) -> str:
return f"email_verify:{token}"
async def send_email(title: str, email: str, context: str):
message = MessageSchema(
subject=title,
recipients=[email],
body=context,
subtype="plain"
)
fm = FastMail(conf)
await fm.send_message(message)
@app.post("/signup")
async def signup_submit(user: User):
# 1) 토큰 생성
token = secrets.token_urlsafe(32)
# 2) Redis에 저장
# key: email_verify:{token}
# value: 회원가입 정보
payload = user.model_dump_json()
r.setex(redis_verify_key(token), VERIFY_TTL_SEC, payload)
# 3) 인증 링크 생성
verify_link = f"{APP_BASE_URL}/verify-email?token={token}"
# 4) 메일 발송
title = "[TEST] 이메일 인증"
content = f"아래 링크를 클릭해서 이메일 인증을 완료하세요:\n\n{verify_link}\n\n(만료: {VERIFY_TTL_SEC//60}분)"
await send_email(title, user.email, content)
return {"ok": True, "message": "verification email sent"}
@app.get("/verify-email")
async def verify_email(token: str):
key = redis_verify_key(token)
verify_value = r.get(key)
if not verify_value:
raise HTTPException(status_code=400, detail="invalid_or_expired_token")
r.delete(key)
user = json.loads(verify_value)
return {"ok": True, "verified_email": user["email"]}
if __name__ == '__main__':
uvicorn.run(app, host="0.0.0.0", port=5000)
원래는 회원가입 페이지를 만들어야 하지만, 간단하게 테스트 코드만 작성해두었다.
import requests
user = {
"email": "zhemfpdlf@gmail.com",
"password": "<PASSWORD>",
"name": "<name>"
}
r = requests.post("http://127.0.0.1:5000/signup", json=user)
r.raise_for_status()
print(r.text)
위의 코드를 실행시켰을 때, 다음과 같이 메일이 온다.
![]()
또한, 해당 링크를 클릭하면, 메일 인증까지 완료된다.
(인증 완료 페이지도 만들고, login 페이지로 리다이렉션까지 진행하면 완벽할 것이다.)
![]()