OAuth 2.0

이성은·2023년 1월 17일
0
post-custom-banner

OAuth

  • 웹이나 앱에서 흔히 찾아볼 수 있는 소셜 로그인 인증 방식은 OAuth 2.0라는 기술을 바탕으로 구현
  • 전통적으로 직접 작성한 서버에서 인증을 처리해주는 것과는 달리, OAuth는 인증을 중개해주는 메커니즘
  • 보안된 리소스에 액세스하기 위해 클라이언트에게 권한을 제공하는 프로세스를 단순화하는 프로토콜
  • 즉, 이미 사용자 정보를 가지고 있는 웹 서비스에서 사용자의 인증을 대신해주고, 접근 권한에 대한 토큰을 발급한 후, 이를 이용해 내 서버에서 인증이 가능해짐
  • 인증을 다른 서비스에 맡길 뿐, 접근 권한 관리(Authorization)는 순전히 내 서버의 몫

소셜 로그인 로직

// .env
REACT_APP_CLIENT_ID=5d885f7ca3233abcf359

// Login.js
import React from "react";
import githubLogo from "./../images/github.png";

export default function Login() {
	const CLIENT_ID = process.env.REACT_APP_CLIENT_ID;

	const loginRequestHandler = () => {
		return window.location.assign(`https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}`);
	};

	return (
		<>
			<div className="left-box">
				<span>
					Education
					<p>for the</p>
					Real World
				</span>
			</div>
			<div className="right-box">
				<div className="right-box">
					<h1>AUTH STATES</h1>
					<h3>OAuth 2.0 소셜 로그인</h3>
					<form onSubmit={(e) => e.preventDefault()}>
						<div className="input-field">
							<button type="submit" onClick={loginRequestHandler}>
								<img id="logo" alt="logo" src={githubLogo} />
								<span> LOGIN WITH GITHUB</span>
							</button>
						</div>
					</form>
				</div>
			</div>
		</>
	);
}

// App.js
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) => {
		axios
			.post("https://localhost:4000/callback", {authorizationCode})
			.then((res) => {
				setAccessToken(res.data.accessToken);
				setIsLogin(true);
			})
			.catch((err) => {
				console.log(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} setAccessToken={setAccessToken} /> : <Login />}
						/>
					</Routes>
				</div>
			</div>
		</BrowserRouter>
	);
}

export default App;

// Mypage.js
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, setAccessToken}) {
	const [githubUser, setGithubUser] = useState(null);
	const [serverResource, setServerResource] = useState(null);
	const [isLoading, setIsLoading] = useState(true);

	const logoutHandler = () => {
		axios
			.delete("https://localhost:4000/logout", {data: {accessToken}})
			.then((res) => {
				setGithubUser(null);
				setServerResource(null);
				setIsLogin(false);
				setAccessToken("");
			})
			.catch((err) => console.log(err));
	};

	useEffect(() => {
		axios
			.post("https://localhost:4000/userinfo", {accessToken})
			.then((res) => {
				const {githubUserData, serverResource} = res.data;
				setGithubUser(githubUserData);
				setServerResource(serverResource);
				setIsLoading(false);
			})
			.catch((err) => console.log(err));
	}, []);

	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>
		</>
	);
}

// server/index.js
const express = require('express');
const cors = require('cors');
const logger = require('morgan');
const fs = require('fs');
const https = require('https');
const controllers = require('./controllers');
const app = express();

const HTTPS_PORT = process.env.HTTPS_PORT || 4000;
//mkcert에서 발급한 인증서를 사용하기 위한 코드
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

const corsOptions = {
  origin: true,
  methods: ['GET', 'POST', 'OPTIONS', 'DELETE'],
  credentials: true,
};

app.use(cors(corsOptions));

app.post('/callback', controllers.callback);
app.post('/userinfo', controllers.userInfo);
app.delete('/logout', controllers.logout);

let server;
if (fs.existsSync('./key.pem') && fs.existsSync('./cert.pem')) {
  const privateKey = fs.readFileSync(__dirname + '/key.pem', 'utf8');
  const certificate = fs.readFileSync(__dirname + '/cert.pem', 'utf8');
  const credentials = {
    key: privateKey,
    cert: certificate,
  };

  server = https.createServer(credentials, app);
  server.listen(HTTPS_PORT, () => console.log(`🚀 HTTPS Server is starting on ${HTTPS_PORT}`));
} else {
  server = app.listen(HTTPS_PORT, () => console.log(`🚀 HTTP Server is starting on ${HTTPS_PORT}`));
}
module.exports = server;

// server/callback.js
require("dotenv").config();
const axios = require("axios");
const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET;

module.exports = async (req, res) => {
	try {
		const result = await axios({
			method: "post",
			url: `https://github.com/login/oauth/access_token`,
			headers: {
				accept: "application/json",
			},
			data: {
				client_id: CLIENT_ID,
				client_secret: CLIENT_SECRET,
				code: req.body.authorizationCode,
			},
		});
		const accessToken = result.data.access_token;

		return res.status(200).send({accessToken});
	} catch (err) {
		return res.status(401).send({message: "error"});
	}
};

// server/userInfo.js
const axios = require("axios");
const serverResource = require("../../data/data.js");

module.exports = async (req, res) => {
	const {accessToken} = req.body;
	return axios
		.get("https://api.github.com/user", {
			headers: {
				Authorization: `token ${accessToken}`,
			},
		})
		.then((res) => res.data)
		.then((githubUserData) => {
			res.send({githubUserData, serverResource});
		})
		.catch((e) => {
			res.sendStatus(403);
		});
};

// server/logout.js
require("dotenv").config();
const axios = require("axios");
const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET;

module.exports = (req, res) => {
	const {accessToken} = req.body;
	axios
		.delete(`https://api.github.com/applications/${CLIENT_ID}/token`, {
			data: {
				access_token: accessToken,
			},
			auth: {
				username: CLIENT_ID,
				password: CLIENT_SECRET,
			},
		})
		.then(() => {
			res.status(205).send("Successfuly Logged Out");
		})
		.catch((e) => {
			console.log(e.response);
		});
};
profile
함께 일하는 프론트엔드 개발자 이성은입니다🐥
post-custom-banner

0개의 댓글