본문 바로가기
Today I Learned/Task

[Task] Supabase로 로그인/회원가입 구현

by YES_developNewbie 2024. 9. 27.

supabase는 PostqreSQL을 기반으로 SQL 쿼리 및 다양한 데이터베이스 작업을 수행할 수 있는 오픈소스 데이터베이스이다. supabase는 인증, 보안 등 백엔드 기능을 제공하기 때문에 백엔드 서버를 구축하지 않아도 클라이언트 앱을 빠르게 개발할 수 있다는 장점이 있다. 이를 활용해 로그인과 회원가입 기능을 구현해보았다. 

 

인증(로그인, 회원가입) API 파일을 별도로 구성하여 회원가입을 수행하는 함수를 만들었다.

// @/api/auth.api.ts

async function signUp(signUpData: AuthData) {
	const res = await supabase.auth.signUp(signUpData);

	if (!!res.error) {
		let message = "";
		switch (res.error.status) {
			case 422:
				message = "이미 있는 로그인 정보입니다";
				break;
			case 400:
				message = "로그인 정보가 올바르지 않습니다";
				break;
		}

		const error = new CustomError(res.error.status as number, message);
		throw error;
	}
	return res.data;
}

 

만약 supabase의 응답으로 에러가 왔을 경우, 에러객체에 따라 메시지와 상태코드를 지정하고, 에러를 throw한다.

그렇지 않다면 supabase의 응답을 반환한다.

 

회원가입 폼에서는 Tanstack-Query의 useMutation 훅을 활용해서 회원가입 작업 수행 및 성공했을 때 혹은 에러가 발생했을 때의 작업을 핸들링 하였다.

// app/(providers)/(root)/(auth)/sign-up/_components/signUpForm.tsx
const { mutate: signUp } = useMutation({
    mutationFn: (signUpData: SignUpData) => AuthAPI.signUp(signUpData),
    onSuccess() {
        router.push("/log-in");
    },
    onError: (data) => {
        throwErrorMsg("global", data.message);
    },
});

 

작업 수행에 성공하였을 경우 router로 로그인 페이지로 이동시켜 사용자가 로그인을 수행할 수 있도록 하였다.

에러가 발생했을 경우, errorMessage State에 메시지를 삽입하고 화면에 표시한다.

 

form의 onSubmit 이벤트를 핸들링하여 input에 작성된 값들을 validation하고 회원가입에 필요한 객체를 구성한다

// app/(providers)/(root)/(auth)/sign-up/_components/signUpForm.tsx
const handleSubmitSignUp: ComponentProps<"form">["onSubmit"] = async (
    e
) => {
    e.preventDefault();

    ... // validation

    const signUpData = {
        email,
        password,
    };
    const response = signUp(signUpData);
};

 

useMutation을 활용해 추출한 함수를 활용해 회원가입 작업을 수행한다.

 

로그인 작업 또한 유사한 형태로 진행한다. 회원가입과 달리 supabase에서는 다양한 로그인 방법을 제공하는데, 나는 email과 password를 기반으로 로그인하는 함수인 signInWithPassword 메서드를 사용하였다.

// @/api/auth.api.ts

async function logIn(logInData: AuthData) {
	const res = await supabase.auth.signInWithPassword(logInData);

	if (!!res.error) {
		let message = "";
		switch (res.error.status) {
			case 400:
				message = "로그인 정보가 올바르지 않습니다";
				break;
		}

		const error = new CustomError(res.error.status as number, message);
		throw error;
	}
	return res.data;
}

 

useMutation을 활용해 success, error 핸들링을 하도록 작성하였다.

// @/(providers)/(root)/(auth)/log-in/_components/LogInForm.tsx

const { mutate: logIn } = useMutation({
		mutationFn: (logInData: AuthData) => AuthAPI.logIn(logInData),
		onSuccess: () => {
			router.replace("/");
		},
		onError: () => {
			return throwErrorMsg("global", "로그인 정보가 잘못되었습니다");
		},
	});

 

로그인을 했다면 로그인이 된 상태가 유지되어야 사용자 경험이 올라간다. 만약 로그인했는데 새로고침을 했다는 이유로 다시 로그인해야 되는 상황만 상상해도 불편하기 때문이다. 따라서 contextAPI를 활용해서 전역변수로 로그인의 여부를 확인할 수 있도록 작성했다.

// @/contexts/auth.context.tsx

...

export function AuthContextProvider({ children }: PropsWithChildren) {
	const [isLoggedIn, setIsLoggedIn] = useState(false);
	const [isAuthInitialized, setIsAuthInitialized] = useState(false);

	const initializeAuth = () => {
		setIsAuthInitialized(true);
	};

	const value = {
		isLoggedIn,
		isAuthInitialized,
		setIsLoggedIn,
		initializeAuth,
	};

	return (
		<AuthContext.Provider value={value}>{children}</AuthContext.Provider>
	);
}

 

AuthProvider 라는 Provider를 별도로 작성하여 사용자가 페이지를 로딩했을 때마다 로그인의 여부를 확인하고 전역변수를 변경해주는 작업을 해주었다.

// @/app/(providers)/_providers/auth.provider.tsx
function AuthProvider({ children }: PropsWithChildren) {
	const { initializeAuth, setIsLoggedIn } = useAuth();

	useEffect(() => {
		supabase.auth.onAuthStateChange((_, session) => {
			if (session?.user) {
				setIsLoggedIn(true);
			} else {
				setIsLoggedIn(false);
			}

			initializeAuth();
		});
	}, []);

	return children;
}
function ProviderLayout({ children }: PropsWithChildren) {
	return (
		<TanstackProvider>
			<AuthContextProvider>
				<AuthProvider>{children}</AuthProvider>
			</AuthContextProvider>
		</TanstackProvider>
	);
}