개요
지난 포스팅에서 코드를 구현하기전에 끝내야 할 모든 설정들을 완료했다.
2025.08.08 - [Web/Next.js] - Next.js 15 + Supabase로 Kakao 로그인 구현하기 - 1
Next.js 15 + Supabase로 Kakao 로그인 구현하기 - 1
개요사이드 프로젝트 진행중에 '카카오 로그인' 기능을 추가하고자 했다.Next.js를 사용하는것도 처음이고,Supbase로 OAuth를 이용해 카카오 로그인 기능을 구현하는 것도 처음이었다.시행착오를 거
jun-coding.tistory.com
이제 공식문서를 따라서 구현해보면서 카카오 로그인 기능을 구현해보려 한다.
Login with Kakao | Supabase Docs
Add Kakao OAuth to your Supabase project
supabase.com
UI는 최대한 간단하게 하고, 기능 완성만 해볼것이다.
로그인 코드 추가하기
공식문서를 보면 다음과 같은 문구가 있다.
만약 서버 사이드 렌더링(SSR)이나 쿠키 기반의 인증 방식을 사용하지 않는다면,
`@supabase/supabase-js`에 있는 `createClient`를 바로 사용하면 된다.
만약 SSR을 사용한다면, Supbase 클라이언트를 만드는데에 SSR 가이드를 참고해라.
위에서 말한 SSR 가이드로 들어가보자.
(Supabase를 이미 Next.js에 설정한 사람들은 가볍게 보면 될 것 같다.)
Creating a Supabase client for SSR | Supabase Docs
Configure your Supabase client to use cookies
supabase.com
`middleware.ts`
대부분의 예시 코드는 여기에 있는 'Connect'를 누르면 나오는 Next.js 설정 코드와 같다.
그런데 'middleware.ts' 파일을 추가로 보여주고 있다.
SSR 가이드에 보면 Middleware 예시 코드에 다음과 같이 설명이 되어있다.
Next.js에서 서버 컴포넌트는 쿠키를 설정할 수 없기 때문에,
클라이언트가 쿠키 갱신을 다루기 위해 미들웨어가 필요하다.
미들웨어는 Supabase에 접근이 필요하거나,
Supabase Auth에 의해 보호받을 모든 라우트 전에 실행된다.
그리고 두 가지 코드가 주어진다.
먼저 `middleware.ts` 파일의 코드다.
(프로젝트의 루트에 하나만 생성하는 것이 Next.js 공식 문서의 권장 방식이다.)
// middleware.ts
import { type NextRequest } from 'next/server'
import { updateSession } from '@/utils/supabase/middleware'
export async function middleware(request: NextRequest) {
return await updateSession(request)
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* Feel free to modify this pattern to include more paths.
*/
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
}
위 코드의 주석을 보면,
주석에 써있는 경로들을 제외한 모든 경로에서 미들웨어를 실행한다.
- `_next/static`(Next.js 정적 파일)
- `_next/image`(Next.js 이미지 최적화)
- `favicon.icon`(파비콘)
- 이미지 파일들(svg, png, jpg 등)
`@/utils/supabase/middleware.ts`
이 미들웨어의 구현부는 `@/utils/supabase/middleware.ts`에 다음과 작성한다.
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function updateSession(request: NextRequest) {
let supabaseResponse = NextResponse.next({
request,
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
supabaseResponse = NextResponse.next({
request,
})
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
// IMPORTANT: Avoid writing any logic between createServerClient and
// supabase.auth.getClaims(). A simple mistake could make it very hard to debug
// issues with users being randomly logged out.
// IMPORTANT: Don't remove getClaims()
const { data } = await supabase.auth.getClaims()
const user = data?.claims
if (
!user &&
!request.nextUrl.pathname.startsWith('/login') &&
!request.nextUrl.pathname.startsWith('/auth')
) {
// no user, potentially respond by redirecting the user to the login page
const url = request.nextUrl.clone()
url.pathname = '/login'
return NextResponse.redirect(url)
}
// IMPORTANT: You *must* return the supabaseResponse object as it is. If you're
// creating a new response object with NextResponse.next() make sure to:
// 1. Pass the request in it, like so:
// const myNewResponse = NextResponse.next({ request })
// 2. Copy over the cookies, like so:
// myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
// 3. Change the myNewResponse object to fit your needs, but avoid changing
// the cookies!
// 4. Finally:
// return myNewResponse
// If this is not done, you may be causing the browser and server to go out
// of sync and terminate the user's session prematurely!
return supabaseResponse
}
이 코드는 앞서 본 `middleware.ts`에서 호출되는 `updateSession()`함수이다.
Supabase 인증 세션을 관리하는 핵심 로직이다.
코드를 나눠서 살펴보자.
먼저, Supbase 서버 클라이언트를 생성하는 부분이다.
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
supabaseResponse = NextResponse.next({
request,
})
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
서버 환경에서 Supabase와 통신할 클라이언트를 생성하고,
쿠키를 통해 인증 토큰을 주고 받도록 설정해준다.
요청에서 `getAll()`로 모든 쿠키를 읽고,
`setAll()`로 요청과 응답 모두에 쿠키를 설정해준다.
다음으로는 사용자 인증을 확인하는 부분이다.
const { data } = await supabase.auth.getClaims()
const user = data?.claims
현재 요청의 인증 토큰에서 사용자 정보를 추출해준다.
로그인 리다이렉트 로직 부분이다.
if (
!user &&
!request.nextUrl.pathname.startsWith('/login') &&
!request.nextUrl.pathname.startsWith('/auth')
) {
const url = request.nextUrl.clone()
url.pathname = '/login'
return NextResponse.redirect(url)
}
if문에서 세션을 체크해서 사용자가 로그인되어 있는지 확인한다.
여기서 만약 로그인하지 않은 사용자가 보호된 페이지에 접근하면,
`/login` 페이지로 자동으로 리다이렉트 한다.
`/login`이나 `/auth`로 시작하는 경로는 비회원도 접근해야 하기 때문에 리다이렉트 해주지 않는다.
그리고 주석에서 강조하는 것이 있는데,
다음을 꼭 지켜줘야 한다.
- `createServerClient`와 `getClaims()` 사이에 다른 로직을 넣으면 안된다.
- `supbaseResponse` 객체를 반드시 그대로 반환해야 한다.
- 쿠키를 임의로 수정하면 세션이 깨질 수 있다.
결론적으로, 이 코드는 '자동 로그인 보호 기능'을 구현하는 로직이다.
로그인 로직 코드 작성하기
유저가 로그인할 때, `signInWithOAuth()`를 'kakao' provider로 설정하여 호출하면 된다.
async function signInWithKakao() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'kakao',
})
}
기본 형식은 위와 같고,
보통 로그인이 성공하면 어떤 페이지로 이동(리다이렉트)해야 하는데,
그 방법이 클라이언트에서 처리하는 것과 서버에서 처리하는 방식이 다르다.
클라이언트 컴포넌트에서는 다음과 같이 처리한다.
브라우저(클라이언트)에서는 자동으로 OAuth provider의 인증 엔드포인트로 리다이렉트 되고,
그 후, 내가 설정한 엔드포인트로 리다이렉트 된다.
async function signInWithKakao() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'kakao',
options: {
redirectTo: `http://example.com/auth/callback`,
},
})
}
내가 프로젝트에 작성한 코드로는 다음과 같다.
아래 코드를 본인이 만든 카카오 로그인 버튼에 이벤트 리스너로 달아주면 된다.
const handleKakaoLogin = async () => {
const supabase = createClient();
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'kakao',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
};
서버 컴포넌트에서는 다음과 같이 처리한다.
서버에서는, OAuth provider의 인증 엔드포인트로의 리디렉션을 처리해야 한다.
`signInWithOAuth()` 메서드는 리다이렉트 할 수 있는 url을 반환해준다.
const { data, error } = await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: 'http://example.com/auth/callback',
},
})
if (data.url) {
redirect(data.url) // use the redirect API for your server framework
}
callback 엔드포인트에서는, 인증 코드를 교환해서 사용자 세션을 저장해야 한다.
`app/auth/callback/route.ts`에 다음과 같은 코드를 만들면 된다.
import { NextResponse } from 'next/server'
// The client you created from the Server-Side Auth instructions
import { createClient } from '@/utils/supabase/server'
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get('code')
// if "next" is in param, use it as the redirect URL
// 만약 성공시에 '/gallery'로 이동하고 싶다면 마지막을 '/' 대신 '/gallery'로 바꿔준다.
let next = searchParams.get('next') ?? '/'
if (!next.startsWith('/')) {
// if "next" is not a relative URL, use the default
// 마찬가지로 여기도 '/gallery'로 바꿔준다.
next = '/'
}
if (code) {
const supabase = await createClient()
const { error } = await supabase.auth.exchangeCodeForSession(code)
if (!error) {
const forwardedHost = request.headers.get('x-forwarded-host') // original origin before load balancer
const isLocalEnv = process.env.NODE_ENV === 'development'
if (isLocalEnv) {
// we can be sure that there is no load balancer in between, so no need to watch for X-Forwarded-Host
return NextResponse.redirect(`${origin}${next}`)
} else if (forwardedHost) {
return NextResponse.redirect(`https://${forwardedHost}${next}`)
} else {
return NextResponse.redirect(`${origin}${next}`)
}
}
}
// return the user to an error page with instructions
return NextResponse.redirect(`${origin}/auth/auth-code-error`)
}
이것을 마지막으로 완료!
'Web > Next.js' 카테고리의 다른 글
Next.js 15 / Image Component (2) | 2025.08.13 |
---|---|
Next.js 15 / CSS Modules (1) | 2025.08.13 |
Next.js 15 / File system conventions - layout.js(ts) (2) | 2025.08.12 |
Next.js 15 + Supabase로 Kakao 로그인 구현하기 - 1 (3) | 2025.08.08 |