diff --git a/.env.example b/.env.example
index 60d8804a..2b1e8aec 100644
--- a/.env.example
+++ b/.env.example
@@ -30,3 +30,8 @@ DATABASE_URL=postgresql://user:password@localhost:5432/teachlink
DB_POOL_MAX=20
DB_CONNECTION_TIMEOUT=5000
DB_IDLE_TIMEOUT=30000
+
+# Discord OAuth Configuration
+DISCORD_CLIENT_ID=your_discord_client_id
+DISCORD_CLIENT_SECRET=your_discord_client_secret
+DISCORD_REDIRECT_URI=http://localhost:3000/api/auth/discord/callback
diff --git a/docs/DISCORD_OAUTH_INTEGRATION.md b/docs/DISCORD_OAUTH_INTEGRATION.md
new file mode 100644
index 00000000..0200568c
--- /dev/null
+++ b/docs/DISCORD_OAUTH_INTEGRATION.md
@@ -0,0 +1,189 @@
+# Discord OAuth Integration
+
+This document describes the Discord OAuth2 integration implementation for the TeachLink authentication flow.
+
+## Overview
+
+The Discord OAuth integration allows users to authenticate using their Discord account, providing a seamless signup/login experience.
+
+## Features
+
+- **OAuth2 Flow**: Implements the standard Discord OAuth2 authorization code flow
+- **Security**: Uses state parameter to prevent CSRF attacks
+- **Email Verification**: Requires Discord accounts to have verified emails
+- **Avatar Support**: Fetches and displays user avatars from Discord
+- **Edge Runtime**: Optimized for Edge deployment for fast performance
+
+## Architecture
+
+### Components
+
+1. **OAuth Utilities** (`src/lib/discord/oauth.ts`)
+ - `getDiscordAuthUrl()`: Generates Discord authorization URL
+ - `exchangeCodeForToken()`: Exchanges authorization code for access token
+ - `getDiscordUser()`: Fetches user information from Discord
+ - `getDiscordAvatarUrl()`: Generates avatar URL with fallback
+ - `generateState()`: Generates random state for CSRF protection
+
+2. **API Routes**
+ - `GET /api/auth/discord`: Initiates OAuth flow
+ - `GET /api/auth/discord/callback`: Handles OAuth callback
+
+3. **UI Components**
+ - `DiscordButton`: Reusable button component for Discord auth
+ - Updated login/signup pages with Discord button
+
+### Flow Diagram
+
+```
+User clicks Discord button
+ ↓
+GET /api/auth/discord
+ ↓
+Generate state, set cookie, redirect to Discord
+ ↓
+User authorizes on Discord
+ ↓
+Discord redirects to callback with code
+ ↓
+GET /api/auth/discord/callback
+ ↓
+Validate state, exchange code for token
+ ↓
+Fetch user info from Discord
+ ↓
+Create/update user session
+ ↓
+Return auth response
+```
+
+## Configuration
+
+Add the following environment variables to your `.env` file:
+
+```env
+DISCORD_CLIENT_ID=your_discord_client_id
+DISCORD_CLIENT_SECRET=your_discord_client_secret
+DISCORD_REDIRECT_URI=http://localhost:3000/api/auth/discord/callback
+```
+
+### Getting Discord OAuth Credentials
+
+1. Go to [Discord Developer Portal](https://discord.com/developers/applications)
+2. Create a new application
+3. Navigate to "OAuth2" → "General"
+4. Copy the Client ID and generate a Client Secret
+5. Add your redirect URI under "Redirects"
+6. Save the credentials in your environment variables
+
+### Production Redirect URI
+
+For production, use your actual domain:
+```env
+DISCORD_REDIRECT_URI=https://yourdomain.com/api/auth/discord/callback
+```
+
+## Security Considerations
+
+1. **CSRF Protection**: State parameter is stored in httpOnly cookie and validated on callback
+2. **HTTPS Required**: In production, always use HTTPS for OAuth callbacks
+3. **Secret Management**: Never commit Discord secrets to version control
+4. **Email Verification**: Only accepts Discord accounts with verified emails
+5. **Rate Limiting**: All OAuth endpoints are rate-limited
+
+## API Reference
+
+### GET /api/auth/discord
+
+Initiates Discord OAuth flow.
+
+**Response:** Redirect to Discord authorization page
+
+**Cookie:** Sets `discord_oauth_state` for CSRF protection
+
+### GET /api/auth/discord/callback
+
+Handles Discord OAuth callback.
+
+**Query Parameters:**
+- `code`: Authorization code from Discord
+- `state`: State parameter for CSRF validation
+- `error`: OAuth error (if any)
+
+**Response:**
+```json
+{
+ "message": "Discord authentication successful",
+ "user": {
+ "id": "user_id",
+ "name": "username",
+ "email": "user@example.com",
+ "avatar": "avatar_url",
+ "provider": "discord",
+ "providerId": "discord_user_id"
+ },
+ "token": "jwt_token"
+}
+```
+
+**Error Responses:**
+- `400`: Invalid parameters, unverified email, or OAuth error
+- `500`: Internal server error
+
+## Testing
+
+### Unit Tests
+
+Test OAuth utility functions:
+```bash
+pnpm test src/lib/discord/__tests__/oauth.test.ts
+```
+
+### Integration Tests
+
+Test API routes:
+```bash
+pnpm test src/app/api/auth/discord/__tests__/route.test.ts
+pnpm test src/app/api/auth/discord/callback/__tests__/route.test.ts
+```
+
+### E2E Tests
+
+Test complete OAuth flow:
+```bash
+pnpm test:e2e e2e/auth/discord.spec.ts
+```
+
+## Future Enhancements
+
+- [ ] Implement token refresh logic
+- [ ] Add Discord role-based access control
+- [ ] Store Discord tokens for API integrations
+- [ ] Add Discord guild membership verification
+- [ ] Implement account linking (multiple OAuth providers)
+
+## Troubleshooting
+
+### Common Issues
+
+1. **"Discord OAuth configuration is missing"**
+ - Ensure all environment variables are set
+ - Check that variables are loaded in the Edge runtime
+
+2. **"Invalid state parameter"**
+ - Clear cookies and try again
+ - Ensure state cookie is being set correctly
+
+3. **"Discord email must be verified"**
+ - User must verify their email on Discord first
+ - Cannot use Discord accounts without verified email
+
+4. **Callback URL mismatch**
+ - Ensure redirect URI matches exactly what's configured in Discord Developer Portal
+ - Check for trailing slashes or protocol differences (http vs https)
+
+## Related Documentation
+
+- [Discord OAuth2 Documentation](https://discord.com/developers/docs/topics/oauth2)
+- [Next.js Edge Runtime](https://nextjs.org/docs/pages/building-your-application/rendering/edge-runtime)
+- [Authentication Flow Documentation](./AUTHENTICATION_FLOW.md)
diff --git a/e2e/auth/discord.spec.ts b/e2e/auth/discord.spec.ts
new file mode 100644
index 00000000..8c16717f
--- /dev/null
+++ b/e2e/auth/discord.spec.ts
@@ -0,0 +1,69 @@
+import { test, expect } from '@playwright/test';
+
+test.describe('Discord OAuth Authentication', () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/login');
+ });
+
+ test('should display Discord button on login page', async ({ page }) => {
+ const discordButton = page.locator('button:has-text("Discord")');
+ await expect(discordButton).toBeVisible();
+ });
+
+ test('should display Discord button on signup page', async ({ page }) => {
+ await page.goto('/signup');
+ const discordButton = page.locator('button:has-text("Discord")');
+ await expect(discordButton).toBeVisible();
+ });
+
+ test('should redirect to Discord when clicking Discord button', async ({ page }) => {
+ const discordButton = page.locator('button:has-text("Discord")');
+
+ // Note: This test will actually redirect to Discord, which requires valid OAuth credentials
+ // For testing purposes, we'll just verify the click action and URL change
+
+ // Mock the redirect for testing
+ await page.route('**/api/auth/discord', route => {
+ route.fulfill({
+ status: 302,
+ headers: {
+ location: 'https://discord.com/oauth2/authorize',
+ },
+ });
+ });
+
+ await discordButton.click();
+
+ // Verify that a request was made to the Discord auth endpoint
+ await expect(page).toHaveURL(/discord\.com/);
+ });
+
+ test('should have accessible Discord button', async ({ page }) => {
+ const discordButton = page.locator('button:has-text("Discord")');
+
+ // Check for accessibility attributes
+ await expect(discordButton).toHaveAttribute('type', 'button');
+
+ // Check that it's keyboard navigable
+ await discordButton.focus();
+ await expect(discordButton).toBeFocused();
+ });
+
+ test('should have consistent Discord button styling across pages', async ({ page }) => {
+ // Check on login page
+ await page.goto('/login');
+ const loginDiscordButton = page.locator('button:has-text("Discord")');
+ const loginClasses = await loginDiscordButton.getAttribute('class');
+
+ // Check on signup page
+ await page.goto('/signup');
+ const signupDiscordButton = page.locator('button:has-text("Discord")');
+ const signupClasses = await signupDiscordButton.getAttribute('class');
+
+ // Both should have similar base classes
+ expect(loginClasses).toContain('px-4');
+ expect(loginClasses).toContain('py-2.5');
+ expect(signupClasses).toContain('px-4');
+ expect(signupClasses).toContain('py-2.5');
+ });
+});
diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx
index a35892d4..ffd0ffcb 100644
--- a/src/app/(auth)/login/page.tsx
+++ b/src/app/(auth)/login/page.tsx
@@ -12,12 +12,17 @@ import { FormError, FieldError } from '../../../components/forms/FormError';
import { SubmitButton } from '../../../components/forms/SubmitButton';
import { useMutation } from '../../../hooks/useMutation';
import { apiClient } from '@/lib/api';
+import { DiscordButton } from '../../../components/auth/DiscordButton';
export default function LoginPage() {
const [showPassword, setShowPassword] = useState(false);
const [successMessage, setSuccessMessage] = useState('');
const router = useRouter();
+ const handleDiscordLogin = () => {
+ window.location.href = '/api/auth/discord';
+ };
+
const {
register,
handleSubmit,
@@ -167,16 +172,41 @@ export default function LoginPage() {
-
- {['Google', 'GitHub'].map((provider) => (
-
- ))}
+
+
+
+
diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx
index 4aed8fba..9cceff0c 100644
--- a/src/app/(auth)/signup/page.tsx
+++ b/src/app/(auth)/signup/page.tsx
@@ -12,12 +12,17 @@ import { FormError, FieldError } from '../../../components/forms/FormError';
import { SubmitButton } from '../../../components/forms/SubmitButton';
import { useMutation } from '../../../hooks/useMutation';
import { apiClient } from '@/lib/api';
+import { DiscordButton } from '../../../components/auth/DiscordButton';
export default function SignupPage() {
const [showPassword, setShowPassword] = useState(false);
const [successMessage, setSuccessMessage] = useState('');
const router = useRouter();
+ const handleDiscordSignup = () => {
+ window.location.href = '/api/auth/discord';
+ };
+
const {
register,
handleSubmit,
@@ -177,7 +182,8 @@ export default function SignupPage() {
{/* Social buttons */}
-
+
+