How the OAuth Flow Is Implemented for Google, Slack, and Microsoft Integrations in Craft-Agents OSS

Craft-Agents OSS implements a unified OAuth 2.0 architecture with provider-specific adaptations for Google, Slack, and Microsoft, using PKCE for Google and Microsoft, HTTP Basic authentication for Slack, and a shared local callback server to capture authorization codes across all three providers.

The lukilabs/craft-agents-oss repository provides a reusable authentication layer that abstracts the differences between these three major identity providers while maintaining strict type safety and security best practices. The implementation lives in the packages/shared/src/auth/ directory and supports complete token lifecycles from initial authorization through refresh.

Unified OAuth Architecture Overview

All three integrations follow an eight-step flow that balances shared infrastructure with provider-specific security requirements:

Step Description Implementation
1. Build Auth URL Provider-specific scopes, client IDs, and PKCE parameters (Google/Microsoft) or HTTP-Basic preparation (Slack) are configured. prepareGoogleOAuth, prepareSlackOAuth, prepareMicrosoftOAuth
2. Open Browser Cross-platform browser window launches automatically. openUrl in shared/src/utils/open-url.ts
3. Local Callback Server HTTP server listens on localhost (or receives relayed traffic for Slack) to capture the authorization code. createCallbackServer in shared/src/auth/callback-server.ts
4. State Verification CSRF protection via state parameter validation; errors propagated immediately. Handled within startGoogleOAuth, startSlackOAuth, startMicrosoftOAuth
5. Token Exchange POST to provider token endpoint returns access token, refresh token, and expiry. exchangeCodeForTokens (provider-specific variants)
6. User Identity Email or workspace name retrieved via provider-specific "who-am-I" endpoints. getUserEmail (Google/Microsoft) or team extraction (Slack)
7. Typed Result Strongly-typed result object containing credentials and metadata. GoogleOAuthResult, SlackOAuthResult, MicrosoftOAuthResult
8. Token Refresh Silent refresh using stored refresh tokens when access tokens expire. refreshGoogleToken, refreshSlackToken, refreshMicrosoftToken

Google OAuth Implementation

The Google integration in packages/shared/src/auth/google-oauth.ts follows the standard OAuth 2.0 Authorization Code flow with PKCE extension, suitable for native applications that cannot securely store client secrets.

Configuration and PKCE Setup

The implementation requires GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET environment variables (lines 29-31). Before initiating the flow, generatePKCE creates a cryptographically random code_verifier and its SHA256 hash code_challenge (S256 method).

Scopes are service-specific via GOOGLE_SERVICE_SCOPES, with userinfo.email always appended via getGoogleScopes to ensure user identification.

Authorization and Token Exchange

The startGoogleOAuth function (lines 82-85) orchestrates the flow:

  1. Authorization URL Construction: Includes client_id, redirect_uri, response_type=code, scope, state, code_challenge, code_challenge_method=S256, access_type=offline, and prompt=consent to force refresh token issuance.

  2. Callback Server: createCallbackServer starts a local HTTP server (default port 6477) to receive the redirect.

  3. Code Exchange: exchangeCodeForTokens POSTs to https://oauth2.googleapis.com/token with the PKCE verifier and client secret, returning access_token, refresh_token, and expires_in.

  4. User Email: getUserEmail calls https://www.googleapis.com/oauth2/v2/userinfo to retrieve the authenticated email address.

Google OAuth Example

import { startGoogleOAuth } from '@craft-agents/shared/auth/google-oauth';

async function loginGmail() {
  const result = await startGoogleOAuth({ service: 'gmail' });
  if (!result.success) {
    console.error('Google OAuth failed:', result.error);
    return;
  }
  console.log('Access token:', result.accessToken);
  console.log('Refresh token:', result.refreshToken);
  console.log('User email:', result.email);
}

The result type is GoogleOAuthResult (line 94), containing the tokens, expiry timestamp, email, and client credentials for future refresh operations.

Slack OAuth Implementation

The Slack integration in packages/shared/src/auth/slack-oauth.ts differs significantly from Google and Microsoft because Slack uses a user-scope OAuth flow with HTTP Basic authentication rather than PKCE.

HTTP Basic Authentication

Slack requires SLACK_OAUTH_CLIENT_ID and SLACK_OAUTH_CLIENT_SECRET as mandatory environment variables (lines 27-28). Unlike Google, Slack does not use PKCE; instead, the client credentials are encoded using HTTP Basic auth (base64(client_id:client_secret)) during the token exchange.

Scopes are defined in SLACK_SERVICE_SCOPES and passed as user_scope (not scope) in the authorization URL via getSlackScopes.

Cloudflare Relay for HTTPS Callback

Slack mandates HTTPS redirect URIs, which poses a challenge for local development. The implementation solves this via a Cloudflare relay:

  • The redirect_uri is set to https://agents.craft.do/auth/slack/callback (lines 58-61)
  • This endpoint forwards the authorization code to the local HTTP server running on localhost
  • The local server is still created via createCallbackServer, maintaining architectural consistency with the other providers

Token Exchange and Workspace Info

The exchangeCodeForTokens function POSTs to https://slack.com/api/oauth.v2.access with the Basic auth header. The response contains:

  • authed_user.access_token (the user token)
  • team object with id and name
  • authed_user.id

The exchangeSlackOAuth function returns the team name as the identifier (since Slack workspace access is the primary authorization unit), wrapping everything in SlackOAuthResult (line 82).

Slack OAuth Example

import { startSlackOAuth } from '@craft-agents/shared/auth/slack-oauth';

async function loginSlack() {
  const result = await startSlackOAuth({ service: 'full' });
  if (!result.success) {
    console.error('Slack OAuth failed:', result.error);
    return;
  }
  console.log('User token:', result.accessToken);
  console.log('Workspace:', result.teamName, `(ID ${result.teamId})`);
}

Microsoft OAuth Implementation

The Microsoft integration in packages/shared/src/auth/microsoft-oauth.ts follows a PKCE-based flow similar to Google but with Microsoft Graph-specific endpoints and scope conventions.

Client-Only Authentication (PKCE)

Microsoft OAuth in Craft-Agents uses PKCE without a client secret, making it suitable for native applications. Only MICROSOFT_OAUTH_CLIENT_ID is required (line 32); the PKCE verifier serves as the security mechanism instead of a shared secret.

The generatePKCE function creates the verifier and S256 challenge identically to the Google implementation.

Microsoft Graph Integration

Scopes are service-specific via MICROSOFT_SERVICE_SCOPES, with mandatory additions in getMicrosoftScopes:

  • User.Read (for user profile access)
  • offline_access (to receive refresh tokens)

The authorization URL includes response_mode=query and prompt=consent alongside standard PKCE parameters.

Token exchange POSTs to https://login.microsoftonline.com/common/oauth2/v2.0/token with the PKCE verifier. The access_token is then used to call Microsoft Graph at https://graph.microsoft.com/v1.0/me.

User email resolution prefers the mail property, falling back to userPrincipalName if the primary email is not available.

Microsoft OAuth Example

import { startMicrosoftOAuth } from '@craft-agents/shared/auth/microsoft-oauth';

async function loginOutlook() {
  const result = await startMicrosoftOAuth({ service: 'outlook' });
  if (!result.success) {
    console.error('Microsoft OAuth failed:', result.error);
    return;
  }
  console.log('Access token:', result.accessToken);
  console.log('User email/UPN:', result.email);
}

The result conforms to MicrosoftOAuthResult (line 100), containing tokens, expiry, and the resolved email address.

Shared Infrastructure Components

The Local Callback Server

All three providers rely on createCallbackServer in packages/shared/src/auth/callback-server.ts (lines 57-89) to capture the OAuth redirect:

  • Port Selection: Iterates from port 6477 upward for up to 100 attempts (START_PORT / MAX_PORT_ATTEMPTS)
  • Request Handling: Parses query strings to extract code and error parameters
  • User Experience: Generates HTML success/failure pages via generateCallbackPage to provide clear feedback to the user
  • Deeplink Support: When deeplinkUrl is provided, the success page redirects back to the Craft-Agents application

Cross-Platform Browser Opening

The openUrl utility in packages/shared/src/utils/open-url.ts handles platform detection to launch the default browser consistently across macOS, Windows, and Linux environments.

Summary

  • Unified Architecture: All three providers use the same eight-step flow built on createCallbackServer, with provider-specific adaptations for security mechanisms.
  • PKCE for Google and Microsoft: Both implementations use PKCE (RFC 7636) to secure the authorization code flow, with Google requiring a client secret while Microsoft operates client-only.
  • Slack HTTP Basic Auth: Slack substitutes PKCE with HTTP Basic authentication and requires a Cloudflare HTTPS relay to satisfy redirect URI requirements.
  • Shared Infrastructure: The callback-server.ts module provides the local HTTP listener, while open-url.ts handles cross-platform browser launching for all providers.
  • Type-Safe Results: Each provider returns a strongly-typed result object (GoogleOAuthResult, SlackOAuthResult, MicrosoftOAuthResult) containing tokens, expiry, and user identity.

Frequently Asked Questions

What is the primary difference between Google and Slack OAuth implementations in Craft-Agents?

The Google implementation uses PKCE (Proof Key for Code Exchange) with a client secret during token exchange, while Slack uses HTTP Basic authentication with base64-encoded credentials and does not implement PKCE. Additionally, Google communicates directly with localhost, whereas Slack requires a Cloudflare HTTPS relay to handle the redirect URI.

Why does the Slack OAuth flow require a Cloudflare relay instead of direct localhost communication?

Slack's OAuth 2.0 implementation mandates HTTPS redirect URIs for security reasons, which prevents direct communication with a local HTTP server running on localhost. The Craft-Agents implementation solves this by redirecting to https://agents.craft.do/auth/slack/callback, which then forwards the authorization code to the local callback server running via createCallbackServer.

How does the callback server handle concurrent OAuth attempts from different providers?

The createCallbackServer function in packages/shared/src/auth/callback-server.ts attempts to bind to ports starting at 6477, iterating up to 100 times to find an available port. Each OAuth flow instance creates its own server instance on a unique available port, allowing concurrent authentication attempts to operate independently without port conflicts.

Is PKCE required for all OAuth providers in the Craft-Agents implementation?

No, PKCE is only required for Google and Microsoft integrations. The Google implementation uses PKCE with an additional client secret, while Microsoft uses PKCE without a client secret (client-only authentication). The Slack implementation deliberately omits PKCE in favor of HTTP Basic authentication during the token exchange phase.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →