Authentication Flows
Authentication flows are the foundation of secure data integration. This guide shows you how to create robust authentication flows using @databite/flow
that handle OAuth, API keys, and custom authentication methods.
Overview
Authentication flows in Databite are built using the @databite/flow
package, which provides a declarative way to create multi-step authentication processes. These flows can handle:
- OAuth 2.0 and OAuth 1.0a
- API key authentication
- Custom authentication methods
- Multi-step verification processes
- Token refresh and management
Basic Authentication Flow
Let’s start with a simple OAuth 2.0 flow:
import { FlowBuilder } from "@databite/flow";
const authFlow = new FlowBuilder()
.generic("oauth_start", {
title: "Connect to Service",
description: "Click the button below to authorize access to your account",
buttonText: "Authorize",
action: "oauth_authorize",
})
.http("oauth_authorize", {
method: "GET",
url: "https://api.service.com/oauth/authorize",
query: {
client_id: "{{config.client_id}}",
redirect_uri: "{{config.redirect_uri}}",
response_type: "code",
scope: "read write",
},
})
.http("oauth_callback", {
method: "POST",
url: "https://api.service.com/oauth/token",
body: {
client_id: "{{config.client_id}}",
client_secret: "{{config.client_secret}}",
code: "{{query.code}}",
grant_type: "authorization_code",
},
})
.transform("extract_tokens", {
access_token: "{{response.access_token}}",
refresh_token: "{{response.refresh_token}}",
expires_in: "{{response.expires_in}}",
})
.build();
Advanced Authentication Patterns
Multi-Step OAuth with Scopes
const advancedAuthFlow = new FlowBuilder()
.generic("select_scopes", {
title: "Select Permissions",
description: "Choose which permissions to grant",
buttonText: "Continue",
action: "oauth_start",
})
.http("oauth_start", {
method: "GET",
url: "https://api.service.com/oauth/authorize",
query: {
client_id: "{{config.client_id}}",
redirect_uri: "{{config.redirect_uri}}",
response_type: "code",
scope: "{{selected_scopes}}",
},
})
.http("oauth_callback", {
method: "POST",
url: "https://api.service.com/oauth/token",
body: {
client_id: "{{config.client_id}}",
client_secret: "{{config.client_secret}}",
code: "{{query.code}}",
grant_type: "authorization_code",
},
})
.transform("extract_tokens", {
access_token: "{{response.access_token}}",
refresh_token: "{{response.refresh_token}}",
expires_in: "{{response.expires_in}}",
scope: "{{response.scope}}",
})
.build();
API Key Authentication
const apiKeyFlow = new FlowBuilder()
.form("api_key_form", {
title: "Enter API Key",
description: "Please enter your API key to continue",
fields: [
{
name: "api_key",
type: "password",
label: "API Key",
required: true,
placeholder: "Enter your API key",
},
],
submitText: "Connect",
action: "validate_api_key",
})
.http("validate_api_key", {
method: "GET",
url: "https://api.service.com/user",
headers: {
Authorization: "Bearer {{form.api_key}}",
},
})
.transform("extract_user_info", {
api_key: "{{form.api_key}}",
user_id: "{{response.id}}",
username: "{{response.username}}",
})
.build();
React Integration
Using the FlowRenderer Component
import { FlowRenderer } from "@databite/flow/react";
import { useFlowExecution } from "@databite/flow/react";
function AuthFlowComponent() {
const { executeFlow, state, error } = useFlowExecution(authFlow);
const handleStart = () => {
executeFlow({
config: {
client_id: process.env.REACT_APP_CLIENT_ID,
client_secret: process.env.REACT_APP_CLIENT_SECRET,
redirect_uri: window.location.origin + "/callback",
},
});
};
return (
<div>
<button onClick={handleStart}>Connect to Service</button>
<FlowRenderer flow={authFlow} state={state} onAction={executeFlow} />
{error && <div className="error">{error.message}</div>}
</div>
);
}
Custom Flow Components
import { FlowBlock, FlowState } from "@databite/flow";
function CustomOAuthBlock({ block, state, onAction }: FlowBlock) {
const handleAuthorize = () => {
// Custom OAuth logic
window.location.href = block.url;
};
return (
<div className="oauth-block">
<h3>{block.title}</h3>
<p>{block.description}</p>
<button onClick={handleAuthorize}>{block.buttonText}</button>
</div>
);
}
Error Handling
Handling Authentication Errors
const robustAuthFlow = new FlowBuilder()
.generic("oauth_start", {
title: "Connect to Service",
description: "Click the button below to authorize access",
buttonText: "Authorize",
action: "oauth_authorize",
})
.http("oauth_authorize", {
method: "GET",
url: "https://api.service.com/oauth/authorize",
query: {
client_id: "{{config.client_id}}",
redirect_uri: "{{config.redirect_uri}}",
response_type: "code",
scope: "read write",
},
onError: "handle_auth_error",
})
.generic("handle_auth_error", {
title: "Authentication Failed",
description: "There was an error during authentication. Please try again.",
buttonText: "Retry",
action: "oauth_start",
})
.http("oauth_callback", {
method: "POST",
url: "https://api.service.com/oauth/token",
body: {
client_id: "{{config.client_id}}",
client_secret: "{{config.client_secret}}",
code: "{{query.code}}",
grant_type: "authorization_code",
},
onError: "handle_token_error",
})
.generic("handle_token_error", {
title: "Token Exchange Failed",
description:
"Failed to exchange authorization code for tokens. Please try again.",
buttonText: "Retry",
action: "oauth_start",
})
.transform("extract_tokens", {
access_token: "{{response.access_token}}",
refresh_token: "{{response.refresh_token}}",
expires_in: "{{response.expires_in}}",
})
.build();
Token Management
Automatic Token Refresh
const tokenRefreshFlow = new FlowBuilder()
.http("refresh_token", {
method: "POST",
url: "https://api.service.com/oauth/token",
body: {
client_id: "{{config.client_id}}",
client_secret: "{{config.client_secret}}",
refresh_token: "{{tokens.refresh_token}}",
grant_type: "refresh_token",
},
})
.transform("update_tokens", {
access_token: "{{response.access_token}}",
refresh_token: "{{response.refresh_token}}",
expires_in: "{{response.expires_in}}",
})
.build();
Token Validation
const tokenValidationFlow = new FlowBuilder()
.http("validate_token", {
method: "GET",
url: "https://api.service.com/user",
headers: {
Authorization: "Bearer {{tokens.access_token}}",
},
})
.transform("extract_user_info", {
user_id: "{{response.id}}",
username: "{{response.username}}",
email: "{{response.email}}",
})
.build();
Best Practices
Security Considerations
Always store sensitive credentials securely and never expose them in
client-side code.
// ✅ Good: Use environment variables
const config = {
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
redirect_uri: process.env.REDIRECT_URI,
};
// ❌ Bad: Hardcoded credentials
const config = {
client_id: "your_client_id",
client_secret: "your_client_secret",
};
Flow Design Patterns
Keep authentication flows simple and focused. Each flow should handle one
authentication method.
// ✅ Good: Single responsibility
const oauthFlow = new FlowBuilder()
.generic("start", {
/* OAuth flow */
})
.build();
const apiKeyFlow = new FlowBuilder()
.form("api_key", {
/* API key flow */
})
.build();
// ❌ Bad: Mixed responsibilities
const mixedFlow = new FlowBuilder()
.generic("start", {
/* OAuth */
})
.form("api_key", {
/* API key */
})
.build();
Error Recovery
const resilientFlow = new FlowBuilder()
.generic("start", {
title: "Connect to Service",
action: "oauth_authorize",
})
.http("oauth_authorize", {
method: "GET",
url: "https://api.service.com/oauth/authorize",
onError: "retry_auth",
})
.generic("retry_auth", {
title: "Retry Authentication",
description: "Authentication failed. Would you like to try again?",
buttonText: "Retry",
action: "oauth_authorize",
})
.build();
Testing Authentication Flows
Unit Testing
import { FlowBuilder } from "@databite/flow";
describe("Authentication Flow", () => {
it("should handle OAuth flow successfully", async () => {
const flow = new FlowBuilder()
.generic("start", { action: "oauth_authorize" })
.http("oauth_authorize", {
method: "GET",
url: "https://api.service.com/oauth/authorize",
})
.build();
const result = await flow.execute({
config: { client_id: "test_client" },
});
expect(result.success).toBe(true);
});
});
Integration Testing
describe("OAuth Integration", () => {
it("should complete full OAuth flow", async () => {
const flow = new FlowBuilder()
.generic("start", { action: "oauth_authorize" })
.http("oauth_authorize", {
method: "GET",
url: "https://api.service.com/oauth/authorize",
})
.http("oauth_callback", {
method: "POST",
url: "https://api.service.com/oauth/token",
})
.build();
// Mock OAuth server responses
const mockServer = setupMockOAuthServer();
const result = await flow.execute({
config: {
client_id: "test_client",
client_secret: "test_secret",
redirect_uri: "http://localhost:3000/callback",
},
});
expect(result.success).toBe(true);
expect(result.tokens).toBeDefined();
});
});
Common Issues and Solutions
Issue: OAuth Redirect URI Mismatch
Ensure the redirect URI in your OAuth configuration matches exactly with what
you provide in the flow.
// ✅ Correct
const redirectUri = "https://yourapp.com/oauth/callback";
// ❌ Incorrect - trailing slash mismatch
const redirectUri = "https://yourapp.com/oauth/callback/";
Issue: Token Expiration
// Handle token expiration gracefully
const flowWithTokenRefresh = new FlowBuilder()
.http("api_call", {
method: "GET",
url: "https://api.service.com/data",
headers: {
Authorization: "Bearer {{tokens.access_token}}",
},
onError: "refresh_token",
})
.http("refresh_token", {
method: "POST",
url: "https://api.service.com/oauth/token",
body: {
refresh_token: "{{tokens.refresh_token}}",
grant_type: "refresh_token",
},
})
.build();
Next Steps
Now that you understand authentication flows, you can:
- Build Custom Connectors: Use authentication flows in your connector definitions
- Implement Data Sync: Set up scheduled data synchronization
- Create React Components: Build user interfaces for authentication
- Handle Errors: Implement robust error handling and recovery
Continue to the Data Synchronization Guide to learn how to sync data after authentication.