React

 

 

 

소스 :https://github.dev/braverokmc79/next-ex09-userAuthentication

1.Lucia 설치

 

https://lucia-auth.com/

 

인증 구현에 대해 배운 내용을 요약하면 다음과 같습니다:

  1. 인증 단계: 인증 과정은 여러 단계를 포함하지만, 구현은 간단합니다.
  2. 추가 라이브러리 사용: 인증 관련 문제를 처리하기 위해 일반적으로 추가 라이브러리를 사용합니다.

Lucia Auth

  • 소개: NextJS 애플리케이션에 사용할 수 있는 유용하고 비교적 새로운 인증 라이브러리입니다.
  • 검색 및 정보: Lucia Auth를 검색하면 관련 페이지에서 모든 정보를 찾을 수 있습니다.
  • 호환성: NextJS 뿐만 아니라 다른 Node.js 앱, Bun, Deno, Cloudflare Workers 앱에서도 사용 가능합니다.

 

설치

npm install lucia
npm install @lucia-auth/adapter-sqlite

 

NextAuth.js

  • 소개: NextJS를 위해 만들어졌지만, 일부 문제로 인해 라우터와 잘 작동하지 않을 수 있습니다.
  • 공식 문서: 공식 문서가 그다지 훌륭하지 않습니다.
  • 사용 권장 여부: 한 번 살펴보는 것도 좋지만, 개인적인 경험으로는 Lucia Auth가 더 직관적이고 사용하기 쉽습니다.

 

Lucia Auth 구현

  1. 설치: 프로젝트에 Lucia Auth와 SQLite 어댑터 패키지를 설치합니다.
  2. 세션 관리: Lucia Auth를 통해 세션을 생성하고 SQLite 데이터베이스에 저장합니다.
  3. 개발 서버 재시작: 필요한 모든 단계를 차근차근 따라가며 개발 서버를 다시 시작합니다.

이 섹션에서는 Lucia Auth를 사용하여 인증을 구현하는 방법을 단계별로 설명할 예정입니다.

 

 

 

 

2.새 Lucia 인증 인스턴스 만들기

 

Lucia를 설치한 후, 인증 세션을 생성하고 인증된 사용자를 식별할 수 있도록 설정해 봅시다. 이를 위해 lib 폴더에 auth.js 파일을 추가하고,

필요한 설정을 진행하겠습니다. 다음은 그 과정에 대한 구체적인 지침입니다.

 

1. 프로젝트 구조 설정

먼저, 프로젝트 구조를 설정하고 필요한 파일을 만듭니다.

project-root/
├── lib/
│   ├── auth.js
│   └── db.js
├── package.json
├── node_modules/
└── ...

 

 

2. db.js 파일 작성

SQLite 데이터베이스를 생성하고 내보내는 db.js 파일을 만듭니다.

https://github.com/braverokmc79/next-ex09-userAuthentication/blob/main/lib/db.js

 

 

3. auth.js 파일 작성

Lucia와 SQLite 어댑터를 설정하는 auth.js 파일을 만듭니다.

import { cookies } from "next/headers"; // Next.js에서 쿠키를 다루기 위한 모듈을 가져옵니다.
import { Lucia } from "lucia"; // Lucia 인증 라이브러리를 가져옵니다.
import { BetterSqlite3Adapter } from '@lucia-auth/adapter-sqlite'; // SQLite 데이터베이스를 위한 Lucia 어댑터를 가져옵니다.

import db from './db'; // SQLite 데이터베이스 연결을 가져옵니다.

const adapter = new BetterSqlite3Adapter(db, {
    user: 'users', // 사용자 정보를 저장하는 테이블 이름을 설정합니다.
    session: 'sessions' // 세션 정보를 저장하는 테이블 이름을 설정합니다.
});

// Lucia 객체를 생성하고 설정을 구성합니다.
const lucia = new Lucia(adapter, {
    sessionCookie: {
        expires: false, // 세션 쿠키가 만료되지 않도록 설정합니다.
        attributes: {
            secure: process.env.NODE_ENV === 'production', // 프로덕션 환경에서는 쿠키를 안전하게 설정합니다.
        }
    }
});

 

 

 

 

 

 

 

 

3.세션 및 세션 쿠키 구성하기

 

 

Lucia를 이용해 인증 세션을 생성하고, 사용자 브라우저에 세션 쿠키를 설정하는 createAuthSession 함수를 추가해 보겠습니다. 이 함수는 특정 사용자에 대해 세션을 생성하고, 해당 세션 ID를 쿠키로 설정합니다. Next.js 애플리케이션에서 이를 구현하는 방법은 다음과 같습니다.

1. auth.js 파일 수정

먼저, auth.js 파일에 createAuthSession 함수를 추가합니다.

import { cookies } from "next/headers"; // Next.js에서 쿠키를 다루기 위한 모듈을 가져옵니다.
import { Lucia } from "lucia"; // Lucia 인증 라이브러리를 가져옵니다.
import { BetterSqlite3Adapter } from '@lucia-auth/adapter-sqlite'; // SQLite 데이터베이스를 위한 Lucia 어댑터를 가져옵니다.

import db from './db'; // SQLite 데이터베이스 연결을 가져옵니다.

const adapter = new BetterSqlite3Adapter(db, {
    user: 'users', // 사용자 정보를 저장하는 테이블 이름을 설정합니다.
    session: 'sessions' // 세션 정보를 저장하는 테이블 이름을 설정합니다.
});

// Lucia 객체를 생성하고 설정을 구성합니다.
const lucia = new Lucia(adapter, {
    sessionCookie: {
        expires: false, // 세션 쿠키가 만료되지 않도록 설정합니다.
        attributes: {
            secure: process.env.NODE_ENV === 'production', // 프로덕션 환경에서는 쿠키를 안전하게 설정합니다.
        }
    }
});

/**
 * 주어진 사용자 ID로 인증 세션을 생성하고 세션 쿠키를 설정합니다.
 * @param {string} userId - 인증 세션을 생성할 사용자 ID
 */
export async function createAuthSession(userId) {
    // 주어진 사용자 ID로 새로운 세션을 생성합니다.
    const session = await lucia.createSession(userId, {});

    // 세션 ID로부터 세션 쿠키를 생성합니다.
    const sessionCookie = lucia.createSessionCookie(session.id);

    // 생성된 세션 쿠키를 클라이언트의 브라우저에 설정합니다.
    cookies().set(
        sessionCookie.name, // 쿠키의 이름을 설정합니다.
        sessionCookie.value, // 쿠키의 값을 설정합니다.
        sessionCookie.attributes // 쿠키의 속성을 설정합니다.
    );
}

 

 

 

2. cookies 함수 사용 설정

Next.js에서 쿠키를 설정하려면 next/headers에서 제공하는 cookies 함수를 사용해야 합니다. 이 함수는 서버에서 실행되는 API 라우트에서 사용 가능합니다.

위의 코드에서 cookies().set을 사용하여 세션 쿠키를 설정합니다. 이는 쿠키 이름, 값, 속성을 설정하여 사용자 브라우저에 쿠키를 저장하는 역할을 합니다.

 

요약

  • auth.js 파일에 createAuthSession 함수를 추가하여 사용자 인증 세션을 생성하고 쿠키를 설정합니다.
  • Next.js API 라우트에서 createAuthSession 함수를 사용하여 로그인 시 인증 세션을 생성하고 브라우저에 쿠키를 설정합니다.
  • next/headers에서 제공하는 cookies 함수를 사용하여 쿠키를 설정합니다.

이제 Next.js 애플리케이션에서 사용자 로그인 시 자동으로 인증 세션이 생성되고, 세션 쿠키가 사용자 브라우저에 설정되어 유지됩니다.

 

 

 

 

 

 

 

 

4.인증 세션 설정

 

사용자 생성 후 새로운 인증 세션을 생성하고 쿠키를 설정하는 작업을 진행해 보겠습니다. 이를 위해 auth-actions.js 파일에서 createAuthSession을 호출하고, 사용자 가입 후 인증 세션을 생성하도록 설정합니다.

 

1. /ib/auth.js 파일 

 

import { cookies } from "next/headers"; // Next.js에서 쿠키를 다루기 위한 모듈을 가져옵니다.
import { Lucia } from "lucia"; // Lucia 인증 라이브러리를 가져옵니다.
import { BetterSqlite3Adapter } from '@lucia-auth/adapter-sqlite'; // SQLite 데이터베이스를 위한 Lucia 어댑터를 가져옵니다.

import db from './db'; // SQLite 데이터베이스 연결을 가져옵니다.

const adapter = new BetterSqlite3Adapter(db, {
    user: 'users', // 사용자 정보를 저장하는 테이블 이름을 설정합니다.
    session: 'sessions' // 세션 정보를 저장하는 테이블 이름을 설정합니다.
});

// Lucia 객체를 생성하고 설정을 구성합니다.
const lucia = new Lucia(adapter, {
    sessionCookie: {
        expires: false, // 세션 쿠키가 만료되지 않도록 설정합니다.
        attributes: {
            secure: process.env.NODE_ENV === 'production', // 프로덕션 환경에서는 쿠키를 안전하게 설정합니다.
        }
    }
});

/**
 * 주어진 사용자 ID로 인증 세션을 생성하고 세션 쿠키를 설정합니다.
 * @param {string} userId - 인증 세션을 생성할 사용자 ID
 */
export async function createAuthSession(userId) {
    // 주어진 사용자 ID로 새로운 세션을 생성합니다.
    const session = await lucia.createSession(userId, {});

    // 세션 ID로부터 세션 쿠키를 생성합니다.
    const sessionCookie = lucia.createSessionCookie(session.id);

    // 생성된 세션 쿠키를 클라이언트의 브라우저에 설정합니다.
    cookies().set(
        sessionCookie.name, // 쿠키의 이름을 설정합니다.
        sessionCookie.value, // 쿠키의 값을 설정합니다.
        sessionCookie.attributes // 쿠키의 속성을 설정합니다.
    );
}

 

 

2. auth-actions.js 파일  수정

createAuthSession 함수가 Next.js의 쿠키 설정 방식을 지원하도록 next-cookies 패키지를 사용하여 쿠키를 설정합니다.

"use server";
import { createAuthSession } from "@/lib/auth";
import { hashUserPassword } from "@/lib/hash";
import { createUser } from "@/lib/users";
import {  redirect } from "next/navigation";

export async function signup({ email, password }) {
  console.log(email, password);



~
유효성 체크




 //비밀번호 암호화 
  const hashedPassword = hashUserPassword(password);

  try {
    const id=createUser(email, hashedPassword);

    //루시아를 통한 인증
    console.log(" 루시아를 통한 인증  :", id);
    await createAuthSession(id);
    
    
    redirect('/training');
  
  } catch (error) {
   // console.log(error);
    if(error.code ==='SQLITE_CONSTRAINT_UNIQUE'){
      return {
        errors: {
          email:"이미 등록된 이메일 입니다."
        },
      };
    }
    throw error;
  }

}

 

 

 

 

 

 

 

 

 

 

 

 

5.활성 인증 세션 확인

 

 

1. auth.js 파일 수정

auth.js 파일에 verifyAuth 함수를 추가하여 인증된 사용자인지 확인할 수 있도록 합니다. 이 함수는 쿠키를 가져와서 세션 ID를 확인하고, 유효한 세션인지 검증합니다.

import { cookies } from "next/headers"; // Next.js에서 쿠키를 다루기 위한 모듈을 가져옵니다.
import { Lucia } from "lucia"; // Lucia 인증 라이브러리를 가져옵니다.
import { BetterSqlite3Adapter } from '@lucia-auth/adapter-sqlite'; // SQLite 데이터베이스를 위한 Lucia 어댑터를 가져옵니다.

import db from './db'; // SQLite 데이터베이스 연결을 가져옵니다.

const adapter = new BetterSqlite3Adapter(db, {
    user: 'users', // 사용자 정보를 저장하는 테이블 이름을 설정합니다.
    session: 'sessions' // 세션 정보를 저장하는 테이블 이름을 설정합니다.
});

// Lucia 객체를 생성하고 설정을 구성합니다.
const lucia = new Lucia(adapter, {
    sessionCookie: {
        expires: false, // 세션 쿠키가 만료되지 않도록 설정합니다.
        attributes: {
            secure: process.env.NODE_ENV === 'production', // 프로덕션 환경에서는 쿠키를 안전하게 설정합니다.
        }
    }
});

/**
 * 주어진 사용자 ID로 인증 세션을 생성하고 세션 쿠키를 설정합니다.
 * @param {string} userId - 인증 세션을 생성할 사용자 ID
 */
export async function createAuthSession(userId) {
    // 주어진 사용자 ID로 새로운 세션을 생성합니다.
    const session = await lucia.createSession(userId, {});

    // 세션 ID로부터 세션 쿠키를 생성합니다.
    const sessionCookie = lucia.createSessionCookie(session.id);

    // 생성된 세션 쿠키를 클라이언트의 브라우저에 설정합니다.
    cookies().set(
        sessionCookie.name, // 쿠키의 이름을 설정합니다.
        sessionCookie.value, // 쿠키의 값을 설정합니다.
        sessionCookie.attributes // 쿠키의 속성을 설정합니다.
    );
}

/**
 * 들어오는 요청이 인증된 사용자로부터 오는지 확인하는 함수입니다.
 * @returns {object} - 사용자와 세션 데이터가 포함된 객체를 반환합니다. 유효하지 않은 경우 null을 반환합니다.
 */
export async function verifyAuth() {
    // 세션 쿠키를 가져옵니다.
    const sessionCookie = cookies().get(lucia.sessionCookieName);

    // 세션 쿠키가 없는 경우 사용자와 세션 데이터를 null로 설정하여 반환합니다.
    if (!sessionCookie) {
        return {
            user: null,
            session: null,
        };
    }

    // 세션 쿠키의 값을 세션 ID로 설정합니다.
    const sessionId = sessionCookie.value;

    // 세션 ID가 없는 경우 사용자와 세션 데이터를 null로 설정하여 반환합니다.
    if (!sessionId) {
        return {
            user: null,
            session: null,
        };
    }

    // 세션 ID를 검증하여 결과를 반환받습니다.
    const result = await lucia.validateSession(sessionId);

    try {
        // 세션이 유효하고 활성 상태인 경우 세션 쿠키를 재생성하여 설정합니다.
        if (result.session && result.session.fresh) {
            const sessionCookie = lucia.createSessionCookie(result.session.id);
            cookies().set(
                sessionCookie.name, 
                sessionCookie.value,
                sessionCookie.attributes 
            );
        }

        // 유효한 세션이 없는 경우 빈 세션 쿠키를 생성하여 설정합니다.
        if (!result.session) {
            const sessionCookie = lucia.createBlankSessionCookie(); 
            cookies().set(
                sessionCookie.name, 
                sessionCookie.value,
                sessionCookie.attributes 
            );
        }
    } catch (err) {
        // 에러를 잡아서 무시합니다.
    }

    // 검증된 결과를 반환합니다.
    return result;
}

 

 

 

 

 

 

 

 

 

6.인증되지 않은 액세스로부터 라우트 보호하기

 

training 를 보호하기 위해 verifyAuth를 사용하여 인증된 사용자만 접근할 수 있도록 설정해 보겠습니다. 이를 위해 verifyAuth 함수를 training 페이지의 getServerSideProps 함수 내에서 호출하여 사용자 인증을 확인하고, 인증되지 않은 사용자는 리디렉션하도록 설정하겠습니다.

1. training 페이지 보호

training페이지를 보호하기 위해 verifyAuth 함수를 호출하고 인증되지 않은 사용자는 시작 페이지로 리디렉션하는 코드를 추가합니다.

import { verifyAuth } from '@/lib/auth';
import { getTrainings } from '@/lib/training';
import { redirect } from 'next/navigation';


export default async function TrainingPage() {
  const result=await verifyAuth();

  if(!result.user){
    return redirect('/');
  }


~

 

이제 training페이지에 접근하려는 사용자가 인증되지 않은 경우, 자동으로 시작 페이지로 리디렉션됩니다.

 

 

 

 

 

 

 

7.로그인 로그아웃 페이지 searchParams:  쿼리 매개변수(검색 매개변수) 로 인증 모드 전환하기

 

auth-form.js

"use client";
import { signup } from "@/actions/auth-actions";
import Link from "next/link";
import { useState } from "react";
//import  {useFormState} from "react-dom";


// 커스텀 훅 정의
// 이 훅은 폼 상태와 폼 제출 동작을 관리합니다.
function useFormState(action) {
  const [formState, setFormState] = useState({ errors: {} });
 
  // 폼 제출 동작 정의
  // 폼 제출 시 호출되며, 서버 액션을 실행하고 결과에 따라 상태를 업데이트합니다.
  const formAction = async (event) => {
    event.preventDefault();
    const formData = new FormData(event.target);
    const email = formData.get("email");
    const password = formData.get("password");
 
    try {
      // 서버 액션 호출 및 결과 처리
      const response = await action({ email, password });
      if (response.errors) {
        setFormState({ errors: response.errors });
      } else {
        setFormState({ errors: {} });
      }
    } catch (errors) {
      setFormState({ errors });
    }
  };
 
  return { formState, formAction };
}


 
export default function AuthForm({mode}) {
  // useFormState 훅을 사용하여 폼 상태와 폼 제출 동작을 가져옵니다.
  const { formState, formAction } = useFormState(signup);
  //const { formState, formAction } = useFormState(signup, null);
 
  return (
    <form id="auth-form" onSubmit={formAction}>
      <div>
        <img src="/images/auth-icon.jpg" alt="A lock icon" />
      </div>
      <p>
        <label htmlFor="email">이메일</label>
        <input type="email" name="email" id="email" required />
      </p>
      <p>
        <label htmlFor="password">비밀번호</label>
        <input type="password" name="password" id="password" required />
      </p>
 
      <div>
        {/* 폼 상태에 오류가 있는 경우 오류 메시지를 표시합니다. */}
        {formState&& formState.errors && (
          <ul id="form-errors">
            {Object.keys(formState.errors).map((error) => (
              <li key={error}>{formState.errors[error]}</li>
            ))}
          </ul>
        )}
      </div>
 
      <p>
        <button type="submit">
              {mode ==='login' ? '로그인' :'회원가입' }
          </button>
      </p>
      <p>
        {mode ==='login' && <Link href="/?mode=signup" >회원가입</Link>}

        {mode ==='signup' &&<Link href="/?mode=login">로그인 하기..</Link> }
      </p>
    </form>
  );
}

 

 

 

components/auth-form.js

import AuthForm from '@/components/auth-form';

export default async function Home({searchParams}) {

  const formMode=searchParams.mode || 'login';  
  return <AuthForm mode={formMode}   />;

}

 

 

 

 

 

8.사용자 로그인 추가(서버 액션을 통해)

 

 

auth-actions.js

/**
 * 로그인 처리
*/
export async function login({ email, password }){
  console.log("로그인 처리");
    
  //유효성 체크
  let errors = {};
  if (email.trim().length<0 || password.trim().length) {
    errors.email = "입력 정보가 유효하지 않습니다.";
  }

  const existingUser=getUserByEmail(email);
  if(!existingUser) {
    return {
      errors: {
        email:"등록된 회원이 아닙니다."
      },
    };
  }


 const isValidPassword=verifyPassword(existingUser.password, password);
 if(!isValidPassword) {
    return {
      errors: {
        password:"비밀번호가 일치 하지 않습니다."
      },
    };
 }

  await createAuthSession(existingUser.id);
  redirect('/training');

}

 

 

 

 

 

 

 

 

9.useFormState  로그인 , 로그아웃 모드 처리

 

auth-actions.js

~

export async function auth(mode, formData){
  if(mode === 'login'){
    return await login(formData);
  }
  
  return await signup(formData);
}

 

 

auth-form.js

~

// 커스텀 훅 정의
// 이 훅은 폼 상태와 폼 제출 동작을 관리합니다.
function useFormState(mode, action) {
  const [formState, setFormState] = useState({ errors: {} });
 
  // 폼 제출 동작 정의
  // 폼 제출 시 호출되며, 서버 액션을 실행하고 결과에 따라 상태를 업데이트합니다.
  const formAction = async (event) => {
    event.preventDefault();
    const formData = new FormData(event.target);
    const email = formData.get("email");
    const password = formData.get("password");
 
    try {
      // 서버 액션 호출 및 결과 처리
      const response = await action(mode, { email, password });
      if (response.errors) {
        setFormState({ errors: response.errors });
      } else {
        setFormState({ errors: {} });
      }
    } catch (errors) {
      setFormState({ errors });
    }
  };
 
  return { formState, formAction };
}


export default function AuthForm({mode}) {
  // useFormState 훅을 사용하여 폼 상태와 폼 제출 동작을 가져옵니다.
  const { formState, formAction } = useFormState(mode, auth);
  //const { formState, formAction } = useFormState(auth.bind(null,mode), {});
  


~

 

 

 

 

 

 

 

 

 

10.인증 전용 레이아웃 추가하기

 

 

 

인증 처리된 페이지들은 (auth) 그룹으로 만든 후에  레이아웃 에서  로그아웃 버튼을 만들어 처리한다.

 

app/(auth)/layout.js

import '../globals.css';
 
export const metadata = {
  title: 'Next Auth',
  description: 'Next.js Authentication',
};
 
export default function AuthRootLayout({ children }) {
  return (
    <>
      <header id="auth-header">
        <p>Welcome back!</p>
        <form>
          <button>Logout</button>
        </form>
      </header>
      {children}
    </>
  );
}

 

 

 

 

 

 

11.사용자 로그아웃 추가

 

lib/auth.js

export async function destroySession(){
   const {session} = await verifyAuth();

   if(!session){
        return{
            errors:"Unauthorized! "
        }
   }


   await lucia.invalidateSession(session.id);

   const sessionCookie = lucia.createBlankSessionCookie(); 
   cookies().set(
       sessionCookie.name, 
       sessionCookie.value,
       sessionCookie.attributes 
   );


}

 

 

 

auth-actions.js

/**
 * 로그아웃 처리
 */
export async function logout(){
  await destroySession();
  redirect('/');
}

 

 

app/(auth)/training/layout.js

import { logout } from '@/actions/auth-actions';
import '../globals.css';
 
export const metadata = {
  title: 'Next Auth',
  description: 'Next.js Authentication',
};
 
export default function AuthRootLayout({ children }) {
  return (
    <>
      <header id="auth-header">
        <p>Welcome back!</p>
        <form action={logout}>
          <button >Logout</button>
        </form>
      </header>
      {children}
    </>
  );
}

 

 

 

 

 

 

 

12.전체 소스

 

1) lib/auth.js

import { cookies } from "next/headers"; // Next.js에서 쿠키를 다루기 위한 모듈을 가져옵니다.
import { Lucia } from "lucia"; // Lucia 인증 라이브러리를 가져옵니다.
import { BetterSqlite3Adapter } from '@lucia-auth/adapter-sqlite'; // SQLite 데이터베이스를 위한 Lucia 어댑터를 가져옵니다.

import db from './db'; // SQLite 데이터베이스 연결을 가져옵니다.

const adapter = new BetterSqlite3Adapter(db, {
    user: 'users', // 사용자 정보를 저장하는 테이블 이름을 설정합니다.
    session: 'sessions' // 세션 정보를 저장하는 테이블 이름을 설정합니다.
});

// Lucia 객체를 생성하고 설정을 구성합니다.
const lucia = new Lucia(adapter, {
    sessionCookie: {
        expires: false, // 세션 쿠키가 만료되지 않도록 설정합니다.
        attributes: {
            secure: process.env.NODE_ENV === 'production', // 프로덕션 환경에서는 쿠키를 안전하게 설정합니다.
        }
    }
});

/**
 * 주어진 사용자 ID로 인증 세션을 생성하고 세션 쿠키를 설정합니다.
 * @param {string} userId - 인증 세션을 생성할 사용자 ID
 */
export async function createAuthSession(userId) {
    // 주어진 사용자 ID로 새로운 세션을 생성합니다.
    const session = await lucia.createSession(userId, {});

    // 세션 ID로부터 세션 쿠키를 생성합니다.
    const sessionCookie = lucia.createSessionCookie(session.id);

    // 생성된 세션 쿠키를 클라이언트의 브라우저에 설정합니다.
    cookies().set(
        sessionCookie.name, // 쿠키의 이름을 설정합니다.
        sessionCookie.value, // 쿠키의 값을 설정합니다.
        sessionCookie.attributes // 쿠키의 속성을 설정합니다.
    );
}




/** ==============================  verifyAuth  처리  =================================================
 * 들어오는 요청이 인증된 사용자로부터 오는지 확인하는 함수입니다.
 * @returns {object} - 사용자와 세션 데이터가 포함된 객체를 반환합니다. 유효하지 않은 경우 null을 반환합니다.
 */
export async function verifyAuth() {
    // 세션 쿠키를 가져옵니다.
    const sessionCookie = cookies().get(lucia.sessionCookieName);

    // 세션 쿠키가 없는 경우 사용자와 세션 데이터를 null로 설정하여 반환합니다.
    if (!sessionCookie) {
        return {
            user: null,
            session: null,
        };
    }

    // 세션 쿠키의 값을 세션 ID로 설정합니다.
    const sessionId = sessionCookie.value;

    // 세션 ID가 없는 경우 사용자와 세션 데이터를 null로 설정하여 반환합니다.
    if (!sessionId) {
        return {
            user: null,
            session: null,
        };
    }

    // 세션 ID를 검증하여 결과를 반환받습니다.
    const result = await lucia.validateSession(sessionId);

    try {
        // 세션이 유효하고 활성 상태인 경우 세션 쿠키를 재생성하여 설정합니다.
        if (result.session && result.session.fresh) {
            const sessionCookie = lucia.createSessionCookie(result.session.id);
            cookies().set(
                sessionCookie.name, 
                sessionCookie.value,
                sessionCookie.attributes 
            );
        }

        // 유효한 세션이 없는 경우 빈 세션 쿠키를 생성하여 설정합니다.
        if (!result.session) {
            const sessionCookie = lucia.createBlankSessionCookie(); 
            cookies().set(
                sessionCookie.name, 
                sessionCookie.value,
                sessionCookie.attributes 
            );
        }
    } catch (err) {
        // 에러를 잡아서 무시합니다.
    }

    // 검증된 결과를 반환합니다.
    return result;
}




/** ==============================  destroySession 로그아웃  처리  ================================================="*/

export async function destroySession(){
   const {session} = await verifyAuth();

   if(!session){
        return{
            errors:"Unauthorized! "
        }
   }


   await lucia.invalidateSession(session.id);

   const sessionCookie = lucia.createBlankSessionCookie(); 
   cookies().set(
       sessionCookie.name, 
       sessionCookie.value,
       sessionCookie.attributes 
   );


}


 

 

 

2) actions/auth-actions.js

"use server";
import { createAuthSession, destroySession } from "@/lib/auth";
import { hashUserPassword, verifyPassword } from "@/lib/hash";
import { createUser, getUser, getUserByEmail } from "@/lib/users";
import {  redirect } from "next/navigation";


/**
 * 회원가입
*/
export async function signup({ email, password }) {
  
  //유효성 체크
  let errors = {};
  if (!email.includes("@")) {
    errors.email = "이메일 정보가 유효하지 않습니다.";
  }

  if (password.trim().length < 4) {
    errors.password = "비밀번호는 적어도 4글자 이상이어야 합니다.";
  }

  if (Object.keys(errors).length > 0) {
    console.log("에러 반환 처리");
    return { errors: errors };
  }

  //비밀번호 암호화 
  const hashedPassword = hashUserPassword(password);

  try {
    const id=createUser(email, hashedPassword);

    //루시아를 통한 인증
    console.log(" 루시아를 통한 인증  :", id);
    await createAuthSession(id);
    
    redirect('/training');
  
  } catch (error) {
   // console.log(error);
    if(error.code ==='SQLITE_CONSTRAINT_UNIQUE'){
      return {
        errors: {
          email:"이미 등록된 이메일 입니다."
        },
      };
    }
    throw error;
  }

}

/**
 * 로그인 처리
*/
export async function login({ email, password }){
  console.log("로그인 처리");
    
  //유효성 체크
  let errors = {};
  if (email.trim().length<0 || password.trim().length) {
    errors.email = "입력 정보가 유효하지 않습니다.";
  }

  const existingUser=getUserByEmail(email);
  if(!existingUser) {
    return {
      errors: {
        email:"등록된 회원이 아닙니다."
      },
    };
  }


 const isValidPassword=verifyPassword(existingUser.password, password);
 if(!isValidPassword) {
    return {
      errors: {
        password:"비밀번호가 일치 하지 않습니다."
      },
    };
 }

  await createAuthSession(existingUser.id);
  redirect('/training');

}



export async function auth(mode, formData){
  if(mode === 'login'){
    return await login(formData);
  }
  
  return await signup(formData);
}



/**
 * 로그아웃 처리
 */
export async function logout(){
  await destroySession();
  redirect('/');
}

 

 

 

 

3)app/(auth)training/layout.js

import { logout } from '@/actions/auth-actions';
import '../globals.css';
 
export const metadata = {
  title: 'Next Auth',
  description: 'Next.js Authentication',
};
 
export default function AuthRootLayout({ children }) {
  return (
    <>
      <header id="auth-header">
        <p>Welcome back!</p>
        <form action={logout}>
          <button >Logout</button>
        </form>
      </header>
      {children}
    </>
  );
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

어두운 임금은 자기 가까이에 있는 당상 일조차 먼 백 리밖에 있는 일조차 먼 백리 밖에 있는 일처럼 알려고도 하지 않는다. -관자

댓글 ( 0)

댓글 남기기

작성