Browser-based OAuth is designed around a user who can sign in and approve access. A VPS is designed around a process with no desktop. The bridge is an SSH local-forwarding tunnel: the browser remains on the laptop, while the callback listener remains private on the VPS. For a broader remote-agent tunnel threat model, see the secure MCP tunnels guide.
This guide focuses on Google’s authorization-code flow, loopback callback design and OpenSSH controls. For broader secure tunnel patterns, see the OpenAI secure MCP tunnels guide.
The practical verdict
Use this topology when the application runs on a remote server but Google login must happen in a user-controlled browser. It avoids putting a public callback endpoint on the VPS and avoids giving a bot your Google password.
The critical invariant is exactness: the redirect URI registered in Google Cloud, the application listener and the laptop-side tunnel must agree on host, port and path.
Understand the topology
Chrome on laptop
http://127.0.0.1:8080/oauth2callback
|
| SSH local forward: laptop:8080 -> VPS:127.0.0.1:8080
v
OAuth app on VPS
listens only on 127.0.0.1:8080
|
v
Google authorization and token endpoints over HTTPS
The browser opens Google’s HTTPS consent page. After approval, Google redirects to the registered loopback callback. The browser connects to the laptop’s local port, and SSH carries the request to the VPS listener.
The callback is not copied into chat. The authorization code is consumed by the application and exchanged directly with Google.
Configure Google OAuth
Google’s web-server OAuth documentation requires the redirect URI to match exactly. Register a URI such as:
http://127.0.0.1:8080/oauth2callback
Use localhost everywhere instead if that is what the application and client use. Do not mix localhost and 127.0.0.1 casually.
Request only the scopes required by the feature. Google recommends incremental authorization and the narrowest scopes possible. Analytics and Search Console read-only access should not automatically become Gmail or Drive access.
The authorization request should use an unpredictable state value, store it server-side and compare it before exchanging the callback code. For refresh-token use when the user is absent, Google documents access_type=offline. A refresh token may not be returned on every later authorization.
Sensitive or restricted scopes can trigger Google verification requirements. Personal, internal, development and testing exceptions vary, so check Google’s current OAuth policies before production launch.
Create the SSH tunnel
On the laptop, run:
ssh -N \ -o ExitOnForwardFailure=yes \ -o ServerAliveInterval=30 \ -o ServerAliveCountMax=3 \ -L 8080:127.0.0.1:8080 \ user@YOUR_VPS_HOST
-N creates a forwarding-only session. ExitOnForwardFailure=yes prevents a misleading tunnel process from remaining after the local bind fails. Explicitly binding the laptop side to loopback avoids exposing the callback to the laptop’s network interfaces.
OpenSSH’s -L syntax forwards a local port to a host and port reachable from the remote side. The destination 127.0.0.1:8080 therefore refers to the VPS loopback interface, not the laptop.
Do not add -g or bind the local side to 0.0.0.0 for an OAuth callback. The callback should be reachable only by the browser on the laptop.
Complete the callback
In a second VPS session, start the application’s login command. A headless-friendly CLI should print a short-lived Google authorization URL instead of requiring a local GUI browser:
# example application command aitraffic auth google login --profile work
If the application currently attempts to open xdg-open, that is harmless on a headless VPS as long as it also prints the URL. Copy the URL from the VPS terminal into Chrome on the laptop and complete consent yourself.
The application should then:
- receive the loopback callback through SSH;
- validate
state; - reject an OAuth error or missing code;
- exchange the code over HTTPS;
- verify the granted scopes and account identity;
- store the refresh token securely;
- show success without printing the token.
If the callback page never loads, check the local listener, tunnel process, exact port and registered redirect URI. Do not paste the callback URL into a chat system to “finish” the login.
Protect tokens and scopes
Google’s OAuth policy requires secure token handling. Keep these private:
- OAuth client JSON and client secret;
- authorization URL if it contains state and client details;
- authorization code;
- callback URL;
- access token and refresh token;
- account identifiers and scope grants.
Store tokens encrypted at rest or in the operating system credential store. Restrict the token file or vault to the service account. Never commit it, include it in a bug report or pass it through an agent prompt.
Handle invalid_grant as a possible revocation or expired refresh token and require a new browser authorization. Revoke and delete tokens when the integration is removed. Do not silently broaden scopes during reauthorization.
Handle failures
| Symptom | Likely cause | Safe response |
|---|---|---|
redirect_uri_mismatch | Host, port, path or trailing slash differs | Compare Google Cloud, app and tunnel character by character |
| Browser connection refused | Tunnel or VPS listener is absent | Check both processes; do not expose a public port as a shortcut |
| OAuth state failure | Wrong/stale callback or interrupted flow | Abort and restart; never exchange a code after state mismatch |
invalid_grant | Revoked, expired or reused code/token | Reauthorize in the browser and rotate affected credentials |
| Consent denied | User or policy rejected a scope | Request less scope or resolve verification; do not retry blindly |
| Tunnel drops | SSH/network interruption | Reconnect and restart the short-lived login flow |
| Port already used | Existing listener or tunnel | Stop the intended disposable process or choose a reviewed port |
SSH forwarding can also be restricted by AllowTcpForwarding, PermitOpen and DisableForwarding in sshd_config. If a tunnel fails on a locked-down VPS, review the server policy rather than weakening it globally.
Security checklist
- [ ] OAuth client JSON is stored only on the VPS, not in chat.
- [ ] Redirect URI matches exactly.
- [ ] Listener binds only to VPS loopback.
- [ ] Laptop forward binds only to laptop loopback.
- [ ] SSH uses a dedicated key and restricted account where possible.
- [ ]
ExitOnForwardFailureand keepalives are enabled. - [ ] OAuth
stateis generated, stored and validated. - [ ] Scopes are minimal and read-only where possible.
- [ ] Tokens are encrypted or stored in a credential vault.
- [ ] Callback URLs and authorization codes are never pasted into an agent.
- [ ] Revocation and reauthorization are tested.
This flow gives the user control of Google login while keeping the server-side callback private. It is simple, auditable and safer than exposing a temporary public OAuth endpoint.
FAQ
The concise answers are in metadata; the topology, callback and token rules above are the operational guide.
Official sources
- Google web-server OAuth
- Google OAuth policies
- Google OAuth scopes
- Sensitive-scope verification
- OpenSSH ssh manual
- OpenSSH server configuration
Related Guides
How to Change Antigravity Themes
Customize themes, dark mode, icons, and color schemes.
Rules & ConfigurationAntigravity Rules Guide
How to build custom rules with AGENTS.md and GEMINI.md.
MCP & IntegrationMCP Servers Setup Guide
Step-by-step guide to connecting MCP servers in Antigravity.
ComparisonBest Antigravity Alternatives 2026
Claude Code, Cursor, Windsurf, Codex, and Kiro compared.
Pricing & QuotaAntigravity Cockpit Guide
Monitor AI quota, track rate limits, and manage credits.
MCP & IntegrationGoogle Stitch + Antigravity Guide
The complete design-to-code workflow with DESIGN.md and Vibe Design.
