๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Project ESG+AI/[์‚ผ์ •KPMG]ESG ๋ฐ์ดํ„ฐ ํ™œ์šฉ ํ’€์Šคํ… ๊ฐœ๋ฐœ

34์ผ์ฐจ. ์นด์นด์˜ค, ๋„ค์ด๋ฒ„, ๊ตฌ๊ธ€, ์•„์ดํฐ ๋กœ๊ทธ์ธ์„ ๋งŒ๋“ค์ž

by GreenJin_S2 2025. 11. 25.

 

 

 

 

์นด์นด์˜ค

https://developers.kakao.com/

 

Kakao Developers

์นด์นด์˜ค API๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋‹ค์–‘ํ•œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•ด ๋ณด์„ธ์š”. ์นด์นด์˜ค ๋กœ๊ทธ์ธ, ๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ, ์นœ๊ตฌ API, ์ธ๊ณต์ง€๋Šฅ API ๋“ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

developers.kakao.com

 

 

๋„ค์ด๋ฒ„

https://developers.naver.com/main/

 

NAVER Developers

๋„ค์ด๋ฒ„ ์˜คํ”ˆ API๋“ค์„ ํ™œ์šฉํ•ด ๊ฐœ๋ฐœ์ž๋“ค์ด ๋‹ค์–‘ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋„๋ก API ๊ฐ€์ด๋“œ์™€ SDK๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ œ๊ณต์ค‘์ธ ์˜คํ”ˆ API์—๋Š” ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ, ๊ฒ€์ƒ‰, ๋‹จ์ถ•URL, ์บก์ฐจ๋ฅผ ๋น„๋กฏ ๊ธฐ๊ณ„๋ฒˆ์—ญ, ์Œ

developers.naver.com

 

 

๊ตฌ๊ธ€

 

https://developers.google.com/?hl=ko

 

Google for Developers - AI์™€ ํด๋ผ์šฐ๋“œ๋ถ€ํ„ฐ ๋ชจ๋ฐ”์ผ๊ณผ ์›น๊นŒ์ง€

๊ฐœ๋ฐœ์ž ๋ฆฌ์†Œ์Šค, ์ปค๋ฎค๋‹ˆํ‹ฐ ์ด๋ฒคํŠธ, ์˜๊ฐ์„ ์ฃผ๋Š” ์Šคํ† ๋ฆฌ๋ฅผ ํƒ์ƒ‰ํ•˜์—ฌ ๋” ์Šค๋งˆํŠธํ•˜๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ๋” ๋น ๋ฅด๊ฒŒ ์ถœ์‹œํ•˜์„ธ์š”.

developers.google.com

 

 

 

์นด์นด์˜ค๋งŒ ์žˆ๋Š”๊ฒŒ ๋งž์Œ, api๋‚˜ auth๊ฐ€ ๋ถ™์œผ๋ฉด ์•ˆ๋จ

 


@gateway/src/main/resources/application.yaml ์—ฌ๊ธฐ์— ์œ ๋ ˆ์นด์—์„œ ์„ค์ •ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ, ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ๋กœ ๋“ฑ๋กํ•ด์„œ ์ฒ˜๋ฆฌํ•ด์ค˜. /auth๋Š” ์„œ๋ธŒ๋ผ์šฐํ„ฐ๋กœ ์ฒ˜๋ฆฌํ•ด์ค˜.

 

 

 

 

 

๊ฒŒ์ดํŠธ ์›จ์ด ์•ผ๋ฏˆ์—์„œ ์ด๋Ÿฌํ•œ ๋ช…๋ น์–ด ์ž…๋ ฅํ•ด์„œ ์‹คํ–‰ํ•˜๊ธฐ

 

 

 

 

spring:
  application:
    name: gateway
 
  config:
    import: "optional:configserver:"
 
  autoconfigure:
    exclude:
      - org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration
      - org.springframework.cloud.autoconfigure.RefreshAutoConfiguration
      - org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration
      - org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration
      - org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration
      - org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration
 
  cloud:
    discovery:
      enabled: true
      client:
        simple:
          instances:
            auth-service:
              - uri: http://auth-service:8082
            user-service:
              - uri: http://user-service:8081
    loadbalancer:
      enabled: true
    gateway:
      routes:
        - id: auth-service
          uri: lb://auth-service
          predicates:
            - Path=/api/auth/**
          filters:
            - RewritePath=/api/(?<segment>.*), /${segment}
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origins:
              - "http://localhost:3000"
              - "http://127.0.0.1:3000"
            allowed-methods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
              - PATCH
            allowed-headers: "*"
            allow-credentials: true
            max-age: 3600

server:
  port: 8080

springdoc:
  api-docs:
    path: /v3/api-docs
  swagger-ui:
    path: /docs
    enabled: true
    operations-sorter: method
    tags-sorter: alpha
    try-it-out-enabled: true
    use-root-path: false
  show-actuator: false
  paths-to-match:
    - /**

management:
  endpoints:
    web:
      exposure:
        include: health,info

@gateway/src/main/resources/application.yaml ์—ฌ๊ธฐ์— ์œ ๋ ˆ์นด์—์„œ ์„ค์ •ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ, ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ๋กœ ๋“ฑ๋กํ•ด์„œ ์ฒ˜๋ฆฌํ•ด์ค˜. /auth๋Š” ์„œ๋ธŒ๋ผ์šฐํ„ฐ๋กœ ์ฒ˜๋ฆฌํ•ด์ค˜.

-lb๊ฐ€ ์žˆ๊ฒŒ ํ•ด์•ผํ•จ

 

์–ด์ œ ํ•œ๋Œ€๋กœ ์นด์นด์˜ค ๋กœ๊ทธ์ธ ๊ตฌํ˜„ํ•œ ๊ฒƒ ์ฒ˜๋Ÿผ ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ์ด๋ž‘ ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ์„ ๊ตฌํ˜„ํ•˜๋ ค๊ณ  ํ•˜๋Š”๋ฐ ๋ฐฑ์—”๋“œ์™€ ํ”„๋ก ํŠธ์—”๋“œ๋กœ ๋”ฐ๋กœ ํ•ด์„œ ์—ฐ๊ฒฐํ• ๊ฑฐ์•ผ

 

๐ŸŸฆ 2. ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•

โœ” 2-1. ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ ํ‚ค(client id, secret) ์–ป๋Š” ๋ฒ•

1) ๋„ค์ด๋ฒ„ ๊ฐœ๋ฐœ์ž ์„ผํ„ฐ ์ ‘์†

https://developers.naver.com

2) "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋“ฑ๋ก" ํด๋ฆญ

3) ๋‹ค์Œ ํ•ญ๋ชฉ ์„ค์ •

  • ์ด๋ฆ„: my-app-login (์•„๋ฌด๊ฑฐ๋‚˜)
  • ์‚ฌ์šฉ API: ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ
  • ํ™˜๊ฒฝ: ์›น
  • Callback URL(์ค‘์š”!)(๋ฐฑ์—”๋“œ ์ฃผ์†Œ ๊ธฐ์ค€)
  •  
    http://localhost:8080/auth/naver/callback

4) ๋“ฑ๋กํ•˜๋ฉด ์•„๋ž˜ ์ •๋ณด๊ฐ€ ๋‚˜์˜ด

  • Client ID
  • Client Secret

โžก ๋ฐฑ์—”๋“œ์— ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ๋„ฃ๊ธฐ

 
NAVER_CLIENT_ID= NAVER_CLIENT_SECRET= NAVER_REDIRECT_URI=http://localhost:8080/auth/naver/callback

๐ŸŸฆ 3. ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•

โœ” 3-1. ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ํ‚ค ๋ฐ›๋Š” ๋ฒ•

1) Google Cloud Console ์ ‘์†

https://console.cloud.google.com

2) ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

3) API ๋ฐ ์„œ๋น„์Šค → OAuth ๋™์˜ ํ™”๋ฉด ์„ค์ •

4) OAuth 2.0 ํด๋ผ์ด์–ธํŠธ ID ๋งŒ๋“ค๊ธฐ

  • ์œ ํ˜•: ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜
  • ์ด๋ฆ„: google-login
  • ์Šน์ธ๋œ redirect URI:
  •  
    http://localhost:8080/auth/google/callback

5) ๋ฐœ๊ธ‰๋œ ์ •๋ณด ํ™•์ธ

  • Client ID
  • Client Secret

โžก ๋ฐฑ์—”๋“œ ํ™˜๊ฒฝ๋ณ€์ˆ˜์— ์ถ”๊ฐ€

 
GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= GOOGLE_REDIRECT_URI=http://localhost:8080/auth/google/callback

 

 

 

ํ™”๋ฉด์ด๋ž‘ ๋ฐฑ์—”๋“œ๋ž‘ ๊ฒฝ๋กœ ์—ฐ๊ฒฐํ•ด์ฃผ๊ธฐ

 

๋ฐฑ์—”๋“œ์—์„œ ๊ฐ ์†Œ์…œ ๋กœ๊ทธ์ธ ์—”๋“œํฌ์ธํŠธ(kakao/login, naver/login, google/login)๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ํ•ด๋‹น ์„œ๋น„์Šค์˜ ํ‚ค์™€ URI๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์ฆ‰, ์•Œ๋ ค์ค˜์•ผ ํ•  ๊ฒƒ
๋ฐฑ์—”๋“œ ํŒ€ ๋˜๋Š” ๋ณธ์ธ์ด ๋ฐฑ์—”๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค๋ฉด: "ํ”„๋ก ํŠธ์—์„œ /api/{provider}/login์„ ํ˜ธ์ถœํ•  ๋•Œ http://localhost:8080/<provider-start>๋กœ ๋ณด๋‚ด๊ณ , ์ฝœ๋ฐฑ ๋๋‚˜๋ฉด http://localhost:3000/dashboard/{provider}๋กœ redirect ํ•ด ์ฃผ์„ธ์š”"๋ผ๊ณ  ์ •๋ฆฌํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค

 

 

 

ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ง„ํ–‰ํ•œ ์ž‘์—…

  • app/(auth)/login/page.tsx
  • ์นด์นด์˜ค/๋„ค์ด๋ฒ„/๊ตฌ๊ธ€ ๋ฒ„ํŠผ์ด ๊ฐ๊ฐ /api/auth/{provider}๋กœ ์ด๋™ํ•˜๋„๋ก ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์ค‘๋ณต๋˜๋Š” ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋กœ์ง์„ redirectToProvider ํ—ฌํผ๋กœ ๋ฌถ์–ด ๊น”๋”ํ•˜๊ฒŒ ์œ ์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • Next.js API ๋ผ์šฐํŠธ ์ถ”๊ฐ€
  • app/api/auth/kakao/route.ts
  • app/api/auth/naver/route.ts
  • app/api/auth/google/route.ts
  • ๊ฐ ๋ผ์šฐํŠธ๋Š” ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ ๋ฐฑ์—”๋“œ ๋กœ๊ทธ์ธ ์‹œ์ž‘ URL๋กœ ๋ฐ”๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•ฉ๋‹ˆ๋‹ค.
  • URL์€ ํ™˜๊ฒฝ๋ณ€์ˆ˜(BACKEND_KAKAO_LOGIN_URL, BACKEND_NAVER_LOGIN_URL, BACKEND_GOOGLE_LOGIN_URL)์—์„œ ์ฝ์œผ๋ฉฐ, ๋ฏธ์„ค์ • ์‹œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ http://localhost:8080/...๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋ฐฑ์—”๋“œ ํŒ€์— ์ „๋‹ฌํ•  ๋‚ด์šฉ

  1. ์•„๋ž˜์™€ ๊ฐ™์€ “๋กœ๊ทธ์ธ ์‹œ์ž‘ URL”์„ ๋…ธ์ถœํ•ด ์ฃผ์„ธ์š”(์‹ค์ œ ๊ฒฝ๋กœ์— ๋งž์ถฐ ์กฐ์ • ๊ฐ€๋Šฅ).
  • ์นด์นด์˜ค: http://localhost:8080/oauth2/kakao/login
  • ๋„ค์ด๋ฒ„: http://localhost:8080/auth/naver/login
  • ๊ตฌ๊ธ€: http://localhost:8080/auth/google/login
  1. ์œ„ URL์„ ํ”„๋ก ํŠธ .env.local์— ๋‹ค์Œ์ฒ˜๋Ÿผ ์ง€์ •ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
     
  1. ์†Œ์…œ ์ธ์ฆ ์™„๋ฃŒ ํ›„ ์ฝœ๋ฐฑ์—์„œ ํ”„๋ก ํŠธ ์„ฑ๊ณต ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•ด ์ฃผ์„ธ์š”.
  • ์นด์นด์˜ค: http://localhost:3000/dashboard/kakao
  • ๋„ค์ด๋ฒ„: http://localhost:3000/dashboard/naver
  • ๊ตฌ๊ธ€: http://localhost:3000/dashboard/google

์ด์ œ ํ”„๋ก ํŠธ์—์„œ๋Š” ๋ฒ„ํŠผ → Next API → ๋ฐฑ์—”๋“œ ๋กœ๊ทธ์ธ ์‹œ์ž‘ URL → ์†Œ์…œ ์ธ์ฆ → ๋ฐฑ์—”๋“œ ์ฝœ๋ฐฑ → ํ”„๋ก ํŠธ ์„ฑ๊ณต ํŽ˜์ด์ง€ ์ˆœ์„œ๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ด์–ด์ง‘๋‹ˆ๋‹ค.

 

 

์นด์นด์˜ค ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด http://localhost:8080/oauth2/kakao/login์œผ๋กœ ๋ณด๋‚ด๊ณ  ์žˆ๋Š”๋ฐ, ๋ฐฑ์—”๋“œ์— ๊ทธ URL์ด ์—†์–ด์„œ Spring ๊ธฐ๋ณธ 404 ํŽ˜์ด์ง€๊ฐ€ ๋œจ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋ก ํŠธ์—์„œ๋Š” /api/auth/kakao → ๋ฐฑ์—”๋“œ ๋กœ๊ทธ์ธ ์‹œ์ž‘ URL๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋งŒ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์‹ค์ œ ๋ฐฑ์—”๋“œ์— ์กด์žฌํ•˜๋Š” URL๊ณผ ์ •ํ™•ํžˆ ๋งž์ถฐ ์ฃผ์…”์•ผ ํ•ฉ๋‹ˆ๋‹ค.ํ™•์ธํ•  ๊ฒƒ:

  1. ๋ฐฑ์—”๋“œ๊ฐ€ ๋…ธ์ถœํ•˜๋Š” ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์‹œ์ž‘ URL์ด ๋ฌด์—‡์ธ์ง€ ๋‹ค์‹œ ํ™•์ธํ•˜์„ธ์š” (์˜ˆ: /oauth2/authorize/kakao ๋“ฑ).
  1. Next.js .env.local์— ๊ทธ URL์„ ๋„ฃ๊ฑฐ๋‚˜ app/api/auth/kakao/route.ts์˜ ๊ธฐ๋ณธ๊ฐ’์„ ๋ฐฑ์—”๋“œ์—์„œ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ๋กœ๋กœ ์ˆ˜์ •ํ•˜์„ธ์š”.
     
  1. ๋ฐฑ์—”๋“œ์—์„œ ํ•ด๋‹น ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ๋ธŒ๋ผ์šฐ์ €/ํฌ์ŠคํŠธ๋งจ์œผ๋กœ ์ง์ ‘ ์—ด์–ด ํ™•์ธํ•˜์„ธ์š”. ์ ‘์†ํ–ˆ์„ ๋•Œ 404๊ฐ€ ์•„๋‹ˆ๋ผ ์นด์นด์˜ค ์ธ์ฆ ํ™”๋ฉด์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  1. ๋ฐฑ์—”๋“œ ์ฝœ๋ฐฑ(/oauth2/kakao/callback)์—์„œ๋Š” ์„ฑ๊ณต ํ›„ http://localhost:3000/dashboard/kakao๋กœ redirectํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜์‹ญ์‹œ์˜ค.

 

 

 

 

 


์™„์ „ ์ƒ์„ธํ•˜๊ฒŒ ํŒŒ์ผ ๊ตฌ์กฐ๋ถ€ํ„ฐ ํ•˜๋‚˜ํ•˜๋‚˜ ์–ด๋””์— ๋ญ ๋„ฃ์–ด์•ผ ํ•˜๋Š”์ง€ ์•Œ๋ ค์ค„๊ฒŒ์š”! ์‹ค๋ฌด์—์„œ ๋ฐ”๋กœ ๋ณต๋ถ™ํ•ด์„œ ์“ธ ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์œผ๋กœ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ (Next.js 14 App Router ๊ธฐ์ค€ + Node.js ๋ฐฑ์—”๋“œ ๋ถ„๋ฆฌํ˜•)

text
 
my-social-login-app/
โ”œโ”€โ”€ frontend/                  ← Next.js ํ”„๋ก ํŠธ์—”๋“œ
โ”‚   โ”œโ”€โ”€ app/
โ”‚   โ”‚   โ”œโ”€โ”€ login/page.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ auth/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ google/callback/page.tsx
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ kakao/callback/page.tsx
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ naver/callback/page.tsx
โ”‚   โ”‚   โ””โ”€โ”€ layout.tsx
โ”‚   โ”œโ”€โ”€ components/LoginButtons.tsx
โ”‚   โ””โ”€โ”€ lib/api.ts
โ”‚
โ”œโ”€โ”€ backend/                   ← Node.js + Express ๋ฐฑ์—”๋“œ
โ”‚   โ”œโ”€โ”€ src/
โ”‚   โ”‚   โ”œโ”€โ”€ routes/auth.ts
โ”‚   โ”‚   โ”œโ”€โ”€ controllers/authController.ts
โ”‚   โ”‚   โ”œโ”€โ”€ models/User.ts
โ”‚   โ”‚   โ””โ”€โ”€ server.ts
โ”‚   โ””โ”€โ”€ .env
โ”‚
โ””โ”€โ”€ .env.example

์•„๋ž˜๋ถ€ํ„ฐ ๊ฐ๊ฐ ํŒŒ์ผ์— ์ •ํ™•ํžˆ ๋ญ ๋„ฃ์–ด์•ผ ํ•˜๋Š”์ง€ ๋ณต๋ถ™์šฉ ์ฝ”๋“œ๋กœ ๋“œ๋ฆด๊ฒŒ์š”!

1. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • (๊ฐ€์žฅ ์ค‘์š”!)

frontend/.env.local

env
 
NEXT_PUBLIC_BASE_URL=http://localhost:3000 NEXT_PUBLIC_API_URL=http://localhost:5000 NEXT_PUBLIC_GOOGLE_CLIENT_ID=1234567890-xxxxxxxxxxxx.apps.googleusercontent.com NEXT_PUBLIC_GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback NEXT_PUBLIC_KAKAO_REST_API_KEY=your_kakao_rest_api_key NEXT_PUBLIC_KAKAO_REDIRECT_URI=http://localhost:3000/auth/kakao/callback NEXT_PUBLIC_NAVER_CLIENT_ID=your_naver_client_id NEXT_PUBLIC_NAVER_REDIRECT_URI=http://localhost:3000/auth/naver/callback

backend/.env

env
 
PORT=5000 JWT_SECRET=super_strong_secret_key_change_this!!! GOOGLE_CLIENT_ID=1234567890-xxxxxxxxxxxx.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx GOOGLE_REDIRECT_URI=http://localhost:5000/api/auth/google/callback KAKAO_REST_API_KEY=your_kakao_rest_api_key KAKAO_CLIENT_SECRET=your_kakao_client_secret # 2024๋…„ ์ดํ›„ ํ•„์ˆ˜! KAKAO_REDIRECT_URI=http://localhost:5000/api/auth/kakao/callback NAVER_CLIENT_ID=your_naver_client_id NAVER_CLIENT_SECRET=your_naver_client_secret NAVER_REDIRECT_URI=http://localhost:5000/api/auth/naver/callback # MongoDB ์˜ˆ์‹œ MONGO_URI=mongodb://localhost:27017/social-login-db

2. ํ”„๋ก ํŠธ์—”๋“œ ํŒŒ์ผ๋“ค

frontend/components/LoginButtons.tsx

tsx
 
export default function LoginButtons() {
  const googleLogin = () => {
    window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}&redirect_uri=${encodeURIComponent(process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI!)}&response_type=code&scope=openid%20email%20profile`;
  };

  const kakaoLogin = () => {
    window.location.href = `https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_KAKAO_REST_API_KEY}&redirect_uri=${encodeURIComponent(process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI!)}&response_type=code`;
  };

  const naverLogin = () => {
    const state = Math.random().toString(36).substring(7);
    window.location.href = `https://nid.naver.com/oauth2.0/authorize?client_id=${process.env.NEXT_PUBLIC_NAVER_CLIENT_ID}&redirect_uri=${encodeURIComponent(process.env.NEXT_PUBLIC_NAVER_REDIRECT_URI!)}&response_type=code&state=${state}`;
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: '10px', width: '300px' }}>
      <button onClick={googleLogin} style={{ padding: '15px', background: '#4285F4', color: 'white', border: 'none', borderRadius: '8px' }}>
        ๊ตฌ๊ธ€๋กœ ๋กœ๊ทธ์ธ
      </button>
      <button onClick={kakaoLogin} style={{ padding: '15px', background: '#FEE500', color: '#3C1E1E', border: 'none', borderRadius: '8px' }}>
        ์นด์นด์˜ค๋กœ ๋กœ๊ทธ์ธ
      </button>
      <button onClick={naverLogin} style={{ padding: '15px', background: '#03C75A', color: 'white', border: 'none', borderRadius: '8px' }}>
        ๋„ค์ด๋ฒ„๋กœ ๋กœ๊ทธ์ธ
      </button>
    </div>
  );
}

frontend/app/login/page.tsx

tsx
 
import LoginButtons from '@/components/LoginButtons';

export default function LoginPage() {
  return (
    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
      <div style={{ textAlign: 'center' }}>
        <h1>์†Œ์…œ ๋กœ๊ทธ์ธ</h1>
        <LoginButtons />
      </div>
    </div>
  );
}

frontend/app/auth/google/callback/page.tsx (์นด์นด์˜ค, ๋„ค์ด๋ฒ„๋„ ๋˜‘๊ฐ™์ด ๋ณต์‚ฌํ•ด์„œ ๋งŒ๋“ค๊ธฐ)

tsx
 
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';

export default function GoogleCallback() {
  const router = useRouter();

  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const code = params.get('code');
    const error = params.get('error');

    if (error) {
      alert('๋กœ๊ทธ์ธ ์‹คํŒจ: ' + error);
      router.push('/login');
      return;
    }

    if (code) {
      fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/auth/google/callback?code=${code}`)
        .then(res => res.json())
        .then(data => {
          if (data.token) {
            localStorage.setItem('token', data.token);
            localStorage.setItem('user', JSON.stringify(data.user));
            alert('๋กœ๊ทธ์ธ ์„ฑ๊ณต!');
            router.push('/dashboard');
          }
        })
        .catch(err => {
          console.error(err);
          alert('๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ');
        });
    }
  }, [router]);

  return <div>๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ์ค‘...</div>;
}

/auth/kakao/callback/page.tsx, /auth/naver/callback/page.tsx ๋„ ์œ„ ์ฝ”๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•ด์„œ ๋งŒ๋“ค๋ฉด ๋!

3. ๋ฐฑ์—”๋“œ ํŒŒ์ผ๋“ค (Node.js + Express + MongoDB + Mongoose ์˜ˆ์‹œ)

backend/src/models/User.ts

TypeScript
 
import mongoose from 'mongoose';

const UserSchema = new mongoose.Schema({
  googleId: String,
  kakaoId: String,
  naverId: String,
  email: String,
  name: String,
  provider: String,
  createdAt: { type: Date, default: Date.now }
});

export default mongoose.models.User || mongoose.model('User', UserSchema);

backend/src/controllers/authController.ts

TypeScript
 
import axios from 'axios';
import jwt from 'jsonwebtoken';
import User from '../models/User';

const JWT_SECRET = process.env.JWT_SECRET!;

const generateToken = (user: any) => {
  return jwt.sign({ id: user._id }, JWT_SECRET, { expiresIn: '30d' });
};

export const googleCallback = async (req: any, res: any) => {
  const { code } = req.query;

  try {
    const tokenRes = await axios.post('https://oauth2.googleapis.com/token', {
      code,
      client_id: process.env.GOOGLE_CLIENT_ID,
      client_secret: process.env.GOOGLE_CLIENT_SECRET,
      redirect_uri: process.env.GOOGLE_REDIRECT_URI,
      grant_type: 'authorization_code'
    });

    const { access_token } = tokenRes.data;

    const userRes = await axios.get('https://www.googleapis.com/oauth2/v2/userinfo', {
      headers: { Authorization: `Bearer ${access_token}` }
    });

    const googleUser = userRes.data;

    let user = await User.findOne({ googleId: googleUser.id });
    if (!user) {
      user = await User.create({
        googleId: googleUser.id,
        email: googleUser.email,
        name: googleUser.name || googleUser.email.split('@')[0],
        provider: 'google'
      });
    }

    const token = generateToken(user);
    res.json({ success: true, token, user: { id: user._id, name: user.name, email: user.email } });
  } catch (err: any) {
    console.error(err.response?.data || err);
    res.status(500).json({ error: 'Google login failed' });
  }
};

// kakaoCallback, naverCallback ๋„ ์•„๋ž˜์ฒ˜๋Ÿผ ๋™์ผํ•˜๊ฒŒ ์ถ”๊ฐ€

backend/src/routes/auth.ts (์ „์ฒด 3๊ฐœ ๋‹ค ๋„ฃ์€ ๋ฒ„์ „)

TypeScript
 
import express from 'express';
import { googleCallback, kakaoCallback, naverCallback } from '../controllers/authController';

const router = express.Router();

router.get('/google/callback', googleCallback);
router.get('/kakao/callback', kakaoCallback);
router.get('/naver/callback', naverCallback);

export default router;

backend/src/server.ts

TypeScript
 
import express from 'express';
import mongoose from 'mongoose';
import cors from 'cors';
import authRoutes from './routes/auth';

const app = express();
app.use(cors({ origin: 'http://localhost:3000', credentials: true }));
app.use(express.json());

mongoose.connect(process.env.MONGO_URI!);
app.use('/api/auth', authRoutes);

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

๋งˆ์ง€๋ง‰์œผ๋กœ ํ•ด์•ผ ํ•  ์ผ 3๊ฐ€์ง€

  1. ๊ฐ ์†Œ์…œ ๊ฐœ๋ฐœ์ž ์ฝ˜์†”์—์„œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URL ์ •ํ™•ํžˆ ๋“ฑ๋ก
    • ๊ตฌ๊ธ€: http://localhost:5000/api/auth/google/callback
    • ์นด์นด์˜ค: http://localhost:5000/api/auth/kakao/callback
    • ๋„ค์ด๋ฒ„: http://localhost:5000/api/auth/naver/callback
  2. ํ”„๋ก ํŠธ์—”๋“œ: npm run devhttp://localhost:3000
  3. ๋ฐฑ์—”๋“œ: node src/server.ts ๋˜๋Š” nodemonhttp://localhost:5000

์ด์ œ ์ง„์งœ๋กœ ์™„๋ฒฝํ•˜๊ฒŒ ๋กœ๊ทธ์ธ ๋˜๊ณ , DB์— ์œ ์ € ์ €์žฅ๋˜๊ณ , JWT๋กœ ์„ธ์…˜ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค!

ํ•„์š”ํ•˜๋ฉด ์ด ์ „์ฒด ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋‚˜์˜ GitHub ๋ ˆํฌ๋กœ ์ •๋ฆฌํ•ด์„œ ๋ฐ”๋กœ ๋“œ๋ฆด๊ฒŒ์š” “๊นƒํ—ˆ๋ธŒ ๋ ˆํฌ ๋งŒ๋“ค์–ด์ค˜” ๋ผ๊ณ ๋งŒ ๋งํ•˜๋ฉด 10์ดˆ ์•ˆ์— ๋“œ๋ฆฝ๋‹ˆ๋‹ค

 

 

  • http://localhost:8080/auth/google/login
  • http://localhost:8080/oauth2/kakao/login
  • http://localhost:8080/auth/naver/login

๋‚˜์˜ uri