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:
-
Authorization URL Construction: Includes
client_id,redirect_uri,response_type=code,scope,state,code_challenge,code_challenge_method=S256,access_type=offline, andprompt=consentto force refresh token issuance. -
Callback Server:
createCallbackServerstarts a local HTTP server (default port 6477) to receive the redirect. -
Code Exchange:
exchangeCodeForTokensPOSTs tohttps://oauth2.googleapis.com/tokenwith the PKCE verifier and client secret, returningaccess_token,refresh_token, andexpires_in. -
User Email:
getUserEmailcallshttps://www.googleapis.com/oauth2/v2/userinfoto 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_uriis set tohttps://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)teamobject withidandnameauthed_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
codeanderrorparameters - User Experience: Generates HTML success/failure pages via
generateCallbackPageto provide clear feedback to the user - Deeplink Support: When
deeplinkUrlis 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.tsmodule provides the local HTTP listener, whileopen-url.tshandles 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →