OAuth

viroovr·2024년 10월 16일

웹 커뮤니티를 개발하면서 사용자의 편의성을 위해 소셜 로그인기능을 추가한 경험이 있다.
Django프레임 에서 allauth 라이브러리를 이용해 구현했지만, 따라하는 수준에 그쳐 더 배우지 못해 아쉬움이 남았다.

한번 알아보자.

OAuth는 주로 소셜 로그인과 같은 경우에 사용되는 권한 위임 프로토콜이다. 사용자가 특정 웹사이트에서 다른 웹사이트의 사용자 정보를 이용하고자 할 때, OAuth를 통해 안전하게 인증과 인가를 수행할 수 있다.

원본 사이트가 다른 웹사이트의 로그인 정보를 알지 않아도 액세스 토큰으로 안전하고 편리하게 사용자 인증을 할 수 있다는 장점이 있다.

다음과 같은 네가지 주체가 존재한다.
1. 리소스 소유자: 사용자를 의미하며, 자신의 자원에 대한 접근 권한을 제어한다.
2. 클라이언트: 리소스 소유자의 정보를 필요로 하는 애플리케이션이다. 예를 들어, 소셜 미디어 애플리케이션이 있다.
3. 리소스 서버: 보호된 자원을 저정하고 있는 서버로, 일반적으로 API를 통해 접근한다.
4. 인가 서버: 사용자의 인증을 담당하고 액세스 토큰을 발급한다.

작동 방식

  1. 사용자 인증: 클라이언트가 리소스 소유자에게 리소스에 대한 접근 권한을 요청한다. 이때 사용자는 인가를 위해 인가 서버로 리디렉션된다.
  2. 인가 부여: 사용자는 인가 서버에서 클라이언트의 접근 요청을 승인한다. 이 과정에서 사용자는 로그인할 수도 있다.
  3. 액세스 토큰 발급: 인가 서버는 클라이언트에게 액세스 토큰을 발급한다. 이 토큰은 클라이언트가 리소스 서버에 요청할 때 사용된다.
  4. 리소스 요청: 클라이언트는 발급받은 액세스 토큰을 사용하여 리소스 서버에 자원 요청을 보낸다.
  5. 자원 접근: 리소스 서버는 액세스 토큰을 검증하고, 유효한 경우 요청된 자원을 클라이언트에 반환한다.

간단히 말해, 클라이언트는 리소스 소유자의 인가를 받고 인가 서버에게서 액세스 토큰을 받는다. 그리고 리소스 서버를 통해 자원에 접근할 때 액세스토큰을 제시해 인증을 받고 리소스에대한 접근 권한을 얻는 것이다.

클라이언트에 대한 신뢰보다 더 신뢰있는 웹사이트에 자원을 저장함으로써 리소스 소유자는 민감정보를 보호할 수 있으며 클라이언트도 민감정보를 저장하고 관리하는 비용을 줄일 수 있다.

한번 OAuth2.0에 대한 간단한 소셜 로그인 예제를 실습해보자.

사전 준비

  1. Google Cloud Console계정을 생성하고 로그인한다.
  2. 새로운 프로젝트를 생성한다.
  3. OAuth 2.0 클라이언트 ID 만들기:
  • 리디렉션 URI를 추가한다.
  • 클라이언트 ID와 클라이언트 시크릿을 기록해 둔다.

1. 사용자 인증 요청

사용자가 애플리케이션에 로그인하려고 할 때, 아래와 같은 URL로 리디렉션된다.

https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=MY_CLIENT_ID&redirect_url=MY_REDIRECT_URL&scope=SCOPE&state=STATE
  • MY_CLIENT_ID: Google Cloud Console에서 발급받은 클라이언트 ID
  • MY_REDIRECT_URL: 등록한 리디렉션 URL
  • SCOPE: 요청할 권한 범위(예: email, profile)
  • STATE: CSRF 공격 방지를 위한 임의의 문자열

2. 인가코드 받기

사용자가 권한을 승인하면, 인가 서버는 리디렉션 URI로 돌아가며 인가 코드를 전달한다. 이 코드를 서버에서 처리한다.

3. 액세스 토큰 요청

서버 측에서 인가코드를 사용해 액세스 토큰을 요청한다. 다음과 같은 POST요청을 보낸다.

POST https://oauth2.googleapis.com/token

요청 본문 (application/x-www-form-urlencoded):

code=AUTHORIZATION_CODE
&client_id=MY_CLIENT_ID
&client_secret=MY_CLIENT_SECRET
&redirect_url=MY_REDIRECT_URI
&grant_type=authorization_code

해당 요청이 성공하면, 응답으로 액세스 토큰과 리프레시 토큰이 반환된다.

4.자원 요청

발급받은 액세스 토큰을 사용해 Google API에 요청을 보낸다. 예를 들어, 사용자의 프로필 정보를 가져오려면 다음과 같이 GET 요청을 보낸다.

GET https://www.googleapis.com/oauth2/v2/userinfo

요청 헤더:

AUTHORIZATION: Bearer ACCESS_TOKEN

5. 결과 처리

Node.js와 Express를 사용한 간단한 예제이다. express는 Node.js 웹 애플리케이션 프레임워크로, HTTP 요청을 쉽게 처리해주기 때문에 사용했다.

const express = require('express')
const axois = require('axios')
const querystring = require('querystring')

const app = express();
const POST = 8080;

const CLIENT_ID = "MY_CLIENT_ID";
const CLIENT_SECRET = "MY_CLIENT_SECRET";
const REDIRECT_URI = "http://localhost:8080/callback";

app.get("/login", (req, res) => {
	const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=${CLIENT_ID}&redirect_url=${REDIRECT_URI}&scope=email profile&state=STATE;
    res.redirect(authUrl);
});
app.get('/callback', async (req, res) => {
	const { code } = req.query;
    
	const tokenResponse = await axois.post('https://oatuh2.googleapis.com/token', queryString.stringify({
    	code,
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
        redirect_uri: REDIRECT_URI,
        grant_type: 'authorization_code',
    }));
    
	const accessToken = tokenResponse.data.access_token;
    
	const userInfoResponse = await 	axios.get('https://www.googleapis.com/oauth2/v2/userinfo', {
    	headers: {Authorization: `Bearer ${accessToken}` }
    });
	
    res.json(userinfoResponse.data);
});
    
app.listen(PORT, () => {
    	console.log(`Server is running on http://localhost:${PORT}`);
});

간단히 예제를 설명하면 다음과 같다.

  1. /login 경로로 접근하면 Google 인증 페이지로 인가 요청과 함께 리디렉션된다.
  2. /callback은 Google 인증 후 사용자가 이 경로로 리디렉션된다. 쿼리 매개변수에서 인가코드를 추출하고 이 코드와 함께 인증에 필요한 정보들을 인가서버에 POST요청을 보낸다.
  3. accessToken을 추출하고 리소스 서버에 Google의 사용자 정보를 요청한다. 전달받은 정보를 저장한다.

/callback이란 중간 리디렉션 경로를 넣어 client와 인가 서버가 서로 만나는 장을 만드는 것 같다.

profile
성장하는 개발자

0개의 댓글