리액트 앱에서 Parse와 Google 로그인 통합 가이드
리액트 구글 로그인
소개
지난 튜토리얼에서는 Parse.User 클래스를 사용하여 앱에 사용자 로그인/로그아웃 기능을 추가했습니다. 이제 구글 로그인 기능을 사용하여 구글에서 사용자 데이터를 가져오고, 로그인하거나 가입하거나 기존 사용자와 연결하는 방법을 배울 것입니다. 이를 위해 react-google-login 라이브러리를 설치하고 구성할 것입니다.
Parse.User.linkWith 메서드는 각기 다른 제공자가 요청하는 올바른 매개변수만 전달하면 모든 서드파티 인증 방법을 사용하여 사용자를 가입시키고 로그인하는 역할을 합니다. 사용자 데이터를 새 또는 기존 Parse.User에 연결한 후, Parse는 장치에 유효한 사용자 세션을 저장합니다. 이후 current 과 같은 메서드를 호출하면 일반 로그인과 마찬가지로 사용자 데이터를 성공적으로 가져올 수 있습니다.
전제 조건
이 튜토리얼을 완료하려면 다음이 필요합니다:
목표
리액트 앱에서 구글 로그인을 사용하여 사용자 로그인 기능을 구축하는 것입니다.
1 - 의존성 설치
React에서 Google 로그인을 활성화하는 가장 인기 있는 방법은 react-google-login을 사용하는 것입니다. 공식 문서를 따라 설정하세요.
Google 자격 증명 페이지에서 OAuth clientId 를 생성하고, 일반적으로 http://localhost:3000을 허용된 URL에 추가하여 로컬 개발 주소를 설정하세요.
2 - Parse와 함께 Google 로그인을 사용하기
이제 Google Sign-in 인증 모달을 호출할 때 응답을 처리할 새로운 메서드를 UserLogIn 컴포넌트 안에 생성해 보겠습니다. 사용자가 Google로 로그인하면 이 호출은 Google에서 사용자 데이터를 가져오고, id, idToken, 및 Google 이메일을 저장해야 합니다. 그런 다음 이 함수는 Parse.User.linkWith 메서드와 이러한 자격 증명을 사용하여 Parse에 로그인하려고 시도합니다. 사용자가 이미 이 Google 인증을 사용하여 가입한 경우, linkWith는 기존 계정을 사용하여 그를 로그인시킵니다.
1 const handleGoogleLoginLoginResponse = async function(response: any): Promise<boolean> {
2 // Check if response has an error
3 if (response.error !== undefined) {
4 console.log(`Error: ${response.error}`);
5 return false;
6 } else {
7 try {
8 // Gather Google user info
9 const userGoogleId: string = response.googleId;
10 const userTokenId: string = response.tokenId;
11 const userEmail: string = response.profileObj.email;
12 // Try to login on Parse using linkWith and these credentials
13 // Create a new Parse.User object
14 const userToLogin: Parse.User = new Parse.User();
15 // Set username and email to match google email
16 userToLogin.set('username', userEmail);
17 userToLogin.set('email', userEmail);
18 try {
19 let loggedInUser: Parse.User = await userToLogin
20 .linkWith('google', {
21 authData: {id: userGoogleId, id_token: userTokenId},
22 });
23 // logIn returns the corresponding ParseUser object
24 alert(
25 `Success! User ${loggedInUser.get('username')} has successfully signed in!`,
26 );
27 // Update state variable holding current user
28 getCurrentUser();
29 return true;
30 } catch (error: any) {
31 // Error can be caused by wrong parameters or lack of Internet connection
32 alert(`Error! ${error.message}`);
33 return false;
34 }
35 } catch (error: any) {
36 console.log("Error gathering Google user info, please try again!")
37 return false;
38 }
39 }
40 }그 후, react-google-login GoogleLogin 컴포넌트를 사용하여 Google SignIn 모달을 호출하고, 이를 JSX 코드에 추가해야 합니다. Google의 기본 스타일을 사용하거나 이 가이드에서 따르는 방식처럼 사용자 정의 스타일을 만들 수 있습니다. 다음은 전체 UserLogin 컴포넌트 코드입니다. react-google-login 버튼과 그것이 이전에 생성된 모달 응답 메서드와 어떻게 연결되어 있는지 주목하세요.
1 import React, { useState, FC, ReactElement } from 'react';
2 import './App.css';
3 import { Button, Divider, Input } from 'antd';
4 import GoogleLogin from 'react-google-login';
5 const Parse = require('parse/dist/parse.min.js');
6
7 export const UserLogin: FC<{}> = (): ReactElement => {
8 // State variables
9 const [username, setUsername] = useState('');
10 const [password, setPassword] = useState('');
11 const [currentUser, setCurrentUser] = useState<Parse.Object | null>(null);
12
13 const doUserLogIn = async function (): Promise<boolean> {
14 // Note that these values come from state variables that we've declared before
15 const usernameValue: string = username;
16 const passwordValue: string = password;
17 try {
18 const loggedInUser: Parse.User = await Parse.User.logIn(usernameValue, passwordValue);
19 // logIn returns the corresponding ParseUser object
20 alert(
21 `Success! User ${loggedInUser.get('username')} has successfully signed in!`,
22 );
23 // To verify that this is in fact the current user, `current` can be used
24 const currentUser: Parse.User = await Parse.User.current();
25 console.log(loggedInUser === currentUser);
26 // Clear input fields
27 setUsername('');
28 setPassword('');
29 // Update state variable holding current user
30 getCurrentUser();
31 return true;
32 } catch (error: any) {
33 // Error can be caused by wrong parameters or lack of Internet connection
34 alert(`Error! ${error.message}`);
35 return false;
36 }
37 };
38
39 const doUserLogOut = async function (): Promise<boolean> {
40 try {
41 await Parse.User.logOut();
42 // To verify that current user is now empty, currentAsync can be used
43 const currentUser: Parse.User = await Parse.User.current();
44 if (currentUser === null) {
45 alert('Success! No user is logged in anymore!');
46 }
47 // Update state variable holding current user
48 getCurrentUser();
49 return true;
50 } catch (error: any) {
51 alert(`Error! ${error.message}`);
52 return false;
53 }
54 };
55
56 // Function that will return current user and also update current username
57 const getCurrentUser = async function (): Promise<Parse.User | null> {
58 const currentUser: (Parse.User | null) = await Parse.User.current();
59 // Update state variable holding current user
60 setCurrentUser(currentUser);
61 return currentUser;
62 }
63
64 const handleGoogleLoginLoginResponse = async function(response: any): Promise<boolean> {
65 // Check if response has an error
66 if (response.error !== undefined) {
67 console.log(`Error: ${response.error}`);
68 return false;
69 } else {
70 try {
71 // Gather Google user info
72 const userGoogleId: string = response.googleId;
73 const userTokenId: string = response.tokenId;
74 const userEmail: string = response.profileObj.email;
75 // Try to login on Parse using linkWith and these credentials
76 // Create a new Parse.User object
77 const userToLogin: Parse.User = new Parse.User();
78 // Set username and email to match google email
79 userToLogin.set('username', userEmail);
80 userToLogin.set('email', userEmail);
81 try {
82 let loggedInUser: Parse.User = await userToLogin
83 .linkWith('google', {
84 authData: {id: userGoogleId, id_token: userTokenId},
85 });
86 // logIn returns the corresponding ParseUser object
87 alert(
88 `Success! User ${loggedInUser.get('username')} has successfully signed in!`,
89 );
90 // Update state variable holding current user
91 getCurrentUser();
92 return true;
93 } catch (error: any) {
94 // Error can be caused by wrong parameters or lack of Internet connection
95 alert(`Error! ${error.message}`);
96 return false;
97 }
98 } catch (error: any) {
99 console.log("Error gathering Google user info, please try again!")
100 return false;
101 }
102 }
103 }
104
105 return (
106 <div>
107 <div className="header">
108 <img
109 className="header_logo"
110 alt="Back4App Logo"
111 src={
112 'https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png'
113 }
114 />
115 <p className="header_text_bold">{'React on Back4App'}</p>
116 <p className="header_text">{'User Login'}</p>
117 </div>
118 {currentUser === null && (
119 <div className="container">
120 <h2 className="heading">{'User Login'}</h2>
121 <Divider />
122 <div className="form_wrapper">
123 <Input
124 value={username}
125 onChange={(event) => setUsername(event.target.value)}
126 placeholder="Username"
127 size="large"
128 className="form_input"
129 />
130 <Input
131 value={password}
132 onChange={(event) => setPassword(event.target.value)}
133 placeholder="Password"
134 size="large"
135 type="password"
136 className="form_input"
137 />
138 </div>
139 <div className="form_buttons">
140 <Button
141 onClick={() => doUserLogIn()}
142 type="primary"
143 className="form_button"
144 color={'#208AEC'}
145 size="large"
146 block
147 >
148 Log In
149 </Button>
150 </div>
151 <Divider />
152 <div className="login-social">
153 <div className="login-social-item login-social-item--facebook">
154 <img className="login-social-item__image" src={'https://findicons.com/files/icons/2830/clean_social_icons/250/facebook.png'} alt=""/>
155 </div>
156 <GoogleLogin
157 clientId="108490793456-0flm4qh8ek4cb4krt7e06980o4sjvado.apps.googleusercontent.com"
158 render={renderProps => (
159 <div className="login-social-item">
160 <img onClick={renderProps.onClick} className="login-social-item__image" src={'data:image/png;base64,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'} alt=""/>
161 </div>
162 )}
163 buttonText="Login"
164 onSuccess={handleGoogleLoginLoginResponse}
165 onFailure={handleGoogleLoginLoginResponse}
166 cookiePolicy={'single_host_origin'}
167 />
168 <div className="login-social-item">
169 <img className="login-social-item__image" src={'data:image/png;base64,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'} alt=""/>
170 </div>
171 </div>
172 <p className="form__hint">Don't have an account? <a className="form__link" href="#">Sign up</a></p>
173 </div>
174 )}
175 {currentUser !== null && (
176 <div className="container">
177 <h2 className="heading">{'User Screen'}</h2>
178 <Divider />
179 <h2 className="heading">{`Hello ${currentUser.get('username')}!`}</h2>
180 <div className="form_buttons">
181 <Button
182 onClick={() => doUserLogOut()}
183 type="primary"
184 className="form_button"
185 color={'#208AEC'}
186 size="large"
187 block
188 >
189 Log Out
190 </Button>
191 </div>
192 </div>
193 )}
194 </div>
195 );
196 };이 클래스를 App.css 파일에 추가하면 이 컴포넌트의 레이아웃을 완전히 렌더링할 수 있습니다.
1 @import '~antd/dist/antd.css';
2
3 .App {
4 text-align: center;
5 }
6
7 html {
8 box-sizing: border-box;
9 outline: none;
10 overflow: auto;
11 }
12
13 *,
14 *:before,
15 *:after {
16 margin: 0;
17 padding: 0;
18 box-sizing: inherit;
19 }
20
21 h1,
22 h2,
23 h3,
24 h4,
25 h5,
26 h6 {
27 margin: 0;
28 font-weight: bold;
29 }
30
31 p {
32 margin: 0;
33 }
34
35 body {
36 margin: 0;
37 background-color: #fff;
38 }
39
40 .container {
41 width: 100%;
42 max-width: 900px;
43 margin: auto;
44 padding: 20px 0;
45 text-align: left;
46 }
47
48 .header {
49 align-items: center;
50 padding: 25px 0;
51 background-color: #208AEC;
52 }
53
54 .header_logo {
55 height: 55px;
56 margin-bottom: 20px;
57 object-fit: contain;
58 }
59
60 .header_text_bold {
61 margin-bottom: 3px;
62 color: rgba(255, 255, 255, 0.9);
63 font-size: 16px;
64 font-weight: bold;
65 }
66
67 .header_text {
68 color: rgba(255, 255, 255, 0.9);
69 font-size: 15px;
70 }
71
72 .heading {
73 font-size: 22px;
74 }
75
76 .flex {
77 display: flex;
78 }
79
80 .flex_between {
81 display: flex;
82 justify-content: space-between;
83 }
84
85 .flex_child {
86 flex: 0 0 45%;
87 }
88
89 .heading_button {
90 margin-left: 12px;
91 }
92
93 .list_item {
94 padding-bottom: 15px;
95 margin-bottom: 15px;
96 border-bottom: 1px solid rgba(0, 0, 0, 0.06);
97 text-align: left;
98 }
99
100 .list_item_title {
101 color: rgba(0, 0, 0, 0.87);
102 font-size: 17px;
103 }
104
105 .list_item_description {
106 color: rgba(0, 0, 0, 0.5);
107 font-size: 15px;
108 }
109
110 .form_input {
111 margin-bottom: 20px;
112 }
113
114 .login-social {
115 display: flex;
116 justify-content: center;
117 margin-bottom: 30px;
118 }
119
120 .login-social-item {
121 width: 54px;
122 height: 54px;
123 border-radius: 54px;
124 padding: 12px;
125 margin: 0 12px;
126 border: 1px solid #e6e6e6;
127 box-shadow: 0 2px 4px #d6d6d6;
128 }
129
130 .login-social-item--facebook {
131 padding: 4px;
132 background-color: #3C5B9B;
133 }
134
135 .login-social-item__image {
136 width: 100%;
137 }
138
139 .form__hint {
140 color: rgba(0, 0, 0, 0.5);
141 font-size: 16px;
142 text-align: center;
143 }새로운 기능을 테스트해 보세요. Google에 로그인할 수 있었고 Parse linkWith 호출이 성공적이었다면, 다음과 같은 성공 메시지를 볼 수 있어야 합니다.

3 - 사용자 로그인 및 세션 생성 확인
Google 로그인이 제대로 작동했는지 확인하려면, Parse 대시보드를 확인하고 새로운 사용자 (Google 인증 데이터가 다른 사용자에 속하지 않는 경우)와 Google authData 매개변수를 포함하고 있는지 확인할 수 있습니다.

대시보드에서 유효한 세션이 생성되었는지 확인할 수도 있으며, 해당 사용자 객체에 대한 포인터를 포함하고 있습니다.

4 - 기존 사용자를 Google 로그인에 연결하기
또 다른 linkWith 가능한 사용법은 기존 사용자를 다른 인증 제공자, 이 경우 Google과 연결하는 것입니다. 다음 기능을 추가하여 linkWith를 호출합니다. 이는 UserLogIn 구성 요소에 로그인하는 것과 동일한 방식입니다. 여기서 유일한 차이점은 빈 Parse.User에서 메서드를 호출하는 대신 현재 로그인한 사용자 객체에서 사용한다는 것입니다.
1 const handleGoogleLoginLinkResponse = async function(response) {
2 // Check if response has an error
3 if (response.error !== undefined) {
4 console.log(`Error: ${response.error}`);
5 return false;
6 } else {
7 try {
8 // Gather Google user info
9 const userGoogleId = response.googleId;
10 const userTokenId = response.tokenId;
11 // Try to link current Parse user using linkWith and these credentials
12 // Get current user
13 const userToLink = await Parse.User.current();
14 try {
15 let loggedInUser = await userToLink
16 .linkWith('google', {
17 authData: {id: userGoogleId, id_token: userTokenId},
18 });
19 // logIn returns the corresponding ParseUser object
20 alert(
21 `Success! User ${loggedInUser.get(
22 'username',
23 )} has successfully linked his Google account!`,
24 );
25 // Update state variable holding current user
26 getCurrentUser();
27 return true;
28 } catch (error) {
29 // Error can be caused by wrong parameters or lack of Internet connection
30 alert(`Error! ${error.message}`);
31 return false;
32 }
33 } catch (error) {
34 console.log("Error gathering Google user info, please try again!")
35 return false;
36 }
37 }
38 }이 기능을 홈 화면의 다른 react-google-login 구성 요소에 할당하세요. 이 구성 요소는 앱에 현재 사용자가 로그인했을 때만 표시됩니다. 새로운 기능을 테스트하고, Parse.User 객체 authData 값이 새로운 인증 제공자 데이터로 업데이트될 것임을 주의하세요. 사용자가 실제로 Parse 서버 대시보드에서 업데이트되었는지 확인하세요.

결론
이 가이드의 끝에서, Google Sign-in을 사용하여 React에서 Parse 사용자에게 로그인, 가입 또는 기존 사용자를 연결하는 방법을 배웠습니다. 다음 가이드에서는 Apple Sign-in을 사용하는 방법을 보여드리겠습니다.