본문 바로가기

자바스크립트

React)Discord 로그인 (with Supabase)

디스코드로 로그인하는 기능을 추가할 필요성이 있었기에 추가했다.

 

처음엔 클라이언트에서의 로그인만 되길 원했으나 아쉽게도 

discord 개발자 문서에 자바스크립트는 node.js만 지원했다.

 

우선 nodejs로 로그인 하는 방법이다.

디스코드 개발자 포탈로 들어가서 앱을 만들어야한다.

https://discord.com/developers/applications로 들어간다.

 

Discord Developer Portal — API Docs for Bots and Developers

Integrate your service with Discord — whether it's a bot or a game or whatever your wildest imagination can come up with.

discord.com

앱을 하나 만들고

 

해당 앱을 클릭하면 좌측 메뉴중 Settings > OAuth2 >General을 클릭한다.

클라이언트 아이디와 클라이언트 시크릿을 복사한다.

(클라이언트 시크릿은 발급 후 disable이 되므로 따로 저장하는 것을 추천)

 

****만약 Client Secret발급 중 인증하라는 팝업이 떴을 경우****

디스코드의 2단계 인증 절차를 적용시켜야한다.

나 같은 경우엔 핸드폰 어플리케이션 중 AUTHY라는 어플을 이용하여 발급 받았다.

AUTHY를 이용한 Discord 2단계 인증 적용 방법

****

다음으론 그 아래 Redirects에 http://localhost:8000/auth/discord을 추가한다.

(본인 로컬 포트에 따라 다름)

 

마지막으로 URL Generator에 들어가 가져오고자 하는 정보를 선택한 다음 

SELECT REDIRECT URL에 방금 입력했던 http://localhost:8000/auth/discord을 선택하면 

GENERATED URL 하나를 발급받는다.

이것 또한 어딘가에 저장해두면 좋다.

 

이제 디스코드의 설정은 끝이났다.

프로젝트를 만들자.

프로젝트에는 기본적으로 axios와 express가 필요하다.

axios외 다른 것을 이용할 시엔 다른 것 이용.

이제 아래 코드를 넣으면 잘 실행된다.

const express = require("express");
const axios = require("axios");
require("dotenv").config();

const PORT = 8000;
const app = express();

app.get("/", (req, res) => {
  res.send(`
        <div style="margin: 300px auto;
        max-width: 400px;
        display: flex;
        flex-direction: column;
        align-items: center;
        font-family: sans-serif;"
        >
            <h3>Welcome to Discord OAuth NodeJS App</h3>
            <p>Click on the below button to get started!</p>
            <a 
                href=
                "방금 발급 받은 GENERATED URL"
                style="outline: none;
                padding: 10px;
                border: none;
                font-size: 20px;
                margin-top: 20px;
                border-radius: 8px;
                background: #6D81CD;
                cursor:pointer;
                text-decoration: none;
                color: white;"
            >
            Login with Discord</a>
        </div>
    `);
});

console.log(process.env.CLIENT_SECRET);

app.get("/auth/discord", async (req, res) => {
  const code = req.query.code;
  const params = new URLSearchParams();
  let user;
  params.append("client_id", process.env.CLIENT_ID);
  params.append("client_secret", process.env.CLIENT_SECRET);
  params.append("grant_type", "authorization_code");
  params.append("code", code);
  params.append("redirect_uri", "http://localhost:8000/auth/discord");
  try {
    const response = await axios.post(
      "https://discord.com/api/oauth2/token",
      params
    );
    const { access_token, token_type } = response.data;
    const userDataResponse = await axios.get(
      "https://discord.com/api/users/@me",
      {
        headers: {
          authorization: `${token_type} ${access_token}`,
        },
      }
    );
    console.log("Data: ", userDataResponse.data);
    user = {
      username: userDataResponse.data.username,
      email: userDataResponse.data.email,
    };
    return res.send(`
            <div style="margin: 300px auto;
            max-width: 400px;
            display: flex;
            flex-direction: column;
            align-items: center;
            font-family: sans-serif;"
            >
                <h3>Welcome ${user.username}</h3>
                <span>Email: ${user.email}</span>
                <p>Your Discord App is connected to NodeJS!</p>
                <img src="${user.avatar}"/>
            </div>
        `);
  } catch (error) {
    console.log("Error", error);
    return res.send("Some error occurred! ");
  }
});

app.listen(PORT, () => {
  console.log(`App started on port ${PORT}`);
});

 

 

하지만 내가 원하던 방식은 아니었다.

분명 간단한 방법이었지만 백엔드와의 통신은 매우 귀찮았다.

그래서 찾았다. 나 대신 통신할 대체재를

Supabase라는 것인데 이 supabase는 구글 firebase를 엔터프라이즈 레벨에서도 사용하능하게 만든 오픈소스 프로젝트이다.

Supabase와 Firebase의 차이점은 몇가지 있지만, 자세한 점은 아래 링크에서 확인.

https://blog.logrocket.com/firebase-vs-supabase-choosing-right-tool-project/

 

Firebase vs. Supabase: Choosing the right tool for your project - LogRocket Blog

Learn everything you need to know about Firebase and Supabase. Compare Firebase vs. Supabase and discover which is best for your project.

blog.logrocket.com

 

우선 본인 환경은 CRA이다.

Supabase를 사용하기 Firebase처럼 몇가지 설정해야 할 것들이 있다.

https://app.supabase.com/projects에 접속하여 프로젝트를 생성한다.

 

Supabase | Supabase

 

app.supabase.com

 

해당 프로젝트를 생성한 후 클릭,

 

Authentication > URL Configuration > Redirect URLs에 https://localhost:3000/success를 등록한다.

본인이 https가 아닌 http일 경우 http로 변경.

다음으로

같은 Authentication에서 들어가면 Supabase가 지원하는 로그인 서비스를 확인할 수 있다.

Discord를 클릭,

Client ID와 Client Scret을 입력하면 된다.

Client ID와 Client Scret은 Discord에서 발급 받은 것을 사용하면 된다.

Redirect URL은 따로 복사한 후 

Discord의 OAuth2 > General > Redirects에 추가하면 된다.

 

이제 설정은 끝이났다.

프로젝트를 하나 생성한 이후,

npm install @supabase/auth-ud-react @supabase/supabase-js react-router-dom

을 설치.

 LoginPage.js 와 Success.js를 생성한다.

 

App.js

import React from "react";
import { Route, Routes } from "react-router-dom";
import SuccessPage from "./pages/SuccessPage";
import LoginPage from "./pages/LoginPage";


const App = () => {
  
  return (
      <Routes>
        <Route path="/success" element={<SuccessPage />} />
        <Route path="/" element={<LoginPage />} />
      </Routes>
  );
};

export default App;

Login.js

import React from "react";

const LoginPage = () => {
  return (
 <h1>login</h1>
  );
};

export default LoginPage;

 

다음에 supabase client를 만든다.

이때 두 개의 값이 필요한데, 하나는 프로젝트 URL이고 다른 하나는 API키이다.

둘은 아래 사진에서 확인할 수 있다.

 

그 다음은 쉽다.

이제 완성된 코드를 보자.

Login.js

import React from "react";
import { createClient } from "@supabase/supabase-js";
import { Auth, ThemeSupa } from "@supabase/auth-ui-react";
import { useNavigate } from "react-router-dom";

const supabase = createClient(
  process.env.REACT_APP_SUPABASE_PROJECT_URL,
  process.env.REACT_APP_SUPABASE_PROJECT_API_KEY
);

const LoginPage = () => {
  const navigate = useNavigate();
  supabase.auth.onAuthStateChange(async (event) => {
    if (event !== "SIGNED_OUT") {
      //forward success url
      navigate("/success");
    } else {
      //forward localhost 3000
      navigate("/");
    }
  });
  return (
    <div style={{ color: "red" }}>
      login
      <Auth
        supabaseClient={supabase}
        appearance={{ theme: ThemeSupa }}
        theme="dark"
        providers={["discord"]}
      />
    </div>
  );
};

export default LoginPage;

 

Success.js

import React, { useEffect, useState } from "react";
import { createClient } from "@supabase/supabase-js";
import { Auth, ThemeSupa } from "@supabase/auth-ui-react";
import { useNavigate } from "react-router-dom";

const supabase = createClient(
  "https://yczuwmfnhwaxfifchaem.supabase.co",
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InljenV3bWZuaHdheGZpZmNoYWVtIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NzMyODMxMjksImV4cCI6MTk4ODg1OTEyOX0.-9ybzuG96idM_Eu_DcKSxDHLpB276RUa7ZbniJ6wwhg"
);

const SuccessPage = () => {
  const [user, setUser] = useState({});
  const navigate = useNavigate();

  useEffect(() => {
    async function getUserData() {
      await supabase.auth.getUser().then((value) => {
        if (value.data?.user) {
          console.log(value.data.user);
          setUser(value.data.user);
        }
      });
    }
    getUserData();
  }, []);

  async function signOutUser() {
    const { error } = await supabase.auth.signOut();
    navigate("/login");
  }
  return (
    <h1 style={{ color: "red" }}>
      {Object.keys(user) !== 0 && (
        <>
          success!!
          <button onClick={() => signOutUser()}>signOut</button>
        </>
      )}
    </h1>
  );
};

export default SuccessPage;

 

완성된 모습

https://localhost:3000

 

https://localhost:3000/success

 

해당 코드는 가공시켜 프로젝트에 적용시켰음.