OAuth Integration

Add OAuth authentication to your DAIN service

Overview

DAIN services support OAuth2 authentication through a built-in OAuth system. This allows your service to authenticate users on their assistant with providers like GitHub, Google, and others.

Configuration

Configure OAuth in your service definition:

import { defineDAINService, createOAuth2Tool } from "@dainprotocol/service-sdk";

const dainService = defineDAINService({
  metadata: { /* ... */ },
  oauth2: {
    baseUrl: process.env.TUNNEL_URL,
    providers: {
      github: {
        clientId: process.env.GITHUB_CLIENT_ID,
        clientSecret: process.env.GITHUB_CLIENT_SECRET,
        authorizationUrl: "https://github.com/login/oauth/authorize",
        tokenUrl: "https://github.com/login/oauth/access_token",
        scopes: ["user", "gist"],
        onSuccess: async (agentId, tokens) => {
          await tokenStore.set(agentId, tokens);
        }
      }
    }
  }
});

Token Storage

Implement secure token storage:

interface OAuth2Tokens {
  accessToken: string;
  refreshToken?: string;
  expiresAt: number;
  provider: string;
  agentId: string;
}

// Development
const tokenStore = new Map<string, OAuth2Tokens>();

// Production
class DatabaseTokenStore {
  async set(agentId: string, tokens: OAuth2Tokens) {
    await db.tokens.upsert({ agentId, ...tokens });
  }
  
  async get(agentId: string): Promise<OAuth2Tokens | null> {
    return db.tokens.findUnique({ where: { agentId } });
  }
}

Authentication Methods

Method 1: OAuth UI Builder

Use the OAuthUIBuilder for clean authentication flows:

import { OAuthUIBuilder } from '@dainprotocol/utils';

const githubTool: ToolConfig = {
  id: "github-tool",
  name: "GitHub Tool",
  description: "Interact with GitHub",
  handler: async (inputs, agentInfo, { app }) => {
    const tokens = await tokenStore.get(agentInfo.id);

    if (!tokens) {
      const authUrl = await app.oauth2?.generateAuthUrl(
        "github", 
        agentInfo.id
      );
      
      return {
        text: "GitHub authentication required",
        data: null,
        ui: new OAuthUIBuilder()
          .title("GitHub Authentication")
          .content("Please authenticate to continue")
          .url(authUrl)
          .provider("github")
          .build()
      };
    }

    // Continue with authenticated request...
  }
};

Method 2: Authentication Helper

Create a reusable authentication helper:

import { Hono } from 'hono';
import { AgentInfo } from '@dainprotocol/service-sdk/service';
import { OAuthUIBuilder } from '@dainprotocol/utils';

interface RequestAuthentication {
  app: Hono;
  agentInfo: AgentInfo;
}

export const requestAuthenticationTool = (
  provider: string,
  { app, agentInfo }: RequestAuthentication
) => {
  const authUrl = await app.oauth2?.generateAuthUrl(provider, agentInfo.id);
  
  return {
    text: `Please authenticate with ${provider}`,
    data: null,
    ui: new OAuthUIBuilder()
      .title(`${provider} Authentication`)
      .content(`Authentication required`)
      .url(authUrl)
      .provider(provider)
      .build()
  };
};

Complete Example: GitHub Gist Service

import { 
  defineDAINService, 
  ToolConfig,
  createOAuth2Tool 
} from "@dainprotocol/service-sdk";
import { OAuthUIBuilder } from '@dainprotocol/utils';

const createGistConfig: ToolConfig = {
  id: "create-gist",
  name: "Create GitHub Gist",
  description: "Creates a new GitHub Gist",
  input: z.object({
    description: z.string(),
    filename: z.string(),
    content: z.string()
  }),
  handler: async ({ description, filename, content }, agentInfo, { app }) => {
    const tokens = tokenStore.get(agentInfo.id);

    if (!tokens) {
      return requestAuthenticationTool("github", { app, agentInfo });
    }

    // Make authenticated request
    const response = await fetch("https://api.github.com/gists", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${tokens.accessToken}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        description,
        public: true,
        files: {
          [filename]: { content }
        }
      })
    });

    const gist = await response.json();

    return {
      text: `Created Gist: ${gist.html_url}`,
      data: { url: gist.html_url },
      ui: new CardUIBuilder()
        .title("Gist Created")
        .content(`URL: ${gist.html_url}`)
        .build()
    };
  }
};

const dainService = defineDAINService({
  metadata: {
    title: "GitHub Service",
    description: "GitHub integration with OAuth"
  },
  oauth2: {
    baseUrl: process.env.TUNNEL_URL,
    providers: {
      github: {
        clientId: process.env.GITHUB_CLIENT_ID,
        clientSecret: process.env.GITHUB_CLIENT_SECRET,
        authorizationUrl: "https://github.com/login/oauth/authorize",
        tokenUrl: "https://github.com/login/oauth/access_token",
        scopes: ["gist"],
        onSuccess: async (agentId, tokens) => {
          await tokenStore.set(agentId, tokens);
        }
      }
    }
  },
  tools: [createOAuth2Tool("github"), createGistConfig]
});

Provider Setup

  1. Register your application in the provider's developer portal
  2. Configure callback URLs:
    https://your-base-url/oauth2/callback/{provider}
    
  3. Store credentials securely:
    GITHUB_CLIENT_ID=your_client_id
    GITHUB_CLIENT_SECRET=your_client_secret
    

Important Notes

  • Use environment variables for credentials
  • Store tokens securely in production
  • HTTPS is required for callbacks
  • Default callback path is /oauth2/callback/{provider}
  • Token refresh is handled automatically
  • Test with tunnel URLs in development