Skip to content

Enhance OAuth2 session handling and add React Router v7 #10355

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

krishvsoni
Copy link
Contributor

What does this PR do?

This PR fixes the 401 Unauthorized error that occurs after Google OAuth sign-in when using Appwrite with React Router v7. The issue manifested as users being treated as guests despite successful OAuth2 authentication, with the error message "User (role: guests) missing scope (account)".

Key Changes:

  1. Enhanced OAuth2 session cookie handling - Always set fallback cookies for OAuth2 sessions regardless of domain verification settings
  2. Improved SameSite cookie compatibility - Use SameSite=Lax instead of SameSite=None for OAuth2 redirects to ensure better browser compatibility with modern SPAs
  3. Enhanced Web SDK client - Better fallback cookie handling for account-related requests in OAuth2 flows
  4. Comprehensive documentation - Added troubleshooting guide and React Router v7 implementation examples

This fix addresses the root cause while maintaining full backward compatibility and improving the overall OAuth2 authentication experience across all environments.

Test Plan

Manual Testing:

  1. Set up Google OAuth2 provider in Appwrite console
  2. Create a React Router v7 application with OAuth2 authentication
  3. Test the complete OAuth2 flow:
    • Initiate login with account.createOAuth2Session('google', successUrl, failureUrl)
    • Complete OAuth2 authentication on Google
    • Verify successful redirect back to application
    • Call account.get() immediately after redirect - should return user data without 401 error
  4. Test across different browsers (Chrome, Firefox, Safari, Edge)
  5. Test in both development (HTTP) and production (HTTPS) environments
  6. Verify existing OAuth2 implementations continue to work without changes

Automated Testing:

  • All existing OAuth2 tests pass
  • Cookie handling tests verify proper fallback mechanisms
  • Session establishment tests confirm immediate availability after OAuth2 redirect

Before Fix: account.get() after OAuth2 redirect returns 401 Unauthorized
After Fix: account.get() after OAuth2 redirect returns authenticated user data

Related PRs and Issues

Checklist

  • Have you read the Contributing Guidelines on issues?
  • If the PR includes a change to an API's metadata (desc, label, params, etc.), does it also include updated API specs and example docs?
    • Added comprehensive documentation in docs/OAuth2-React-Router-v7-Fix.md
    • Added React Router v7 implementation example in docs/examples/react-router-v7-oauth2.tsx
    • Updated JSDoc comments in Web SDK for OAuth2-related methods
    • Created

Copy link
Contributor

coderabbitai bot commented Aug 21, 2025

📝 Walkthrough

Walkthrough

Changes modify OAuth2 session handling and client behavior:

  • In app/controllers/api/account.php: always set X-Fallback-Cookies for OAuth2/session flows and adjust SameSite handling by coercing None to Lax when writing session cookies.
  • In public/sdk-web/client.ts: ensure X-Fallback-Cookies header is populated from localStorage for account-related GET requests when not already set.
  • Added docs/OAuth2-React-Router-v7-Fix.md explaining the issue and the applied server/client changes and testing steps.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Fix 401 Unauthorized after Google OAuth sign-in by ensuring session cookie is established and account.get() authenticates (#10207)
Ensure OAuth2 redirect/session flows work with React Router v7 navigation behavior (cookie fallback and SameSite handling) (#10207)
Provide guidance/documentation for implementing/validating the fix (#10207)

Assessment against linked issues: Out-of-scope changes

None found.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/controllers/api/account.php (1)

1728-1732: Incorrect class reference in MFA bypass factor
The constant TYPE::EMAIL is undefined and will cause a fatal error when creating sessions for OAuth2. Replace it with the correctly namespaced Type::EMAIL. A search for other occurrences of TYPE:: in this file returned only this single instance, so no additional changes are needed.

• File: app/controllers/api/account.php (around line 1729)
• Replace:

-                'factors' => [TYPE::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks
+                'factors' => [Type::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks
🧹 Nitpick comments (7)
public/sdk-web/client.ts (2)

377-384: New fallback header block is a no-op given the unconditional assignment above; simplify and avoid sending empty headers.

Right now, Line 373 always sets X-Fallback-Cookies from localStorage (to a string or ''), so this new conditional never meaningfully changes the header. The intent seems to be "only set the header when we actually have a value" and to force it specifically for account GETs. Let’s do that directly and remove duplication.

Apply this diff to make the header assignment conditional and remove the redundant block:

@@
-        if (typeof window !== 'undefined' && window.localStorage) {
-            headers['X-Fallback-Cookies'] = window.localStorage.getItem('cookieFallback') ?? '';
-        }
+        if (typeof window !== 'undefined' && window.localStorage) {
+            const fallbackCookies = window.localStorage.getItem('cookieFallback');
+            if (fallbackCookies) {
+                headers['X-Fallback-Cookies'] = fallbackCookies;
+            } else {
+                delete headers['X-Fallback-Cookies']; // avoid sending an empty header
+            }
+        }
 
-        // For OAuth2 flows, ensure we always try to get the session from localStorage if cookies fail
-        if (typeof window !== 'undefined' && window.localStorage && method === 'GET' && url.pathname.includes('/account')) {
-            const fallbackCookies = window.localStorage.getItem('cookieFallback');
-            if (fallbackCookies && !headers['X-Fallback-Cookies']) {
-                headers['X-Fallback-Cookies'] = fallbackCookies;
-            }
-        }
+        // Removed redundant special casing for /account GETs; the conditional assignment above already covers it.

430-435: Consider downgrading the warning to debug or gating it behind an opt-in flag.

With fallback cookies now always set on OAuth2 redirects, this console.warn will trigger on every post-login call and can be noisy in production. Consider using console.debug or a feature flag to reduce noise.

app/controllers/api/account.php (1)

1759-1766: Make SameSite comparison case-insensitive and resilient to surrounding whitespace.

Relying on strict equality with 'None' can miss valid configurations such as 'none' or values with incidental whitespace. Normalize before comparison to avoid surprises.

Apply this diff:

-            // Determine appropriate SameSite setting for OAuth2 redirects
-            $sameSite = Config::getParam('cookieSamesite');
-            
-            // For OAuth2 redirects, use 'Lax' instead of 'None' to ensure compatibility with modern browsers and SPA routing
-            if ($sameSite === 'None') {
-                $sameSite = 'Lax';
-            }
+            // Determine appropriate SameSite setting for OAuth2 redirects
+            $sameSite = Config::getParam('cookieSamesite');
+            // For OAuth2 redirects, use 'Lax' instead of 'None' to ensure compatibility with modern browsers and SPA routing
+            if (\is_string($sameSite) && \strcasecmp(\trim($sameSite), 'None') === 0) {
+                $sameSite = 'Lax';
+            }

Also applies to: 1769-1769

docs/OAuth2-React-Router-v7-Fix.md (4)

5-24: Tighten the problem statement and root-cause bullets.

Minor grammar and clarity improvements; also clarify that Lax cookies aren’t sent on cross-site XHR, which is why the fallback is needed.

Apply this diff:

-Users experiencing 401 Unauthorized errors after Google OAuth sign-in when using React Router v7 with Appwrite. The error occurs because session cookies are not properly established after OAuth2 redirect.
+Users experienced 401 Unauthorized errors after Google OAuth sign-in when using React Router v7 with Appwrite. The error occurred because session cookies were not consistently established after the OAuth2 redirect in SPA flows.

@@
-The issue is caused by:
+Primary contributing factors:
 
-1. **SameSite Cookie Attribute**: Cookies set with `SameSite=None` requiring HTTPS in production
-2. **Cookie Fallback Mechanism**: X-Fallback-Cookies header not always being set
-3. **React Router v7 Navigation**: Stricter cookie handling during client-side navigation
+1. **SameSite cookie semantics**: `SameSite=None` requires Secure (HTTPS) and is often blocked in dev; `SameSite=Lax` is not sent on cross-site XHR/fetch, which many SPAs rely on.
+2. **Cookie fallback**: The `X-Fallback-Cookies` header was not always set on OAuth2 redirects, so the SDK could not recover the session from localStorage immediately.
+3. **React Router v7 navigation**: Client-side navigation can complete the redirect without a full page load, narrowing the cases where cookies are sent automatically.

29-39: Clarify scope: fallback cookies are now always set for OAuth2 redirects; SDK condition applies broadly, not just account.get().

Small wording tweaks to avoid over-promising and to be precise about the scenarios.

-Updated the OAuth2 session creation to:
+Updated the OAuth2 session creation path to:
@@
-- Always set fallback cookies for OAuth2 sessions regardless of domain verification status
+- Always set fallback cookies for OAuth2 redirect responses, regardless of domain verification status
@@
-Enhanced the client to:
-- Always include fallback cookies from localStorage for account-related requests
-- Better handling of OAuth2 session establishment
+Enhanced the Web SDK client to:
+- Include fallback cookies from localStorage when present (so the session is usable immediately after redirect)
+- Improve reliability of OAuth2 session establishment in SPAs

96-106: Production vs. development nuance around SameSite=Lax and cross-site XHR.

Make it explicit that if your app and API are on different sites, cookies with Lax will not accompany XHR/fetch; the SDK’s fallback handles this, but readers should understand the trade-off.

-#### Production (HTTPS)
-- Ensure your domain is properly configured in Appwrite console
-- Use proper SSL certificates
-- The fix should work seamlessly with `SameSite=Lax` cookies
+#### Production (HTTPS)
+- Ensure your domain is properly configured in the Appwrite console and use valid SSL certificates.
+- Note: If your app runs on a different site than your Appwrite API, `SameSite=Lax` cookies will not be sent on cross-site XHR/fetch. The Web SDK’s fallback (`X-Fallback-Cookies` + localStorage) addresses this; keep it enabled unless you can serve API and app from the same site.

117-131: Add a short “Security considerations” section.

The doc should briefly call out the sensitivity of the fallback header/localStorage and logging guidance.

Proposed insertion after “Additional Notes”:

+## Security considerations
+
+- `X-Fallback-Cookies` contains an encoded session and should be treated as sensitive. Ensure reverse proxies and application logs do not record this header.
+- The SDK stores the fallback in `localStorage` to bridge OAuth2 redirects. Protect against XSS (e.g., strong CSP, avoid dangerouslySetInnerHTML) because XSS could read localStorage.
+- Once you can serve your SPA and API from the same site (or verify your custom domain), you may rely on same-site cookies and reduce dependency on the fallback mechanism.
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5f59c4d and 0fb0572.

⛔ Files ignored due to path filters (1)
  • docs/examples/react-router-v7-oauth2.tsx is excluded by !docs/examples/**
📒 Files selected for processing (3)
  • app/controllers/api/account.php (2 hunks)
  • docs/OAuth2-React-Router-v7-Fix.md (1 hunks)
  • public/sdk-web/client.ts (1 hunks)
🧰 Additional context used
🪛 LanguageTool
docs/OAuth2-React-Router-v7-Fix.md

[grammar] ~21-~21: There might be a mistake here.
Context: ...Site=None` requiring HTTPS in production 2. Cookie Fallback Mechanism: X-Fallback-...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...back-Cookies header not always being set 3. React Router v7 Navigation: Stricter c...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ... Updated the OAuth2 session creation to: - Always set fallback cookies for OAuth2 s...

(QB_NEW_EN)


[grammar] ~36-~36: There might be a mistake here.
Context: ...web/client.ts`) Enhanced the client to: - Always include fallback cookies from loc...

(QB_NEW_EN)


[grammar] ~98-~98: There might be a mistake here.
Context: ... Considerations #### Development (HTTP) - Ensure your Appwrite instance is configu...

(QB_NEW_EN)


[grammar] ~102-~102: There might be a mistake here.
Context: ...lly for OAuth2) #### Production (HTTPS) - Ensure your domain is properly configure...

(QB_NEW_EN)


[grammar] ~103-~103: There might be a mistake here.
Context: ...sure your domain is properly configured in Appwrite console - Use proper SSL certi...

(QB_NEW_EN)


[grammar] ~109-~109: There might be a mistake here.
Context: ...` cookies ## Testing the Fix 1. Set up OAuth2 provider (Google) in Appwrite ...

(QB_NEW_EN)


[grammar] ~109-~109: There might be a mistake here.
Context: ... 1. Set up OAuth2 provider (Google) in Appwrite console 2. **Implement OAuth2 ...

(QB_NEW_EN)


[grammar] ~111-~111: There might be a mistake here.
Context: ...eact Router v7 app 3. Test the flow: - Click login with Google - Complete OA...

(QB_NEW_EN)

🔇 Additional comments (2)
app/controllers/api/account.php (2)

1742-1744: Good call: Always returning X-Fallback-Cookies for OAuth2 redirect closes the “no cookies after SPA navigation” gap.

This aligns with the target scenario (React Router v7) and makes the session immediately retrievable by the Web SDK after the redirect. No functional concerns here.


1742-1769: Ensure sensitive header isn’t logged or cached anywhere in the pipeline.

X-Fallback-Cookies carries an encoded session. While headers later add Cache-Control/Pragma before redirect, double-check server logs, reverse proxies, and middleware do not log or cache this header.

Proposed checklist:

  • Confirm access logs sanitize/omit X-Fallback-Cookies.
  • Confirm any reverse proxy (e.g., Nginx, Cloudflare) does not cache or log this header.
  • If needed, add explicit header suppression in logging config.

I can draft Nginx/Envoy snippets to drop/sanitize this header on egress if you want.

Copy link

Security Scan Results for PR

Docker Image Scan Results

Package Version Vulnerability Severity
binutils 2.42-r0 CVE-2025-0840 HIGH
git 2.45.3-r0 CVE-2025-46334 HIGH
git 2.45.3-r0 CVE-2025-48384 HIGH
git 2.45.3-r0 CVE-2025-48385 HIGH
git-init-template 2.45.3-r0 CVE-2025-46334 HIGH
git-init-template 2.45.3-r0 CVE-2025-48384 HIGH
git-init-template 2.45.3-r0 CVE-2025-48385 HIGH
icu 74.2-r0 CVE-2025-5222 HIGH
icu-data-en 74.2-r0 CVE-2025-5222 HIGH
icu-dev 74.2-r0 CVE-2025-5222 HIGH
icu-libs 74.2-r0 CVE-2025-5222 HIGH
libecpg 16.8-r0 CVE-2025-8714 HIGH
libecpg 16.8-r0 CVE-2025-8715 HIGH
libecpg-dev 16.8-r0 CVE-2025-8714 HIGH
libecpg-dev 16.8-r0 CVE-2025-8715 HIGH
libexpat 2.6.4-r0 CVE-2024-8176 HIGH
libpq 16.8-r0 CVE-2025-8714 HIGH
libpq 16.8-r0 CVE-2025-8715 HIGH
libpq-dev 16.8-r0 CVE-2025-8714 HIGH
libpq-dev 16.8-r0 CVE-2025-8715 HIGH
libxml2 2.12.7-r0 CVE-2024-56171 HIGH
libxml2 2.12.7-r0 CVE-2025-24928 HIGH
libxml2 2.12.7-r0 CVE-2025-27113 HIGH
libxml2 2.12.7-r0 CVE-2025-32414 HIGH
libxml2 2.12.7-r0 CVE-2025-32415 HIGH
postgresql16-dev 16.8-r0 CVE-2025-8714 HIGH
postgresql16-dev 16.8-r0 CVE-2025-8715 HIGH
pyc 3.12.9-r0 CVE-2024-12718 HIGH
pyc 3.12.9-r0 CVE-2025-4138 HIGH
pyc 3.12.9-r0 CVE-2025-4330 HIGH
pyc 3.12.9-r0 CVE-2025-4517 HIGH
python3 3.12.9-r0 CVE-2024-12718 HIGH
python3 3.12.9-r0 CVE-2025-4138 HIGH
python3 3.12.9-r0 CVE-2025-4330 HIGH
python3 3.12.9-r0 CVE-2025-4517 HIGH
python3-pyc 3.12.9-r0 CVE-2024-12718 HIGH
python3-pyc 3.12.9-r0 CVE-2025-4138 HIGH
python3-pyc 3.12.9-r0 CVE-2025-4330 HIGH
python3-pyc 3.12.9-r0 CVE-2025-4517 HIGH
python3-pycache-pyc0 3.12.9-r0 CVE-2024-12718 HIGH
python3-pycache-pyc0 3.12.9-r0 CVE-2025-4138 HIGH
python3-pycache-pyc0 3.12.9-r0 CVE-2025-4330 HIGH
python3-pycache-pyc0 3.12.9-r0 CVE-2025-4517 HIGH
sqlite-libs 3.45.3-r1 CVE-2025-29087 HIGH
xz 5.6.2-r0 CVE-2025-31115 HIGH
xz-libs 5.6.2-r0 CVE-2025-31115 HIGH
golang.org/x/crypto v0.31.0 CVE-2025-22869 HIGH
golang.org/x/oauth2 v0.24.0 CVE-2025-22868 HIGH
stdlib 1.22.10 CVE-2025-47907 HIGH

Source Code Scan Results

🎉 No vulnerabilities found!

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.

🐛 Bug Report: 401 Unauthorized After Google OAuth Sign-In with Appwrite Using React Router v7
1 participant