몇년전만 하더라도 특정 웹 앱의 서비스를 이용하기 위해선 해당 웹 앱에 회원가입을 하는 것이 우선이었습니다. 하지만 소셜 로그인이 보편화된 현재는 대부분의 사람들이 네이버 또는 카카오에 이미 가입된 계정을 이용해 빠르게 서비스에 가입하는 것을 택하고 있습니다.
오오쓰는 직접 서버에서 인증과 관련된 로직을 처리할 필요없이 인증을 중개하는 외부 서버를 이용한 기술입니다. 보안된 리소스에 액세스하기 위해 클라이언트에게 권한을 제공하는 프로세스를 단순화하는 프로토콜입니다.
웹이나 앱에서 흔히 찾아볼 수 있는 소셜 로그인 인증 방식은 OAuth 2.0라는 기술을 바탕으로 구현됩니다.
Resorce Owner
사용자 및 정보 제공자
Client
리소스 오너를 대신하여 보호된 리소스에 엑세스하는 어플리케이션
Local Server
클라이언트의 요청을 수락하고 응답할 수 있는 서버
Resorce Server
사용자의 정보를 저장하고 있는 서버
Authorization Server
Access Token을 발급하며 인증을 담당하는 서버
Authorization Grant
클라이언트가 Access Token을 발급받는 방법
Authorization Code Grant Type
| Refresh Token Grant Type
Authorization Code
Access Token을 발급받기 위한 Code
Access Token
보호된 리소스에 액세스하는 데 사용되는 인증 토큰, Resource Server에 접근 가능
Refresh Token
발급받은 Access Token이 만료될 때 갱신하는 토큰
Authorization Code를 받아 Authorization Code를 통해 Access Token을 받는 방식을 말합니다. 이 유형은 Access Token이 사용자나 브라우저에 표시되지 않는다는 것을 의미하므로 Access Token이 다른 사람에게 누출될 위험이 줄어듭니다.
Access Token을 발급받은 후 Access Token이 만료된 경우 Refresh Token을 활용해 새로운 Access Token으로 교환하는 데 사용됩니다. 이를 통해 사용자와의 추가 상호 작용 없이 계속 유효한 액세스 토큰을 가질 수 있습니다.
사용자 인증은 개발자에게 친숙한 Github을 이용합니다. 이를 통해 외부 인증 서버(Github)에게 Access Token을 받아오고 다시 유저의 인증 정보를 요청하는 플로우를 구현합니다.
REACT_APP_CLIENT_ID= 유저 아이디 정보
import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Login from "./pages/Login";
import Mypage from "./pages/Mypage";
import { useEffect, useState } from "react";
import axios from "axios";
function App() {
const [isLogin, setIsLogin] = useState(false);
const [accessToken, setAccessToken] = useState("");
const getAccessToken = async (authorizationCode) => {
const result = await axios.post("https://localhost:4000/callback", {
authorizationCode,
});
const { accessToken } = result.data;
try {
setIsLogin(true);
setAccessToken(accessToken);
} catch (err) {
alert(err);
}
};
useEffect(() => {
const url = new URL(window.location.href);
const authorizationCode = url.searchParams.get("code");
if (authorizationCode) {
getAccessToken(authorizationCode);
}
}, []);
return (
<BrowserRouter>
<div className="main">
<div className="container">
<Routes>
<Route
path="/"
element={
isLogin ? (
<Mypage accessToken={accessToken} setIsLogin={setIsLogin} />
) : (
<Login />
)
}
/>
</Routes>
</div>
</div>
</BrowserRouter>
);
}
export default App;
const CLIENT_ID = process.env.REACT_APP_CLIENT_ID;
import axios from "axios";
import React, { useEffect, useState } from "react";
import Loading from "./components/Loading";
import User from "./components/UserInfo";
export default function Mypage({ accessToken, setIsLogin }) {
const [githubUser, setGithubUser] = useState(null);
const [serverResource, setServerResource] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const logoutHandler = () => {
// TODO: /logout을 통해 사용자가 로그아웃되도록 구현하세요.
axios
.delete("https://localhost:4000/logout", {
data: { accessToken },
})
.then((res) => setIsLogin(false))
.catch((e) => alert(e.response.stateusText));
};
useEffect(() => {
// TODO: /userinfo를 통해 사용자 정보를 받아오세요.
axios
.post("https://localhost:4000/userinfo", { accessToken })
.then((res) => {
const { githubUserData, serverResource } = res.data;
setGithubUser(githubUserData);
setServerResource(serverResource);
setIsLoading((state) => !state);
})
.catch((e) => alert(e.response.stateusText));
}, [accessToken]);
return (
<>
<div className="left-box">
{!isLoading && (
<span>
{`${githubUser.login}`}님,
<p>반갑습니다!</p>
</span>
)}
</div>
<div className="right-box">
<div className="input-field">
{isLoading ? (
<Loading />
) : (
<User
githubUser={githubUser}
serverResource={serverResource}
logoutHandler={logoutHandler}
/>
)}
</div>
</div>
</>
);
}