# How to Handle Authentication Tokens in Retool

- Tool: Retool
- Difficulty: Beginner
- Time required: 20-25 min
- Compatibility: Retool Cloud and Self-hosted
- Last updated: March 2026

## TL;DR

Retool handles OAuth 2.0 token refresh automatically when configured in the resource settings — enable 'Refresh token' and provide the token endpoint URL. For bearer token APIs, store tokens as secret environment variables and reference them as {{ environment.variables.API_TOKEN }}. Never display raw tokens in UI components. Use JS Query to decode JWT payloads for debugging.

## Token Management in Retool: Resources, OAuth, and JWTs

Authentication tokens are the credentials that Retool uses to communicate with external APIs on behalf of your app. Managing them correctly is the difference between an app that keeps working and one that silently fails when tokens expire.

Retool's resource system handles most token complexity automatically: OAuth 2.0 resources can be configured to auto-refresh access tokens before they expire. For bearer token APIs, tokens are stored encrypted in resource credentials or environment variables.

The most common token issues in Retool: OAuth access tokens expire (typically after 1 hour) and the app starts returning 401 errors; JWT tokens need inspection for debugging; teams unsure whether to use shared vs per-user credentials.

## Before you start

- A Retool app connected to at least one REST API or OAuth-based resource
- Admin access to Retool Settings to configure resources
- Basic understanding of OAuth 2.0 flow (access token + refresh token)

## Step-by-step guide

### 1. Configure OAuth 2.0 token refresh in resource settings

Navigate to Settings → Resources and open the resource that uses OAuth 2.0. In the authentication section, ensure 'OAuth 2.0' is selected as the auth type. Find the 'Access token URL' field and verify it points to the OAuth provider's token endpoint (e.g., https://api.service.com/oauth/token). Enable 'Refresh token' if the option is available. Set the 'Refresh token URL' (often the same as the access token URL). Retool will automatically use the refresh token to obtain a new access token before the current one expires, preventing 401 errors in production.

**Expected result:** Resource is configured with OAuth 2.0 and automatic token refresh. Queries continue working after the 1-hour access token expiry.

### 2. Understand per-user vs shared credentials

Retool resources support two authentication modes: (1) Shared credentials — one set of credentials used by all users of the app. Appropriate for service-to-service APIs where you own a service account. (2) Per-user OAuth — each Retool user authenticates with the external service using their own account. Data operations are scoped to that user's permissions. Per-user OAuth is configured in the resource settings under 'Authentication type' → 'OAuth 2.0 (per user)'. Users see a 'Connect your account' button the first time they use the app.

**Expected result:** Resource is configured with the appropriate credential sharing mode for the use case.

### 3. Store bearer tokens as environment/configuration variables

For APIs that use static bearer tokens (not OAuth), store the token as a Retool environment variable, not hardcoded in query bodies. Navigate to Settings → Environment Variables (or Settings → Configuration Variables). Create a new variable named 'EXTERNAL_API_TOKEN', mark it as 'Secret', and enter the token value. In your REST API queries, set the Authorization header to: Bearer {{ environment.variables.EXTERNAL_API_TOKEN }}. Secret variables are encrypted at rest and not visible to non-admin users.

```
// REST API resource header configuration:
// Header: Authorization
// Value: Bearer {{ environment.variables.EXTERNAL_API_TOKEN }}

// Or in a JS Query making a fetch call (avoid this — use resource queries):
// fetch(url, {
//   headers: {
//     'Authorization': `Bearer ${environment.variables.EXTERNAL_API_TOKEN}`
//   }
// });
```

**Expected result:** API token is stored securely as a secret environment variable. Queries use it without exposing it in the codebase.

### 4. Inspect JWT payloads for debugging in JS Queries

When debugging authentication issues, you may need to inspect a JWT token's payload. Write a JS Query to decode the JWT payload (the middle section, base64-encoded). Never log or display the full JWT in UI components — only decode it in the DevTools console or a temporary debug query. The decoded payload shows the token's claims: user ID, expiry time, scopes, and audience.

```
// JS Query: inspectJWT (for debugging only — remove from production)
// Decodes the JWT payload without verifying signature

const token = environment.variables.JWT_TOKEN;

if (!token) {
  console.log('No JWT token found in environment variables');
  return null;
}

try {
  // JWT has three sections: header.payload.signature
  const parts = token.split('.');
  if (parts.length !== 3) throw new Error('Not a valid JWT format');

  // Decode the payload (middle section)
  const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));

  const now = Math.floor(Date.now() / 1000);
  const expiresIn = payload.exp - now;

  console.log('JWT payload:', JSON.stringify(payload, null, 2));
  console.log(`Token expires in: ${expiresIn} seconds (${(expiresIn / 60).toFixed(1)} minutes)`);

  return {
    sub: payload.sub,
    exp: new Date(payload.exp * 1000).toISOString(),
    expiresInSeconds: expiresIn,
    isExpired: expiresIn < 0,
    scopes: payload.scope || payload.scopes,
  };
} catch (err) {
  console.error('Failed to decode JWT:', err.message);
  return null;
}
```

**Expected result:** JWT payload is decoded and logged to the browser console, showing expiry time and claims.

### 5. Handle token expiration errors with query error handlers

Even with refresh token configuration, token errors can occur. Add an 'On failure' event handler to critical API queries that checks for 401 errors. If the error is authentication-related, show a user-friendly message and provide a 'Reconnect' button that triggers the OAuth re-authorization flow. For apps with shared credentials, the error handler can also alert an admin via utils.showNotification().

```
// Query 'On failure' event handler (JS Query)
// Triggered when an API query returns a 401 or 403 error

const error = query1.error;
const statusCode = error?.statusCode || error?.response?.status;

if (statusCode === 401) {
  utils.showNotification({
    title: 'Session expired',
    description: 'Your session has expired. Please reconnect your account.',
    notificationType: 'warning',
    duration: 10,
  });
  // Show reconnect UI
  await reconnectBanner.setValue(true);
} else if (statusCode === 403) {
  utils.showNotification({
    title: 'Access denied',
    description: 'You do not have permission to perform this action.',
    notificationType: 'error',
  });
} else {
  utils.showNotification({
    title: 'API error',
    description: `Request failed: ${error?.message || 'Unknown error'}`,
    notificationType: 'error',
  });
}
```

**Expected result:** Token expiration errors display user-friendly messages instead of raw error objects.

## Complete code example

File: `JS Query: validateAndRefreshToken`

```javascript
// JS Query: validateAndRefreshToken
// Run on page load to check token validity before user starts working
// Uses inspectJWT logic to check expiry, shows warning if near expiry

const token = environment.variables.API_TOKEN;

if (!token) {
  utils.showNotification({
    title: 'Configuration error',
    description: 'API token not configured. Contact your administrator.',
    notificationType: 'error',
    duration: 0, // Persistent notification
  });
  return;
}

// Check if token is a JWT and inspect expiry
try {
  const parts = token.split('.');
  if (parts.length === 3) {
    const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
    const now = Math.floor(Date.now() / 1000);
    const expiresIn = payload.exp - now;

    if (expiresIn < 0) {
      utils.showNotification({
        title: 'Token expired',
        description: 'The API token has expired. Please update it in Settings → Resources.',
        notificationType: 'error',
        duration: 0,
      });
    } else if (expiresIn < 3600) {
      // Warn if token expires within 1 hour
      utils.showNotification({
        title: 'Token expiring soon',
        description: `API token expires in ${Math.floor(expiresIn / 60)} minutes.`,
        notificationType: 'warning',
        duration: 8,
      });
    }
  }
} catch {
  // Not a JWT — static API key, no expiry check needed
}
```

## Common mistakes

- **Storing an API bearer token in a query header hardcoded as 'Authorization: Bearer eyJhb...'** — undefined Fix: Use a Retool secret environment variable: Authorization: Bearer {{ environment.variables.API_TOKEN }}. Hard-coded tokens are exposed in query definitions and visible to anyone with app edit access.
- **Not enabling refresh token in OAuth resource config, causing apps to fail after the access token expires** — undefined Fix: In the resource settings, enable 'Refresh token' and configure the refresh token URL. Retool will automatically exchange the refresh token for a new access token before expiry.
- **Using the JWT decode technique to make security decisions (e.g., checking if user is admin based on decoded claims)** — undefined Fix: JWT signature is not verified in the browser-side decode. Only use it for debugging. All authorization decisions must be enforced server-side in your API, not client-side based on decoded token claims.

## Best practices

- Always store API tokens as secret configuration variables — never hardcode them in query bodies or UI components
- Enable OAuth refresh token configuration in Retool resources to prevent auth interruptions from token expiry
- Use per-user OAuth credentials for services where user-level audit trails and permission scoping are required
- Add 'On failure' handlers to critical queries that check for 401/403 responses and provide actionable user messages
- Rotate static API tokens regularly and update them in Retool's environment variables without code changes
- Never expose token values in Text components, table cells, or any part of the UI

## Frequently asked questions

### Can I see OAuth tokens that Retool is using internally in resource queries?

No — for security, Retool does not expose the actual OAuth access tokens to app developers or end users. You can verify that OAuth is configured and working by testing the resource connection in Settings → Resources. If you need to debug token issues, check the Debug Panel's Network tab for HTTP 401 responses from your API.

### What happens in Retool when an OAuth access token expires and there is no refresh token?

Queries using that resource will return a 401 Unauthorized error. Users of per-user OAuth resources will see a prompt to re-authorize their account. For shared credential resources, an admin must manually regenerate the token in the resource settings. This is why enabling refresh token support is critical for production apps.

### Can I pass a Retool user's identity token to my backend API for user-level authorization?

Yes — use {{ retoolContext.currentUser.email }} or {{ retoolContext.currentUser.id }} in REST API query headers or parameters. You can also configure your Retool resource to forward the user's identity using the 'Forward user context' option in some resource types. For more secure identity forwarding, implement custom OIDC token passing through an environment variable set by SSO.

---

Source: https://www.rapidevelopers.com/retool-tutorial/how-to-handle-authentication-tokens-in-retool
© RapidDev — https://www.rapidevelopers.com/retool-tutorial/how-to-handle-authentication-tokens-in-retool
