Skip to content

Fix: OAuth2 token missing provider name#11561

Merged
Meldiron merged 5 commits into1.9.xfrom
fix-oauth-token-flow-provider-param
Mar 23, 2026
Merged

Fix: OAuth2 token missing provider name#11561
Meldiron merged 5 commits into1.9.xfrom
fix-oauth-token-flow-provider-param

Conversation

@Meldiron
Copy link
Contributor

What does this PR do?

Ensures oauth provider is preserved in token oauth flow, same as session oauth flow.

Test Plan

New tests

Related PRs and Issues

x

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?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

📝 Walkthrough

Walkthrough

The changes update OAuth2 session handling in the Account controller to wrap generated OAuth tokens in HS256 JWTs containing both secret and provider claims. The createSession method is modified to decode and validate these JWT-wrapped tokens, extracting provider information and inner secrets, while tightening validation to reject certain token types without proper claims. Composer script configuration is updated to include --memory-limit=1G flags for PHPStan analysis commands. OAuth2 integration tests are expanded to explicitly validate redirect flows, session state transitions, and provider metadata throughout the authentication sequence.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: fixing OAuth2 token flow to include the provider name.
Description check ✅ Passed The description directly relates to the changeset by explaining the motivation: ensuring OAuth provider preservation in token OAuth flow, with new tests provided.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-oauth-token-flow-provider-param

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

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

@github-actions
Copy link

github-actions bot commented Mar 16, 2026

Security Scan Results for PR

Docker Image Scan Results

Package Version Vulnerability Severity
zlib 1.3.1-r2 CVE-2026-22184 CRITICAL
zlib-dev 1.3.1-r2 CVE-2026-22184 CRITICAL

Source Code Scan Results

🎉 No vulnerabilities found!

@github-actions
Copy link

github-actions bot commented Mar 16, 2026

🔄 PHP-Retry Summary

Flaky tests detected across commits:

Commit 90f0282 - 1 flaky test
Test Retries Total Time Details
UsageTest::testDatabaseStatsCollectionsAPI 1 10.19s Logs
Commit afd8d8a - 4 flaky tests
Test Retries Total Time Details
UsageTest::testDatabaseStatsCollectionsAPI 1 10.28s Logs
RealtimeCustomClientTest::testChannelExecutions 1 12.80s Logs
RealtimeCustomClientTest::testRelationshipPayloadHidesRelatedDoc 1 30.19s Logs
RealtimeCustomClientTest::testConcurrentRealtimeTrafficCoroutines 1 30.13s Logs
Commit 07ff923 - 3 flaky tests
Test Retries Total Time Details
RealtimeCustomClientTest::testChannelTablesDBRowUpdate 1 120.32s Logs
UsageTest::testFunctionsStats 1 10.13s Logs
UsageTest::testPrepareSitesStats 1 7ms Logs
Commit 682105c - 3 flaky tests
Test Retries Total Time Details
UsageTest::testFunctionsStats 1 10.15s Logs
UsageTest::testPrepareSitesStats 1 7ms Logs
TablesDBCustomClientTest::testEnforceCollectionPermissions 1 92ms Logs

@Meldiron Meldiron marked this pull request as ready for review March 16, 2026 16:07
@github-actions
Copy link

github-actions bot commented Mar 16, 2026

✨ Benchmark results

  • Requests per second: 1,715
  • Requests with 200 status code: 308,824
  • P99 latency: 0.098441836

⚡ Benchmark Comparison

Metric This PR Latest version
RPS 1,715 1,132
200 308,824 203,750
P99 0.098441836 0.206375948

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: 1

🧹 Nitpick comments (1)
tests/e2e/Services/Account/AccountCustomClientTest.php (1)

2187-2216: Extract redirect-chain assertions into a helper to reduce drift.

The two OAuth redirect walkthroughs are almost identical. A small helper (e.g., followOAuthRedirectChain) would keep expectations consistent and make future flow changes safer.

Also applies to: 2247-2277

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

In `@tests/e2e/Services/Account/AccountCustomClientTest.php` around lines 2187 -
2216, Extract the repeated OAuth redirect walkthrough into a private helper
(e.g., followOAuthRedirectChain) to centralize the
GET->301->GET->301->GET->301->GET->200 sequence and reduce duplicated
assertions; implement a method that accepts the Client instance and initial URL,
performs successive oauthClient->call(Client::METHOD_GET, ..., followRedirects:
false) calls, asserts the intermediate 301 status-codes and the expected
location prefixes (the same prefixes currently asserted with
assertStringStartsWith), checks for the two session cookies ('a_session_' .
$this->getProject()['$id'] . '_legacy' and 'a_session_' .
$this->getProject()['$id']), returns the final response and extracted
oauthUserCookie, and then replace both duplicated blocks (around the current
assertions using Client::METHOD_GET and cookie checks) with calls to this helper
to reuse the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/Services/Account/AccountCustomClientTest.php`:
- Around line 2187-2203: The test reads $response['headers']['location']
repeatedly without checking that the 'location' key exists, which makes it
brittle and triggers static-analysis errors; update the AccountCustomClientTest
method to guard each redirect hop by asserting or checking that
isset($response['headers']['location']) (or array_key_exists) before indexing
and failing the test with a clear message if missing, then use the guarded value
for subsequent Client::call calls and apply the same pattern to the other
redirect hops around lines 2247-2274 to ensure CI stability.

---

Nitpick comments:
In `@tests/e2e/Services/Account/AccountCustomClientTest.php`:
- Around line 2187-2216: Extract the repeated OAuth redirect walkthrough into a
private helper (e.g., followOAuthRedirectChain) to centralize the
GET->301->GET->301->GET->301->GET->200 sequence and reduce duplicated
assertions; implement a method that accepts the Client instance and initial URL,
performs successive oauthClient->call(Client::METHOD_GET, ..., followRedirects:
false) calls, asserts the intermediate 301 status-codes and the expected
location prefixes (the same prefixes currently asserted with
assertStringStartsWith), checks for the two session cookies ('a_session_' .
$this->getProject()['$id'] . '_legacy' and 'a_session_' .
$this->getProject()['$id']), returns the final response and extracted
oauthUserCookie, and then replace both duplicated blocks (around the current
assertions using Client::METHOD_GET and cookie checks) with calls to this helper
to reuse the logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c3c39f67-5cc4-48da-b47e-19f0d03b848a

📥 Commits

Reviewing files that changed from the base of the PR and between 392926b and afd8d8a.

📒 Files selected for processing (4)
  • app/config/collections/common.php
  • app/controllers/api/account.php
  • composer.json
  • tests/e2e/Services/Account/AccountCustomClientTest.php

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 16, 2026

Greptile Summary

This PR fixes a bug where the OAuth2 provider name was not being preserved when using the token-based OAuth2 flow (/account/tokens/oauth2/:provider), causing the resulting session to show a generic oauth2 provider instead of the actual provider name (e.g. mock, github, etc.). The session-based flow already worked correctly; this patch brings the token flow to parity.

The fix is in two parts:

  • When creating the OAuth2 token in the redirect handler, the $provider string is now stored directly on the tokens document.
  • When later exchanging that token for a session (/account/sessions/token), the provider is read from the token document (falling back to SESSION_PROVIDER_OAUTH2 for backwards compatibility).

A new schema attribute (provider, VARCHAR(128)) is added to the tokens collection definition in common.php to support this, backed by a thorough new end-to-end test.

Key concern: The tokens collection schema change in common.php is not accompanied by a database migration. The migrateCollections() method in V23.php (or a new V24.php) must include a case 'tokens': block that calls createAttributeFromCollection($this->dbForProject, 'tokens', 'provider'). Without this, existing installations that upgrade will be missing the column and will receive a Structure exception the first time any user attempts the OAuth2 token flow.

Confidence Score: 2/5

  • Not safe to merge — missing database migration will break the OAuth2 token flow on all existing upgraded installations.
  • The logic fix and test are correct, but the absence of a migration to add the provider column to the tokens collection table means any existing Appwrite project upgrading to this version will encounter a Structure exception when the OAuth2 token flow is invoked. This is a regression for all upgrading users of the token-based OAuth2 flow.
  • app/config/collections/common.php — schema change lacks a migration; the corresponding src/Appwrite/Migration/Version/V23.php (or a new V24) needs a case 'tokens': entry.

Important Files Changed

Filename Overview
app/config/collections/common.php Adds provider attribute to the tokens collection schema, but no corresponding database migration exists — existing installations upgrading will be missing this column, breaking the OAuth2 token flow at runtime.
app/controllers/api/account.php Correctly stores provider in the OAuth2 token document and reads it back (with a safe fallback to SESSION_PROVIDER_OAUTH2) when creating a session from the token — the logic fix itself is sound.
tests/e2e/Services/Account/AccountCustomClientTest.php Comprehensive new test that exercises both the session-based and token-based OAuth2 flows and asserts provider === 'mock' in the resulting session — good coverage of the fix.
composer.json Minor cleanup: tab-to-space normalisation and adds --memory-limit=1G to the check script, matching the existing analyze script — no functional impact.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Appwrite
    participant OAuthProvider
    participant DB as Database

    Client->>Appwrite: GET /account/tokens/oauth2/:provider
    Appwrite->>OAuthProvider: Redirect to OAuth provider
    OAuthProvider->>Appwrite: OAuth callback with code
    Appwrite->>DB: createDocument('tokens', {type:OAUTH2, provider:providerName})
    Note over DB: provider field now stored (NEW)
    Appwrite->>Client: Redirect to success URL with token params

    Client->>Appwrite: POST /account/sessions/token
    Appwrite->>DB: Verify token document, read provider attribute
    Note over DB: Falls back to 'oauth2' if provider missing
    Appwrite->>DB: createDocument('sessions', {provider: tokenProvider})
    Appwrite->>Client: 201 Session with correct provider name
Loading

Last reviewed commit: afd8d8a

@Meldiron Meldiron changed the base branch from 1.8.x to 1.9.x March 23, 2026 10:42
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.

🧹 Nitpick comments (1)
app/controllers/api/account.php (1)

212-226: Consider handling the missing provider case more gracefully.

The current logic throws USER_INVALID_TOKEN at line 219 if the secret decodes as a valid JWT but lacks a provider claim. This exception isn't caught by the JWTException catch block, so it propagates up.

While practically unlikely (an attacker would need the symmetric key to craft such a JWT), this conflates two distinct error conditions:

  1. "This is a malformed OAuth2 JWT"
  2. "This is not an OAuth2 JWT at all"

Consider restructuring so that a JWT without provider is treated the same as a non-JWT (fall through), and let the validation at line 244 catch OAuth2 tokens without providers. This would be more defensive:

Suggested approach
     try {
         $jwtDecoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 60, 0);
         $payload = $jwtDecoder->decode($secret);
 
-        if (empty($payload['provider'])) {
-            throw new Exception(Exception::USER_INVALID_TOKEN);
+        if (!empty($payload['provider']) && !empty($payload['secret'])) {
+            $oauthProvider = $payload['provider'];
+            $secret = $payload['secret'];
         }
-
-        $oauthProvider = $payload['provider'];
-        $secret = $payload['secret'];
     } catch (\Ahc\Jwt\JWTException) {
         // Not a JWT — use secret as-is (non-OAuth flows)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/account.php` around lines 212 - 226, The JWT decode block
using $jwtDecoder currently throws Exception::USER_INVALID_TOKEN when
$payload['provider'] is empty, which escapes the JWTException catch and
conflates "malformed OAuth2 JWT" with "not an OAuth2 JWT"; change the logic in
the try block so that if $payload['provider'] is missing you do NOT throw
Exception::USER_INVALID_TOKEN but instead treat it like a non-JWT: leave
$oauthProvider as null and preserve $secret (or reset $secret to the original
raw value) so execution falls through to the existing downstream validation
(which will catch invalid OAuth2 tokens), while still only catching
\Ahc\Jwt\JWTException for real decode errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/controllers/api/account.php`:
- Around line 212-226: The JWT decode block using $jwtDecoder currently throws
Exception::USER_INVALID_TOKEN when $payload['provider'] is empty, which escapes
the JWTException catch and conflates "malformed OAuth2 JWT" with "not an OAuth2
JWT"; change the logic in the try block so that if $payload['provider'] is
missing you do NOT throw Exception::USER_INVALID_TOKEN but instead treat it like
a non-JWT: leave $oauthProvider as null and preserve $secret (or reset $secret
to the original raw value) so execution falls through to the existing downstream
validation (which will catch invalid OAuth2 tokens), while still only catching
\Ahc\Jwt\JWTException for real decode errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2d95ddc7-0514-49fc-bc35-8b222de22730

📥 Commits

Reviewing files that changed from the base of the PR and between afd8d8a and 682105c.

📒 Files selected for processing (2)
  • app/controllers/api/account.php
  • composer.json
✅ Files skipped from review due to trivial changes (1)
  • composer.json

@Meldiron Meldiron merged commit 7114611 into 1.9.x Mar 23, 2026
77 of 78 checks passed
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.

2 participants