5.7 KiB
5.7 KiB
Login Page Redesign + OAuth Support
Overview
Redesign the login/register pages with a left-right split layout featuring prominent branding, and add Google/GitHub/Apple OAuth login via standard server-side redirect flow.
UI Design
Layout
- Desktop: 50/50 left-right split
- Mobile: Brand area hidden or collapsed to compact top banner; form area full-width
Left Panel (Brand Area)
Shared AuthBranding component used by both Login and Register pages.
- Dark/gradient background (fox-amber → fox-orange gradient from existing CSS variables)
- Large product icon (~80px SVG fox logo)
- Product name "AgentFox" (large heading font)
- Slogan "API Docs for LLMs, Done Right"
- 3 feature highlights, each with icon + text:
- "Multi-level API retrieval for minimal token usage"
- "Import OpenAPI specs in seconds"
- "Works with any MCP-compatible LLM"
Right Panel (Form Area)
- Light background, vertically centered
- Title: "Sign in to your account" (login) / "Create your account" (register)
- Email + Password inputs (reuse existing input styles)
- Primary action button
- Divider: "── or continue with ──"
- Three OAuth buttons in a row: Google / GitHub / Apple (each with official SVG icon)
- Footer link: "Don't have an account? Sign up" / "Already have an account? Sign in"
OAuth Architecture
Flow (Standard Server-Side Redirect)
Browser clicks OAuth button
→ GET /api/auth/oauth/:provider
→ Server builds authorization URL with state param, 302 redirects to Provider
→ User authorizes on Provider's page
→ Provider redirects to GET /api/auth/oauth/:provider/callback?code=xxx&state=yyy
→ Server validates state, exchanges code for access_token
→ Server fetches user info (email, name, avatar)
→ Server finds or creates user (see Account Linking below)
→ Server issues JWT (accessToken + refreshToken)
→ Server 302 redirects to frontend /login/callback?accessToken=xxx&refreshToken=xxx
Account Linking Strategy
On OAuth callback, the server resolves the user in this order:
- Look up
OAuthAccountby(provider, providerAccountId)→ if found, use linkedUser - If no OAuthAccount match, look up
Userbyemail→ if found, createOAuthAccountlinking to existing user - If no User match, create new
User(passwordHash=null, name and avatarUrl from provider) + newOAuthAccount
Security
- CSRF protection: Generate random
stateparameter per auth request, store in in-memory Map with 10-minute TTL, validate on callback - Token delivery: Tokens passed via URL query params; frontend immediately consumes and clears URL
- Secrets: All client secrets stay server-side; no OAuth SDK loaded in frontend
Provider Configuration
| Provider | Auth URL | Token URL | UserInfo URL | Scopes |
|---|---|---|---|---|
| accounts.google.com/o/oauth2/v2/auth | oauth2.googleapis.com/token | www.googleapis.com/oauth2/v2/userinfo | email, profile | |
| GitHub | github.com/login/oauth/authorize | github.com/login/oauth/access_token | api.github.com/user + /user/emails | user:email |
| Apple | appleid.apple.com/auth/authorize | appleid.apple.com/auth/token | (decoded from id_token) | name, email |
Environment Variables
# Already in .env.example
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
# New
APPLE_CLIENT_ID=
APPLE_TEAM_ID=
APPLE_KEY_ID=
APPLE_PRIVATE_KEY=
OAUTH_CALLBACK_BASE_URL=http://localhost:3000
Frontend Callback Page (/login/callback)
- Extracts
accessTokenandrefreshTokenfrom URL search params - Stores in localStorage, updates AuthContext
- Redirects to
/dashboard(or saved redirect target) - Shows loading spinner during processing
- Shows error message with retry link on failure
File Changes
New Files
| File | Purpose |
|---|---|
packages/server/src/routes/oauth.ts |
OAuth routes (/:provider, /:provider/callback) |
packages/server/src/lib/oauth-providers.ts |
Provider configs + token exchange + userinfo fetch |
packages/web/src/pages/LoginCallback.tsx |
OAuth callback landing page |
packages/web/src/components/AuthBranding.tsx |
Shared left-panel brand component |
packages/web/src/components/OAuthButtons.tsx |
Third-party login button group |
Modified Files
| File | Change |
|---|---|
packages/server/src/index.ts |
Register /auth/oauth route |
packages/web/src/pages/Login.tsx |
Refactor to left-right split layout |
packages/web/src/pages/Register.tsx |
Refactor to left-right split layout |
packages/web/src/App.tsx |
Add /login/callback route |
packages/web/src/lib/i18n.tsx |
Add translation keys |
.env.example |
Add Apple OAuth env vars |
No Changes Needed
prisma/schema.prisma— OAuthAccount model already exists- JWT signing logic — reuse existing
generateAccessToken/generateRefreshToken - Existing email/password auth — unchanged
No New Dependencies
- OAuth token exchange: Node native
fetch - Apple JWT client_secret signing: Node
cryptobuilt-in - No Passport.js, no OAuth libraries
User Action Required
Before testing OAuth, the developer must register apps on each provider:
- Google: Google Cloud Console → OAuth 2.0 Client → redirect URI:
{OAUTH_CALLBACK_BASE_URL}/auth/oauth/google/callback - GitHub: GitHub Developer Settings → OAuth App → callback URL:
{OAUTH_CALLBACK_BASE_URL}/auth/oauth/github/callback - Apple: Apple Developer → Services ID + Key → return URL:
{OAUTH_CALLBACK_BASE_URL}/auth/oauth/apple/callback(requires HTTPS)
Apple Sign In requires a paid Apple Developer account ($99/year) and HTTPS for callbacks. If unavailable, the Apple button can be displayed as "Coming Soon".