Skip to main content

OAuth Providers

Steward supports OAuth sign-in with Google, Discord, and Twitter/X. All flows use the PKCE (Proof Key for Code Exchange) extension for security, with no client secret exposed to the browser.

Supported Providers

How It Works (PKCE Flow)

The popup-based flow keeps the user on your page while authentication happens in a separate window.

SDK Usage

Configuration Options

Redirect Flow (Non-Popup)

For environments where popups are blocked, use the redirect flow:

React Usage

The <StewardLogin> component renders OAuth buttons based on which providers are enabled on the server:
The component automatically queries GET /auth/providers to discover which OAuth providers are available. Buttons are only shown for providers that are both enabled on the server and not disabled via props.

Setting Up OAuth Apps

Google

  1. Go to Google Cloud Console
  2. Create a new OAuth 2.0 Client ID (Web application)
  3. Add authorized redirect URI:
  4. Copy the Client ID and Client Secret

Discord

  1. Go to Discord Developer Portal
  2. Create a new application
  3. Go to OAuth2 settings
  4. Add redirect URI:
  5. Copy the Client ID and Client Secret

Twitter/X

  1. Go to Twitter Developer Portal
  2. Create a project and app
  3. Enable OAuth 2.0 with PKCE
  4. Add callback URL:
  5. Copy the Client ID

Redirect URIs

All OAuth providers require a registered redirect URI. The pattern is:
For local development:

API Endpoints

Device Authorization Flow

For CLIs, TVs, and other input-constrained clients, Steward supports a bounded RFC 8628-style device flow. Device codes expire after 10 minutes, polling starts at a 5 second interval, rapid polling returns slow_down, and an approved code is consumed on first successful token exchange.
The verification UI should authenticate the user normally, then approve or deny the displayed code:
When clientId is supplied, issuance requires an enabled tenant app client and token polling must present the same client id. Polling returns RFC-style errors: authorization_pending, slow_down, access_denied, expired_token, invalid_client, invalid_request, and unsupported_grant_type. Native app clients can additionally send platform identifiers on device-flow requests:
If either header or the matching JSON fields (native_bundle_id, native_package_name, nativeBundleId, nativePackageName) is supplied, Steward validates the identifier format and checks it against the enabled app client’s allowedBundleIds or allowedPackageNames. Accepted identifiers are bound to the issued device code, so /auth/device/token must present the same identifier. A different identifier returns invalid_client and leaves the approval state unchanged. This is deterministic server allowlist enforcement; full device attestation remains the responsibility of the OS, app store, or attestation provider used by the native app. Minimal iOS-bound device-code request:
The response echoes the accepted identifier using snake_case:
The token poll must repeat the same app client and native identifier, either as headers or JSON fields:

Security

  • All flows use PKCE (S256 challenge method) to prevent authorization code interception
  • State parameter prevents CSRF attacks
  • Code verifier is stored client-side and never sent to the provider
  • Popup polling includes a 5-minute timeout
  • Device authorization codes are tenant/app-client bound, short-lived, rate-limited, and single-use
  • Provider tokens are encrypted before storage; Steward sessions return short-lived access tokens and rotated refresh tokens