카테고리 없음

Supabase edge functions 로 카카오 로그인을 구현해보자

호리둥절 2023. 6. 13. 09:42

Supabase edge functions에 대해 글을 작성한게 있는데 사용하시는 분들이 있으신거같아서 이번에는 edge functions를 사용하여 oauth로그인을 구현해보겠습니다.

 

개발환경은 ionic 7 버전을 사용하고 있습니다 ~! angular를 사용하시는 분도 쉽게따라할수 있으니 따라해보시길 바랍니다~!

 

구현하기전 사전에 작성해둔 Supabase edge functions의 사용법을 읽고와주세요 !! 

https://develop-const.tistory.com/34 

 

Supabase edge functions를 사용해보자

supabase functions 란? Supabase Edge Functions은 Supabase의 서버리스 함수 기능 중 하나로, 전 세계의 엣지 네트워크에 함수를 배포하는 기능입니다. 이를 통해 API 요청에 대한 응답 시간을 단축하고 사용자

develop-const.tistory.com

 

✔  카카오 로그인 흐름에 대해 이해하기  

우선 카카오 로그인을 구현하기에 앞서 카카오 로그인 흐름에 대해 이해해보는 시간을 가지도록 하겠습니다.

카카오 개발자 센터에 아주 자세히 설명이 되어있으니 참고해 주세요 ! 

 

✔  카카오 개발자 센터에서 내 애플리케이션 등록하기 

[ 애플리케이션 등록하기 ]

https://developers.kakao.com/console/app

 

카카오계정

 

accounts.kakao.com

위에 사이트에 들어가셔서 애플리케이션을 추가하면 앱키가 생성된것을 볼수있습니다. 이제 이 앱키를 가지고 카카오 로그인을 구현해보겠습니다.

 

[ redirect url 등록하기 ]

내애플리케이션> 앱 설정> 플랫폼에 들어가셔서 아래와같이 redirect url을 등록하여 주시면 됩니다. 현재 개발환경이 localhost:8100이기 때문에 아래와 같이 세팅하였습니다. 

프론트 엔드 코드 작성하기 

[ login.html ]

    <div
        (click)="loginWithKakao()"
        class="flex w-full justify-center bg-[#F7E600] py-3 rounded-[10px] shadow-[1px_2px_2px_0px_rgba(0,0,0,0.25)]"
      >
        <img src="assets/icon/kakaologo.svg" />
        <p class="pl-2">카카오로 시작하기</p>
      </div>

 

[ login.ts ]

@Component({
  selector: 'app-login',
  templateUrl: './login.page.html',
  standalone: true,
  imports: [IonicModule, CommonModule, FormsModule],
  providers: [AuthService]
})

export class LoginPage {
  constructor(
    public authService: AuthService,
    private router: Router,
    private route: ActivatedRoute) { }


    loginWithKakao() {
      // 카카오 로그인 버튼 클릭 시 동작
      window.location.href = `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${this.REST_API_KEY}&redirect_uri=${this.REDIRECT_URI + "?loginState=Kakao"}`;
   }
  }

여기서 아까 발급받은 REST API 키를 REST_API_KEY에 넣어주시고 카카오 로그인 이후 되돌아올 URL을 REDIRECT_URL에 넣어주시면 됩니다. 필자는 localhost:8100/login으로 하였습니다.

이렇게 코드를 작성하신후 카카오 시작하기 버튼을 클릭하면 loginWithKakao()함수를 통해  사용자가 카카오 로그인 페이지로 리다이렉트되고

 

로그인한 후에는 인가 코드가 반환됩니다.

 

http://localhost:8100/login?loginState=Kakao&code=Vae1C0lQ1HAecjZSOcA3mXIkTrr5e9btnr1Sk1bh4h8TPOxA0x6B_q_HBCStADpi0Etk3gorDSAAAAGIrp_cMA

여기서 code를 이용하여 토큰정보를 얻어올 수 있습니다.

 

 

백앤드 코드 작성하기 (supabase edge functions) 

자 이제 supabase edge functions를 사용하여 백앤드 코드를 작성해보겠습니다.

 

[ supabase파일 만들기 ]

supabase functions new sns-login

 

[ 백앤드 코드 작성하기 ]

[ supabase > functions > sns-log > index.ts ]

 

서버 시작 및 CORS 설정

import { serve } from "https://deno.land/std@0.168.0/http/server.ts";

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

serve(async (req: Request) => {
  // ...
});

serve 함수는 Deno의 내장 HTTP 서버를 시작하는 데 사용됩니다. CORS 헤더를 설정하여 다른 도메인에서의 요청도 허용합니다

 

요청 처리

if (req.method === 'OPTIONS') {
  return new Response('ok', { headers: corsHeaders });
}
let endPoint = req.url.split("login")[1];
if (endPoint == '/kakao') {
  console.log("카카오로그인");
  return handleKakaoLogin(req);
}

요청의 HTTP 메서드와 엔드포인트를 확인하여 카카오 로그인 요청인 경우 handleKakaoLogin 함수를 호출합니다.

 

카카오 로그인 처리

async function handleKakaoLogin(request: Request) {
  console.log(request);
  if (request.method === 'POST') {
    const { code } = await request.json();
    console.log(code);
    try {
      // 카카오 액세스 토큰 얻기
      const accessToken = await getKakaoAccessToken(code);

      // 카카오 API를 호출하여 사용자 정보 가져오기
      const userInfo = await getKakaoUserInfo(accessToken);

      // 카카오 사용자 정보 처리
      // 예를 들어, 사용자 이름, 이메일, 프로필 사진 등을 가져와서 처리할 수 있습니다.

      const data = {
        message: 'Kakao login successful',
        userInfo: userInfo,
      };

      return new Response(JSON.stringify(data), { headers: corsHeaders });
    } catch (error) {
      const errorData = {
        error: 'Kakao login failed',
        message: error,
      };

      return new Response(JSON.stringify(errorData), {
        headers: corsHeaders,
        status: 400,
      });
    }
  } else {
    const errorData = {
      error: 'Invalid request method',
      message: 'Only POST requests are supported',
    };

    return new Response(JSON.stringify(errorData), {
      headers: corsHeaders,
      status: 400,
    });
  }
}

카카오 액세스 토큰얻기

async function getKakaoAccessToken(code: string) {
  const response = await fetch('https://kauth.kakao.com/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: 'client_id',
      redirect_uri: 'http://localhost:8100/login?loginState=Kakao',
      code: code,
    }),
  });

  if (response.ok) {
    const tokenData = await response.json();
    return tokenData.access_token;
  } else {
    throw new Error('Failed to fetch Kakao access token');
  }
}

위의 코드에서 fetch 함수를 사용하여 카카오의 토큰 엔드포인트에 POST 요청을 보내고, 필요한 매개변수와 함께 전달합니다. 공적인 응답을 받으면 JSON 데이터에서 액세스 토큰을 추출하여 반환합니다. 그렇지 않은 경우에는 오류를 throw합니다

 

사용자 정보를 가져오기

async function getKakaoUserInfo(accessToken: string) {
  const response = await fetch('https://kapi.kakao.com/v2/user/me', {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  if (response.ok) {
    const userInfo = await response.json();
    return userInfo;
  } else {
    throw new Error('Failed to fetch user information from Kakao API');
  }
}

위의 코드에서는 fetch 함수를 사용하여 카카오 사용자 정보 엔드포인트에 GET 요청을 보냅니다. 요청 헤더에는 액세스 토큰이 Authorization 헤더에 포함되어 전송됩니다. 성공적인 응답을 받으면 JSON 데이터에서 사용자 정보를 추출하여 반환합니다. 그렇지 않은 경우에는 오류를 throw합니다.

이러한 방식으로 카카오 로그인을 처리하고, 액세스 토큰을 얻어와서 사용자 정보를 가져올 수 있습니다.

 

[ 전체코드보기 ]

import { serve } from "https://deno.land/std@0.168.0/http/server.ts"


const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

serve(async (req: Request) => {

      if (req.method === 'OPTIONS') {
        return new Response('ok', { headers: corsHeaders })
      }
      let endPoint = req.url.split("login")[1]
      if (endPoint == '/kakao') {
        console.log("카카오로그인")
        return handleKakaoLogin(req);
      } 
 })
 
 // 카카오 로그인 요청 처리
async function handleKakaoLogin(request: Request) {
  console.log(request)
  if (request.method === 'POST') {
    const { code } = await request.json();
    console.log(code)
    try {
      // 카카오 액세스 토큰 얻기
      const accessToken = await getKakaoAccessToken(code);

      // 카카오 API를 호출하여 사용자 정보 가져오기
      const userInfo = await getKakaoUserInfo(accessToken);

      // 카카오 사용자 정보 처리
      // 예를 들어, 사용자 이름, 이메일, 프로필 사진 등을 가져와서 처리할 수 있습니다.

      const data = {
        message: 'Kakao login successful',
        userInfo: userInfo,
      };

      return new Response(
        JSON.stringify(data),
        { headers: corsHeaders },
      );
    } catch (error) {
      const errorData = {
        error: 'Kakao login failed',
        message: error,
      };

      return new Response(
        JSON.stringify(errorData),
        { headers: corsHeaders, status: 400 },
      );
    }
  } else {
    const errorData = {
      error: 'Invalid request method',
      message: 'Only POST requests are supported',
    };

    return new Response(
      JSON.stringify(errorData),
      { headers: corsHeaders, status: 400 },
    );
  }
}


// 카카오 액세스 토큰 얻기
async function getKakaoAccessToken(code: string) {
  const response = await fetch('https://kauth.kakao.com/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: 'client_id',
      redirect_uri: 'http://localhost:8100/login?loginState=Kakao',
      code: code,
    }),
  });

  if (response.ok) {
    const tokenData = await response.json();
    return tokenData.access_token;
  } else {
    throw new Error('Failed to fetch Kakao access token');
  }
}

// 카카오 사용자 정보 가져오기
async function getKakaoUserInfo(accessToken: string) {
  const response = await fetch('https://kapi.kakao.com/v2/user/me', {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  if (response.ok) {
    const userInfo = await response.json();
    return userInfo;
  } else {
    throw new Error('Failed to fetch user information from Kakao API');
  }
}

 

[ 작성한 백앤드 코드 배포하기 ]

supabase functions deploy sns-login

배포후 Enforce JWT Verification 꺼주시는거 잊지마세요 !

 

 

[ 배포한 코드를 사용하여 사용자정보 얻어오기 ]

@Component({
  selector: 'app-login',
  templateUrl: './login.page.html',
  standalone: true,
  imports: [IonicModule, CommonModule, FormsModule],
  providers: [AuthService]
})

export class LoginPage {
  constructor(
    public authService: AuthService,
    private router: Router,
    private route: ActivatedRoute) { }

	ngOnInit(){
    
      const authorizationCode = this.route.snapshot.queryParamMap.get('code');
      
      if(authorizationCode){
      	return
       }
       
       this.http.post('https://{{Reference_ID}}.functions.supabase.co/meal-log/login/kakao', { "code": authorizationCode })
        .subscribe(result => {console.log(result)})
    }
    
    loginWithKakao() {
      // 카카오 로그인 버튼 클릭 시 동작
      window.location.href = `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${this.REST_API_KEY}&redirect_uri=${this.REDIRECT_URI + "?loginState=Kakao"}`;
   }
  }

deploy 할때 적어준 참조아이디를 reference_id에 넣어줍니다.

  • 첫 번째 줄에서는 현재 URL의 쿼리 매개변수에서 code 값을 가져옵니다. 이는 이전 단계에서 카카오 로그인 완료 후 리디렉션된 URL에 포함된 인증 코드입니다.
  • authorizationCode가 존재하는 경우, 코드 실행을 중단하고 반환합니다. 이는 인증 코드가 존재하지 않을 때, 다른 작업을 수행하지 않도록 하는 역할을 합니다.
  • authorizationCode가 존재하는 경우, 해당 코드를 사용하여 Supabase 서버로 POST 요청을 보냅니다. Supabase 서버의 URL은 'https://{{Reference_ID}}.functions.supabase.co/meal-log/login/kakao'로 지정되어 있습니다. {{Reference_ID}}는 Supabase 프로젝트의 식별자입니다. 요청의 본문에는 인증 코드가 포함된 JSON 데이터가 전달됩니다.

 

혹시 네이버 로그인이나 구글로그인도 필요하시다면 추가로 글을 작성해보겠습니다 ㅎㅎ..!!