Google OAuth Integration - API Documentation
Synced from the source repositories. Do not edit by hand.
Version: 1.0 Status: Implementation Ready Created: 2025-11-08 Feature: Google Sign Up/Login
Table of Contents
- Overview
- Architecture
- Google Cloud Setup
- Database Schema
- API Endpoints
- Authentication Flow
- Security Considerations
- Environment Variables
- Error Handling
- Testing Guide
- UI Integration Guide
Overview
Goals
Enable users to sign up and log in to TurfAI using their Google accounts through OAuth 2.0, providing:
- Seamless Authentication: One-click Google sign-in
- Auto-registration: Automatically create user accounts from Google profiles
- Account Linking: Link existing email accounts to Google OAuth
- Profile Data: Import Google profile picture and verified email
- Security: Leverage Google's authentication security
Benefits
- User Experience: Faster signup/login without password management
- Security: OAuth 2.0 standard with Google's security infrastructure
- Email Verification: Google accounts are pre-verified
- Profile Data: Automatic profile picture and display name
- Trust: Users trust Google authentication
Architecture
Component Diagram
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ │ │ │ │ │
│ Frontend │────────▶│ DMS │────────▶│ Google │
│ (UI/Web) │ │ (Strapi) │ │ OAuth │
│ │ │ │ │ │
└─────────────┘ └──────────────┘ └─────────────┘
│ │ │
│ │ │
│ 1. Click "Sign in with Google" │
│────────────────────────────────────────────────▶│
│ │ │
│ 2. Redirect to /api/connect/google │
│◀──────────────────────── │
│ │ │
│ 3. Google OAuth consent screen │
│────────────────────────────────────────────────▶│
│ │ │
│ 4. User authorizes (email, profile) │
│◀────────────────────────────────────────────────│
│ │ │
│ 5. Redirect to callback with auth code │
│────────────────────────▶ │
│ │ │
│ 6. Exchange code for tokens │
│ │────────────────────────▶│
│ │ │
│ 7. Get user profile from Google │
│ │◀────────────────────────│
│ │ │
│ 8. Create/update user in DB │
│ │ │
│ 9. Return JWT + redirect to dashboard │
│◀──────────────────────── │
│ │ │
│ 10. Store JWT, access app │
│ │ │Authentication Flow Types
- New User Signup: Google account → Auto-create TurfAI user → Login
- Existing User Login: Google account → Find TurfAI user → Login
- Account Linking: Email user → Connect Google → Linked account
- Repeat Login: Google account → JWT refresh → Login
Google Cloud Setup
Prerequisites
- Google Cloud Project (existing or new)
- Admin access to Google Cloud Console
- Domain ownership (for production)
Step 1: Create OAuth 2.0 Credentials
-
Go to Google Cloud Console
-
Navigate to APIs & Services → Credentials
-
Click + CREATE CREDENTIALS → OAuth 2.0 Client ID
-
Configure OAuth consent screen (if not already done):
- User Type: External (for public access)
- App name: TurfAI
- User support email: Your email
- Developer contact: Your email
- Scopes: Add
emailandprofilescopes - Test users: Add your test email addresses
-
Create OAuth Client ID:
- Application type: Web application
- Name: TurfAI DMS OAuth Client
- Authorized JavaScript origins: (if needed for frontend)
https://turfai.iohttps://sandbox.turfai.iohttp://localhost:3000(development)
- Authorized redirect URIs:
- Production:
https://turfai-dms-eatorcypia-uc.a.run.app/api/connect/google/callback - Sandbox:
https://sandbox-dms.turfai.io/api/connect/google/callback - Development:
http://localhost:1337/api/connect/google/callback
- Production:
-
Save the Client ID and Client Secret
Step 2: Enable Required APIs
- Navigate to APIs & Services → Library
- Enable:
- Google+ API (for profile data)
- People API (for enhanced profile)
Step 3: OAuth Consent Screen Configuration
App Information:
- App name: TurfAI
- App logo: Upload TurfAI logo
- Application homepage: https://turfai.io
- Privacy policy: https://turfai.io/privacy
- Terms of service: https://turfai.io/terms
Scopes:
email- Read user email addressprofile- Read user basic profile infoopenid- Authenticate user identity
Publishing Status:
- Development: Testing mode (max 100 test users)
- Production: Submit for verification (required for public access)
Database Schema
Users Table Extensions
Extend the existing Strapi users table with Google OAuth fields:
-- Add Google OAuth fields to users table
ALTER TABLE users ADD COLUMN IF NOT EXISTS google_id VARCHAR(255) UNIQUE;
ALTER TABLE users ADD COLUMN IF NOT EXISTS google_email VARCHAR(255);
ALTER TABLE users ADD COLUMN IF NOT EXISTS google_profile_picture TEXT;
ALTER TABLE users ADD COLUMN IF NOT EXISTS oauth_provider VARCHAR(50) DEFAULT 'email';
ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verified BOOLEAN DEFAULT FALSE;
ALTER TABLE users ADD COLUMN IF NOT EXISTS google_connected_at TIMESTAMP;
ALTER TABLE users ADD COLUMN IF NOT EXISTS google_raw_profile JSONB;
-- Create index for faster Google ID lookups
CREATE INDEX IF NOT EXISTS idx_users_google_id ON users(google_id);
CREATE INDEX IF NOT EXISTS idx_users_oauth_provider ON users(oauth_provider);
-- Add comments for documentation
COMMENT ON COLUMN users.google_id IS 'Unique Google user identifier (sub claim from JWT)';
COMMENT ON COLUMN users.google_email IS 'Email from Google account (may differ from primary email)';
COMMENT ON COLUMN users.google_profile_picture IS 'Google profile picture URL';
COMMENT ON COLUMN users.oauth_provider IS 'Authentication provider: email, google, etc.';
COMMENT ON COLUMN users.email_verified IS 'Email verification status (auto-true for Google)';
COMMENT ON COLUMN users.google_connected_at IS 'Timestamp when Google was first connected';
COMMENT ON COLUMN users.google_raw_profile IS 'Raw Google profile data for debugging';New Table: OAuth Connections Audit Log
Track OAuth connection events for security and debugging:
CREATE TABLE oauth_connections (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
provider VARCHAR(50) NOT NULL,
provider_user_id VARCHAR(255),
connection_type VARCHAR(50), -- 'signup', 'login', 'link', 'reauth'
ip_address INET,
user_agent TEXT,
success BOOLEAN DEFAULT TRUE,
error_message TEXT,
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_oauth_connections_user ON oauth_connections(user_id);
CREATE INDEX idx_oauth_connections_provider ON oauth_connections(provider);
CREATE INDEX idx_oauth_connections_created ON oauth_connections(created_at);User Schema Example
{
"id": 123,
"username": "john_doe_google",
"email": "john.doe@gmail.com",
"display_name": "John Doe",
"password": null, // Google users may not have password
"google_id": "1234567890abcdef",
"google_email": "john.doe@gmail.com",
"google_profile_picture": "https://lh3.googleusercontent.com/a/...",
"oauth_provider": "google",
"email_verified": true,
"google_connected_at": "2025-11-08T10:30:00Z",
"google_raw_profile": {
"sub": "1234567890abcdef",
"name": "John Doe",
"given_name": "John",
"family_name": "Doe",
"picture": "https://...",
"email": "john.doe@gmail.com",
"email_verified": true,
"locale": "en"
},
"role": {
"id": 1,
"name": "Authenticated",
"type": "authenticated"
},
"created_at": "2025-11-08T10:30:00Z",
"updated_at": "2025-11-08T10:30:00Z"
}API Endpoints
1. Initiate Google OAuth Flow
Endpoint: GET /api/connect/google
Description: Redirects user to Google OAuth consent screen
Authentication: Not required
Query Parameters:
redirect_url(optional): URL to redirect after successful auth
Request:
GET /api/connect/google?redirect_url=https://turfai.io/dashboard
Host: turfai-dms-eatorcypia-uc.a.run.appResponse:
- HTTP 302 Redirect to Google OAuth consent screen
- User will be prompted to authorize TurfAI
Implementation Notes:
- Strapi built-in endpoint
- Automatically constructs OAuth URL with client ID and scopes
- Includes CSRF state parameter for security
2. OAuth Callback Handler
Endpoint: GET /api/connect/google/callback
Description: Handles Google OAuth callback, creates/updates user, returns JWT
Authentication: Not required (handled by OAuth code)
Query Parameters:
code: Authorization code from Googlestate: CSRF state parameter
Request (from Google redirect):
GET /api/connect/google/callback?code=4/0AY0e-g7...&state=abc123
Host: turfai-dms-eatorcypia-uc.a.run.appResponse - New User Signup:
HTTP/1.1 302 Found
Location: https://turfai.io/dashboard?jwt=eyJhbGciOiJIUzI1NiIs...
Set-Cookie: jwt=eyJhbGciOiJIUzI1NiIs...; HttpOnly; Secure; SameSite=LaxResponse - Existing User Login:
HTTP/1.1 302 Found
Location: https://turfai.io/dashboard?jwt=eyJhbGciOiJIUzI1NiIs...Error Response - OAuth Denied:
HTTP/1.1 302 Found
Location: https://turfai.io/login?error=access_denied&error_description=User+denied+accessError Response - Invalid Code:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid authorization code",
"data": {
"provider": "google"
}
}Implementation Notes:
- Strapi built-in endpoint with custom controller override
- Exchanges auth code for access token
- Fetches user profile from Google
- Creates new user if doesn't exist (auto-registration)
- Updates existing user if found by email or google_id
- Returns JWT token in URL and cookie
3. Get Google OAuth Status
Endpoint: GET /api/auth/google/status
Description: Check if current user has Google account connected
Authentication: Required (JWT)
Request:
GET /api/auth/google/status
Host: turfai-dms-eatorcypia-uc.a.run.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Response - Connected:
{
"google_connected": true,
"google_email": "john.doe@gmail.com",
"google_profile_picture": "https://lh3.googleusercontent.com/a/...",
"connected_at": "2025-11-08T10:30:00Z",
"can_disconnect": true,
"has_password": false
}Response - Not Connected:
{
"google_connected": false,
"can_connect": true,
"current_provider": "email"
}Error - Unauthorized:
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid token"
}4. Link Google Account to Existing User
Endpoint: POST /api/auth/google/link
Description: Link Google account to existing email-based account
Authentication: Required (JWT)
Request:
POST /api/auth/google/link
Host: turfai-dms-eatorcypia-uc.a.run.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"google_auth_code": "4/0AY0e-g7..."
}Response - Success:
{
"success": true,
"message": "Google account linked successfully",
"google_email": "john.doe@gmail.com",
"google_profile_picture": "https://...",
"linked_at": "2025-11-08T10:30:00Z"
}Error - Already Linked:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Google account already linked to this user"
}Error - Email Mismatch:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Google account email does not match your TurfAI email",
"data": {
"turfai_email": "john@example.com",
"google_email": "different@gmail.com",
"suggestion": "Please use a Google account with matching email or contact support"
}
}Error - Google Account In Use:
{
"statusCode": 409,
"error": "Conflict",
"message": "This Google account is already linked to another TurfAI account",
"data": {
"google_email": "john.doe@gmail.com"
}
}5. Disconnect Google Account
Endpoint: POST /api/auth/google/disconnect
Description: Disconnect Google OAuth from user account
Authentication: Required (JWT)
Request:
POST /api/auth/google/disconnect
Host: turfai-dms-eatorcypia-uc.a.run.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Response - Success:
{
"success": true,
"message": "Google account disconnected successfully",
"can_still_login": true,
"login_methods": ["email"]
}Error - Cannot Disconnect:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Cannot disconnect Google account - no alternative login method available",
"data": {
"reason": "no_password",
"suggestion": "Please set a password before disconnecting Google account",
"set_password_url": "/api/auth/set-password"
}
}Error - Not Connected:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Google account is not connected to this user"
}6. Get User Profile with OAuth Info
Endpoint: GET /api/users/me
Description: Get current user profile including OAuth connection status
Authentication: Required (JWT)
Request:
GET /api/users/me
Host: turfai-dms-eatorcypia-uc.a.run.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Response:
{
"id": 123,
"username": "john_doe",
"email": "john.doe@gmail.com",
"display_name": "John Doe",
"profile_picture": "https://lh3.googleusercontent.com/a/...",
"email_verified": true,
"oauth_provider": "google",
"google_connected": true,
"google_email": "john.doe@gmail.com",
"role": {
"id": 1,
"name": "Authenticated",
"type": "authenticated"
},
"created_at": "2025-11-08T10:30:00Z",
"updated_at": "2025-11-08T10:30:00Z"
}Authentication Flow
Flow 1: New User Signup via Google
1. User visits TurfAI signup page
2. User clicks "Sign up with Google"
3. Frontend redirects to: GET /api/connect/google
4. DMS redirects to Google OAuth consent screen
5. User authorizes TurfAI (email + profile scopes)
6. Google redirects to: GET /api/connect/google/callback?code=...
7. DMS exchanges code for access token
8. DMS fetches user profile from Google API
9. DMS checks if user exists (by google_id or email):
- NOT EXISTS: Create new user
* username = email prefix + random suffix
* email = google email
* google_id = Google sub
* google_email = Google email
* google_profile_picture = Google picture URL
* email_verified = true
* oauth_provider = 'google'
* role = 'Authenticated'
10. DMS generates JWT token
11. DMS redirects to frontend: https://turfai.io/dashboard?jwt=...
12. Frontend stores JWT in localStorage/cookie
13. User is logged in and redirected to dashboardFlow 2: Existing User Login via Google
1. User visits TurfAI login page
2. User clicks "Sign in with Google"
3. Frontend redirects to: GET /api/connect/google
4. DMS redirects to Google OAuth consent screen
5. User authorizes (or skips if already authorized)
6. Google redirects to: GET /api/connect/google/callback?code=...
7. DMS exchanges code for access token
8. DMS fetches user profile from Google API
9. DMS finds existing user by google_id
10. DMS updates user profile (picture, last login)
11. DMS generates JWT token
12. DMS redirects to frontend with JWT
13. User is logged inFlow 3: Email User Links Google Account
1. User logged in with email/password
2. User goes to Profile/Settings
3. User clicks "Connect Google Account"
4. Frontend calls: POST /api/auth/google/link-init
5. DMS returns Google OAuth URL
6. Frontend opens OAuth URL in popup/redirect
7. User authorizes on Google
8. Google redirects to callback
9. DMS links Google account to existing user:
- Validates email match (optional)
- Sets google_id, google_email, google_profile_picture
- oauth_provider remains 'email' (primary method)
10. DMS closes popup/redirects back
11. User can now login with either email or GoogleFlow 4: Disconnect Google Account
1. User logged in (has Google connected)
2. User goes to Profile/Settings → Connected Accounts
3. User clicks "Disconnect Google"
4. Frontend confirms: "Are you sure?"
5. Frontend calls: POST /api/auth/google/disconnect
6. DMS validates:
- User has alternative login (email + password)
- If no password: Error "Set password first"
7. DMS disconnects:
- Sets google_id = null
- Sets google_email = null
- Sets google_profile_picture = null
- oauth_provider = 'email'
8. User can no longer login with Google
9. User must use email/passwordSecurity Considerations
1. State Parameter (CSRF Protection)
Problem: OAuth redirect could be hijacked
Solution: Use state parameter to validate redirect
// Generate state on init
const state = crypto.randomBytes(16).toString('hex');
await redis.set(`oauth:state:${state}`, userId, 'EX', 600); // 10 min expiry
// Validate on callback
const validState = await redis.get(`oauth:state:${state}`);
if (!validState) {
throw new Error('Invalid state parameter - possible CSRF attack');
}
await redis.del(`oauth:state:${state}`); // One-time use2. Token Security
Access Tokens: Never stored in database (used immediately and discarded)
Refresh Tokens: Not used (Google sign-in is stateless, re-auth required)
JWT Tokens: Strapi-generated, short-lived (default 30 days, configurable)
3. Email Verification
Google Users: Email automatically verified (Google validates emails)
Email Conflicts: If email exists, link accounts or reject based on policy
4. Account Takeover Prevention
Scenario: User A has email X, User B tries to signup with Google using email X
Solution:
// On Google OAuth callback
const existingUser = await strapi.query('user').findOne({ email: googleEmail });
if (existingUser && !existingUser.google_id) {
// Email exists but not linked to Google
// OPTION 1: Auto-link (if email verified on both sides)
if (existingUser.email_verified && googleProfile.email_verified) {
await linkGoogleAccount(existingUser.id, googleProfile);
return generateJWT(existingUser);
}
// OPTION 2: Reject and ask to login first
throw new Error('Email already registered. Please login and connect Google from settings.');
}5. Profile Picture Security
Validation: Ensure Google picture URL is from googleusercontent.com
Storage: Store URL only (do not download/re-upload for security)
Fallback: Use initials avatar if Google picture unavailable
6. Scope Limitation
Only Request Necessary Scopes:
- ✅
email- Get user email - ✅
profile- Get basic profile (name, picture) - ✅
openid- OpenID Connect - ❌ NOT
drive- Not needed for sign-in - ❌ NOT
calendar- Not needed for sign-in
7. Rate Limiting
Implement rate limiting on OAuth endpoints:
// Max 10 OAuth attempts per IP per hour
app.use('/api/connect/google', rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 10,
message: 'Too many OAuth attempts, please try again later'
}));Environment Variables
DMS Environment Variables
# Google OAuth Configuration
GOOGLE_OAUTH_CLIENT_ID=1234567890-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com
GOOGLE_OAUTH_CLIENT_SECRET=GOCSPX-abc123def456ghi789jkl012mno
GOOGLE_OAUTH_REDIRECT_URI=https://turfai-dms-eatorcypia-uc.a.run.app/api/connect/google/callback
# OAuth Settings
OAUTH_AUTO_REGISTER=true # Auto-create user on first Google login
OAUTH_DEFAULT_ROLE=authenticated # Default role for OAuth users
OAUTH_EMAIL_VERIFICATION_REQUIRED=false # Skip email verification for Google users
OAUTH_ALLOW_ACCOUNT_LINKING=true # Allow linking Google to existing email accounts
OAUTH_REQUIRE_EMAIL_MATCH=false # Require email match when linking (true = strict)
# Frontend Configuration
FRONTEND_URL=https://turfai.io
OAUTH_SUCCESS_REDIRECT=/dashboard
OAUTH_ERROR_REDIRECT=/login
# Security
OAUTH_STATE_TTL=600 # State parameter TTL in seconds (10 min)
OAUTH_SESSION_TTL=2592000 # JWT session TTL in seconds (30 days)Development vs Production
Development (dms/.env.development):
GOOGLE_OAUTH_CLIENT_ID=dev-client-id.apps.googleusercontent.com
GOOGLE_OAUTH_CLIENT_SECRET=dev-secret
GOOGLE_OAUTH_REDIRECT_URI=http://localhost:1337/api/connect/google/callback
FRONTEND_URL=http://localhost:3000Production (dms/.env.production):
GOOGLE_OAUTH_CLIENT_ID=prod-client-id.apps.googleusercontent.com
GOOGLE_OAUTH_CLIENT_SECRET=prod-secret
GOOGLE_OAUTH_REDIRECT_URI=https://turfai-dms-eatorcypia-uc.a.run.app/api/connect/google/callback
FRONTEND_URL=https://turfai.ioError Handling
Common Errors
1. Invalid Redirect URI
Error from Google:
Error 400: redirect_uri_mismatch
The redirect URI in the request does not match the ones authorized for the OAuth client.Cause: Redirect URI not added to Google Console
Fix: Add exact redirect URI to Google OAuth Client authorized redirect URIs
2. Access Denied
Error:
{
"error": "access_denied",
"error_description": "User denied access"
}Cause: User clicked "Cancel" on Google consent screen
Handling: Redirect to login page with friendly message
3. Invalid Authorization Code
Error:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid authorization code"
}Causes:
- Code already used (one-time use)
- Code expired (valid for ~10 minutes)
- Code from wrong OAuth client
Fix: User must retry OAuth flow
4. Email Already Registered
Error:
{
"statusCode": 409,
"error": "Conflict",
"message": "Email already registered with different provider",
"data": {
"email": "john@example.com",
"registered_provider": "email",
"suggestion": "Please login with email and link Google from settings"
}
}Handling: Show message with link to login page
5. Google Account Already Linked
Error:
{
"statusCode": 409,
"error": "Conflict",
"message": "This Google account is already linked to another user",
"data": {
"google_email": "john@gmail.com"
}
}Handling: User must use different Google account or contact support
Testing Guide
Unit Tests
Test 1: OAuth URL Generation
describe('Google OAuth', () => {
it('should generate valid OAuth URL', () => {
const url = generateGoogleOAuthUrl();
expect(url).toContain('accounts.google.com/o/oauth2/v2/auth');
expect(url).toContain('client_id=');
expect(url).toContain('redirect_uri=');
expect(url).toContain('scope=email%20profile%20openid');
expect(url).toContain('state=');
});
});Test 2: New User Creation
it('should create new user from Google profile', async () => {
const googleProfile = {
sub: 'google_123',
email: 'test@gmail.com',
name: 'Test User',
picture: 'https://lh3.googleusercontent.com/a/test'
};
const user = await createUserFromGoogleProfile(googleProfile);
expect(user.google_id).toBe('google_123');
expect(user.email).toBe('test@gmail.com');
expect(user.email_verified).toBe(true);
expect(user.oauth_provider).toBe('google');
});Test 3: Account Linking
it('should link Google to existing email user', async () => {
// Create email user
const emailUser = await strapi.entityService.create('plugin::users-permissions.user', {
data: { email: 'test@example.com', username: 'testuser', password: 'hashed' }
});
// Link Google
const googleProfile = {
sub: 'google_456',
email: 'test@gmail.com'
};
await linkGoogleAccount(emailUser.id, googleProfile);
const updatedUser = await strapi.entityService.findOne('plugin::users-permissions.user', emailUser.id);
expect(updatedUser.google_id).toBe('google_456');
expect(updatedUser.oauth_provider).toBe('email'); // Primary provider unchanged
});Integration Tests
Test 4: Complete OAuth Flow
it('should complete full OAuth flow', async () => {
// 1. Initiate OAuth
const response1 = await request(app).get('/api/connect/google');
expect(response1.status).toBe(302);
expect(response1.header.location).toContain('accounts.google.com');
// 2. Mock Google callback with auth code
const mockCode = 'mock_auth_code_123';
const response2 = await request(app)
.get('/api/connect/google/callback')
.query({ code: mockCode, state: 'valid_state' });
expect(response2.status).toBe(302);
expect(response2.header.location).toContain('dashboard');
expect(response2.header.location).toContain('jwt=');
});Manual Testing
Test Scenario 1: New User Signup
- Clear database of test user
- Open browser to
http://localhost:3000/signup - Click "Sign up with Google"
- Authorize with test Google account
- Verify redirect to dashboard
- Verify user created in database with google_id
- Verify profile picture displayed
Test Scenario 2: Existing User Login
- Use existing Google-authenticated user
- Logout from TurfAI
- Click "Sign in with Google"
- Should login immediately (no consent screen)
- Verify redirect to dashboard
Test Scenario 3: Account Linking
- Create email user:
test@example.com - Login with email/password
- Go to Settings → Connected Accounts
- Click "Connect Google"
- Authorize with Google
- Verify Google account linked
- Logout and login with Google
- Verify same user account
Test Scenario 4: Error Handling
- Initiate OAuth flow
- Click "Cancel" on Google consent
- Verify redirect to login with error message
- Re-initiate and complete successfully
UI Integration Guide
Frontend Implementation
Step 1: Add Google Sign In Button
Login Page (/login):
import { useState } from 'react';
import { Button } from '@/components/ui/button';
export default function LoginPage() {
const handleGoogleLogin = () => {
// Redirect to DMS OAuth endpoint
const dmsUrl = process.env.NEXT_PUBLIC_DMS_URL || 'http://localhost:1337';
const redirectUrl = `${window.location.origin}/auth/callback`;
window.location.href = `${dmsUrl}/api/connect/google?redirect_url=${encodeURIComponent(redirectUrl)}`;
};
return (
<div className="login-container">
<h1>Login to TurfAI</h1>
{/* Email/Password Form */}
<form onSubmit={handleEmailLogin}>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
<div className="divider">OR</div>
{/* Google Sign In Button */}
<Button
onClick={handleGoogleLogin}
variant="outline"
className="google-signin-btn"
>
<img src="/google-icon.svg" alt="Google" />
Sign in with Google
</Button>
</div>
);
}Step 2: Handle OAuth Callback
Callback Page (/auth/callback):
'use client';
import { useEffect } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
export default function AuthCallbackPage() {
const router = useRouter();
const searchParams = useSearchParams();
useEffect(() => {
// Get JWT from URL (returned by DMS)
const jwt = searchParams.get('jwt');
const error = searchParams.get('error');
if (error) {
// Handle OAuth error
console.error('OAuth error:', error);
router.push(`/login?error=${error}`);
return;
}
if (jwt) {
// Store JWT in localStorage
localStorage.setItem('jwt', jwt);
// Update auth context/state
// ...
// Redirect to dashboard
router.push('/dashboard');
} else {
// No JWT, redirect to login
router.push('/login?error=no_token');
}
}, [searchParams, router]);
return (
<div className="auth-callback">
<p>Completing authentication...</p>
</div>
);
}Step 3: Profile Settings - Connected Accounts
Settings Page (/settings/accounts):
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Avatar } from '@/components/ui/avatar';
export default function ConnectedAccountsSettings() {
const [googleStatus, setGoogleStatus] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchGoogleStatus();
}, []);
const fetchGoogleStatus = async () => {
try {
const response = await fetch(`${process.env.NEXT_PUBLIC_DMS_URL}/api/auth/google/status`, {
headers: {
'Authorization': `Bearer ${localStorage.getItem('jwt')}`
}
});
const data = await response.json();
setGoogleStatus(data);
} catch (error) {
console.error('Failed to fetch Google status:', error);
} finally {
setLoading(false);
}
};
const handleConnectGoogle = () => {
const dmsUrl = process.env.NEXT_PUBLIC_DMS_URL;
window.location.href = `${dmsUrl}/api/connect/google`;
};
const handleDisconnectGoogle = async () => {
if (!confirm('Are you sure you want to disconnect your Google account?')) {
return;
}
try {
const response = await fetch(`${process.env.NEXT_PUBLIC_DMS_URL}/api/auth/google/disconnect`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('jwt')}`
}
});
if (response.ok) {
alert('Google account disconnected successfully');
fetchGoogleStatus();
} else {
const error = await response.json();
alert(error.message);
}
} catch (error) {
console.error('Failed to disconnect Google:', error);
alert('Failed to disconnect Google account');
}
};
if (loading) return <div>Loading...</div>;
return (
<div className="connected-accounts">
<h2>Connected Accounts</h2>
<div className="account-card google">
<div className="account-header">
<img src="/google-icon.svg" alt="Google" />
<h3>Google</h3>
</div>
{googleStatus?.google_connected ? (
<div className="account-connected">
<Avatar src={googleStatus.google_profile_picture} />
<div className="account-info">
<p className="email">{googleStatus.google_email}</p>
<p className="connected-date">
Connected on {new Date(googleStatus.connected_at).toLocaleDateString()}
</p>
</div>
<Button
onClick={handleDisconnectGoogle}
variant="destructive"
disabled={!googleStatus.can_disconnect}
>
Disconnect
</Button>
{!googleStatus.has_password && (
<p className="warning">
⚠️ Set a password before disconnecting Google
</p>
)}
</div>
) : (
<div className="account-not-connected">
<p>Connect your Google account for easy sign-in</p>
<Button onClick={handleConnectGoogle}>
Connect Google
</Button>
</div>
)}
</div>
</div>
);
}Step 4: API Service Helper
Services (/services/authService.ts):
const DMS_URL = process.env.NEXT_PUBLIC_DMS_URL || 'http://localhost:1337';
export const authService = {
// Initiate Google OAuth
googleLogin: (redirectUrl?: string) => {
const url = new URL(`${DMS_URL}/api/connect/google`);
if (redirectUrl) {
url.searchParams.set('redirect_url', redirectUrl);
}
window.location.href = url.toString();
},
// Get Google connection status
getGoogleStatus: async () => {
const response = await fetch(`${DMS_URL}/api/auth/google/status`, {
headers: {
'Authorization': `Bearer ${localStorage.getItem('jwt')}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch Google status');
}
return response.json();
},
// Disconnect Google account
disconnectGoogle: async () => {
const response = await fetch(`${DMS_URL}/api/auth/google/disconnect`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('jwt')}`
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
return response.json();
}
};Implementation Checklist
Phase 1: Google Cloud Setup ✅
- Create Google Cloud Project
- Enable Google+ API and People API
- Configure OAuth consent screen
- Create OAuth 2.0 Client ID
- Add authorized redirect URIs
- Save Client ID and Client Secret
Phase 2: Database Schema ✅
- Run database migration to add Google OAuth fields
- Create
oauth_connectionsaudit table - Add indexes for performance
- Test schema with sample data
Phase 3: Strapi Configuration ✅
- Install required npm packages (
@strapi/plugin-users-permissions) - Configure
dms/config/plugins.jswith Google provider - Add environment variables to
.env - Override default OAuth callbacks (if needed)
- Test OAuth endpoints
Phase 4: Custom Endpoints ✅
- Implement
GET /api/auth/google/status - Implement
POST /api/auth/google/link - Implement
POST /api/auth/google/disconnect - Add validation and error handling
- Test all endpoints
Phase 5: Security & Testing ✅
- Implement state parameter validation
- Add rate limiting to OAuth endpoints
- Test account linking scenarios
- Test error handling (denied access, invalid code)
- Security audit
Phase 6: Documentation ✅
- Complete API documentation (this document)
- Create UI integration guide
- Document error codes and messages
- Create testing guide
- Update deployment docs with OAuth env vars
Phase 7: UI Integration (UI Team)
- Add Google Sign In button to login/signup
- Implement OAuth callback handling
- Create Connected Accounts settings page
- Add Google profile picture to navbar
- Test complete flow end-to-end
Next Steps
- Review this document with team
- Create Google Cloud OAuth credentials
- Run database migrations
- Configure Strapi Google OAuth plugin
- Test OAuth flow in development
- Deploy to staging and test
- Provide documentation to UI team
- Deploy to production
Document Version: 1.0 Last Updated: 2025-11-08 Next Review: After Phase 5 completion