Authentication

What is Authentication in Orchesty? #

Authentication in Orchesty is the process of securely managing credentials and access tokens for external services. Instead of hardcoding API keys or tokens in your connectors, Orchesty provides a structured way to store, manage, and use credentials through Applications.

Key concepts:

  • Applications: Classes that define authentication methods and credential forms
  • ApplicationInstall: User-specific credential storage
  • Forms: UI for users to enter their credentials in Orchesty Admin
  • Automatic token management: OAuth2 token refresh, token expiration handling

Why Use Applications? #

Without Applications, you would need to:

  • Store credentials in code (insecure)
  • Manually handle OAuth2 flows
  • Rebuild connectors for each authentication change
  • Manage token refresh logic repeatedly

With Applications, you get:

  • Secure credential storage per user
  • Automatic OAuth2 token refresh
  • Reusable authentication across connectors
  • User-friendly credential management in Orchesty Admin

Authentication Types #

1. Basic Authentication (API Keys, Passwords) #

For services using static credentials like API keys, passwords, or tokens.

Use ABasicApplication for:

  • API key authentication
  • Username/password authentication
  • Static bearer tokens
  • Custom header authentication

2. OAuth2 Authentication #

For services using OAuth 2.0 authorization.

Use AOAuth2Application for:

  • Google APIs
  • Facebook API
  • GitHub API
  • Salesforce
  • Any service using OAuth 2.0

3. OAuth1 Authentication #

For legacy services using OAuth 1.0a (less common).

Available in PHP SDK as OAuth1ApplicationAbstract

How Authentication Works #

Architecture #

graph TB
    User[User in Orchesty Admin]
    Application[Your Application Class]
    AppInstall[ApplicationInstall in Database]
    Connector[Your Connector]
    API[External API]
    
    User -->|Enters credentials| Application
    Application -->|Saves to| AppInstall
    Connector -->|Requests credentials| AppInstall
    Connector -->|Uses credentials| API
    Application -->|Refreshes tokens| AppInstall

OAuth2 Flow #

sequenceDiagram
    participant User
    participant Admin as Orchesty Admin
    participant App as Your Application
    participant OAuth as OAuth Provider
    participant DB as Database
    
    User->>Admin: Click "Connect" button
    Admin->>App: authorize()
    App->>User: Redirect to OAuth Provider
    User->>OAuth: Grant permissions
    OAuth->>App: Redirect with auth code
    App->>OAuth: Exchange code for tokens
    OAuth->>App: Return access + refresh tokens
    App->>DB: Save tokens to ApplicationInstall
    App->>Admin: Show "Connected" status
    
    Note over App,DB: Later, when token expires
    App->>OAuth: Request new token with refresh token
    OAuth->>App: Return new access token
    App->>DB: Update ApplicationInstall

Implementing Basic Authentication #

Step 1: Create Application Class #

import ABasicApplication from '@orchesty/nodejs-sdk/lib/Application/Base/ABasicApplication';
import CoreFormsEnum from '@orchesty/nodejs-sdk/lib/Application/Base/CoreFormsEnum';
import Field from '@orchesty/nodejs-sdk/lib/Application/Model/Form/Field';
import FieldType from '@orchesty/nodejs-sdk/lib/Application/Model/Form/FieldType';
import Form from '@orchesty/nodejs-sdk/lib/Application/Model/Form/Form';
import FormStack from '@orchesty/nodejs-sdk/lib/Application/Model/Form/FormStack';
import { ApplicationInstall } from '@orchesty/nodejs-sdk/lib/Application/Database/ApplicationInstall';
import RequestDto from '@orchesty/nodejs-sdk/lib/Transport/Curl/RequestDto';
import { HttpMethods } from '@orchesty/nodejs-sdk/lib/Transport/HttpMethods';
import ProcessDto from '@orchesty/nodejs-sdk/lib/Utils/ProcessDto';

export default class MailchimpApplication extends ABasicApplication {
    
    public getName(): string {
        return 'mailchimp';
    }
    
    public getPublicName(): string {
        return 'Mailchimp';
    }
    
    public getDescription(): string {
        return 'Email marketing platform';
    }
    
    public getLogo(): string {
        return 'base64-encoded-logo-here';
    }
    
    // Define the credential form
    public getFormStack(): FormStack {
        const form = new Form(CoreFormsEnum.AUTHORIZATION_FORM, 'API Credentials');
        
        form.addField(new Field(
            FieldType.TEXT,
            'api_key',
            'API Key',
            null,
            true  // required
        ));
        
        form.addField(new Field(
            FieldType.TEXT,
            'server_prefix',
            'Server Prefix',
            'us1',  // default value
            true
        ));
        
        const formStack = new FormStack();
        formStack.addForm(form);
        
        return formStack;
    }
    
    // Create authenticated requests
    public getRequestDto(
        dto: ProcessDto,
        applicationInstall: ApplicationInstall,
        method: HttpMethods,
        url?: string,
        data?: unknown
    ): RequestDto {
        const settings = applicationInstall.getSettings();
        const apiKey = settings[CoreFormsEnum.AUTHORIZATION_FORM]['api_key'];
        const serverPrefix = settings[CoreFormsEnum.AUTHORIZATION_FORM]['server_prefix'];
        
        const fullUrl = url?.includes('://')
            ? url
            : `https://${serverPrefix}.api.mailchimp.com/3.0${url}`;
        
        const requestDto = new RequestDto(fullUrl, method, dto, data);
        requestDto.setHeaders({
            'Authorization': `Bearer ${apiKey}`,
            'Content-Type': 'application/json'
        });
        
        return requestDto;
    }
    
    // Check if credentials are valid
    public isAuthorized(applicationInstall: ApplicationInstall): boolean {
        const settings = applicationInstall.getSettings();
        const authForm = settings[CoreFormsEnum.AUTHORIZATION_FORM];
        
        return !!(authForm?.api_key && authForm?.server_prefix);
    }
}

Step 2: Register Application #

import DIContainer from '@orchesty/nodejs-sdk/lib/DIContainer/Container';
import MailchimpApplication from './applications/MailchimpApplication';

const container = new DIContainer();
container.setApplication(new MailchimpApplication());

Step 3: Use in Connector #

import AConnector from '@orchesty/nodejs-sdk/lib/Connector/AConnector';
import ProcessDto from '@orchesty/nodejs-sdk/lib/Utils/ProcessDto';
import { HttpMethods } from '@orchesty/nodejs-sdk/lib/Transport/HttpMethods';
import MailchimpApplication from '../applications/MailchimpApplication';

export default class MailchimpGetListsConnector extends AConnector {
    
    public getName(): string {
        return 'mailchimp-get-lists';
    }
    
    public async processAction(dto: ProcessDto): Promise<ProcessDto> {
        // Get application and credentials
        const appInstall = await this.getApplicationInstallFromProcess(dto);
        const app = this.getApplication<MailchimpApplication>();
        
        // Create authenticated request
        const requestDto = app.getRequestDto(
            dto,
            appInstall,
            HttpMethods.GET,
            '/lists'
        );
        
        // Send request
        const response = await this.getSender().send(requestDto);
        const lists = JSON.parse(response.getBody());
        
        dto.setJsonData(lists);
        return dto;
    }
}

Implementing OAuth2 Authentication #

Step 1: Create OAuth2 Application #

import AOAuth2Application from '@orchesty/nodejs-sdk/lib/Authorization/Type/OAuth2/AOAuth2Application';
import { CLIENT_ID, CLIENT_SECRET } from '@orchesty/nodejs-sdk/lib/Authorization/Type/OAuth2/IOAuth2Application';
import CoreFormsEnum from '@orchesty/nodejs-sdk/lib/Application/Base/CoreFormsEnum';
import { ApplicationInstall } from '@orchesty/nodejs-sdk/lib/Application/Database/ApplicationInstall';
import Field from '@orchesty/nodejs-sdk/lib/Application/Model/Form/Field';
import FieldType from '@orchesty/nodejs-sdk/lib/Application/Model/Form/FieldType';
import Form from '@orchesty/nodejs-sdk/lib/Application/Model/Form/Form';
import FormStack from '@orchesty/nodejs-sdk/lib/Application/Model/Form/FormStack';
import RequestDto from '@orchesty/nodejs-sdk/lib/Transport/Curl/RequestDto';
import { HttpMethods } from '@orchesty/nodejs-sdk/lib/Transport/HttpMethods';
import ProcessDto from '@orchesty/nodejs-sdk/lib/Utils/ProcessDto';

export default class GoogleApplication extends AOAuth2Application {
    
    public getName(): string {
        return 'google';
    }
    
    public getPublicName(): string {
        return 'Google';
    }
    
    public getDescription(): string {
        return 'Google APIs';
    }
    
    public getLogo(): string {
        return 'base64-encoded-logo';
    }
    
    // OAuth2 authorization endpoint
    public getAuthUrl(): string {
        return 'https://accounts.google.com/o/oauth2/v2/auth';
    }
    
    // OAuth2 token endpoint
    public getTokenUrl(): string {
        return 'https://oauth2.googleapis.com/token';
    }
    
    // Requested scopes
    public getScopes(applicationInstall: ApplicationInstall): string[] {
        return [
            'https://www.googleapis.com/auth/userinfo.email',
            'https://www.googleapis.com/auth/drive.readonly'
        ];
    }
    
    // Credential form (Client ID and Secret)
    public getFormStack(): FormStack {
        const form = new Form(CoreFormsEnum.AUTHORIZATION_FORM, 'OAuth2 Credentials');
        
        form.addField(new Field(
            FieldType.TEXT,
            CLIENT_ID,
            'Client ID',
            null,
            true
        ));
        
        form.addField(new Field(
            FieldType.PASSWORD,
            CLIENT_SECRET,
            'Client Secret',
            null,
            true
        ));
        
        const formStack = new FormStack();
        formStack.addForm(form);
        
        return formStack;
    }
    
    // Create authenticated requests with access token
    public getRequestDto(
        dto: ProcessDto,
        applicationInstall: ApplicationInstall,
        method: HttpMethods,
        url?: string,
        data?: unknown
    ): RequestDto {
        const accessToken = this.getAccessToken(applicationInstall);
        
        const requestDto = new RequestDto(url ?? '', method, dto, data);
        requestDto.setHeaders({
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json'
        });
        
        return requestDto;
    }
}

Step 2: Register OAuth2 Application #

import DIContainer from '@orchesty/nodejs-sdk/lib/DIContainer/Container';
import { OAuth2Provider } from '@orchesty/nodejs-sdk/lib/Authorization/Provider/OAuth2/OAuth2Provider';
import GoogleApplication from './applications/GoogleApplication';

const container = new DIContainer();

// OAuth2 apps need the provider
const oauth2Provider = container.get(OAuth2Provider);
const googleApp = new GoogleApplication(oauth2Provider);

container.setApplication(googleApp);

Step 3: Handle Token Refresh in Connector #

import AConnector from '@orchesty/nodejs-sdk/lib/Connector/AConnector';
import ProcessDto from '@orchesty/nodejs-sdk/lib/Utils/ProcessDto';
import { HttpMethods } from '@orchesty/nodejs-sdk/lib/Transport/HttpMethods';
import GoogleApplication from '../applications/GoogleApplication';

export default class GoogleDriveListFilesConnector extends AConnector {
    
    public getName(): string {
        return 'google-drive-list-files';
    }
    
    public async processAction(dto: ProcessDto): Promise<ProcessDto> {
        const appInstall = await this.getApplicationInstallFromProcess(dto);
        const app = this.getApplication<GoogleApplication>();
        
        // Check if token is expired
        const expires = appInstall.getExpires();
        if (expires && expires < new Date()) {
            // Token expired - refresh it
            const refreshedInstall = await app.refreshAuthorization(appInstall);
            
            // Update in database
            await this.getDbClient()
                .getApplicationRepository()
                .update(refreshedInstall);
        }
        
        // Make authenticated request
        const requestDto = app.getRequestDto(
            dto,
            appInstall,
            HttpMethods.GET,
            'https://www.googleapis.com/drive/v3/files'
        );
        
        const response = await this.getSender().send(requestDto);
        dto.setData(response.getBody());
        
        return dto;
    }
}

Common Authentication Patterns #

Pattern: Multiple Authentication Methods #

Some APIs support multiple auth methods:

export default class FlexibleAuthApplication extends ABasicApplication {
    
    public getFormStack(): FormStack {
        const form = new Form(CoreFormsEnum.AUTHORIZATION_FORM, 'Authentication');
        
        // Option 1: API Key
        form.addField(new Field(
            FieldType.TEXT,
            'api_key',
            'API Key (Optional)',
            null,
            false
        ));
        
        // Option 2: Username/Password
        form.addField(new Field(
            FieldType.TEXT,
            'username',
            'Username (Optional)',
            null,
            false
        ));
        
        form.addField(new Field(
            FieldType.PASSWORD,
            'password',
            'Password (Optional)',
            null,
            false
        ));
        
        const formStack = new FormStack();
        formStack.addForm(form);
        
        return formStack;
    }
    
    public getRequestDto(
        dto: ProcessDto,
        applicationInstall: ApplicationInstall,
        method: HttpMethods,
        url?: string,
        data?: unknown
    ): RequestDto {
        const settings = applicationInstall.getSettings();
        const authForm = settings[CoreFormsEnum.AUTHORIZATION_FORM];
        
        const requestDto = new RequestDto(url ?? '', method, dto, data);
        
        // Use API key if provided
        if (authForm.api_key) {
            requestDto.setHeaders({
                'X-API-Key': authForm.api_key
            });
        }
        // Otherwise use basic auth
        else if (authForm.username && authForm.password) {
            requestDto.setAuth(authForm.username, authForm.password);
        }
        
        return requestDto;
    }
}

Pattern: Custom Headers #

public getRequestDto(
    dto: ProcessDto,
    applicationInstall: ApplicationInstall,
    method: HttpMethods,
    url?: string,
    data?: unknown
): RequestDto {
    const settings = applicationInstall.getSettings();
    const authForm = settings[CoreFormsEnum.AUTHORIZATION_FORM];
    
    const requestDto = new RequestDto(url ?? '', method, dto, data);
    requestDto.setHeaders({
        'X-API-Key': authForm.api_key,
        'X-API-Secret': authForm.api_secret,
        'X-Tenant-ID': authForm.tenant_id
    });
    
    return requestDto;
}

Pattern: Dynamic Base URL #

public getRequestDto(
    dto: ProcessDto,
    applicationInstall: ApplicationInstall,
    method: HttpMethods,
    url?: string,
    data?: unknown
): RequestDto {
    const settings = applicationInstall.getSettings();
    const authForm = settings[CoreFormsEnum.AUTHORIZATION_FORM];
    
    // Use user's region-specific URL
    const baseUrl = authForm.region === 'eu'
        ? 'https://eu.api.example.com'
        : 'https://us.api.example.com';
    
    const fullUrl = url?.includes('://')
        ? url
        : `${baseUrl}${url}`;
    
    const requestDto = new RequestDto(fullUrl, method, dto, data);
    requestDto.setHeaders({
        'Authorization': `Bearer ${authForm.api_key}`
    });
    
    return requestDto;
}

Pattern: Additional Configuration Fields #

public getFormStack(): FormStack {
    const authForm = new Form(CoreFormsEnum.AUTHORIZATION_FORM, 'Authentication');
    authForm.addField(new Field(FieldType.TEXT, 'api_key', 'API Key', null, true));
    
    // Add configuration form
    const configForm = new Form('config_form', 'Configuration');
    configForm.addField(new Field(
        FieldType.SELECT_BOX,
        'region',
        'Region',
        'us',
        true,
        null,
        [
            { key: 'us', value: 'United States' },
            { key: 'eu', value: 'Europe' },
            { key: 'ap', value: 'Asia Pacific' }
        ]
    ));
    
    configForm.addField(new Field(
        FieldType.TEXT,
        'account_id',
        'Account ID',
        null,
        true
    ));
    
    const formStack = new FormStack();
    formStack.addForm(authForm);
    formStack.addForm(configForm);
    
    return formStack;
}

Credential Storage #

ApplicationInstall Structure #

Credentials are stored in ApplicationInstall documents:

{
  "user": "user@example.com",
  "name": "mailchimp",
  "settings": {
    "authorization_form": {
      "api_key": "encrypted-value",
      "server_prefix": "us1"
    },
    "config_form": {
      "region": "us",
      "account_id": "12345"
    }
  },
  "expires": null,  // For OAuth2: token expiration timestamp
  "enabled": true,
  "created": "2024-01-15T10:30:00Z"
}

Accessing Credentials #

// Get the ApplicationInstall
const appInstall = await this.getApplicationInstallFromProcess(dto);

// Access settings
const settings = appInstall.getSettings();
const authForm = settings[CoreFormsEnum.AUTHORIZATION_FORM];
const apiKey = authForm['api_key'];

// Access configuration
const configForm = settings['config_form'];
const region = configForm['region'];

Security #

  • Sensitive fields (passwords, tokens) are encrypted in the database
  • Never log credentials
  • Use FieldType.PASSWORD for sensitive fields in forms
  • Credentials are isolated per user

Best Practices #

1. Use Descriptive Field Names #

// Good
form.addField(new Field(FieldType.TEXT, 'api_key', 'API Key'));
form.addField(new Field(FieldType.TEXT, 'workspace_id', 'Workspace ID'));

// Bad
form.addField(new Field(FieldType.TEXT, 'key', 'Key'));
form.addField(new Field(FieldType.TEXT, 'id', 'ID'));

2. Provide Default Values #

form.addField(new Field(
    FieldType.TEXT,
    'api_version',
    'API Version',
    'v2',  // default value
    false
));

3. Validate Credentials #

public isAuthorized(applicationInstall: ApplicationInstall): boolean {
    const settings = applicationInstall.getSettings();
    const authForm = settings[CoreFormsEnum.AUTHORIZATION_FORM];
    
    // Check all required fields are present
    return !!(
        authForm?.api_key &&
        authForm?.api_key.length > 0 &&
        authForm?.workspace_id
    );
}

4. Handle OAuth2 Token Refresh #

public async processAction(dto: ProcessDto): Promise<ProcessDto> {
    const appInstall = await this.getApplicationInstallFromProcess(dto);
    const app = this.getApplication<MyOAuth2Application>();
    
    // Always check expiration before API calls
    if (appInstall.getExpires() && appInstall.getExpires()! < new Date()) {
        const refreshed = await app.refreshAuthorization(appInstall);
        await this.getDbClient().getApplicationRepository().update(refreshed);
    }
    
    // Continue with API call...
}

5. Use Select Boxes for Fixed Options #

form.addField(new Field(
    FieldType.SELECT_BOX,
    'environment',
    'Environment',
    'production',
    true,
    null,
    [
        { key: 'sandbox', value: 'Sandbox' },
        { key: 'production', value: 'Production' }
    ]
));

Testing Authentication #

Test Application Registration #

GET http://localhost:8080/application/list

Test Authorization Form #

GET http://localhost:8080/application/google/form

Test OAuth2 Authorization #

GET http://localhost:8080/application/google/authorize?user=user@example.com

Test if Authorized #

GET http://localhost:8080/application/google/users/user@example.com/authorized

Troubleshooting #

Credentials Not Found #

try {
    const appInstall = await this.getApplicationInstallFromProcess(dto);
} catch (error) {
    dto.setStopProcess(
        ResultCode.STOP_AND_FAILED,
        'Application not installed for this user. Please configure credentials in Orchesty Admin.'
    );
    return dto;
}

OAuth2 Token Expired #

if (!app.isAuthorized(appInstall)) {
    dto.setStopProcess(
        ResultCode.STOP_AND_FAILED,
        'OAuth2 authorization expired. Please reconnect the application.'
    );
    return dto;
}

Invalid Credentials #

try {
    const response = await this.getSender().send(requestDto);
} catch (error) {
    if (error.response?.status === 401) {
        dto.setStopProcess(
            ResultCode.STOP_AND_FAILED,
            'Invalid credentials. Please check your API key.'
        );
        return dto;
    }
    throw error;
}
  • Connector - How connectors use applications
  • Data Flow - Understanding ApplicationInstall in the data flow
  • Webhooks - Webhook applications require authentication
  • Error Handling - Handling authentication errors

API References #

Next Steps #

  1. Learn about Connectors to use authentication in your integrations
  2. Explore Form documentation to create custom credential forms
  3. Read about Webhooks for webhook-based authentication
  4. Check ApplicationInstall for credential management details
© 2025 Orchesty Solutions. All rights reserved.