# Security Best Practices

## Overview

When building Crowdin apps, securing your custom API endpoints is critical to prevent unauthorized access and potential security vulnerabilities. This guide covers essential security practices for protecting your application endpoints.

**Critical Security Requirement:** **Always protect your custom endpoints with proper authentication middleware.** Failing to do so can allow users with access to one module to call actions from other modules they shouldn't have access to.

**Note:** For lower-level JWT validation or Crowdin ID parsing in custom code paths, see [App Functions](/app-project-module/reference/app-functions/).

## The Security Problem

When you create custom endpoints using Express, they are **not automatically protected**. Without proper middleware, a user who has:

- Access to **only one module** (e.g., `module-a`)
- A valid JWT token for that module

Can potentially:

- Use their JWT token to call endpoints from **other modules** (e.g., `module-b`, `module-c`)
- Access data and perform actions they shouldn't have permission to access

This happens because the JWT token itself is valid for your application, but without the `moduleKey` check, there's no verification that the token is authorized for the specific endpoint being called.

## Protecting Endpoints

### Using Middleware

The recommended approach is to use the `crowdinClientMiddleware` for all custom endpoints:

```js title="index.js"
import crowdinModule from '@crowdin/app-project-module';
import { crowdinClient as crowdinClientMiddleware, jsonResponse as jsonResponseMiddleware } from '@crowdin/app-project-module/middlewares';

const app = crowdinModule.express();

const configuration = {
  baseUrl: process.env.BASE_URL,
  clientId: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  name: 'My App',
  identifier: 'my-app',
  // ... other configuration
};

// Create JWT middleware with proper moduleKey configuration
const jwtMiddleware = crowdinClientMiddleware({
  config: configuration,
  optional: false,
  checkSubscriptionExpiration: false,
  moduleKey: ['my-ai-provider', 'my-settings-menu'], // Only these modules can access this endpoint
});

// Apply middleware to your custom route
app.use('/api/my-endpoint', jwtMiddleware, jsonResponseMiddleware, myRouter);
```

### Using establishCrowdinConnection

If you're using the `addCrowdinEndpoints` method, you can use `establishCrowdinConnection` with `moduleKey`:

```js title="index.js"
import crowdinModule from '@crowdin/app-project-module';

const app = crowdinModule.express();

const configuration = {
  // ... your configuration
};

const crowdinApp = crowdinModule.addCrowdinEndpoints(app, configuration);

app.get('/api/custom-action', async (req, res) => {
  try {
    // Establish connection with moduleKey validation
    const { client, context } = await crowdinApp.establishCrowdinConnection({
      authRequest: req,
      moduleKey: ['my-module-key'] // Only this module can call this endpoint
    });

    // Your endpoint logic here
    const result = await performCustomAction(client, context);

    res.status(200).send({ success: true, data: result });
  } catch (e) {
    return res.status(403).send({ error: 'Access denied' });
  }
});
```

## Module Key Configuration

### Understanding Module Keys

The `moduleKey` parameter specifies which modules are **authorized** to call a specific endpoint. When a request comes in:

1. The JWT token is validated
2. The module that made the request is extracted from the JWT payload (`jwtPayload.module`)
3. This module is checked against the allowed `moduleKey` list
4. If the module is not in the list, the request is **rejected with 403 error**

### Single Module

Allow only one specific module to access the endpoint:

```js
const jwtMiddleware = crowdinClientMiddleware({
  config: configuration,
  moduleKey: 'my-specific-module', // String for single module
  // ... other options
});
```

### Multiple Modules

Allow multiple modules to access the same endpoint:

```js
const jwtMiddleware = crowdinClientMiddleware({
  config: configuration,
  moduleKey: ['module-a', 'module-b', 'module-c'], // Array for multiple modules
  // ... other options
});
```

### No Module Key (Insecure - Not Recommended)

**Danger:** **WARNING:** Omitting `moduleKey` or setting it to `undefined` will accept requests from **any module** that has a valid JWT token. Only use this for public endpoints that don't require module-specific authorization.

```js
const jwtMiddleware = crowdinClientMiddleware({
  config: configuration,
  moduleKey: undefined, // ⚠️ Accepts any module - use with extreme caution
  // ... other options
});
```

## Middleware Configuration Options

The `crowdinClientMiddleware` accepts the following configuration:

| Option                        | Type                        | Default | Description                                                                 |
|-------------------------------|----------------------------|---------|-----------------------------------------------------------------------------|
| `config`                      | `Config`                   | -       | Your application configuration object (required)                            |
| `optional`                    | `boolean`                  | `false` | If `true`, allows requests without credentials (use for optional auth)     |
| `checkSubscriptionExpiration` | `boolean`                  | `true`  | If `true`, checks if the organization's subscription is active             |
| `moduleKey`                   | `string \| string[] \| undefined` | -       | Module(s) authorized to access this endpoint                                |

### Example: Optional Authentication

For endpoints that work with or without authentication:

```js title="index.js"
const optionalJwtMiddleware = crowdinClientMiddleware({
  config: configuration,
  optional: true, // Allows unauthenticated requests
  moduleKey: ['my-module'],
});

app.get('/api/public-or-private', optionalJwtMiddleware, (req, res) => {
  if (req.crowdinContext) {
    // User is authenticated - provide personalized data
    return res.json({ data: getPrivateData(req.crowdinContext) });
  } else {
    // User is not authenticated - provide public data
    return res.json({ data: getPublicData() });
  }
});
```

### Example: Subscription Checks

For paid apps, you might want to verify the subscription status:

```js title="index.js"
const paidFeatureMiddleware = crowdinClientMiddleware({
  config: configuration,
  checkSubscriptionExpiration: true, // Verify active subscription
  moduleKey: ['premium-feature-module'],
});

app.post('/api/premium-action', paidFeatureMiddleware, (req, res) => {
  // This will only be reached if subscription is active
  // req.subscriptionInfo contains subscription details
  performPremiumAction(req.crowdinApiClient);
  res.json({ success: true });
});
```

## Real-World Example

Here's a complete example for an AI provider application with multiple endpoints:

```js title="index.js"
import crowdinModule from '@crowdin/app-project-module';
import { crowdinClient as crowdinClientMiddleware, jsonResponse as jsonResponseMiddleware } from '@crowdin/app-project-module/middlewares';

const app = crowdinModule.express();

const configuration = {
  name: 'AI Translation Provider',
  identifier: 'ai-translation-provider',
  baseUrl: process.env.BASE_URL,
  clientId: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  // ... other configuration

  aiProvider: [
    {
      key: 'my-ai-provider',
      name: 'My AI Provider',
      // ... AI provider configuration
    }
  ]
};

// Middleware for AI provider endpoints
const aiProviderJwtMiddleware = crowdinClientMiddleware({
  config: configuration,
  optional: false,
  checkSubscriptionExpiration: true,
  moduleKey: ['my-ai-provider'], // Only AI provider module can access
});

// Middleware for settings endpoints (accessible from multiple modules)
const settingsJwtMiddleware = crowdinClientMiddleware({
  config: configuration,
  optional: false,
  checkSubscriptionExpiration: false,
  moduleKey: ['my-ai-provider', 'my-settings-menu'], // Multiple modules
});

// Protected AI endpoints
app.post('/api/ai/translate',
  aiProviderJwtMiddleware,
  jsonResponseMiddleware,
  async (req, res) => {
    const { crowdinApiClient, crowdinContext } = req;
    // Only accessible from the AI provider module
    const translation = await performTranslation(req.body, crowdinContext);
    res.send({ translation });
  }
);

// Protected settings endpoints
app.get('/api/settings',
  settingsJwtMiddleware,
  jsonResponseMiddleware,
  async (req, res) => {
    const { crowdinContext } = req;
    // Accessible from both AI provider and settings menu
    const settings = await getSettings(crowdinContext);
    res.send({ settings });
  }
);

app.listen(3000, () => console.log('Secure app started'));
```

## Common Mistakes to Avoid

### ❌ No Middleware Protection

```js
// INSECURE - No authentication at all
app.get('/api/sensitive-data', (req, res) => {
  res.send({ data: getSensitiveData() });
});
```

### ❌ Missing moduleKey

```js
// INSECURE - Any module with valid JWT can access
const jwtMiddleware = crowdinClientMiddleware({
  config: configuration,
  // moduleKey is missing!
});

app.use('/api/admin-actions', jwtMiddleware, adminRouter);
```

### ❌ Incorrect moduleKey

```js
// INSECURE - Wrong module key doesn't match your actual modules
const jwtMiddleware = crowdinClientMiddleware({
  config: configuration,
  moduleKey: ['wrong-module-key'], // This module doesn't exist in your app
});
```

### ✅ Correct Implementation

```js
// SECURE - Proper middleware with correct moduleKey
const jwtMiddleware = crowdinClientMiddleware({
  config: configuration,
  optional: false,
  checkSubscriptionExpiration: true,
  moduleKey: ['my-actual-module-key'], // Matches your registered module
});

app.use('/api/protected', jwtMiddleware, jsonResponseMiddleware, protectedRouter);
```

## Request Context

When using the middleware, the following properties are added to the Express request object:

```js
// Available properties on the request object:
req.crowdinContext      // JWT payload and context information
req.crowdinApiClient    // Crowdin API client instance (if credentials available)
req.subscriptionInfo    // Subscription details (if checkSubscriptionExpiration is true)
req.logInfo             // Context-aware info logger
req.logError            // Context-aware error logger
```

You can use these in your endpoint handlers:

```js
app.get('/api/my-endpoint', jwtMiddleware, (req, res) => {
  const { crowdinContext, crowdinApiClient, logInfo } = req;

  logInfo('Processing request', { userId: crowdinContext.jwtPayload.sub });

  // Use crowdinContext and crowdinApiClient
  // ...
});
```

## Security Checklist

Before deploying your Crowdin app, verify:

- [ ] All custom endpoints use either `crowdinClientMiddleware` or `establishCrowdinConnection`
- [ ] Every middleware configuration includes the correct `moduleKey` parameter
- [ ] Module keys match the actual module identifiers registered in your app configuration
- [ ] Sensitive endpoints have `optional: false` to require authentication
- [ ] Paid features use `checkSubscriptionExpiration: true`
- [ ] Error handling doesn't expose sensitive information
- [ ] HTTPS is used in production environments
- [ ] Logging is implemented for security auditing

## Additional Resources

- [API Module Documentation](/app-project-module/tools/api/)
- [Auth Guard Module](/app-project-module/modules/auth-guard/)
- [Database Security](/app-project-module/database/)
- [Error Handling](/app-project-module/errors-handling/)