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_idandclient_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 field | What vet discovers |
|---|---|
authorization_endpoint | Authorization URL |
token_endpoint | Token exchange URL |
userinfo_endpoint | User info URL |
revocation_endpoint | Token revocation URL |
introspection_endpoint | Token introspection URL |
jwks_uri | JSON Web Key Set URL |
end_session_endpoint | Logout URL |
device_authorization_endpoint | Device 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
});
Scan only OAuth-related checks
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