OAuth Setup
Reactor Identity supports OAuth 2.0 / OpenID Connect for social login. This guide walks through provider registration, server configuration, and client-side integration.
Prerequisites: Running Reactor server with [auth] configured, a frontend app, OAuth app credentials from your provider.
1. Provider registration
Section titled “1. Provider registration”- Go to GitHub Developer Settings → OAuth Apps → New OAuth App
- Set Authorization callback URL:
https://api.myapp.com/auth/v1/callback/github
- Copy Client ID and generate a Client Secret
- Go to Google Cloud Console → APIs & Services → Credentials
- Create OAuth 2.0 Client ID (Web application)
- Add authorized redirect URI:
https://api.myapp.com/auth/v1/callback/google
- Copy Client ID and Client Secret
Any OpenID Connect provider works. You need:
- Issuer URL (e.g.
https://accounts.example.com) - Client ID and secret
- Redirect URI:
https://api.myapp.com/auth/v1/callback/{provider_id}
2. Register providers in Reactor
Section titled “2. Register providers in Reactor”OAuth providers are stored in the auth schema. Register via the admin API or a migration:
-- migrations/002_oauth_providers.sql
INSERT INTO _reactor_auth.oauth_providers (id, name, type, config, enabled)VALUES ( 'github', 'GitHub', 'oauth2', '{ "client_id": "Ov23li...", "client_secret": "vault:auth/github_secret", "authorize_url": "https://github.com/login/oauth/authorize", "token_url": "https://github.com/login/oauth/access_token", "userinfo_url": "https://api.github.com/user", "scopes": ["read:user", "user:email"], "pkce": false }'::jsonb, true ), ( 'google', 'Google', 'oidc', '{ "client_id": "123456789.apps.googleusercontent.com", "client_secret": "vault:auth/google_secret", "issuer": "https://accounts.google.com", "scopes": ["openid", "email", "profile"], "pkce": true }'::jsonb, true );Store client secrets in vault rather than inline:
# Via admin API or vault CLIreactor vault set auth/github_secret "ghp_..."reactor vault set auth/google_secret "GOCSPX-..."3. Server configuration
Section titled “3. Server configuration”Ensure Reactor.toml has the correct public URL:
[auth]data_key = "{{ env REACTOR_AUTH_DATA_KEY }}"public_url = "https://api.myapp.com"jwt_issuer = "reactor-auth"jwt_audience = "reactor"access_ttl_secs = 3600refresh_ttl_secs = 604800For local development:
[auth]public_url = "http://localhost:8000"And register localhost callbacks with your OAuth provider:
http://localhost:8000/auth/v1/callback/github4. Frontend integration
Section titled “4. Frontend integration”Initiate OAuth flow
Section titled “Initiate OAuth flow”import { createClient } from "@reactor/client";
const reactor = createClient({ url: "https://api.myapp.com" });
export function signInWithGitHub() { const redirectTo = `${window.location.origin}/auth/callback`; window.location.href = reactor.auth.getOAuthUrl({ provider: "github", redirect_to: redirectTo, });}
export function signInWithGoogle() { const redirectTo = `${window.location.origin}/auth/callback`; window.location.href = reactor.auth.getOAuthUrl({ provider: "google", redirect_to: redirectTo, });}Handle callback
Section titled “Handle callback”import { useEffect } from "react";import { useNavigate, useSearchParams } from "react-router-dom";import { reactor } from "../lib/auth";
export function AuthCallback() { const [params] = useSearchParams(); const navigate = useNavigate();
useEffect(() => { async function handleCallback() { const code = params.get("code"); const provider = params.get("provider") ?? "github";
if (!code) { navigate("/login?error=missing_code"); return; }
const { session, user } = await reactor.auth.exchangeCodeForSession({ provider, code, redirect_to: `${window.location.origin}/auth/callback`, });
// Store session localStorage.setItem("reactor_session", JSON.stringify(session));
// Redirect based on new vs returning user if (user.created_at === user.updated_at) { navigate("/onboarding"); } else { navigate("/dashboard"); } }
handleCallback(); }, [params, navigate]);
return <p>Signing you in...</p>;}React login buttons
Section titled “React login buttons”export function OAuthButtons() { return ( <div className="flex flex-col gap-3"> <button onClick={signInWithGitHub} className="btn-oauth"> Continue with GitHub </button> <button onClick={signInWithGoogle} className="btn-oauth"> Continue with Google </button> </div> );}5. Link existing accounts
Section titled “5. Link existing accounts”Allow email/password users to link OAuth providers:
// Requires authenticated sessionawait reactor.auth.linkIdentity({ provider: "github", redirect_to: `${window.location.origin}/settings/accounts/callback`,});Unlink:
await reactor.auth.unlinkIdentity({ provider: "github" });6. Access OAuth tokens (for API calls)
Section titled “6. Access OAuth tokens (for API calls)”Store provider tokens securely for calling third-party APIs on behalf of users:
// Server-side function only — never expose to clientconst { data: identity } = await reactor.auth.admin.getUserIdentities(userId);const githubToken = identity.find(i => i.provider === "github")?.access_token;
const repos = await fetch("https://api.github.com/user/repos", { headers: { Authorization: `Bearer ${githubToken}` },});7. Reactor.cloud CLI login
Section titled “7. Reactor.cloud CLI login”The same OAuth flow powers reactor cloud login:
reactor cloud login# Opens browser → GitHub/Google OAuth# Callback returns API key stored in OS keychainThis uses the control plane’s OAuth endpoints (/cloud/v1/auth/login), separate from project auth.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Fix |
|---|---|
redirect_uri_mismatch | Verify callback URL matches provider registration exactly |
invalid_client | Check client ID and secret; ensure secret is in vault |
| User created but no email | Add user:email scope (GitHub) or email scope (Google) |
| PKCE error | Enable pkce: true for providers that require it |
| CORS error on callback | Callback is a redirect, not XHR — ensure you’re using window.location.href |
# Debug auth issuesreactor logs --capability auth --since 10mcurl -H "Authorization: Bearer $ADMIN_TOKEN" \ http://localhost:8000/_admin/doctor | jq '.auth'Security checklist
Section titled “Security checklist”- Redirect URIs restricted to your domains only
- Client secrets in vault, not in migrations committed to git
- PKCE enabled for public clients
- Minimal OAuth scopes requested
- Provider tokens accessed server-side only
Related
Section titled “Related”- Security — auth hardening
- Multi-tenant app — org-scoped OAuth
- AI chatbot backend — authenticated chat