Super Kawaii Cute Cat Kaoani
본문 바로가기
🏃‍♀️ 대외활동/UMC 7기 - Node.js

[UMC 7th Server] Chapter 9. 로그인 및 회원 가입 기능 구현

by wonee1 2024. 11. 24.
728x90

 

 Chapter 9. 로그인 및 회원 가입 기능 구현

 

 

 

실습 인증☑️


 

 

Google Login 구현해보기

 

더보기

👉 Passport 라이브러리

다음과 같은 명령어를 사용해서 라이브러리 설치를 진행했습니다.

npm install \\
  express-session \\
  passport \\
  passport-google-oauth20

 

 

👉 Google 로그인 인증 키 발급

1️⃣ Google Cloud Platform 접속하기

 

 

2️⃣ Project 생성하기

 

 

 

 

 

 

 

 

3️⃣ OAuth 2.0 Client ID 생성하기

 

APIs and services 로 이동 후 설정 완료

OAuth2 정보를 등록하기 위해서 다음 절차 진행

 

 

좌측의 Credentials 탭을 클릭한 후, 상단 Create Credentials 버튼을 클릭한 후, OAuth client ID 항목을 선택 후 내용을 입력해줬습니다.

 

 

 

 

4️⃣ Client ID, Client secret 조회하기

 

생성을 하고 나면 팝업이 뜨면서 Client ID와 Client secret을 조회 가능!

 

 

해당 내용을 .env에 반영

 

 

👉 인증을 위한 코드 추가

 

src/auth.config.js 파일

 

 

이 내용을 추가해줘서 먼저 Google 로그인 후 전달 받은 사용자의 프로필 정보에 이메일이 포함되어 있는지 확인하고, 이메일을 이용해서 사용자를 조회하게 한다.

const googleVerify = async (profile) => {
    const email = profile.emails?.[0]?.value;
    if (!email) {
      throw new Error(`profile.email was not found: ${profile}`);
    }
  
    const user = await prisma.user.findFirst({ where: { email } });
    if (user !== null) {
      return { id: user.id, email: user.email, name: user.name };
    }
  
    const created = await prisma.user.create({
      data: {
        email,
        name: profile.displayName,
        gender: "추후 수정",
        birth: new Date(1970, 0, 1),
        address: "추후 수정",
        detailAddress: "추후 수정",
        phoneNumber: "추후 수정",
      },
    });
  
    return { id: created.id, email: created.email, name: created.name };
  };

 

 

 

👉 Passport 사용하기 - 세션 (Session)

다음 명령어로 라이브러리를 설치해주었습니다.

npm install @quixo3/prisma-session-store

 

 

 

 

그리고 Prisma Schema를 수정해주었습니다.

model Session {
  id        String   @id
  sid       String   @unique
  data      String   @db.VarChar(512)
  expiresAt DateTime @map("expires_at")

  @@map("session")
}

 

 

 

 

그 다음 index.js코드를 수정해주었습니다

 

 

 

👉 Passport 사용하기 - Route

app.get("/oauth2/login/google", passport.authenticate("google"));
app.get(
  "/oauth2/callback/google",
  passport.authenticate("google", {
    failureRedirect: "/oauth2/login/google",
    failureMessage: true,
  }),
  (req, res) => res.redirect("/")
);

 

 

 

👉 Passport 사용하기 - 미들웨어

index.js 코드를 수정한 다음 env 파일도 수정해줍니다

// src/index.js
import express from "express";
import cors from "cors";
import swaggerAutogen from "swagger-autogen";
import swaggerUiExpress from "swagger-ui-express";
import { handleStoreSignUp,handleListStoreReviews} from "./controller/store.controller.js";
import { handleReviewSignUp } from "./controller/review.controller.js";
import { handleMissionSignUp,handleListStoreMissions,handleListUserInProgressMissions,handleCompleteUserMission   } from "./controller/mission.controller.js";
import { handleChallengeSignUp } from "./controller/challenge.controller.js";
import { handleListUserReviews, handleUserLogin,handleUserSignUp } from './controller/user.controller.js';

import { PrismaSessionStore } from "@quixo3/prisma-session-store";
import session from "express-session";
import passport from "passport";
import { googleStrategy } from "./auth.config.js";
import { prisma } from "./db.config.js";

dotenv.config();

passport.use(googleStrategy);
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());
app.use(cors());
app.use(express.static("public")); // 정적 파일 접근
app.use(express.json()); // request의 본문을 json으로 해석할 수 있도록 함 (JSON 형태의 요청 body를 파싱하기 위함)
app.use(express.urlencoded({ extended: false })); // 단순 객체 문자열 형태로 본문 데이터 해석

app.use(
  session({
    cookie: {
      maxAge: 7 * 24 * 60 * 60 * 1000, // ms
    },
    resave: false,
    saveUninitialized: false,
    secret: process.env.EXPRESS_SESSION_SECRET,
    store: new PrismaSessionStore(prisma, {
      checkPeriod: 2 * 60 * 1000, // ms
      dbRecordIdIsSessionId: true,
      dbRecordIdFunction: undefined,
    }),
  })
);

app.use((req, res, next) => {
    res.success = (success) => {
      return res.json({ resultType: "SUCCESS", error: null, success });
    };
  
    res.error = ({ errorCode = "unknown", reason = null, data = null }) => {
      return res.json({
        resultType: "FAIL",
        error: { errorCode, reason, data },
        success: null,
      });
    };
  
    next();
});
  
app.use(
  "/docs",
  swaggerUiExpress.serve,
  swaggerUiExpress.setup({}, {
    swaggerOptions: {
      url: "/openapi.json",
    },
  })
);

app.get("/openapi.json", async (req, res, next) => {
  // #swagger.ignore = true
  const options = {
    openapi: "3.0.0",
    disableLogs: true,
    writeOutputFile: false,
  };
  const outputFile = "/dev/null"; // 파일 출력은 사용하지 않습니다.
  const routes = ["./src/index.js"];
  const doc = {
    info: {
      title: "UMC 7th",
      description: "UMC 7th Node.js 테스트 프로젝트입니다.",
    },
    host: "localhost:3000",
  };

  const result = await swaggerAutogen(options)(outputFile, routes, doc);
  res.json(result ? result.data : null);
});

// 가게 추가 API
app.post("/api/stores", handleStoreSignUp);

// 가게 리뷰 추가 API
app.post("/api/reviews", handleReviewSignUp);

// 가게 미션 추가 API
app.post("/api/missions", handleMissionSignUp);

// 가게의 미션 도전하기 API
app.post("/api/challenges", handleChallengeSignUp);

// 리뷰 목록 확인 
app.get("/api/stores/:storeId/reviews", handleListStoreReviews)

// 내가 작성한 리뷰 목록 확인 
app.get('/api/users/:userId/reviews', handleListUserReviews);

// 특정 가게의 미션 목록 조회 API
app.get('/api/stores/:storeId/missions', handleListStoreMissions);

// 특정 사용자의 진행 중인 미션 목록 조회 API
app.get('/api/users/:userId/missions/in-progress', handleListUserInProgressMissions);

// 특정 사용자의 진행 중인 미션 완료로 업데이트 API
app.put('/api/users/:userId/missions/:missionId/complete', handleCompleteUserMission);

//유저 로그인 API 
app.post("/api/users/login", handleUserLogin);

// 유저 회원가입 API
app.post("/api/users/signup", handleUserSignUp);

app.get("/oauth2/login/google", passport.authenticate("google"));
app.get(
  "/oauth2/callback/google",
  passport.authenticate("google", {
    failureRedirect: "/oauth2/login/google",
    failureMessage: true,
  }),
  (req, res) => res.redirect("/")
);

app.use((err, req, res, next) => {
    if (res.headersSent) {
      return next(err);
    }
  
    res.status(err.statusCode || 500).error({
      errorCode: err.errorCode || "unknown",
      reason: err.reason || err.message || null,
      data: err.data || null,
    });
  
});

app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

app.use(passport.session());
EXPRESS_SESSION_SECRET="secretvaluerandom"

 

 

👉 테스트 해보기

처음에는 undefined가 출력 되는 것을 확인했습니다.

 

 

http://localhost:3000/oauth2/login/google 로 이동 후 로그인 후 /으로 이동하는 것을 확인

 

 

 

 

 

 

🎯핵심 키워드 정리


 

💠OAuth 2.0 이란?

 

OAuth 2.0은 인터넷 사용자들이 비밀번호를 공유하지 않고도 다른 애플리케이션이나 서비스에 자신의 정보를 안전하게 제공할 수 있도록 해주는 인증 및 권한 부여 프레임워크을 말한다.

  • 인증(Authentication):
    • 사용자가 누구인지 확인하는 과정.
    • 예: Google 계정을 통해 로그인.
  • 권한 부여(Authorization):
    • 사용자가 특정 서비스나 리소스에 접근할 수 있도록 허가하는 과정.
    • 예: Google Drive에서 파일 접근 권한을 제3자 애플리케이션에 부여.
  1. 사용자가 애플리케이션에 로그인 요청:
    • 애플리케이션은 사용자를 대신하여 OAuth 제공자(예: Google, Facebook)에 인증 요청을 보냄.
  2. 사용자가 제공자에서 로그인:
    • 사용자는 자신의 자격 증명(예: 이메일, 비밀번호)으로 제공자에 로그인.
  3. 사용자 권한 부여:
    • 사용자는 애플리케이션이 특정 리소스(예: 이메일, 프로필 정보)에 접근할 수 있는 권한을 부여.
  4. 토큰 발급:
    • 제공자는 애플리케이션에 엑세스 토큰을 발급.
  5. 리소스 접근:
    • 애플리케이션은 엑세스 토큰을 사용해 제공자의 API를 호출하여 사용자 리소스에 접근.

 

💠HTTP Cookie란?

  • HTTP Cookie는 웹 브라우저와 웹 서버 간에 상태 정보를 저장하고 교환하기 위해 사용되는 작은 데이터 조각
  • 주로 사용자 세션을 관리하고, 사용자 환경을 맞춤화하며, 추적 목적으로 사용

 

Cookie의 주요 특징

  1. 키-값 쌍:
    • 쿠키는 key=value 형태로 저장됩니다.
    • 예: sessionId=abc123;.
  2. 저장 위치:
    • 쿠키는 브라우저에 저장되며, 동일한 웹 서버에 요청을 보낼 때 자동으로 포함됩니다.
  3. 전송:
    • HTTP 요청 헤더의 Set-Cookie 필드로 서버가 브라우저에 전달.
    • 브라우저는 이후 요청의 Cookie 필드로 서버에 쿠키를 전송.

 


 

 

 

✏️ 9주차 필기 


 

 

 

 

 

 


 

 

 

🔥 미션 인증


 

 

🔏 로그인 및 회원 가입 기능 구현

 

더보기

먼저 네이버 개발자 센터에서 애플리케이션을 등록하고 클라이언트 아이이디와 클라이언트 시크릿을 발급받아야합니다.

 

 

1️⃣ 네이버 개발자 센터 등록

 

 

 

오픈 API 이용신청을 누르고 들어간다.

 

 

 

2️⃣Application 등록

 

 

 

이후 등록을 한 뒤에 애플리케이션 정보를 확인합니다.

 

 

 

3️⃣설정 및 코드 추가

 

npm install passport-naver를 입력하여 라이브러리를 설치하였습니다.

 

 

 

.env 파일에 아까 받은 Client ID랑 Client Secret과 REDIRECT URL를 입력해줍니다.

 

그 다음 db.config.js 를 다음과 같이 입력해줍니다.

export const naverStrategy = new NaverStrategy(
  {
    clientID: process.env.PASSPORT_NAVER_CLIENT_ID,
    clientSecret: process.env.PASSPORT_NAVER_CLIENT_SECRET,
    callbackURL: process.env.PASSPORT_NAVER_REDIRECT_URI,
  },
  async (accessToken, refreshToken, profile, done) => {
    console.log("Naver profile:", profile);

    const email = profile.emails?.[0]?.value || null; // 이메일이 없을 경우 null 처리
    if (!email) {
      throw new Error(`profile.email was not found: ${JSON.stringify(profile)}`);
    }

    try {
      // 기존 사용자 검색
      const user = await prisma.users.findFirst({ where: { email } });
      console.log("User found:", user);

      if (user !== null) {
        return { id: user.id, email: user.email, name: user.user_name };
      }

      // 새로운 사용자 생성
      const created = await prisma.users.create({
        data: {
          email,
          user_name: profile.displayName || "네이버 사용자",
          gender: "추후 수정",
          birth: new Date(1970, 0, 1),
          address: "추후 수정",
          detailAddress: "추후 수정",
          phoneNumber: "추후 수정",
          naverId: profile.id, // 네이버 사용자 고유 ID 저장
          password: null, // 명시적으로 null 설정
        },
      });
      console.log("User created:", created);
      return { id: created.id, email: created.email, name: created.user_name };
    } catch (error) {
      console.error("Error in naverVerify:", error);
      throw error;
    }

  }
);

// Passport Configuration
passport.use(naverStrategy);

export default passport;

다음 라우터를 따로 등록해줍니다.

import express from "express";
import passport from "passport";

const router = express.Router();

router.get("/auth/naver", passport.authenticate("naver"));//로그인 메인화면 

router.get(
  "/auth/naver/callback",
  passport.authenticate("naver", { failureRedirect: "/login" }),
  (req, res) => {
    res.redirect("/");
  }
);

export default router;

index.js 코드에선 다음과 같이 입력해줍니다.

//네이버 로그인 확인 
app.use(naverAuthRouter);

그런 다음 http://localhost:3000/auth/naver에 접속하면 다음과 같은 화면이 뜨면서 잘 실행되는 것을 확인할 수 있습니다.

 

 

 

 

 

728x90