Skip to main content

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:
  1. Build Custom Connectors: Use authentication flows in your connector definitions
  2. Implement Data Sync: Set up scheduled data synchronization
  3. Create React Components: Build user interfaces for authentication
  4. Handle Errors: Implement robust error handling and recovery
Continue to the Data Synchronization Guide to learn how to sync data after authentication.
I