feat(sso): add social login providers (Google, GitHub, RoboLearn) - #18
Conversation
Implement environment-driven social login with Better Auth:
- Google OAuth 2.0 via built-in socialProviders
- GitHub OAuth via built-in socialProviders
- RoboLearn SSO via genericOAuth plugin (custom OIDC)
Key changes:
- auth.ts: Add conditional socialProviders and genericOAuth config
- auth-client.ts: Add genericOAuthClient plugin
- sign-in-form.tsx: Add social login buttons with icons and handlers
- .env.example: Document all social provider env vars
- docs/social-login-providers.md: Comprehensive setup guide
Features:
- All providers toggle via environment variables (no code changes)
- Account linking for same-email users across providers
- Auto-join default organization for new social sign-ups
- OAuth parameter preservation for SSO flows
Note: genericOAuth uses /api/auth/oauth2/callback/{providerId} path
(different from built-in /api/auth/callback/{provider})
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds social login provider support to the Taskflow SSO Platform, enabling users to authenticate via Google OAuth, GitHub OAuth, and RoboLearn SSO (custom OIDC). The implementation leverages Better Auth's built-in socialProviders for Google and GitHub, and the genericOAuth plugin for RoboLearn's OIDC integration. All providers are environment-driven, allowing operators to enable/disable them via environment variables without code changes.
Key Changes:
- Added conditional social provider configuration to Better Auth using environment variables
- Implemented social sign-in UI with provider-specific buttons and icons in the sign-in form
- Created comprehensive documentation covering setup, redirect URIs, database behavior, and troubleshooting
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
sso-platform/src/lib/auth.ts |
Added socialProviders block for Google/GitHub and conditional genericOAuth plugin for RoboLearn OIDC |
sso-platform/src/lib/auth-client.ts |
Added genericOAuthClient plugin to support RoboLearn on the client side |
sso-platform/src/components/sign-in-form.tsx |
Added social login buttons with SVG icons, handleSocialSignIn handler, OAuth parameter preservation, and error handling |
sso-platform/docs/social-login-providers.md |
Comprehensive guide covering redirect URI patterns, provider setup, database behavior, and troubleshooting |
sso-platform/.env.example |
Added 10 environment variables for social provider configuration with setup instructions |
web-dashboard/src/app/page.tsx |
Unrelated wording change in marketing copy ("revolutionizes" → "rethinks") |
specs/008-social-login-providers/* |
Complete specification, planning, and task tracking documents for the feature |
history/prompts/008-social-login-providers/* |
Prompt history documenting the development process including a redirect URI fix |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ```typescript | ||
| accountLinking: { | ||
| enabled: true, | ||
| trustedProviders: ["google", "github", "robolearn"], | ||
| } | ||
| ``` |
There was a problem hiding this comment.
The documentation describes an accountLinking configuration with trustedProviders that doesn't exist in the actual auth.ts file. This code block shows a configuration that is not present in the implementation.
Either:
- Add the actual
accountLinkingconfiguration toauth.tsif account linking is needed - Update the documentation to reflect that Better Auth's default account linking behavior is being used
- Clarify whether account linking is actually enabled by default in Better Auth
Without this configuration, the documented behavior (linking accounts with the same email across providers) may not work as described.
| ```typescript | |
| accountLinking: { | |
| enabled: true, | |
| trustedProviders: ["google", "github", "robolearn"], | |
| } | |
| ``` | |
| > **Note:** Account linking is enabled by default in Better Auth. If a user logs in with multiple providers (Google, GitHub, RoboLearn) using the same email address, their accounts will be linked to a single user record automatically. No explicit configuration is required. |
| } | ||
| } catch (error) { | ||
| console.error(`[SignIn] Social sign-in error (${provider}):`, error); | ||
| setErrors({ general: `Failed to sign in with ${provider}. Please try again.` }); |
There was a problem hiding this comment.
The error handling in handleSocialSignIn only sets setIsLoading(false) in the catch block. However, if the social sign-in redirects successfully (which is the expected behavior), the loading state remains true forever.
While this might not cause visible issues (since the user is redirected away), it's better practice to ensure state cleanup happens in a finally block or remove the manual loading state reset entirely since the component will unmount on redirect.
Consider either:
- Removing the
setIsLoading(false)from the catch block since the redirect will unmount the component - Adding a
finallyblock withsetIsLoading(false)for consistency (though it won't execute on redirect)
| setErrors({ general: `Failed to sign in with ${provider}. Please try again.` }); | |
| setErrors({ general: `Failed to sign in with ${provider}. Please try again.` }); | |
| } finally { |
| } | ||
| } catch (error) { | ||
| console.error(`[SignIn] Social sign-in error (${provider}):`, error); | ||
| setErrors({ general: `Failed to sign in with ${provider}. Please try again.` }); |
There was a problem hiding this comment.
The error message format capitalizes the provider name inconsistently. For example, when the provider is 'google', the error message will read "Failed to sign in with google" instead of "Failed to sign in with Google".
Consider capitalizing the provider name in the error message:
setErrors({ general: `Failed to sign in with ${provider.charAt(0).toUpperCase() + provider.slice(1)}. Please try again.` });Or create a mapping for display names:
const providerNames = { google: 'Google', github: 'GitHub', robolearn: 'RoboLearn' };
setErrors({ general: `Failed to sign in with ${providerNames[provider]}. Please try again.` });refactor: remove unused favicon.ico file
Summary
Changes
Backend (
sso-platform/src/lib/)socialProvidersblock for Google/GitHub, addgenericOAuthplugin for RoboLearn OIDCgenericOAuthClientplugin for RoboLearn supportFrontend (
sso-platform/src/components/)handleSocialSignInhandler, loading states, error handlingConfiguration
Documentation
Key Implementation Details
/api/auth/callback/google/api/auth/callback/github/api/auth/oauth2/callback/robolearnTest Plan
Environment Variables
🤖 Generated with Claude Code