1️⃣ 개발 환경
frontend: React.js, JavaScript, axios
backend: Django REST, Python, django-allauth, simple-jwt
2️⃣ 오류가 발생한 상황
OAuth 기능을 개발하고 있었습니다.
구글 로그인 기능을 django-allauth 라이브러리를 이용해 구현하고 있었는데,
정상적인 상황이라면 인증에 사용할 구글 계정을 선택하는 화면이 나와야 하는데,
해당 화면 대신 아래와 같은 에러 메세지 화면이 나타났습니다.
에러 메세지를 읽어보니,
400: redirect_uri_mismatch라 합니다.
리다이렉트 URI가 바르게 설정되지 않고 있다는 것을 확인할 수 있습니다.
에러가 발생한 상황을 이해하기 위하여,
전반적인 프로그램의 로직을 우선 살펴보겠습니다.
우선, 아래와 같이 프론트엔드 단에서 로그인 화면의 가장 하단부에, 구글 계정으로 로그인 할 수 있는 버튼을 생성해 주었습니다.
버튼 디자인의 경우 "react-google-button"
라이브러리를 다운받아 사용하였습니다.
import GoogleButton from "react-google-button";
export default function Login() {
//구글로그인에 불필요한 로직 모두 생략
const handleGoogleBtnClick = (e) => {
e.preventDefault();
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?client_id=${입력}&redirect_uri=${입력}scope=email%20profile&response_type=code&state=${입력}&access_type=online&service=${입력}&flowName=GeneralOAuthFlow`;
};
return (
<>
<form onSubmit={handleSubmit}>
{/*구글로그인에 불필요한 폼 모두 생략*/}
<GoogleButton onClick={handleGoogleBtnClick} />
</form>
</>
);
}
생성된 Sign in With Google 버튼을 클릭하면,
아래의 handleGoogleBtnClick
이벤트 핸들러가 동작합니다.
const handleGoogleBtnClick = (e) => {
e.preventDefault();
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?client_id=${입력}&redirect_uri=${입력}scope=email%20profile&response_type=code&state=${입력}&access_type=online&service=${입력}&flowName=GeneralOAuthFlow`;
};
window.location.href
에 값을 할당하면, 해당 값을 가진 url로 화면을 이동시킬 수 있습니다.
저의 경우, 구글 로그인이 진행되는 url 주소를 복사하여 붙여넣었습니다.
민감한 정보는 모두 ${입력}
처리 하였습니다.
구글 로그인은 아래의 경로에서 진행됩니다.
https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?쿼리부분
쿼리 부분엔 아래의 사항들이 포함됩니다.
- client_id
- redirect_uri - 인코딩되어 입력됨
- scope - 나의 앱이 접근할 구글의 정보 범위 ex) 이메일, 프로필조회
- response_type - code(인증코드) 또는 token(엑세스 토큰)
- state - CSRF 공격 방어 목적의 랜덤 문자열
- access_type - online(세션 내에서만 권한유지) 또는 offline(로그아웃해도 권한유지)
- service - 구글 자체 파라미터
- flowName - 구글 자체 파라미터
하지만, 이것을 일일이 작성하진 않습니다.
DRF와 allauth를 사용할 경우 구글 로그인이 진행되는 엔드포인트를 확인할 수 있는 방법을 알아보겠습니다.
우선 allauth 라이브러리를 설치한 뒤,
pip install django-allauth
루트 urls.py
의 urlpatterns
에 아래의 내용을 추가해줍니다.
path('accounts/', include('allauth.urls')),
이제, 로컬호스트주소/accounts/
로 시작하는 경로로, django-allauth
라이브러리에서 기본적으로 제공하는 다양한 페이지에 접속할 수 있습니다.
그 중, 로컬호스트주소/accounts/login
엔드포인트로 접속하면,
다음과 같은 화면이 나타납니다.
하단부의 third-party 섹션에, 저희가 settings.py 파일에 작성한 인증 provider 가 잘 나타납니다.
third-party 섹션의 구글을 클릭해주면,
위와 같은 페이지로 이동됩니다.
여기서 continue버튼을 눌러줍니다.
그럼 다음과 같이 구글 계정으로 로그인하기 페이지가 등장합니다.
여기서의 url을 복사하여 프론트엔드 코드에서 구글 로그인하기 버튼 클릭 이벤트 핸들러 함수 내부의 이동 url 값으로 전달하면 됩니다.
redirect URI란, OAuth 인증을 완료하면 이동할 페이지의 주소를 의미합니다.
OAuth의 동작 원리는 아래와 같이 정리할 수 있습니다.
- 프론트엔드에서 버튼을 클릭하면 구글 Auth의 백엔드 페이지로 이동
- 백엔드 페이지에서 구글을 통해 유저 인증 진행
- 인증이 완료되면 다시 프론트엔드 주소로 돌아와야함 --> 어디로 돌아올지가 redirect URI.
즉, 구글 계정으로 로그인에 성공하면 사용자를 어떤 화면으로 이동시킬 것인지 지정해주는 공간이 redirect URI입니다.
해당 에러는, 제가 redirect_uri 관련 설정을 아무것도 해주지 않아 발생하였습니다.
OAuth를 구현하고자 한다면, 정상적으로 인증이 완료된 이후 이동할 페이지의 주소인 redirect_uri 관련 설정을 올바르게 해주어야 합니다.
해결과정은 생각보다 간단합니다.
우선, 장고의 settings.py에 아래의 내용을 추가해줍니다.
LOGIN_REDIRECT_URL = 'http://localhost:3000/auth/callback/'
여기서 주의할 점은, 구글 API console의 승인된 리디렉션 URI
부분에도 장고 백엔드의 LOGIN_REDIRECT_URL
에 넘겨준 엔드포인트와 동일한 값을 입력해야 한다는 것입니다.
이후, 구글 로그인이 정상 완료된 후 로드할 프론트엔드 페이지를 작성해야 합니다.
먼저 백엔드에서 지정한 리디렉션 주소인 auth/callback
경로에 해당하는 라우팅을 해주겠습니다. 컴포넌트는 이후 바로 생성하겠습니다.
import { BrowserRouter, Route, Routes } from "react-router-dom";
import AuthCallback from "./components/AuthCallback";
function App() {
return (
<>
<BrowserRouter>
<Routes>
{/*기타 경로 생략*/}
<Route path="/auth/callback/" element={<AuthCallback />} />
</Routes>
</BrowserRouter>
</>
);
}
export default App;
이제 리디렉션 URI로 진입하면 로드될 페이지인 AuthCallback.jsx 컴포넌트를 작성하겠습니다.
컴포넌트의 전체 코드는 아래와 같습니다.
import React, { useEffect } from "react";
import { useNavigate } from "react-router-dom";
const AuthCallback = () => {
const navigate = useNavigate();
useEffect(() => {
// URL에서 인증 정보를 추출합니다.
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get("token");
if (token) {
// 액세스 토큰을 로컬 저장소에 저장합니다.
localStorage.setItem("authToken", token);
// 홈 페이지로 리디렉션합니다.
navigate("/");
} else {
// 오류 처리
console.error("No token found");
}
}, [navigate]);
return (
<div>
<h1>Processing Authentication...</h1>
</div>
);
};
export default AuthCallback;
위의 코드는 구글 로그인이 정상 수행되면 실행됩니다.
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get("token"); // 예: 액세스 토큰
if (token) {
// 액세스 토큰을 로컬 저장소에 저장합니다.
localStorage.setItem("authToken", token);
// 홈 페이지로 리디렉션합니다.
navigate("/");
}
window.location.search
에는 url의 쿼리 부분이 담깁니다.
이를 URLSearchParams
에 전달하여 url의 쿼리 매개변수를 조작할 여러 메소드를 활용할 수 있습니다.
const token = urlParams.get("token")
구글 로그인이 성공되면, url의 쿼리 부분에 token이란 이름의 매개변수와 함께 인증된 유저의 토큰이 전달됩니다.
urlParams는 URLSearchParams클래스의 인스턴스이기에, URLSearchParams의 기능을 모두 사용하여 url의 쿼리 매개변수를 자유로이 조작할 수 있습니다.
위의 코드의 경우, get메소드를 통해 token 매개변수의 value에 해당하는 실제 토큰값을 받아와 이를 로컬스토리지에 저장하도록 하고 있습니다.
여기까지의 작업을 모두 완료하였다면,
문제 없이 구글 로그인 기능이 동작하여야 합니다.
그런데, 어쩐 일인지 계속하여 400 mismatch 에러가 발생했습니다.
에러의 세부 정보를 살펴보니 구글 로그인의 요청 redirect_uri가 http://localhost:3000/어쩌구
가 아니라, http://127.0.0.1:8000/어쩌구
로 되어있음을 확인할 수 있습니다.
현재 Google API Console의 승인된 리디렉션 URI에는 http://127.0.0.1:8000/어쩌구
가 없기 때문에, 400: rediret_uri_mismatch 에러가 발생한 것입니다.
Google API Console의 승인된 리디렉션 URI에 해당 uri를 추가해주면 간단히 해결할 수 있습니다.
이제 모든 에러가 사라졌습니다.
400: redirect_uri_mismatch 에러가 발생하면 아래의 두 가지를 점검해볼 수 있습니다.
django-allauth의 초기 세팅 및 프론트엔드와 연동하기까지의 모든 과정은 별도의 포스팅으로 다뤄볼 예정입니다.
감사합니다.