Skip to content

[Feature] 구글 로그인 구현#39

Draft
kongwoojin wants to merge 7 commits intomainfrom
feature/#38-google-login
Draft

[Feature] 구글 로그인 구현#39
kongwoojin wants to merge 7 commits intomainfrom
feature/#38-google-login

Conversation

@kongwoojin
Copy link
Member

@kongwoojin kongwoojin commented Feb 22, 2026

✨ 요약

  • 로그인 화면 구현
  • 구글 OAuth 로그인 구현
  • SecureStore를 통한 토큰 저장 로직 구현
  • svg transformer 추가
  • 에러 핸들링이 아직 없습니다

😎 해결한 이슈

Summary by CodeRabbit

  • New Features
    • Google Sign-In authentication enabling users to sign in with their Google account.
    • Secure token storage for persistent authentication.
    • Smoother sign-in redirect flow for more reliable authentication.
    • App configuration updated to support authentication services.

@kongwoojin kongwoojin self-assigned this Feb 22, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

Adds Google Sign-In integration: Expo config plugin with iOS URL scheme, Metro SVG transformer, new Google auth and backend token exchange services, secure token storage, package deps, and app initialization calling Google sign-in setup before redirecting.

Changes

Cohort / File(s) Summary
Expo config & bundler
app.config.ts, metro.config.js, package.json
Added @react-native-google-signin/google-signin plugin (iosUrlScheme from env); created Metro config to use react-native-svg-transformer; added runtime and dev dependencies.
App entry
app/index.tsx
Calls configureGoogleSignIn() during initialization and returns Redirect; previous UI wrapped in a block comment.
Google auth flow
services/auth/google.ts
Added configureGoogleSignIn() and googleSignIn() that verify Play Services, perform sign-in, and obtain idToken; handles standard sign-in errors.
Backend token exchange
services/auth/konect.ts
Added TokenResponse interface and getTokens(provider, idToken?, accessToken?, redirectUri?) which POSTs to /auth/oauth/token, returns tokens or null on failure, and optionally saves tokens.
Secure token storage
services/tokenstore.ts
Added functions saveTokens, getAccessToken, getRefreshToken, and removeTokens using expo-secure-store.

Sequence Diagram

sequenceDiagram
    participant App as App/Index
    participant Google as Google SDK
    participant Backend as Backend API
    participant Store as Secure Store

    App->>App: configureGoogleSignIn()
    App->>Google: googleSignIn()
    Google->>Google: verifyPlayServices / initiate sign-in
    Google-->>App: idToken / accessToken

    App->>Backend: POST /auth/oauth/token (provider: GOOGLE, idToken)
    Backend->>Backend: validate idToken, create access/refresh tokens
    Backend-->>App: TokenResponse { accessToken, refreshToken, redirectUrl? }

    App->>Store: saveTokens(accessToken, refreshToken)
    Store-->>App: success
    App->>App: Redirect to authenticated flow
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I hopped in with a tiny ring,
Configured keys to make things sing,
Tokens tucked in vaults so deep,
SVGs transformed while I nap and peep,
Sign-in done — now off to spring! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "[Feature] 구글 로그인 구현" clearly summarizes the main change: implementing Google login functionality, which aligns with the core objectives of adding Google OAuth sign-in integration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#38-google-login

Comment @coderabbitai help to get the list of available commands and usage tips.

@kongwoojin kongwoojin changed the title Feature/#38 google login [Feature] 구글 로그인 구현 Feb 22, 2026
@kongwoojin kongwoojin review requested due to automatic review settings February 22, 2026 03:57
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
app/index.tsx (1)

1-15: Move Google Sign-In setup into useEffect to avoid render-time side effects.

Calling configureGoogleSignIn() during render executes on every re-render (and twice in Strict Mode). Use a mount-only effect instead.

✅ Proposed change
-import { StyleSheet, Text, View, StatusBar, Pressable } from 'react-native';
+import { StyleSheet, Text, View, StatusBar, Pressable } from 'react-native';
+import { useEffect } from 'react';
@@
 export default function Index() {
-  configureGoogleSignIn();
+  useEffect(() => {
+    configureGoogleSignIn();
+  }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/index.tsx` around lines 1 - 15, The call to configureGoogleSignIn() is
done during render in the Index component causing render-time side effects; move
that setup into a mount-only effect by importing useEffect from 'react' and
invoking configureGoogleSignIn() inside a useEffect with an empty dependency
array in the Index component, and remove the direct call from the render body so
configuration only runs once on mount.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app.config.ts`:
- Around line 33-39: Add a guard in app.config.ts to validate
process.env.EXPO_PUBLIC_IOS_URL_SCHEME before configuring the
'@react-native-google-signin/google-signin' plugin: check that
EXPO_PUBLIC_IOS_URL_SCHEME is defined and non-empty, and if not either throw a
clear build-time error or skip/disable adding the plugin to the plugins array so
the misconfiguration is caught early; reference the plugins array entry for
'@react-native-google-signin/google-signin' and the iosUrlScheme property when
implementing the check.

In `@package.json`:
- Line 23: The PR added "@react-native-google-signin/google-signin" but missed
the required Expo config plugin and Metro transformer setup; add the Google
Sign-In Expo config plugin entry in app.json (providing iosUrlScheme or Firebase
config files) so prebuild recognizes native config for the package, and update
project Metro configuration to extend Expo's transformer to include
react-native-svg-transformer; ensure developers know this package cannot run in
Expo Go and must use a dev build (npx expo prebuild --clean) after adding the
plugin and metro.config.js changes.

In `@services/auth/google.ts`:
- Around line 9-15: The configureGoogleSignIn function should validate
EXPO_PUBLIC_WEB_CLIENT_ID and EXPO_PUBLIC_IOS_CLIENT_ID before calling
GoogleSignin.configure: check that process.env.EXPO_PUBLIC_WEB_CLIENT_ID (and
process.env.EXPO_PUBLIC_IOS_CLIENT_ID when running on iOS) are non-empty strings
and if not, throw or call processLogger.error (or throw new Error) with a clear
message indicating which client ID is missing; only then call
GoogleSignin.configure(...) so misconfiguration fails fast instead of causing
opaque signIn() errors. Ensure the validation references the
configureGoogleSignIn function and the GoogleSignin.configure call so the guard
is colocated with the configuration step.
- Around line 25-32: After calling GoogleSignin.signIn() and checking
isSuccessResponse(response), add an explicit guard that verifies
response.data.idToken exists before calling getTokens('google', ...); if idToken
is missing, call console.error (or process logger) with a clear message
including the full response object (e.g., "Missing idToken in Google sign-in
response") and return null instead of passing an empty string to getTokens;
update the code paths around GoogleSignin.signIn, isSuccessResponse, and
getTokens to reflect this early-guard and improved logging.

In `@services/tokenstore.ts`:
- Around line 6-9: The saveTokens function currently calls
SecureStore.setItemAsync twice (ACCESS_TOKEN_KEY and REFRESH_TOKEN_KEY) which
can leave state inconsistent if the second write fails; change saveTokens to
perform an atomic save by writing both tokens as one JSON string under a single
key (e.g., TOKENS_KEY) or implement a rollback strategy: write the second token,
and if any write rejects delete the first key to restore clean state; update
references to ACCESS_TOKEN_KEY/REFRESH_TOKEN_KEY usage accordingly (or add a
TOKENS_KEY) and ensure SecureStore.setItemAsync and SecureStore.deleteItemAsync
are used to implement the chosen approach in the saveTokens function.

---

Nitpick comments:
In `@app/index.tsx`:
- Around line 1-15: The call to configureGoogleSignIn() is done during render in
the Index component causing render-time side effects; move that setup into a
mount-only effect by importing useEffect from 'react' and invoking
configureGoogleSignIn() inside a useEffect with an empty dependency array in the
Index component, and remove the direct call from the render body so
configuration only runs once on mount.

Comment on lines 25 to 32
const response = await GoogleSignin.signIn();

if (isSuccessResponse(response)) {
return await getTokens('google', response.data.idToken ?? '');
} else {
console.log('Google Sign-In Cancelled');
return null;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n services/auth/google.ts

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 1816


🏁 Script executed:

rg -A 10 "function getTokens|const getTokens|export.*getTokens" services/auth/

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 707


🏁 Script executed:

rg "GoogleSignin|isSuccessResponse" --type ts -A 3

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 1211


🏁 Script executed:

sed -n '1,50p' services/auth/konect.ts

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 1205


🏁 Script executed:

rg "isSuccessResponse" -B 5 -A 15 --type ts

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 2116


🏁 Script executed:

rg "getTokens\(" services/auth/ -B 2 -A 2 --type ts

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 359


🏁 Script executed:

ls -la services/auth/ && cat -n services/auth/*.ts | head -100

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 3488


Add explicit guard for missing idToken and improve error logging.

After isSuccessResponse(response), the code passes an empty string to getTokens if idToken is absent. Instead of silently failing with a request to the backend, guard early and log the error explicitly:

Suggested fix
     if (isSuccessResponse(response)) {
-      return await getTokens('google', response.data.idToken ?? '');
+      const idToken = response.data.idToken;
+      if (!idToken) {
+        console.error('Google Sign-In missing idToken');
+        return null;
+      }
+      return await getTokens('google', idToken);
     } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const response = await GoogleSignin.signIn();
if (isSuccessResponse(response)) {
return await getTokens('google', response.data.idToken ?? '');
} else {
console.log('Google Sign-In Cancelled');
return null;
}
const response = await GoogleSignin.signIn();
if (isSuccessResponse(response)) {
const idToken = response.data.idToken;
if (!idToken) {
console.error('Google Sign-In missing idToken');
return null;
}
return await getTokens('google', idToken);
} else {
console.log('Google Sign-In Cancelled');
return null;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/auth/google.ts` around lines 25 - 32, After calling
GoogleSignin.signIn() and checking isSuccessResponse(response), add an explicit
guard that verifies response.data.idToken exists before calling
getTokens('google', ...); if idToken is missing, call console.error (or process
logger) with a clear message including the full response object (e.g., "Missing
idToken in Google sign-in response") and return null instead of passing an empty
string to getTokens; update the code paths around GoogleSignin.signIn,
isSuccessResponse, and getTokens to reflect this early-guard and improved
logging.

Comment on lines +6 to +9
export async function saveTokens(accessToken: string, refreshToken: string) {
await SecureStore.setItemAsync(ACCESS_TOKEN_KEY, accessToken);
await SecureStore.setItemAsync(REFRESH_TOKEN_KEY, refreshToken);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and read the tokenstore.ts file
find . -name "tokenstore.ts" -type f

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 95


🏁 Script executed:

cat -n ./services/tokenstore.ts

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 933


🏁 Script executed:

# Search for all usages of saveTokens
rg "saveTokens" -A 3 -B 1

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 931


🏁 Script executed:

cat -n ./services/auth/konect.ts | head -50

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 1513


🏁 Script executed:

# Search for usage of getAccessToken and getRefreshToken to see how tokens are used
rg "getAccessToken|getRefreshToken" -A 2 -B 1 | head -60

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 462


🏁 Script executed:

# Search for imports of these token getters
rg "from.*tokenstore" | head -20

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 138


🏁 Script executed:

# Search for SecureStore usage to see if tokens are accessed elsewhere
rg "SecureStore\." -A 1 | grep -v "^--$"

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 670


🏁 Script executed:

# Search for context, hooks, or auth state management
rg "useAuth|useToken|AuthContext|TokenContext" -l | head -20

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 53


🏁 Script executed:

# List all files in services directory
ls -la ./services/

Repository: BCSDLab/KONECT_REACT_NATIVE

Length of output: 435


🌐 Web query:

expo-secure-store setItemAsync atomicity transaction behavior

💡 Result:

expo-secure-store does not provide transactions across multiple keys. Each setItemAsync() call is an independent write of one key/value, backed by the platform’s storage primitives. (docs.expo.dev)

What “atomic” means here

Per-key write (single call)

  • iOS: setItemAsync() maps to a Keychain add-or-update: it calls SecItemAdd(...), and on duplicate it calls SecItemUpdate(...) for that key. This makes the operation effectively a single-item replace (either the new value is stored for that key or an error is thrown). (raw.githubusercontent.com)
  • Android: values are stored in a SecureStore-specific SharedPreferences file, and Expo writes using SharedPreferences.Editor.putString(...).commit() (a synchronous commit that returns success/failure). (raw.githubusercontent.com)

Multi-key “transaction”

  • There is no API to group multiple setItemAsync() calls into an all-or-nothing commit, and no isolation guarantees (another write can interleave between two of your writes). This follows from the underlying implementations: Keychain item operations and SharedPreferences edits are not exposed as a cross-item transaction by expo-secure-store. (raw.githubusercontent.com)

Concurrency / “last write wins”

  • If you call setItemAsync() concurrently for the same key, whichever write reaches the native store last will be the value you read afterward (iOS updates on duplicate; Android overwrites the stored string). (raw.githubusercontent.com)

If you need transactional behavior

  • Put the related fields into one JSON blob under a single key (so the “transaction” becomes a single per-key write), or implement an app-level scheme (e.g., write new state under a versioned key, then atomically flip a “currentVersion” pointer key). (No built-in support in SecureStore.) (docs.expo.dev)

Citations:


Implement atomic storage for access and refresh tokens.

expo-secure-store does not support transactions across multiple keys—each setItemAsync() call is independent. If the second write fails, the app ends up with only one token stored, creating an inconsistent auth state that can cause API failures on refresh.

Wrap the writes to rollback on failure:

🛡️ Suggested rollback on failure
 export async function saveTokens(accessToken: string, refreshToken: string) {
-  await SecureStore.setItemAsync(ACCESS_TOKEN_KEY, accessToken);
-  await SecureStore.setItemAsync(REFRESH_TOKEN_KEY, refreshToken);
+  try {
+    await SecureStore.setItemAsync(ACCESS_TOKEN_KEY, accessToken);
+    await SecureStore.setItemAsync(REFRESH_TOKEN_KEY, refreshToken);
+  } catch (error) {
+    await SecureStore.deleteItemAsync(ACCESS_TOKEN_KEY);
+    await SecureStore.deleteItemAsync(REFRESH_TOKEN_KEY);
+    throw error;
+  }
 }

Alternatively, store both tokens as a single JSON object under one key for true atomic writes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/tokenstore.ts` around lines 6 - 9, The saveTokens function currently
calls SecureStore.setItemAsync twice (ACCESS_TOKEN_KEY and REFRESH_TOKEN_KEY)
which can leave state inconsistent if the second write fails; change saveTokens
to perform an atomic save by writing both tokens as one JSON string under a
single key (e.g., TOKENS_KEY) or implement a rollback strategy: write the second
token, and if any write rejects delete the first key to restore clean state;
update references to ACCESS_TOKEN_KEY/REFRESH_TOKEN_KEY usage accordingly (or
add a TOKENS_KEY) and ensure SecureStore.setItemAsync and
SecureStore.deleteItemAsync are used to implement the chosen approach in the
saveTokens function.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
services/auth/google.ts (2)

50-52: Log unknown errors before returning null.

When an error is caught but lacks a recognized code, the function silently returns null. Logging the error here would aid debugging unexpected failures.

🔧 Proposed fix
     } else {
+      console.error('Google Sign-In Unknown Error:', error);
       return null;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/auth/google.ts` around lines 50 - 52, The catch branch in
services/auth/google.ts currently returns null silently in the else block;
update that branch to log the unexpected error before returning null (use the
project's logger if available, e.g., processLogger.error or console.error) and
include the error object and contextual message (e.g., "Unexpected error in
[function name or Google auth handler]") so unexpected failures are visible in
logs.

28-32: Remove redundant nullish coalescing.

Line 32 uses response.data.idToken ?? '' but lines 28-31 already guarantee idToken is truthy when reaching line 32. The fallback is dead code.

🧹 Suggested cleanup
       if (!response.data.idToken) {
         console.log('Google Sign-In Failed: No ID Token');
         return null;
       }
-      return await getTokens('google', response.data.idToken ?? '');
+      return await getTokens('google', response.data.idToken);
     } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/auth/google.ts` around lines 28 - 32, The code is passing a
redundant fallback to getTokens after already checking idToken; in
services/auth/google.ts locate the call to getTokens('google',
response.data.idToken ?? '') and remove the nullish coalescing so you pass the
guaranteed token directly (use response.data.idToken or response.data.idToken!
if TypeScript needs a non-null assertion) so dead-code fallback '' is
eliminated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@services/auth/google.ts`:
- Around line 9-15: The configureGoogleSignIn function should validate
EXPO_PUBLIC_WEB_CLIENT_ID and EXPO_PUBLIC_IOS_CLIENT_ID before calling
GoogleSignin.configure; update configureGoogleSignIn to read
process.env.EXPO_PUBLIC_WEB_CLIENT_ID and process.env.EXPO_PUBLIC_IOS_CLIENT_ID,
verify they are non-empty, and if either is missing log/throw a clear error (or
return early) describing which env var is absent instead of calling
GoogleSignin.configure with undefined values so sign-in failures become
actionable.

---

Nitpick comments:
In `@services/auth/google.ts`:
- Around line 50-52: The catch branch in services/auth/google.ts currently
returns null silently in the else block; update that branch to log the
unexpected error before returning null (use the project's logger if available,
e.g., processLogger.error or console.error) and include the error object and
contextual message (e.g., "Unexpected error in [function name or Google auth
handler]") so unexpected failures are visible in logs.
- Around line 28-32: The code is passing a redundant fallback to getTokens after
already checking idToken; in services/auth/google.ts locate the call to
getTokens('google', response.data.idToken ?? '') and remove the nullish
coalescing so you pass the guaranteed token directly (use response.data.idToken
or response.data.idToken! if TypeScript needs a non-null assertion) so dead-code
fallback '' is eliminated.

@kongwoojin kongwoojin marked this pull request as draft February 22, 2026 07:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant