# Salesforce OAuth - Backend Callback Flow

## Overview
This document explains the backend-handled OAuth callback flow for Salesforce authentication, where the backend receives the authorization code directly from Salesforce and handles the token exchange.

---

## Architecture Flow

```
┌─────────────────┐
│  Mobile App     │
│  (SalesforceAuth)│
└────────┬────────┘
         │ 1. User taps Salesforce
         │
         ▼
┌─────────────────────────────────────────────────────────┐
│ Build OAuth URL with backend callback:                  │
│ https://login.salesforce.com/services/oauth2/authorize  │
│   ?client_id=YOUR_CLIENT_ID                             │
│   &redirect_uri=YOUR_BACKEND_URL/callback               │
│       ?user_uuid=USER_ID&service_name=salesforce        │
│   &response_type=code                                   │
│   &scope=api refresh_token offline_access...            │
└────────────────┬────────────────────────────────────────┘
                 │ 2. Open in WebView
                 ▼
        ┌────────────────┐
        │   Salesforce   │
        │  Login Page    │
        └────────┬───────┘
                 │ 3. User authenticates
                 │
                 ▼
        ┌────────────────────────────────────────────┐
        │ Salesforce redirects to backend callback:  │
        │ https://YOUR_BACKEND/callback              │
        │   ?user_uuid=USER_ID                       │
        │   &service_name=salesforce                 │
        │   &code=AUTHORIZATION_CODE                 │
        └────────┬───────────────────────────────────┘
                 │ 4. Backend receives callback
                 ▼
        ┌────────────────────────────────────────────┐
        │          Your Backend Server               │
        │                                            │
        │ • Extract: authorization_code, user_uuid,  │
        │           service_name from query params   │
        │                                            │
        │ • Exchange code for tokens with Salesforce:│
        │   POST /services/oauth2/token              │
        │     grant_type=authorization_code          │
        │     code=CODE                              │
        │     client_id=YOUR_CLIENT_ID               │
        │     client_secret=YOUR_CLIENT_SECRET       │
        │     redirect_uri=YOUR_CALLBACK_URL         │
        │                                            │
        │ • Store tokens (encrypted) in database     │
        │   associated with user_uuid                │
        │                                            │
        │ • Return HTML page or redirect with        │
        │   status indicator                         │
        └────────┬───────────────────────────────────┘
                 │ 5. Backend responds
                 │
                 ▼
        ┌────────────────────────────────────────────┐
        │ Backend returns success/error page:        │
        │                                            │
        │ Success: ?status=success                   │
        │   OR     ?success=true                     │
        │                                            │
        │ Error:   ?status=error                     │
        │          &message=Error+description        │
        └────────┬───────────────────────────────────┘
                 │ 6. WebView detects callback response
                 ▼
        ┌────────────────┐
        │  Mobile App    │
        │ • Detect backend callback URL             │
        │ • Check for status=success or error       │
        │ • Show success/error to user              │
        │ • Close auth modal                        │
        └────────────────┘
```

---

## Backend Implementation

### Endpoint: `GET /api/v1/users/oauth/callback`

#### Query Parameters (Received from Salesforce)
```typescript
{
  user_uuid: string;        // User ID passed in redirect_uri
  service_name: string;     // 'salesforce' passed in redirect_uri
  code: string;             // Authorization code from Salesforce
  state?: string;           // Optional CSRF token
}
```

#### Implementation Steps

##### 1. Extract Query Parameters
```javascript
const { code: authorization_code, user_uuid, service_name } = req.query;

// Validate required parameters
if (!authorization_code || !user_uuid || !service_name) {
  return res.redirect(`/oauth/error?message=Missing required parameters`);
}

// Verify service_name is 'salesforce'
if (service_name !== 'salesforce') {
  return res.redirect(`/oauth/error?message=Invalid service name`);
}
```

##### 2. Exchange Authorization Code for Tokens
```javascript
const axios = require('axios');

try {
  const tokenResponse = await axios.post(
    'https://login.salesforce.com/services/oauth2/token',
    null,
    {
      params: {
        grant_type: 'authorization_code',
        code: authorization_code,
        client_id: process.env.SALESFORCE_CLIENT_ID,
        client_secret: process.env.SALESFORCE_CLIENT_SECRET,
        redirect_uri: `${process.env.BACKEND_URL}/api/v1/users/oauth/callback?user_uuid=${user_uuid}&service_name=salesforce`
      },
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    }
  );

  const {
    access_token,
    refresh_token,
    instance_url,
    id,
    issued_at,
    signature
  } = tokenResponse.data;

  // Store tokens...
  
} catch (error) {
  console.error('Token exchange failed:', error);
  return res.redirect(`/oauth/error?message=${encodeURIComponent(error.message)}`);
}
```

##### 3. Store Tokens Securely
```javascript
// Encrypt tokens before storing
const encryptedAccessToken = encrypt(access_token);
const encryptedRefreshToken = encrypt(refresh_token);

await db.integrations.upsert({
  where: {
    user_id: user_uuid,
    service_name: 'salesforce'
  },
  update: {
    access_token: encryptedAccessToken,
    refresh_token: encryptedRefreshToken,
    instance_url: instance_url,
    token_issued_at: new Date(parseInt(issued_at)),
    updated_at: new Date()
  },
  create: {
    user_id: user_uuid,
    service_name: 'salesforce',
    access_token: encryptedAccessToken,
    refresh_token: encryptedRefreshToken,
    instance_url: instance_url,
    token_issued_at: new Date(parseInt(issued_at)),
    created_at: new Date()
  }
});
```

##### 4. Return Success Response
```javascript
// Option 1: Redirect to success page with status
res.redirect('/oauth/success?status=success&service=salesforce');

// Option 2: Return HTML page that mobile app can detect
res.send(`
  <!DOCTYPE html>
  <html>
  <head>
    <title>Authentication Successful</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
      body {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
        margin: 0;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      }
      .container {
        text-align: center;
        background: white;
        padding: 40px;
        border-radius: 12px;
        box-shadow: 0 10px 40px rgba(0,0,0,0.2);
      }
      .success-icon {
        font-size: 64px;
        color: #10b981;
        margin-bottom: 20px;
      }
      h1 {
        color: #1f2937;
        margin-bottom: 10px;
      }
      p {
        color: #6b7280;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="success-icon">✓</div>
      <h1>Authentication Successful!</h1>
      <p>Your Salesforce account has been connected.</p>
      <p>You can close this window now.</p>
    </div>
    <script>
      // Mobile app will detect this URL and close the WebView
      window.location.search = '?status=success';
    </script>
  </body>
  </html>
`);
```

##### 5. Handle Errors
```javascript
// If token exchange or storage fails
res.send(`
  <!DOCTYPE html>
  <html>
  <head>
    <title>Authentication Failed</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
      body {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
        margin: 0;
        background: linear-gradient(135deg, #f87171 0%, #ef4444 100%);
      }
      .container {
        text-align: center;
        background: white;
        padding: 40px;
        border-radius: 12px;
        box-shadow: 0 10px 40px rgba(0,0,0,0.2);
      }
      .error-icon {
        font-size: 64px;
        color: #ef4444;
        margin-bottom: 20px;
      }
      h1 {
        color: #1f2937;
        margin-bottom: 10px;
      }
      p {
        color: #6b7280;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="error-icon">✗</div>
      <h1>Authentication Failed</h1>
      <p>${errorMessage || 'An error occurred during authentication.'}</p>
      <p>Please try again.</p>
    </div>
    <script>
      window.location.search = '?status=error&message=${encodeURIComponent(errorMessage)}';
    </script>
  </body>
  </html>
`);
```

---

## Mobile App Detection Logic

The mobile app (SalesforceAuth component) detects the backend callback using these helper functions:

### Detection Functions
```typescript
// Check if URL is the backend callback URI
const isBackendCallbackUri = (url: string): boolean => {
  return url.includes('/api/v1/users/oauth/callback');
};

// Check if backend callback indicates success
const isCallbackSuccess = (url: string): boolean => {
  const status = getQueryParamFromUrl(url, 'status');
  const success = getQueryParamFromUrl(url, 'success');
  return status === 'success' || success === 'true';
};

// Check if backend callback indicates error
const isCallbackError = (url: string): boolean => {
  const status = getQueryParamFromUrl(url, 'status');
  const error = getQueryParamFromUrl(url, 'error');
  return status === 'error' || status === 'failure' || !!error;
};
```

### Navigation State Handler
```typescript
handleNavigationStateChange = (navState) => {
  const { url, loading } = navState;
  
  // Check if this is the backend callback URL
  if (isBackendCallbackUri(url)) {
    console.log('Backend callback detected');
    
    // Show processing modal
    setState({ processing: true, processingStep: 'Processing...' });
    
    // Check for success
    if (isCallbackSuccess(url)) {
      handleBackendSuccess();
      return;
    }
    
    // Check for error
    if (isCallbackError(url)) {
      const errorMessage = getQueryParamFromUrl(url, 'message') || 'Authentication failed';
      handleBackendError(errorMessage);
      return;
    }
  }
};
```

---

## Complete Backend Example (Node.js/Express)

```javascript
const express = require('express');
const axios = require('axios');
const crypto = require('crypto');

const router = express.Router();

// Encryption utilities
function encrypt(text) {
  const algorithm = 'aes-256-gcm';
  const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
  const iv = crypto.randomBytes(16);
  
  const cipher = crypto.createCipheriv(algorithm, key, iv);
  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  
  const authTag = cipher.getAuthTag();
  
  return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}

// OAuth Callback Endpoint
router.get('/api/v1/users/oauth/callback', async (req, res) => {
  const { code: authorization_code, user_uuid, service_name } = req.query;
  
  console.log('[OAuth Callback] Received:', { user_uuid, service_name, hasCode: !!authorization_code });
  
  // Validate required parameters
  if (!authorization_code || !user_uuid || !service_name) {
    return res.send(errorPage('Missing required parameters'));
  }
  
  if (service_name !== 'salesforce') {
    return res.send(errorPage('Invalid service name'));
  }
  
  try {
    // Exchange authorization code for tokens
    const tokenResponse = await axios.post(
      'https://login.salesforce.com/services/oauth2/token',
      null,
      {
        params: {
          grant_type: 'authorization_code',
          code: authorization_code,
          client_id: process.env.SALESFORCE_CLIENT_ID,
          client_secret: process.env.SALESFORCE_CLIENT_SECRET,
          redirect_uri: `${process.env.BACKEND_URL}/api/v1/users/oauth/callback?user_uuid=${user_uuid}&service_name=salesforce`
        },
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        }
      }
    );
    
    const {
      access_token,
      refresh_token,
      instance_url,
      id,
      issued_at
    } = tokenResponse.data;
    
    console.log('[OAuth Callback] Token exchange successful');
    
    // Encrypt and store tokens
    const encryptedAccessToken = encrypt(access_token);
    const encryptedRefreshToken = encrypt(refresh_token);
    
    await db.integrations.upsert({
      where: {
        user_id: user_uuid,
        service_name: 'salesforce'
      },
      update: {
        access_token: encryptedAccessToken,
        refresh_token: encryptedRefreshToken,
        instance_url: instance_url,
        token_issued_at: new Date(parseInt(issued_at)),
        updated_at: new Date()
      },
      create: {
        user_id: user_uuid,
        service_name: 'salesforce',
        access_token: encryptedAccessToken,
        refresh_token: encryptedRefreshToken,
        instance_url: instance_url,
        token_issued_at: new Date(parseInt(issued_at)),
        created_at: new Date()
      }
    });
    
    console.log('[OAuth Callback] Tokens stored successfully');
    
    // Return success page
    return res.send(successPage());
    
  } catch (error) {
    console.error('[OAuth Callback] Error:', error);
    return res.send(errorPage(error.message));
  }
});

// Success page HTML
function successPage() {
  return `
    <!DOCTYPE html>
    <html>
    <head>
      <title>Success</title>
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <style>
        body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
        .container { background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); display: inline-block; }
        .icon { font-size: 64px; color: #10b981; margin-bottom: 20px; }
        h1 { color: #1f2937; }
        p { color: #6b7280; }
      </style>
    </head>
    <body>
      <div class="container">
        <div class="icon">✓</div>
        <h1>Authentication Successful!</h1>
        <p>Your Salesforce account has been connected.</p>
      </div>
      <script>
        setTimeout(() => { window.location.search = '?status=success'; }, 500);
      </script>
    </body>
    </html>
  `;
}

// Error page HTML
function errorPage(message) {
  return `
    <!DOCTYPE html>
    <html>
    <head>
      <title>Error</title>
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <style>
        body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: linear-gradient(135deg, #f87171 0%, #ef4444 100%); }
        .container { background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); display: inline-block; }
        .icon { font-size: 64px; color: #ef4444; margin-bottom: 20px; }
        h1 { color: #1f2937; }
        p { color: #6b7280; }
      </style>
    </head>
    <body>
      <div class="container">
        <div class="icon">✗</div>
        <h1>Authentication Failed</h1>
        <p>${message || 'An error occurred.'}</p>
      </div>
      <script>
        setTimeout(() => { window.location.search = '?status=error&message=${encodeURIComponent(message)}'; }, 500);
      </script>
    </body>
    </html>
  `;
}

module.exports = router;
```

---

## Configuration Checklist

### Salesforce Connected App
- ✅ Callback URL: `https://YOUR_BACKEND_URL/api/v1/users/oauth/callback`
- ✅ OAuth Scopes: api, refresh_token, offline_access, profile, email, address, phone

### Mobile App (SalesforceAuth.tsx)
- ✅ `BACKEND_BASE_URL`: Your backend domain
- ✅ `CALLBACK_PATH`: `/api/v1/users/oauth/callback`
- ✅ `CLIENT_ID`: Salesforce Consumer Key

### Backend Environment Variables
- ✅ `SALESFORCE_CLIENT_ID`: Consumer Key from Salesforce
- ✅ `SALESFORCE_CLIENT_SECRET`: Consumer Secret from Salesforce
- ✅ `BACKEND_URL`: Your public backend URL
- ✅ `ENCRYPTION_KEY`: 32-byte hex string for token encryption

---

## Testing

1. **Test OAuth Flow**:
   - Open app → CRM & Sales → Salesforce
   - Complete Salesforce login
   - Verify redirect to backend callback
   - Check backend logs for token exchange
   - Verify success page appears
   - Confirm modal closes automatically

2. **Test Error Handling**:
   - Simulate invalid credentials
   - Simulate network failure
   - Verify error page displays
   - Confirm retry functionality works

3. **Verify Token Storage**:
   - Check database for encrypted tokens
   - Verify user_id association
   - Confirm refresh_token is stored

---

## Security Considerations

1. **HTTPS Only**: Always use HTTPS for callback URLs
2. **Token Encryption**: Encrypt tokens before storing in database
3. **Client Secret**: Never expose client secret to mobile app
4. **State Parameter**: Add CSRF protection using state parameter
5. **Token Expiration**: Implement token refresh logic
6. **Access Control**: Validate user_uuid belongs to authenticated session

---

## Troubleshooting

| Issue | Solution |
|-------|----------|
| Callback URL mismatch | Ensure Salesforce Connected App callback URL exactly matches backend URL with query params |
| Token exchange fails | Check client_id and client_secret are correct |
| Authorization code expired | Codes expire in 15 minutes - ensure timely exchange |
| Backend not reachable | Verify backend URL is publicly accessible (use ngrok for local testing) |
| WebView doesn't detect success | Ensure backend returns HTML with `?status=success` in URL |

---

**Last Updated**: October 2025  
**Version**: 2.0.0 (Backend Callback Flow)

