If your app requests sensitive Google OAuth scopes (Gmail write, Calendar, etc.), Google requires a CASA Tier 2 audit. Vet’s OAuth probes test the same flows the assessor will check.

Prerequisites

  • Your OAuth client_id and client_secret
  • The app running and accessible over HTTPS
  • OAuth endpoints reachable (authorization, token, etc.)

Run an OAuth-aware scan

vet https://your-app.com \
  --client-id your_client_id \
  --client-secret your_client_secret

This runs all standard probes (headers, TLS, CORS, etc.) plus OAuth-specific probes that test:

  • State parameter handling
  • Authorization code handling
  • Redirect URI validation
  • Client secret enforcement
  • PKCE verification
  • Token exposure in URLs/errors

Endpoint discovery with OAuth

When --client-id and --client-secret are provided, vet automatically probes:

  • /.well-known/openid-configuration
  • /.well-known/oauth-authorization-server

If these return valid JSON, vet extracts endpoint URLs:

Well-known fieldWhat vet discovers
authorization_endpointAuthorization URL
token_endpointToken exchange URL
userinfo_endpointUser info URL
revocation_endpointToken revocation URL
introspection_endpointToken introspection URL
jwks_uriJSON Web Key Set URL
end_session_endpointLogout URL
device_authorization_endpointDevice flow URL

All discovered endpoints are scanned for headers, CORS, error disclosure, and more.

What the OAuth probes test

These probes map to OWASP ASVS V2 (Authentication) controls. They require --client-id and --client-secret to run. Without these flags, OAuth probes are skipped.

State parameter replay (V2.1.1)

Sends a previously-consumed state value to the authorization endpoint. The server must reject replayed state tokens.

Pass: Server returns an error for reused state.
Fail: Server accepts the replayed state.

Authorization code replay (V2.1.2)

Attempts to exchange a consumed authorization code a second time. The server must reject the replayed code.

Pass: Token endpoint returns an error for reused code.
Fail: Token endpoint issues new tokens for a replayed code.

Redirect URI mismatch (V2.1.3)

Sends an authorization request with a redirect_uri that doesn’t match the registered URI. The server must reject it.

Pass: Server rejects the mismatched redirect URI.
Fail: Server accepts an unregistered redirect URI.

Redirect URI open redirect (V2.1.4)

Appends query parameters to the redirect_uri to test for open redirect vulnerabilities.

Pass: Server rejects the modified URI.
Fail: Server redirects to the modified URI.

Missing client secret (V2.1.5)

Sends a token exchange request without client_secret. Confidential clients must require it.

Pass: Token endpoint rejects the request.
Fail: Token endpoint issues tokens without client authentication.

PKCE code verifier mismatch (V2.1.6)

Sends a wrong code_verifier for the code_challenge used during authorization. The server must reject the mismatched verifier.

Pass: Token endpoint rejects the mismatched verifier.
Fail: Token endpoint issues tokens with a wrong verifier.

Combine with OpenAPI for full coverage

For the most thorough scan, provide both OAuth credentials and an OpenAPI spec:

vet https://your-app.com \
  --client-id your_client_id \
  --client-secret your_client_secret \
  --openapi https://your-app.com/openapi.json

This discovers all documented API paths and scans them for headers, CORS, error disclosure, and injection — in addition to OAuth flow testing.

Common OAuth findings and fixes

State parameter not validated

Finding: V2.1.1 — Server accepts replayed state tokens.

Fix: Generate a unique, cryptographically random state value per authorization request. Store it server-side (or in an HMAC’d cookie). On callback, verify the state matches and hasn’t been used before. Delete it after use.

// Generate
const state = crypto.randomUUID();
await redis.set(`oauth:state:${state}`, '1', 'EX', 300); // 5min TTL

// Verify on callback
const valid = await redis.del(`oauth:state:${state}`); // atomic check + delete
if (!valid) throw new Error('Invalid or replayed state');

Authorization code reuse accepted

Finding: V2.1.2 — Token endpoint issues tokens for a replayed authorization code.

Fix: Mark authorization codes as consumed immediately upon first use. If a code is presented a second time, revoke all tokens issued from that code (RFC 6749 §10.5).

Unregistered redirect URI accepted

Finding: V2.1.3 — Server accepts redirect URIs that don’t exactly match the registered URI.

Fix: Use exact string matching for redirect_uri validation. Do not allow prefix matching, subdomain matching, or query parameter additions. Register all valid redirect URIs in your OAuth provider configuration.

Tokens exposed in URLs

Finding: V8.1.1 — Access tokens or refresh tokens appear in Location headers or redirect URLs.

Fix: Never pass tokens as URL query parameters. Use the Authorization header for bearer tokens. For OAuth flows, ensure tokens are only returned in response bodies, never in redirect URLs (use the authorization code flow, not the implicit flow).

Missing Cache-Control on token endpoint

Finding: V9.1.8 — Token endpoint doesn’t set Cache-Control: no-store.

Fix: Add Cache-Control: no-store to all authentication-related endpoints (token, authorize, login):

app.post('/oauth/token', (req, res) => {
  res.set('Cache-Control', 'no-store');
  res.set('Pragma', 'no-cache');
  // ... issue token
});

If you’ve fixed OAuth findings and want to re-verify just those:

# Re-scan auth category with verbose output
vet https://your-app.com \
  --client-id your_client_id \
  --client-secret your_client_secret \
  --only auth \
  --verbose