# How y-gui Implements OAuth Token Refresh with Scheduled Tasks in Cloudflare Workers

> Discover how y-gui uses Cloudflare Workers scheduled tasks to automatically refresh OAuth tokens every hour, ensuring continuous access. Learn about the token refresh mechanism and D1 database integration.

- Repository: [luohy15/y-gui](https://github.com/luohy15/y-gui)
- Tags: how-to-guide
- Published: 2026-03-06

---

**The y-gui application automatically refreshes OAuth tokens every hour using a Cloudflare Workers cron trigger that executes `checkAndRefreshTokens` for all user workspaces, storing updated credentials in a D1 database.**

y-gui is an open-source integration management system built on Cloudflare Workers that maintains persistent Google OAuth connections. The application stores integration configurations—including sensitive refresh tokens—in a D1 database and employs an automated token refresh mechanism to ensure uninterrupted API access without manual intervention.

## Core Token Refresh Mechanism

The token refresh logic resides in [`backend/src/utils/token-refresh.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/token-refresh.ts) and operates through two coordinated functions that handle the OAuth token lifecycle.

### refreshIntegrationToken: Exchanging Credentials with Google

The `refreshIntegrationToken` function manages the actual token exchange with Google's OAuth infrastructure. When invoked, it POSTs the stored `refresh_token` to the Google OAuth endpoint along with the application's `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`. Upon receiving a successful response containing a new `access_token` and `expires_in` value, the function persists these updated credentials back to the integration record via `IntegrationD1Repository`.

According to the source code at lines 15-75 of [`backend/src/utils/token-refresh.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/token-refresh.ts), this function encapsulates the entire exchange workflow, handling the HTTP request construction and database update operations atomically.

### checkAndRefreshTokens: Proactive Expiry Inspection

Before triggering an exchange, the system must determine which tokens require refresh. The `checkAndRefreshTokens` function (lines 88-130 of [`backend/src/utils/token-refresh.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/token-refresh.ts)) queries all integrations associated with a specific user prefix from the `CHAT_DB` D1 instance and inspects each token's `expiry_date`.

If the remaining lifetime falls below a configurable threshold—defaulting to approximately 5 hours—the function invokes `refreshIntegrationToken` for that specific integration. This proactive approach ensures tokens remain valid during periods of active use while minimizing unnecessary API calls to Google's authorization servers.

## Scheduled Task Architecture

y-gui leverages Cloudflare Workers' cron trigger capabilities to automate the refresh workflow across all user workspaces without requiring external scheduling infrastructure.

### Cron Configuration in wrangler.toml

The scheduled execution is configured in [`backend/wrangler.toml`](https://github.com/luohy15/y-gui/blob/main/backend/wrangler.toml) (lines 35-38), where a cron trigger is registered to fire every hour:

```toml
[triggers]
crons = ["0 * * * *"]

```

This configuration ensures the Workers runtime invokes the scheduled handler consistently, providing a reliable window for token maintenance regardless of application traffic patterns.

### The Scheduled Handler Implementation

The entry point for automated execution is defined in [`backend/src/index.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/index.ts), which exports a `scheduled` handler alongside the standard fetch handler. When the cron trigger fires, the handler executes a two-phase refresh process across lines 13-35:

```typescript
// Scheduled task – automatically invoked by Cloudflare Workers
export default {
  fetch: app.fetch,
  async scheduled(event, env, ctx) {
    console.log('⏰ Token refresh start');
    await checkAndRefreshTokens(env, '');               // default space
    const prefixes = await listUserPrefixes(env.CHAT_R2);
    for (const p of prefixes) await checkAndRefreshTokens(env, p);
    console.log('✅ Token refresh finished');
  },
};

```

First, it calls `checkAndRefreshTokens` for the default workspace (empty prefix). Then, it retrieves all user-specific prefixes and iterates through each, executing the refresh check for every user's integrations. This architecture ensures comprehensive coverage across multi-tenant deployments.

### User Prefix Enumeration via R2 Storage

The `listUserPrefixes` utility function, implemented in [`backend/src/utils/storage.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/storage.ts), queries the Cloudflare R2 bucket (`CHAT_R2`) to discover all active user workspaces. This dynamic discovery mechanism allows the scheduled task to adapt to new users automatically without requiring manual registry updates or static configuration lists.

## Manual Invocation and Development Workflows

While the scheduled task handles production automation, developers can manually trigger the token refresh mechanism for testing or debugging purposes. The following example demonstrates invoking `checkAndRefreshTokens` with custom parameters:

```typescript
// Manual trigger (e.g., during development)
import { checkAndRefreshTokens } from './utils/token-refresh';

await checkAndRefreshTokens(
  {
    CHAT_DB: myD1Database,
    GOOGLE_CLIENT_ID: 'my-client-id',
    GOOGLE_CLIENT_SECRET: 'my-client-secret',
  },
  'user123',          // user prefix
  15 * 60 * 1000      // refresh if expiring within 15 minutes
);

```

This flexibility allows precise control over refresh thresholds during development, enabling testing with shorter expiry windows than the production default.

## Summary

- y-gui stores OAuth credentials in a Cloudflare D1 database and uses a dual-function approach in [`backend/src/utils/token-refresh.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/token-refresh.ts) to manage token lifecycle
- The `refreshIntegrationToken` function exchanges refresh tokens for new access tokens via Google's OAuth endpoint, while `checkAndRefreshTokens` orchestrates proactive refreshes based on configurable expiry thresholds (default ~5 hours)
- An hourly cron trigger configured in [`backend/wrangler.toml`](https://github.com/luohy15/y-gui/blob/main/backend/wrangler.toml) drives the automated workflow through a `scheduled` handler exported from [`backend/src/index.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/index.ts) (lines 13-35)
- The system dynamically discovers all user workspaces using `listUserPrefixes` from R2 storage, ensuring complete multi-tenant coverage during each execution cycle

## Frequently Asked Questions

### How does y-gui determine when to refresh an OAuth token?

The system compares the token's `expiry_date` against the current time using a configurable threshold. If a token expires within approximately 5 hours (or a custom millisecond value passed to `checkAndRefreshTokens`), the function flags it for refresh. This threshold prevents service interruptions while avoiding excessive token rotation that could trigger Google API rate limits.

### What happens if the Google OAuth endpoint is unavailable during a scheduled refresh?

The `refreshIntegrationToken` function handles the HTTP exchange logic in [`backend/src/utils/token-refresh.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/token-refresh.ts). If the endpoint is unavailable or returns an error, the function fails to obtain new credentials and the existing database record remains unchanged. The next scheduled execution (within one hour) retries the operation, as the token remains flagged for refresh due to its proximity to expiry.

### Can the token refresh mechanism handle multiple Google integrations per user?

Yes. The `checkAndRefreshTokens` function retrieves all integrations for a given user prefix and iterates through each record independently. Each integration maintains its own `expiry_date` and refresh credentials, allowing the system to manage multiple concurrent OAuth connections—such as Gmail, Calendar, and Drive—under a single user account.

### Where are the user workspace prefixes stored and how are they discovered?

User prefixes are enumerated from the Cloudflare R2 bucket using the `listUserPrefixes` function defined in [`backend/src/utils/storage.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/utils/storage.ts). During the scheduled execution in [`backend/src/index.ts`](https://github.com/luohy15/y-gui/blob/main/backend/src/index.ts), the system queries R2 to dynamically discover all active prefixes, then invokes the refresh check for each workspace. This eliminates the need to maintain a static list of users in the application configuration.