Skip to content

fix(google): enforce OAuth authority in strict mode - #217

Open
ikatkov wants to merge 1 commit into
vercel-labs:mainfrom
ikatkov:fix-google-bearer-validation
Open

fix(google): enforce OAuth authority in strict mode#217
ikatkov wants to merge 1 commit into
vercel-labs:mainfrom
ikatkov:fix-google-bearer-validation

Conversation

@ikatkov

@ikatkov ikatkov commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #218.

The Google emulator currently treats any nonempty bearer credential as authenticated through the core fallback user. That makes an unknown token, a revoked access token, and a refresh token presented as an access token indistinguishable from a valid credential. Google resource routes also ignore the scopes granted during OAuth.

This adds an opt-in google.strict_scopes mode, following the compatibility pattern already used by the Linear and Slack emulators. When enabled, Google now:

  • accepts only unexpired access tokens issued by its authorization code or refresh flow
  • preserves revoked access-token records and rejects those credentials after /oauth2/revoke
  • rejects refresh tokens on resource APIs
  • checks documented Google scope strings for userinfo, Gmail, Calendar, and Drive routes
  • returns the existing Google error envelope with 401 UNAUTHENTICATED for invalid credentials and 403 insufficientPermissions for scope misses

Strict mode defaults to false. Existing users can continue using arbitrary local bearer tokens without completing OAuth, with unchanged response statuses and bodies.

Reproduction

Use a Google seed with an OAuth client, a user, and at least one Drive item:

google:
  users:
    - email: repro@example.com
      name: Repro User
  oauth_clients:
    - client_id: repro-google-client
      client_secret: repro-google-secret
      redirect_uris:
        - http://localhost:9/callback
  drive_items:
    - id: drv_repro_folder
      user_email: repro@example.com
      name: Repro
      mime_type: application/vnd.google-apps.folder
      parent_ids: [root]

Start v0.10.0 with npx emulate@0.10.0 start --service google --port 4700 --seed emulate.config.yaml, then compare these requests:

  1. Call /drive/v3/files with Bearer totally-bogus-token.
  2. Complete the authorization code flow for the Drive scope and call the same route with the issued access token.
  3. Revoke that access token through /oauth2/revoke, then call Drive with it again.
  4. Complete another authorization code flow with only openid, then call Drive, Gmail, and Calendar data routes.
  5. Present an issued refresh token to a resource route.

On v0.10.0, all five resource credential variants return 200 and expose seeded data. With this change and google.strict_scopes: true, the results are:

Scenario v0.10.0 This PR, strict mode
Unknown bearer token 200 401 UNAUTHENTICATED
Issued token with matching scope 200 200
Revoked access token 200 401 UNAUTHENTICATED
openid-only token on Drive, Gmail, or Calendar 200 403 insufficientPermissions
Refresh token on a resource route 200 401 UNAUTHENTICATED

Compatibility

strict_scopes is absent or false by default. Tests compare response bodies as well as status codes for unknown, revoked, underscoped, and refresh credentials to guard the existing permissive workflow.

The starter config, package README, root README, docs site, Google skill, and CLI help now document the option.

Tests

  • pnpm test
  • pnpm type-check
  • pnpm lint
  • pnpm build

Follow-ups

The Microsoft and GitHub emulators have the same adjacent unknown-bearer fallback gap. They are intentionally out of scope here and can use the same opt-in pattern in separate changes. Consent denial and fault injection are also outside this PR.

@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

@ikatkov is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

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.

Google emulator accepts unknown and revoked bearer tokens and ignores scopes

1 participant