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.PASSWORDfor 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;
}
Related Concepts #
- 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 #
- ABasicApplication - Basic authentication
- AOAuth2Application - OAuth2 authentication
- ApplicationInstall - Credential storage
- Form - Creating credential forms
- Field - Form field types
- FormStack - Multiple forms
Next Steps #
- Learn about Connectors to use authentication in your integrations
- Explore Form documentation to create custom credential forms
- Read about Webhooks for webhook-based authentication
- Check ApplicationInstall for credential management details