Skip to main content

Credential Management

Radarboard stores API credentials in an encrypted database instead of .env files. This enables credential management through the settings UI without server restarts.

How it works

  1. Encryption: All credentials are encrypted at rest using AES-256-GCM
  2. Storage: Encrypted data stored in the widget_credentials table
  3. Master key: A single ENCRYPTION_KEY environment variable provides the encryption key
  4. Per-service: Each external service has its own credential record (e.g., sentry, vercel, linear)

Setting up

Add an ENCRYPTION_KEY to your .env:
# Generate a random 32-byte hex key
openssl rand -hex 32
ENCRYPTION_KEY=your_64_character_hex_key_here
This is the only secret that must remain in .env. All other API credentials can be managed through the settings UI.

Credential types

API Key (manual entry)

For services like RevenueCat, Sentry, and BetterStack. Enter the token directly in the Integrations settings tab.

OAuth (browser redirect)

For services like GitHub and Google Search Console. The flow:
  1. Enter your OAuth app’s Client ID and Client Secret
  2. Click “Connect with
  3. Authorize on the provider’s website
  4. Tokens are stored automatically

For Google: gws CLI shortcut

Instead of creating a Google Cloud OAuth app manually, you can use the Google Workspace CLI:
npx @googleworkspace/cli auth setup
npx @googleworkspace/cli auth login -s webmasters
Then click “Import from gws CLI” in the Google Search Console integration settings.

Adding credentials to a new widget

When creating a new widget, declare its auth requirements in the descriptor:
export const myWidgetDescriptor: WidgetDescriptor = {
  // ...other fields
  auth: {
    id: "my-service",
    name: "My Service",
    type: "api_key",
    fields: [
      { key: "apiKey", label: "API Key", type: "password" },
    ],
    testEndpoint: "/api/credentials/test",
    docsUrl: "https://my-service.com/docs/api",
  },
};
For OAuth providers:
auth: {
  id: "my-oauth-service",
  name: "My OAuth Service",
  type: "oauth",
  fields: [
    { key: "clientId", label: "Client ID", type: "text" },
    { key: "clientSecret", label: "Client Secret", type: "password" },
  ],
  docsUrl: "https://my-service.com/oauth/apps",
  oauth: {
    provider: "my-provider",  // must match a key in oauth-providers.ts
    scopes: ["read", "write"],
    setupInstructions: "Create an OAuth app at ...",
  },
},
Then add the provider to apps/app/lib/oauth-providers.ts.

1Password CLI Backup (optional)

If you use 1Password, you can backup and restore credentials via the op CLI:
# Install 1Password CLI
brew install 1password-cli

# Sign in
op signin
Then in the app:
  • Export to 1Password: POST /api/credentials/1password with { action: "export", vault: "Dev" }
  • Import from 1Password: POST /api/credentials/1password with { action: "import", vault: "Dev" }
Credentials are stored as 1Password items with the title prefix “Radarboard - ”.
The 1Password CLI must be installed and authenticated on the server. This only works for local/self-hosted deployments.

Security

  • Credentials encrypted with AES-256-GCM (random IV per encryption)
  • Master key stored as environment variable (not in database)
  • OAuth state parameter prevents CSRF attacks
  • Cookies are httpOnly, secure (production), sameSite: lax
  • Tokens never appear in URLs or browser history