Next.js 커스텀 로그인 구현

이준석·2021년 10월 11일
13

Web

목록 보기
4/8
post-thumbnail

혹시나 예제 코드가 필요하신 분들은 해당 링크에서 받아가세요!

서론

오늘은 저번시간에 이어서 NextAuth.js 사용법에 대해서 알아볼 예정이다.

저번 포스팅이 너무 길어지는 탓에 간략하게 맛보기만 다뤘었는데 이번에는 조금 더 NextAuth에 대해서 살펴보고 커스텀해서 사용 할 수 있는 방법에 대해서 다뤄보도록 하겠습니다.

본론

1. 지난 시간 복습

  • npm install --save next-authnpm install -D @types/next-auth 설치
  • pages > api > auth > [...nextauth].ts 파일 생성
  • [...nextauth].ts 파일에 다음 내용을 작성
import NextAuth from "next-auth"
import Providers from "next-auth/providers"
import {NextApiRequest} from "next";

export default NextAuth({
    providers: [
        Providers.Credentials({
            name: "email-password-credential",
            credentials: {
                email: { label: "Email", type: "email", placeholder: "test@test.com" },
                password: { label: "Password", type: "password" }
            },
            async authorize(credentials: Record<any, any>, req: NextApiRequest){
                return credentials;
            }
        })
    ]
})
  • pages > index.tsx 화면을 다음과 같이 수정
import { useSession, signIn, signOut } from "next-auth/client"

const Login = () => {
    const [session, loading] = useSession();

    if (session) {
        return (
            <>
                Signed in as {session.user.email} <br />
                <button onClick={() => signOut()}>Sign out</button>
            </>
        )
    }
    return (
        <>
            Not signed in <br />
            <button onClick={() => signIn()}>Sign in</button>
        </>
    )
}

export default Login;

2. 커스텀 로그인 뷰 만들기

물론 NextAuth 에서 생성해주는 로그인 뷰를 그대로 사용하고 싶은 사람들은 필요 없는 내용일지 모르지만 아마도 직접 만든 로그인 화면에 NextAuth를 붙이고 싶은 사람들이 더 많을 것 같다.

참고 링크

따라서 자신이 만든 로그인 화면에서 로그인 버튼을 클릭하면 NextAuth 인증이 이루어질 수 있는 방법을 소개하도록 하겠다.

먼저 [...nextauth].ts 파일을 열 수 있도록 한다. 그 다음 설정을 하나 추가해주도록 하자.

export default NextAuth({
    providers: [
        Providers.Credentials({
            name: "email-password-credential",
            credentials: {
                email: { label: "Email", type: "email", placeholder: "test@test.com" },
                password: { label: "Password", type: "password" }
            },
            async authorize(credentials: Record<any, any>, req: NextApiRequest){
                return credentials;
            }
        })
    ],
    // 추가
    pages: {
        signIn: '/login',
    }
})

여기서 추가되는 pages 라는 설정은 기존에 NextAuth 가 설정해둔 여러가지 화면들을 자기가 원하는 화면으로 매핑시켜주는 설정이라고 생각하면 쉬울 것 같다. 로그인 뷰 말고도 여러가지 커스텀 화면을 매핑할 수 있으므로 더 많은 정보를 원하면 링크에서 찾아보면 될 것 같다.

따라서 현재 우리는 로그인 버튼을 누르면 /login URL로 이동하도록 만들어두었다.
(http://localhost:3000/login 으로 이동하게 된다.)

그럼 한 번 살펴보도록 하자.

http://localhost:3000 으로 들어가서 Sign in 버튼을 클릭하면 다음과 같은 화면이 뜬다.

현재는 login 화면을 만들지 않았기 때문에 404 뜨는것이 당연합니다.
URL에 http://localhost:3000/login?callback=<콜백주소> 라고 뜨면 정상적으로 설정된 것 입니다.

일단 설정은 정상적으로 되었으니 로그인 화면을 만들어보도록 하자. Next.jspages 하위에 파일을 생성하면 라우팅이 자동으로 되므로 pages 하위에 login.tsx 파일을 하나 생성하면 http://localhost:3000/login 주소로 접속할 때 해당 화면이 실행이 될 것이다.

login.tsx 화면은 다음과 같이 작성해본다.

const Login = () => {
    return (
        <form>
            <label>
                이메일 :
                <input type="email" name="email" placeholder="test@test.com" />
            </label>
            <label>
                비밀번호 :
                <input type="password" name="password" />
            </label>
            <button type="submit">로그인</button>
        </form>
    )
}
export default Login;

화면 디자인은 따로 하지 않았습니다. (제가 잘 못해서..^^;;)

작성하고 저장한다음 http://localhost:3000/login 으로 들어가면 다음과 같은 화면이 나온다.

3. 커스텀 로그인 뷰에 NextAuth 연동하기

아직까지는 로그인 버튼을 클릭하면 화면이 새로고침만 되고 아무런 반응이 일어나지 않을 것이다. 이제부터 로그인 버튼을 누르면 정상적으로 로그인 처리가 될 수 있도록 해보자

다시 login.tsx 파일을 열고 다음과 같이 코드를 추가한다.

import { signIn } from "next-auth/client";

const Login = () => {
    // 추가
    const login = async (e: any) => {
        // 원래 실행되는 이벤트 취소
        e.preventDefault();
        // Form 안에서 이메일, 패스워드 가져오기
        const email = e.target.email.value;
        const password = e.target.password.value;
        const response = await signIn("email-password-credential", {
            email,
            password,
            redirect: false
        });
        console.log(response);
    }

    return (
        // onSubmit에 login 함수 등록
        // 로그인 버튼을 클릭하면 login 함수가 실행된다.
        <form onSubmit={login}>
            <label>
                이메일 :
                <input type="email" name="email" placeholder="test@test.com" />
            </label>
            <label>
                비밀번호 :
                <input type="password" name="password" />
            </label>
            <button type="submit">로그인</button>
        </form>
    )
}

export default Login;

지금 이 코드에서 봐야할 핵심 부분은 signIn 이다. 무언가 길게 string으로 적히고 옵션값이 정의되어있는데, signIn 이 바로 [...nextauth.ts] 에 정의된 Provider 를 호출하는 함수이다. 그 중에서도 따옴표 사이에 적힌 ID 값에 해당하는 Provider 를 호출한다.

하지만 우리는 ProviderID를 지정해준 적이 없다. 그럼 지정해주면 된다.

[...nextauth.ts] 파일을 열고 다음과 같이 수정해준다.

providers: [
        Providers.Credentials({
            // 수정
            id: "email-password-credential",
            name: 'Credentials',
            type: 'credentials',
            credentials: {
                email: { label: "Email", type: "email", placeholder: "test@test.com" },
                password: { label: "Password", type: "password" }
            },
            async authorize(credentials: Record<any, any>, req: NextApiRequest){
                return credentials;
            }
        })
    ],

name 옵션은 NextAuth에서 만들어주는 Form 태그의 로그인 버튼에 노출될 텍스트를 적는 부분이다.

이렇게 수정을 완료했으면 값을 입력하고 로그인 버튼을 클릭해보자.
(redirect를 false를 했기 때문에 callback URL로 이동하지 않는다.)

다음과 같이 response 정보가 error: null 인 형태로 떴다면 아주아주 지금까지 잘 따라와준거다. 우리가 입력한 emailpassword는 정보는 그럼 어디로 들어간걸까?

이전에도 말했듯이 [...nextauth].ts 파일의 authorize 부분의 credentials 파라미터에에 JSON 형태로 전달된다. 콘솔로 찍어보면 다음과 같이 나온다.

            async authorize(credentials: Record<any, any>, req: NextApiRequest){
                console.log(credentials);
                return credentials;
            }


따라서 authorize 부분에서 이제 로그인 인증 처리를 해주면 되는데 우리는 간략하게 if 문을 이용해서 해보도록 하자. authorize 코드를 다음과 같이 수정해준다.

            async authorize(credentials: Record<any, any>, req: NextApiRequest){
                const email = credentials.email;
                const password = credentials.password;
                if(email === "test@test.com" && password === "test"){
                    return credentials;
                }
                throw new Error("아이디 혹은 패스워드가 틀립니다.");
            }

이메일이 test@test.com 이면서 패스워드가 test 이면 credentials 를 리턴해주면서 정상적으로 인증이 되었다고 알려줍니다. 반대로 이메일이나 패스워드중 둘 중 하나라도 틀리면 에러를 던져서 사용자에게 알려줄 수 있도록 합니다.

❗ 여기서 유저들에게 정보 전달은 명확하게 한다고 아이디가 틀리면 아이디가 틀렸다, 패스워드가 틀리면 패스워드가 틀렸다고 특정해서 전달하시는 분들이 간혹 있다. 그렇게 하게 되면 해커들이 아이디의 존재 유무를 파악하고 해킹 공격을 할 수 있기 때문에 로그인 부분에서는 불친절하게 에러 메시지를 간결하게 띄우는 것이 좋다.

그리고 코드를 저장한 후 다시 로그인을 진행해보자.

  • 이메일 패스워드를 제대로 입력한 경우
  • 이메일을 다르게 입력한 경우

    Response 응답이 다르게 들어온 것을 알 수 있다.

따라서 ResponseError 의 유무를 이용해서 인증이 되었는지, 아니면 에러 메시지를 띄워서 사용자에게 로그인을 다시 시도하게 할지를 확인하면 될 것 같다.

결론

오늘 포스팅이면 로그인 부분에 대해서는 끝낼 수 있을 줄 알았는데 하다보니 내용이 많이 길어졌다. 다음 포스팅을 마지막으로 로그인을 마무리하고 본격적인 Next.js의 SSR 렌더링에 대해서 알아보는 시간을 가질 수 있도록 하겠다.

긴 글 읽어주셔서 감사드리고 혹시 잘못된 부분이 있어가 추가했으면 하는 내용들이 있으면 댓글로 작성해주시면 빠르게 피드백 하겠습니다.

profile
호주 워홀중

1개의 댓글

comment-user-thumbnail
2021년 10월 30일

좋은글 잘봤읍니다,,, ^^

답글 달기