# Agility CMS & Management API TypeScript SDK

## Authentication & Setup

This guide covers how to authenticate with the Agility CMS Management API and initialize the SDK for making API requests.

**🚀 NEW: Simplified Authentication**
We now provide a streamlined authentication experience with secure token storage and automatic token refresh. The new `ApiClient.auth()` method handles the entire OAuth flow internally.

### Prerequisites
1. Valid Agility CMS credentials
2. A configured OAuth application in your Agility CMS instance
3. A valid redirect URI for OAuth flow
4. **Node.js**: For secure token storage, keytar is required (`npm install keytar`)

### Setup Instructions

#### Install keytar for Secure Token Storage
```bash
npm install keytar
```

**Platform Requirements:**
- **macOS**: Uses Keychain (built-in)
- **Windows**: Uses Windows Credential Store (built-in)
- **Linux**: Requires `libsecret` development package
  ```bash
  # Ubuntu/Debian
  sudo apt-get install libsecret-1-dev
  
  # Red Hat/CentOS
  sudo yum install libsecret-devel
  ```

### Authentication Approaches

You have two options for authentication:

#### 1. **Recommended: Simple OAuth Flow**
Use the new `ApiClient.auth()` method for streamlined authentication:

```typescript
import * as mgmtApi from '@agility/management-sdk';

// Simple OAuth authentication
const apiClient = new mgmtApi.ApiClient();
await apiClient.auth();

// Client is now authenticated - make API calls directly
const guid = "your-website-guid";
const locale = "en-us";
const contentItem = await apiClient.contentMethods.getContentItem(22, guid, locale);
```

The `auth()` method:
- Generates OAuth authorization URL
- Handles token exchange automatically
- Stores tokens securely using keytar
- Provides automatic token refresh

#### 2. **Manual Token Management (Existing Pattern)**
For applications that need to handle tokens manually, the traditional approach is still fully supported:

```typescript
import * as mgmtApi from "@agility/management-sdk";

// Manual token approach (still supported)
const options = new mgmtApi.Options();
options.token = "your-access-token";
const apiClient = new mgmtApi.ApiClient(options);

const guid = "your-website-guid";
const locale = "en-us";

const contentItem = await apiClient.contentMethods.getContentItem(22, guid, locale);
console.log(JSON.stringify(contentItem));
```

For custom OAuth implementations, you can handle the OAuth flow manually:

Before using the SDK, you must authenticate against the Agility Management API to obtain a valid access token. This token is required for all subsequent API requests.

The authentication process uses OAuth 2.0 and requires multiple steps:

#### Step 1: Initiate Authorization Flow

First, initiate the authorization flow by making a GET request to the authorization endpoint:

```javascript
const authUrl = 'https://mgmt.aglty.io/oauth/authorize';

//if you wish to implement offline access using refresh tokens, use this URL (enables refresh tokens)
//const authUrl = 'https://mgmt.aglty.io/oauth/authorize?scope=offline-access '; 

const params = new URLSearchParams({
  response_type: 'code',
  redirect_uri: 'YOUR_REDIRECT_URI',
  state: 'YOUR_STATE',
  scope: 'openid profile email offline_access'
});

// Redirect the user to the authorization URL
window.location.href = `${authUrl}?${params.toString()}`;
```

#### Step 2: Exchange Authorization Code for Access Token

After successful authentication, you'll receive an authorization code at your redirect URI. Use this code to obtain an access token:

```javascript
const response = await fetch('https://mgmt.aglty.io/oauth/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  body: new URLSearchParams({
    code: 'YOUR_AUTHORIZATION_CODE'
  })
});

const { access_token, refresh_token, expires_in } = await response.json();
```

#### Step 3: Initialize the SDK

Use the obtained token to initialize the SDK:

```javascript
import * as mgmtApi from "@agility/management-sdk";

// Initialize the Options Class with your authentication token
let options = new mgmtApi.Options();
options.token = access_token; // Use the token obtained from authentication

// Initialize the APIClient Class
let apiClient = new mgmtApi.ApiClient(options);

let guid = "<<Provide the Guid of the Website>>";
let locale = "<<Provide the locale of the Website>>"; // Example: en-us

// Now you can make authenticated requests
var contentItem = await apiClient.contentMethods.getContentItem(22, guid, locale);
console.log(JSON.stringify(contentItem));
```

#### Step 4: Refresh Access Tokens

When the access token expires, use the refresh token to obtain a new access token:

```javascript
const response = await fetch('https://mgmt.aglty.io/oauth/refresh', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    refresh_token: 'YOUR_REFRESH_TOKEN'
  })
});

const { access_token, refresh_token, expires_in } = await response.json();
```

### Important Authentication Notes

- **Token Lifetime**: The access token has a limited lifetime (typically 1 hour)
- **Refresh Tokens**: The refresh token can be used to obtain new access tokens
- **Security**: Store refresh tokens securely and never expose them in client-side code
- **Error Handling**: Implement proper error handling for authentication failures

### Making API Requests

Once authenticated, you can make requests using the SDK:

#### Using New Simple Authentication (Recommended)
```typescript
import * as mgmtApi from '@agility/management-sdk';

// Simple OAuth authentication
const apiClient = new mgmtApi.ApiClient();
await apiClient.auth();

// Client is now authenticated and handles token refresh automatically
const guid = "your-website-guid";
const locale = "en-us";
const contentItem = await apiClient.contentMethods.getContentItem(22, guid, locale);
console.log(JSON.stringify(contentItem));
```

#### Manual Client Creation (Existing Pattern)
```javascript
import * as mgmtApi from "@agility/management-sdk";

//initialize the Options Class
let options = new mgmtApi.Options();

options.token = "<<Provide Auth Token>>"
//Initialize the APIClient Class
let apiClient = new mgmtApi.ApiClient(options);

let guid = "<<Provide the Guid of the Website>>";
let locale = "<<Provide the locale of the Website>>"; //Example: en-us

//make the request: get a content item with the ID '22'
var contentItem = await apiClient.contentMethods.getContentItem(22,guid, locale);

//To log the response of the contentItem object in console.
console.log(JSON.stringify(contentItem));
```

### OAuth Flow Examples

#### For Web Applications

```javascript
// Redirect user to authorization URL
function initiateAuth() {
    const authUrl = 'https://mgmt.aglty.io/oauth/authorize';
    const params = new URLSearchParams({
        response_type: 'code',
        redirect_uri: window.location.origin + '/auth/callback',
        state: generateRandomState(),
        scope: 'openid profile email offline_access'
    });
    
    window.location.href = `${authUrl}?${params.toString()}`;
}

// Handle callback in your redirect URI endpoint
async function handleAuthCallback(code) {
    try {
        const response = await fetch('https://mgmt.aglty.io/oauth/token', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: new URLSearchParams({
                code: code
            })
        });
        
        const tokens = await response.json();
        
        // Store tokens securely
        localStorage.setItem('access_token', tokens.access_token);
        localStorage.setItem('refresh_token', tokens.refresh_token);
        
        return tokens;
    } catch (error) {
        console.error('Authentication failed:', error);
        throw error;
    }
}
```

#### For Server-Side Applications

```javascript
// Express.js example
app.get('/auth/agility', (req, res) => {
    const authUrl = 'https://mgmt.aglty.io/oauth/authorize';
    const params = new URLSearchParams({
        response_type: 'code',
        redirect_uri: 'https://yourapp.com/auth/callback',
        state: req.session.state,
        scope: 'openid profile email offline_access'
    });
    
    res.redirect(`${authUrl}?${params.toString()}`);
});

app.get('/auth/callback', async (req, res) => {
    const { code, state } = req.query;
    
    // Verify state parameter
    if (state !== req.session.state) {
        return res.status(400).send('Invalid state parameter');
    }
    
    try {
        const response = await fetch('https://mgmt.aglty.io/oauth/token', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: new URLSearchParams({
                code: code
            })
        });
        
        const tokens = await response.json();
        
        // Store tokens in session or database
        req.session.tokens = tokens;
        
        res.redirect('/dashboard');
    } catch (error) {
        res.status(500).send('Authentication failed');
    }
});
```

### Token Management

#### Automatic Token Refresh

```javascript
class AgilityTokenManager {
    constructor(refreshToken) {
        this.refreshToken = refreshToken;
        this.accessToken = null;
        this.expiresAt = null;
    }
    
    async getValidToken() {
        if (!this.accessToken || Date.now() >= this.expiresAt) {
            await this.refreshAccessToken();
        }
        return this.accessToken;
    }
    
    async refreshAccessToken() {
        try {
            const response = await fetch('https://mgmt.aglty.io/oauth/refresh', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    refresh_token: this.refreshToken
                })
            });
            
            const tokens = await response.json();
            
            this.accessToken = tokens.access_token;
            this.refreshToken = tokens.refresh_token; // Update refresh token if provided
            this.expiresAt = Date.now() + (tokens.expires_in * 1000);
            
            return tokens;
        } catch (error) {
            console.error('Token refresh failed:', error);
            throw error;
        }
    }
}

// Usage
const tokenManager = new AgilityTokenManager('your-refresh-token');
const token = await tokenManager.getValidToken();

const options = new mgmtApi.Options();
options.token = token;
const apiClient = new mgmtApi.ApiClient(options);
```

### Error Handling

#### Common Authentication Errors

```javascript
async function makeAuthenticatedRequest() {
    try {
        const apiClient = new mgmtApi.ApiClient({ token: accessToken });
        const result = await apiClient.contentMethods.getContentItem(123, guid, locale);
        return result;
    } catch (error) {
        if (error.response?.status === 401) {
            // Token expired or invalid
            console.log('Authentication failed - refreshing token');
            await refreshAccessToken();
            
            // Retry the request
            const apiClient = new mgmtApi.ApiClient({ token: newAccessToken });
            return await apiClient.contentMethods.getContentItem(123, guid, locale);
        } else if (error.response?.status === 403) {
            // Insufficient permissions
            console.error('Access denied - insufficient permissions');
            throw new Error('User does not have permission for this operation');
        } else {
            // Other errors
            console.error('API request failed:', error.message);
            throw error;
        }
    }
}
```

### Migration Guide

#### Migrating from Manual OAuth to Simple Authentication

**Before (Manual OAuth):**
```typescript
// Old approach - manual OAuth handling
import * as mgmtApi from '@agility/management-sdk';

const options = new mgmtApi.Options();
options.token = 'manually-obtained-token';
const apiClient = new mgmtApi.ApiClient(options);

// Manual token refresh required
const contentItem = await apiClient.contentMethods.getContentItem(22, guid, locale);
```

**After (Simple Authentication):**
```typescript
// New approach - automatic OAuth handling
import * as mgmtApi from '@agility/management-sdk';

const apiClient = new mgmtApi.ApiClient();
await apiClient.auth();

// Automatic token refresh handled internally
const contentItem = await apiClient.contentMethods.getContentItem(22, guid, locale);
```

#### Migrating from AuthMethods Class

**Before (AuthMethods Class):**
```typescript
import { AuthMethods } from '@agility/management-sdk';

const auth = new AuthMethods();
const client = auth.createAuthenticatedClient('token');
```

**After (Simple Authentication):**
```typescript
import * as mgmtApi from '@agility/management-sdk';

const apiClient = new mgmtApi.ApiClient();
await apiClient.auth();
```

### Advanced Features

#### Secure Token Storage
The new authentication system uses OS-level secure storage:

- **macOS**: Keychain Access
- **Windows**: Windows Credential Store  
- **Linux**: libsecret (GNOME Keyring)

Tokens are encrypted and stored securely, with automatic cleanup on logout.

#### Automatic Token Refresh
The SDK now handles token refresh automatically:

```typescript
const apiClient = new mgmtApi.ApiClient();
await apiClient.auth();

// Token refresh happens automatically on 401 errors
const contentItem = await apiClient.contentMethods.getContentItem(22, guid, locale);
// If token was expired, it gets refreshed transparently
```

#### Custom OAuth Configuration
You can customize the OAuth flow:

```typescript
const apiClient = new mgmtApi.ApiClient();
await apiClient.auth({
    redirectUri: 'https://yourapp.com/custom-callback',
    scope: 'openid profile email offline_access',
    region: 'us' // or 'ca', 'eu', 'au', 'dev'
});
```

#### Manual Token Exchange
For custom OAuth implementations:

```typescript
const apiClient = new mgmtApi.ApiClient();

// Exchange authorization code for tokens
const tokens = await apiClient.exchangeCodeForToken({
    code: 'authorization_code_from_callback',
    redirectUri: 'https://yourapp.com/auth/callback'
});

// Tokens are automatically stored and used
```

### Best Practices

#### Security Considerations

1. **Use secure token storage (automatic with new auth)**
   ```typescript
   // ✅ Recommended - automatic secure storage
   const apiClient = new mgmtApi.ApiClient();
   await apiClient.auth(); // Tokens stored securely in OS keychain
   
   // ✅ CI/CD - manual token (for automated environments)
   const options = new mgmtApi.Options();
   options.token = process.env.AGILITY_ACCESS_TOKEN;
   const apiClient = new mgmtApi.ApiClient(options);
   
   // ❌ Bad - hardcoded tokens
   options.token = 'your-secret-token'; // Never do this
   ```

2. **Leverage automatic token refresh**
   ```typescript
   // ✅ Automatic token refresh (new approach)
   const apiClient = new mgmtApi.ApiClient();
   await apiClient.auth();
   
   // Token refresh happens automatically on 401 errors
   const contentItem = await apiClient.contentMethods.getContentItem(22, guid, locale);
   ```

3. **Handle authentication errors gracefully**
   ```typescript
   const apiClient = new mgmtApi.ApiClient();
   
   try {
       await apiClient.auth();
       const contentItem = await apiClient.contentMethods.getContentItem(22, guid, locale);
   } catch (error) {
       if (error.message.includes('OAuth flow requires manual implementation')) {
           // Handle OAuth flow manually
           console.log('Visit the OAuth URL to authenticate');
       } else {
           // Handle other auth errors
           console.error('Authentication failed:', error);
       }
   }
   ```

4. **Clear tokens on logout**
   ```typescript
   const apiClient = new mgmtApi.ApiClient();
   await apiClient.auth();
   
   // ... use API client ...
   
   // Clear tokens securely on logout
   await apiClient.clearToken();
   ```

#### Performance Optimization

1. **Reuse API client instances**
   ```javascript
   // ✅ Good - single instance
   const apiClient = new mgmtApi.ApiClient(options);
   
   // Use the same instance for multiple requests
   const content1 = await apiClient.contentMethods.getContentItem(1, guid, locale);
   const content2 = await apiClient.contentMethods.getContentItem(2, guid, locale);
   ```

2. **Implement request caching**
   ```javascript
   class CachedAgilityClient {
       constructor(options) {
           this.client = new mgmtApi.ApiClient(options);
           this.cache = new Map();
       }
       
       async getContentItem(id, guid, locale) {
           const cacheKey = `content-${id}-${guid}-${locale}`;
           
           if (this.cache.has(cacheKey)) {
               return this.cache.get(cacheKey);
           }
           
           const result = await this.client.contentMethods.getContentItem(id, guid, locale);
           this.cache.set(cacheKey, result);
           
           return result;
       }
   }
   ```

---

## Navigation
- [← Back to Main Documentation](../README.md)
- [CI/CD & Automated Environments](./cicd.md)
- [AssetMethods](./asset-methods.md)
- [BatchMethods](./batch-methods.md)
- [ContentMethods](./content-methods.md)
- [ContainerMethods](./container-methods.md)
- [InstanceMethods](./instance-methods.md)
- [InstanceUserMethods](./instance-user-methods.md)
- [ModelMethods](./model-methods.md)
- [PageMethods](./page-methods.md)
- [ServerUserMethods](./server-user-methods.md)
- [WebhookMethods](./webhook-methods.md)
- [Multi-Instance Operations](./multi-instance-operations.md) 