# How to Set Up Vercel OAuth Authentication for Open Agents

> Learn to set up Vercel OAuth authentication for Open Agents using PKCE flow. Securely store tokens and sync to sandboxed CLI environments. Explore our detailed guide.

- Repository: [Vercel Labs/open-agents](https://github.com/vercel-labs/open-agents)
- Tags: how-to-guide
- Published: 2026-04-16

---

**Open Agents implements the standard OAuth 2.0 PKCE flow to authenticate users with Vercel, storing encrypted tokens in a PostgreSQL database and syncing them to sandboxed CLI environments through three core layers: PKCE helpers in [`oauth.ts`](https://github.com/vercel-labs/open-agents/blob/main/oauth.ts), persistent storage in [`token.ts`](https://github.com/vercel-labs/open-agents/blob/main/token.ts), and sandbox configuration in [`vercel-cli-auth.ts`](https://github.com/vercel-labs/open-agents/blob/main/vercel-cli-auth.ts).**

This guide explains how to implement Vercel OAuth authentication for Open Agents, an open-source project by Vercel Labs that enables AI agents to deploy and manage Vercel projects. The repository provides a complete, production-ready OAuth 2.0 PKCE implementation with automatic token refresh and secure sandbox integration.

## Configure Vercel Credentials

Before implementing the flow, add your Vercel OAuth application credentials to the environment variables of your Open Agents deployment:

```bash
NEXT_PUBLIC_VERCEL_APP_CLIENT_ID=<your-vercel-client-id>
VERCEL_APP_CLIENT_SECRET=<your-vercel-client-secret>

```

The client ID is public and used to build authorization URLs, while the client secret remains server-only and is used for token exchange. Store these in your Vercel dashboard or `.env.local` file for local development.

## OAuth Implementation Architecture

The Vercel OAuth authentication for Open Agents is organized into three logical layers that handle distinct responsibilities:

| Layer | Responsibility | Core Source Files |
|-------|---------------|-------------------|
| **PKCE & Token Exchange** | Generates code verifiers, derives code challenges, builds authorization URLs, exchanges codes for tokens, refreshes expired tokens, and handles revocation. | [`apps/web/lib/vercel/oauth.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/oauth.ts) |
| **Persistent Storage** | Reads and writes encrypted tokens to the `users` table, decrypts tokens on demand, and automatically refreshes expired tokens before returning them. | [`apps/web/lib/vercel/token.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/token.ts) |
| **Sandbox Sync** | Mirrors stored tokens into the Vercel CLI configuration files ([`auth.json`](https://github.com/vercel-labs/open-agents/blob/main/auth.json) and [`.vercel/project.json`](https://github.com/vercel-labs/open-agents/blob/main/.vercel/project.json)) that sandboxed processes expect for authentication. | [`apps/web/lib/sandbox/vercel-cli-auth.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/sandbox/vercel-cli-auth.ts) |

## Step 1: Initiate the Login Flow

To start the Vercel OAuth authentication for Open Agents, generate a PKCE pair and redirect the user to Vercel's authorization endpoint. This typically happens in a server-side route such as `/api/auth/vercel`.

```typescript
import { 
  generateCodeVerifier, 
  generateCodeChallenge, 
  getVercelAuthorizationUrl 
} from '@/lib/vercel/oauth';

export async function GET(request: Request) {
  // 1. Create PKCE values
  const verifier = generateCodeVerifier();
  const challenge = await generateCodeChallenge(verifier);
  
  // 2. Build the authorization URL
  const authUrl = getVercelAuthorizationUrl({
    clientId: process.env.NEXT_PUBLIC_VERCEL_APP_CLIENT_ID!,
    redirectUri: 'https://your-site.com/api/auth/vercel/callback',
    state: crypto.randomUUID(),  // CSRF protection
    codeChallenge: challenge,
  });
  
  // 3. Store the verifier in a signed cookie or session
  // Then redirect the user to Vercel
  return Response.redirect(authUrl);
}

```

The `generateCodeVerifier` function creates a cryptographically random string, while `generateCodeChallenge` hashes it using SHA-256. These helper functions are defined in [`apps/web/lib/vercel/oauth.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/oauth.ts).

## Step 2: Handle the OAuth Callback

After the user authorizes your application, Vercel redirects to your callback URL with an authorization `code` and `state` parameter. Exchange this code for access and refresh tokens, then persist them securely.

```typescript
import { exchangeVercelCode } from '@/lib/vercel/oauth';
import { db } from '@/lib/db/client';
import { encrypt } from '@/lib/crypto';
import { users } from '@/lib/db/schema';

export async function GET(request: Request) {
  const url = new URL(request.url);
  const code = url.searchParams.get('code');
  const state = url.searchParams.get('state');
  
  // Retrieve the code verifier you stored in Step 1
  const verifier = await getVerifierFromCookieOrSession(state);
  
  // Exchange the code for tokens
  const tokenResponse = await exchangeVercelCode({
    code: code!,
    codeVerifier: verifier,
    clientId: process.env.NEXT_PUBLIC_VERCEL_APP_CLIENT_ID!,
    clientSecret: process.env.VERCEL_APP_CLIENT_SECRET!,
    redirectUri: 'https://your-site.com/api/auth/vercel/callback',
  });
  
  // Encrypt and store the tokens
  await db.insert(users).values({
    id: currentUserId,
    provider: 'vercel',
    accessToken: encrypt(tokenResponse.access_token),
    refreshToken: tokenResponse.refresh_token 
      ? encrypt(tokenResponse.refresh_token) 
      : null,
    tokenExpiresAt: new Date(Date.now() + tokenResponse.expires_in * 1000),
    externalId: await fetchVercelUserId(tokenResponse.access_token),
  });
  
  return Response.redirect('/');
}

```

The `exchangeVercelCode` function handles the POST request to Vercel's token endpoint and returns the standard OAuth 2.0 token response including `access_token`, `refresh_token`, and `expires_in`.

## Step 3: Retrieve and Refresh Tokens

When your application needs to call the Vercel API or spawn a sandboxed CLI, use the `getUserVercelAuthInfo` function from [`apps/web/lib/vercel/token.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/token.ts). This helper automatically refreshes expired tokens before returning them.

```typescript
import { getUserVercelAuthInfo } from '@/lib/vercel/token';

const authInfo = await getUserVercelAuthInfo(userId);

if (authInfo) {
  const { token, expiresAt, externalId } = authInfo;
  // token is guaranteed to be fresh – refresh happens automatically if needed
  console.log(`Access token expires at: ${expiresAt}`);
}

```

The `getUserVercelAuthInfo` implementation in [`apps/web/lib/vercel/token.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/token.ts) performs the following steps:

1. Reads the encrypted row from the `users` table defined in [`apps/web/lib/db/schema.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/db/schema.ts)
2. Decrypts the access token using helpers from [`apps/web/lib/crypto.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/crypto.ts)
3. Checks the expiration timestamp against the current time
4. Calls `refreshVercelToken` from [`apps/web/lib/vercel/oauth.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/oauth.ts) if expired
5. Persists the new token back to the database and returns the updated auth info

## Step 4: Sync Tokens to Sandbox Environment

When running a sandboxed Vercel CLI process (e.g., for user-submitted agents), you must mirror the stored token into the CLI configuration files. The `syncVercelCliAuthToSandbox` function in [`apps/web/lib/sandbox/vercel-cli-auth.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/sandbox/vercel-cli-auth.ts) handles this.

```typescript
import { 
  getVercelCliSandboxSetup, 
  syncVercelCliAuthToSandbox 
} from '@/lib/sandbox/vercel-cli-auth';

// Build the sandbox setup for the current user/session
const setup = await getVercelCliSandboxSetup({
  userId,
  sessionRecord: {
    vercelProjectId: 'proj_123',
    vercelProjectName: 'my-project',
    vercelTeamId: authInfo?.externalId ?? null,
  },
});

// Write the files inside the sandbox container
await syncVercelCliAuthToSandbox({ sandbox, setup });

```

This writes the [`auth.json`](https://github.com/vercel-labs/open-agents/blob/main/auth.json) file to `~/.local/share/com.vercel.cli/auth.json` and optionally creates [`.vercel/project.json`](https://github.com/vercel-labs/open-agents/blob/main/.vercel/project.json) for project linking, ensuring the Vercel CLI inside the sandbox can authenticate without user intervention.

## Optional: Revoke Vercel Access

If a user disconnects their Vercel account, call `revokeVercelToken` from [`apps/web/lib/vercel/oauth.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/oauth.ts) and remove the stored credentials from your database.

```typescript
import { revokeVercelToken } from '@/lib/vercel/oauth';

await revokeVercelToken({
  token: storedAccessToken,
  clientId: process.env.NEXT_PUBLIC_VERCEL_APP_CLIENT_ID!,
  clientSecret: process.env.VERCEL_APP_CLIENT_SECRET!,
});

// Clear the database row
await db.delete(users).where(eq(users.id, userId));

```

This performs a POST request to Vercel's token revocation endpoint, invalidating the tokens remotely.

## Summary

- **Vercel OAuth authentication for Open Agents** implements the OAuth 2.0 PKCE flow to securely connect user accounts without exposing client secrets to the browser.
- The implementation spans three core files: [`apps/web/lib/vercel/oauth.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/oauth.ts) for PKCE and token exchange, [`apps/web/lib/vercel/token.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/token.ts) for encrypted persistent storage, and [`apps/web/lib/sandbox/vercel-cli-auth.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/sandbox/vercel-cli-auth.ts) for sandbox CLI configuration.
- Always store the `VERCEL_APP_CLIENT_SECRET` server-side while exposing only `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID` to the client.
- Use `getUserVercelAuthInfo` to automatically handle token refresh when calling the Vercel API or spawning sandboxes.
- Sync tokens to sandbox environments using `syncVercelCliAuthToSandbox` to enable authenticated Vercel CLI operations inside isolated processes.

## Frequently Asked Questions

### What OAuth flow does Open Agents use for Vercel authentication?

Open Agents uses the **OAuth 2.0 PKCE (Proof Key for Code Exchange)** flow. This method generates a random code verifier in the client, hashes it to create a code challenge, and sends only the challenge to the authorization server. According to the source code in [`apps/web/lib/vercel/oauth.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/oauth.ts), this prevents authorization code interception attacks while allowing secure authentication without a backend-stored client secret.

### How are Vercel tokens stored securely in Open Agents?

All tokens are encrypted using symmetric encryption before storage in the PostgreSQL database. The [`apps/web/lib/vercel/token.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/token.ts) file handles reading and writing to the `users` table defined in [`apps/web/lib/db/schema.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/db/schema.ts), utilizing encryption helpers from [`apps/web/lib/crypto.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/crypto.ts). Access tokens, refresh tokens, and expiration timestamps are all stored in encrypted form, ensuring that database leaks do not expose live Vercel credentials.

### How does Open Agents handle token expiration automatically?

The `getUserVercelAuthInfo` function in [`apps/web/lib/vercel/token.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/token.ts) automatically checks token expiration against the current time. If the access token has expired, it transparently calls `refreshVercelToken` from [`apps/web/lib/vercel/oauth.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/oauth.ts) to obtain a new access token using the stored refresh token. The new tokens are then encrypted and persisted back to the database before returning the fresh credentials to the caller, ensuring uninterrupted API access.

### Can users disconnect or revoke Vercel access in Open Agents?

Yes, users can revoke access by calling the `revokeVercelToken` function exported from [`apps/web/lib/vercel/oauth.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/vercel/oauth.ts). This function sends a revocation request to Vercel's token endpoint, invalidating both the access and refresh tokens remotely. After revocation, the application should delete the corresponding encrypted token records from the database using the Drizzle ORM client, completing the disconnection process.