ABOUT ME

-

  • Rust: 카카오 소셜 로그인 하기 (JWT, actix-rs, react.js)
    컴퓨터/Rust 2023. 7. 10. 18:55
    728x90
    반응형

    mongodb 유저 정보
    카카오톡 프로필 이미지

     

    소개: 이 글에서는 React.js 프트엔드에서 간단하게 카카오 로그인을 구현한 예제이다.

    프론트만으로 해결하려 했으나 redirect 등의 문제로 백엔드를 Rust로 구현하게 되었다. (React.js + Actix-rs)

    왜냐하면 vercel로 배포했기 때문에 프론트만 업로드 되어있다...프론트+백엔드 같이 배포하면 더 쉬워진다.

    백엔드는 fly.io와 같은 사이트를 이용해서 퍼블릭 주소로 서비스 해준다.

     

    일단 https://developers.kakao.com/console/app 에서 앱을 만들고 카카오 로그인 기능을 켜준다.

    그다음 아래에 REDIRECT URI에 개발 홈페이지 주소를 적어준다.

     

    https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api

     

    Kakao Developers

    카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

    developers.kakao.com

     

    React.js

    프론트엔드에서는 일단 카카오 REST 키를 이용해서 할 것이다. 다른 방법들도 있으니 그건 인터넷 참고

    아바타 버튼을 누르면 handleLoginOpen 함수를 불러서 rest api 키와 리다이렉트 경로를 지정해서 넘어가준다.

    일단 넘어가줘야 한다. 그냥 fetch로 하면 OAuth2 방식이므로 잘 안된다.

    javascript
    const handleLoginOpen = () => { const rest_api_key = process.env.REACT_APP_KAKAO_REST_KEY; //REST API KEY const redirect_uri = "http://나의 리다이렉트 경로/oauth"; //Redirect URI // oauth 요청 URL const kakaoURL = `https://kauth.kakao.com/oauth/authorize?client_id=${rest_api_key}&redirect_uri=${redirect_uri}&response_type=code`; window.location.href = kakaoURL; // redirect user to Kakao login page }; ... <Link to="#" onClick={handleLoginOpen}> <Avatar /> </Link>

     

    리다이렉트 경로는 백엔드를 연결했다. (로그인 -> 백엔드에서 -> access_token 얻기 -> 유저 정보 얻기 -> mongodb에 유저 정보 저장하기 -> JWT 토큰 만들어서 프론트 주소로 재경로 -> 프론트에서 oauth?token=... 데이터를 로컬에 저장)

     

    Rust

    몽고DB에 연결하고 kauth.kakao.com에 맞는 파라미터를 만들고 요청하고 JWT 토큰을 만드는 그러한 코드이다.

    rust
    #[get("/oauth")] async fn handle_auth(conn: web::Data<DbPool>, data: web::Query<AuthRequest>) -> impl Responder { let code = &data.code; println!("{}", code); let users = conn .lock() .unwrap() .database("libra") .collection::<User>("users"); // Here, use the code to request an access token from Kakao API let client = reqwest::Client::new(); let params = [ ("grant_type", "authorization_code"), ("client_id", KAKAO_REST_KEY), ("redirect_uri", REDIRECT_URI), ("code", &code), ]; let res = client .post("https://kauth.kakao.com/oauth/token") .header( "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8", ) .form(&params) .send() .await .unwrap(); // let text = res.text().await.unwrap(); // println!("Response: {}", text); let kakao_response: KakaoResponse = res.json().await.unwrap(); let access_token = kakao_response.access_token; println!("Access token: {}", access_token); // Here, use the access token to request user information from Kakao API let res = client .get("https://kapi.kakao.com/v2/user/me") .bearer_auth(&access_token) .send() .await .unwrap(); // let text = res.text().await.unwrap(); // println!("Response2: {}", text); let user_info: User = res.json().await.unwrap(); // create a new user document let user_doc = doc! { "$set": { "access_token": &access_token, // add other fields as needed } }; // update the user document in the collection, or insert if it does not exist users .update_one( doc! { "id": &user_info.id, "connected_at": &user_info.connected_at }, user_doc, UpdateOptions::builder().upsert(true).build(), ) .await .unwrap(); // Here, generate a JWT and send it to the front end let my_claims = Claims { sub: user_info.id, // user identifier exp: (SystemTime::now() + Duration::from_secs(kakao_response.refresh_token_expires_in as u64)) // expires .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs() as usize, }; let key = EncodingKey::from_secret(SECRET_KEY.as_ref()); let token: String = encode(&Header::default(), &my_claims, &key).unwrap(); // HttpResponse::Ok().json(json!({ "token": token })) let redirect_url = format!("http:// 프론트 경로/oauth?token={}", token); // redirect the client to the URL HttpResponse::Found() .append_header(("Location", redirect_url)) .finish() }

     

    React.js

    react-router-dom v6+ 기준으로 oauth 링크를 만들어 주었다. (백엔드에서 프론트주소/oauth?token=... 으로 이동하기에)

    oauth로 하면 catch all이다, regex 표현으로 저기에 대충 맞으면 다 저기로 넘어간다.

     

    App.js

    javascript
    import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import Home from "./pages/Home/Home"; import HandleAuth from "./pages/HandleAuth/HandleAuth"; import SearchResult from "./pages/SearchResult/SearchResult"; import "./App.css"; function App() { return ( <div className="app"> <Router> <Routes> {/* Home (the one with the search on) */} <Route path="/" element={<Home />} /> {/* SearchPage (The results page) */} <Route path="search" element={<SearchResult />} /> <Route path="oauth" element={<HandleAuth />} /> </Routes> </Router> </div> ); } export default App;

     

    HandleAuth.js

    oauth에서 로컬에 저장하고 홈 (/)으로 이동한다.

    javascript
    import { useEffect } from "react"; import { useNavigate, useLocation } from "react-router-dom"; const HandleAuth = () => { const location = useLocation(); const navigate = useNavigate(); useEffect(() => { // extract the token from the URL const queryParams = new URLSearchParams(location.search); const token = queryParams.get("token"); if (token) { // save the token in local storage localStorage.setItem("jwt", token); console.log(token); // redirect the user to the Home page navigate("/"); } else { console.error("No token in URL"); } }, [location, navigate]); return null; // or you can return a loading spinner or a message while the token is being fetched }; export default HandleAuth;

     

    끝이다.

    일단 kauth 페이지들은 CORS가 열려있어서 모든 호출이 가능하고 (credentials이 필요 없다는 이야기)

    인가요청은 카카오 측 페이지로 이동하고 리다이렉트 해서 AJAX 방식 (fetch()...)로 하면 안 된다.

    (로그인 이후 -> 리다이렉트 URI에서 token 조회는 fetch로 가능)

    JS SDK의 Kakao.Auth.login 방식은 팝업을 띄우고 팝업 내 통신으로 별도 토큰 요청 없이, 토큰까지 리턴 받는 방식이 있는데 그건 SDK를 참고한다.

     

    728x90

    댓글