Skip to main content
Glama
oauth-flow.spec.ts8.87 kB
/** * End-to-end tests for OAuth authentication flow */ import { test, expect } from '@playwright/test'; test.describe('OAuth Authentication Flow', () => { test.beforeEach(async ({ page }) => { // Set up page context for OAuth testing await page.setExtraHTTPHeaders({ 'User-Agent': 'MCP-WordPress-Remote-E2E-Test', }); }); test('should complete hosted WordPress OAuth flow', async ({ page }) => { // Start the OAuth flow by navigating to the authorization URL await page.goto('/auth/start'); // Should redirect to hosted WordPress authorization page await expect(page).toHaveURL(/public-api\.wordpress\.com\/oauth2\/authorize/); // Check that required OAuth parameters are present const url = new URL(page.url()); expect(url.searchParams.get('response_type')).toBe('code'); expect(url.searchParams.get('client_id')).toBeTruthy(); expect(url.searchParams.get('redirect_uri')).toContain('127.0.0.1:3000/callback'); expect(url.searchParams.get('scope')).toBeTruthy(); expect(url.searchParams.get('state')).toBeTruthy(); // PKCE parameters for OAuth 2.1 compliance expect(url.searchParams.get('code_challenge')).toBeTruthy(); expect(url.searchParams.get('code_challenge_method')).toBe('S256'); // In a real test, we would: // 1. Mock the hosted WordPress login page // 2. Fill in test credentials // 3. Submit the form // 4. Handle the redirect back to our callback // For now, we'll simulate the callback const state = url.searchParams.get('state'); const callbackUrl = `http://127.0.0.1:3000/callback?code=test_auth_code&state=${state}`; await page.goto(callbackUrl); // Should show success page await expect(page.locator('text=Authorization successful')).toBeVisible(); // Should have stored the auth tokens // This would be verified by checking the auth storage }); test('should handle OAuth authorization errors', async ({ page }) => { // Simulate an OAuth error response await page.goto('/callback?error=access_denied&error_description=User%20denied%20authorization&state=test_state'); // Should show error page await expect(page.locator('text=Authorization failed')).toBeVisible(); await expect(page.locator('text=access_denied')).toBeVisible(); }); test('should validate state parameter in callback', async ({ page }) => { // Try callback with invalid state await page.goto('/callback?code=test_code&state=invalid_state'); // Should show error for invalid state await expect(page.locator('text=Invalid state parameter')).toBeVisible(); }); test('should handle PKCE verification in callback', async ({ page }) => { // This test would verify that PKCE verification works correctly // It requires coordinating the code verifier from the authorization request // with the callback verification await page.goto('/auth/start'); // Extract state and code challenge from authorization URL const authUrl = new URL(page.url()); const state = authUrl.searchParams.get('state'); const codeChallenge = authUrl.searchParams.get('code_challenge'); expect(state).toBeTruthy(); expect(codeChallenge).toBeTruthy(); // Simulate successful callback with proper state await page.goto(`/callback?code=valid_auth_code&state=${state}`); // Should complete successfully if PKCE verification passes await expect(page.locator('text=Authorization successful')).toBeVisible(); }); test('should handle self-hosted WordPress OAuth flow', async ({ page, context }) => { // Set up for self-hosted WordPress site await context.addInitScript(() => { (globalThis as any).localStorage.setItem('wp_site_type', 'self-hosted'); (globalThis as any).localStorage.setItem('wp_site_url', 'https://example.com'); }); await page.goto('/auth/start'); // Should redirect to self-hosted WordPress authorization endpoint await expect(page).toHaveURL(/example\.com\/wp-json\/oauth\/v2\/authorize/); // Check OAuth parameters for self-hosted const url = new URL(page.url()); expect(url.searchParams.get('response_type')).toBe('code'); expect(url.searchParams.get('client_id')).toBeTruthy(); // Self-hosted sites should use OAuth 2.1 with PKCE expect(url.searchParams.get('code_challenge')).toBeTruthy(); expect(url.searchParams.get('code_challenge_method')).toBe('S256'); }); test('should handle OAuth token refresh flow', async ({ page, context }) => { // Set up existing expired tokens await context.addInitScript(() => { const expiredToken = { access_token: 'expired_token', token_type: 'Bearer', expires_in: 3600, obtained_at: Date.now() - (2 * 60 * 60 * 1000), // 2 hours ago refresh_token: 'valid_refresh_token', }; (globalThis as any).localStorage.setItem('wp_oauth_tokens', JSON.stringify(expiredToken)); }); // Try to make an API request that would trigger token refresh await page.goto('/api/test'); // Should automatically refresh the token and complete the request // This would be indicated by successful API response await expect(page.locator('text=API request successful')).toBeVisible(); }); test('should handle concurrent OAuth flows', async ({ browser }) => { // Open multiple pages to simulate concurrent OAuth attempts const context1 = await browser.newContext(); const context2 = await browser.newContext(); const page1 = await context1.newPage(); const page2 = await context2.newPage(); // Start OAuth flow in both contexts simultaneously await Promise.all([ page1.goto('/auth/start'), page2.goto('/auth/start'), ]); // Both should get different state parameters const url1 = new URL(page1.url()); const url2 = new URL(page2.url()); const state1 = url1.searchParams.get('state'); const state2 = url2.searchParams.get('state'); expect(state1).toBeTruthy(); expect(state2).toBeTruthy(); expect(state1).not.toBe(state2); // Both should be able to complete their flows independently await Promise.all([ page1.goto(`/callback?code=code1&state=${state1}`), page2.goto(`/callback?code=code2&state=${state2}`), ]); await expect(page1.locator('text=Authorization successful')).toBeVisible(); await expect(page2.locator('text=Authorization successful')).toBeVisible(); await context1.close(); await context2.close(); }); test('should handle OAuth timeout scenarios', async ({ page }) => { await page.goto('/auth/start'); // Wait for longer than typical OAuth timeout await page.waitForTimeout(5000); // Try to complete the flow after timeout const url = new URL(page.url()); const state = url.searchParams.get('state'); await page.goto(`/callback?code=late_code&state=${state}`); // Should handle gracefully (either success or appropriate timeout message) const hasSuccess = await page.locator('text=Authorization successful').isVisible(); const hasTimeout = await page.locator('text=Authorization timeout').isVisible(); expect(hasSuccess || hasTimeout).toBe(true); }); test('should implement proper security measures', async ({ page }) => { // Test CSRF protection await page.goto('/callback?code=test_code&state=malicious_state'); await expect(page.locator('text=Invalid state parameter')).toBeVisible(); // Test parameter validation await page.goto('/callback?code=&state=valid_state'); await expect(page.locator('text=Missing required parameters')).toBeVisible(); // Test code injection protection await page.goto('/callback?code=<script>alert("xss")</script>&state=valid_state'); // Should not execute the script and should show appropriate error const alertDialogs: any[] = []; page.on('dialog', dialog => { alertDialogs.push(dialog); dialog.accept(); }); await page.waitForTimeout(1000); expect(alertDialogs).toHaveLength(0); // No script should have executed }); test('should work across different browsers', async ({ browserName, page }) => { // This test will run for each browser configured in playwright.config.ts test.skip(browserName === 'webkit' && process.platform === 'linux', 'WebKit on Linux not supported'); // Basic OAuth flow should work in all browsers await page.goto('/auth/start'); // Should redirect to authorization endpoint await expect(page).toHaveURL(/oauth2\/authorize/); // OAuth parameters should be present const url = new URL(page.url()); expect(url.searchParams.get('client_id')).toBeTruthy(); expect(url.searchParams.get('code_challenge')).toBeTruthy(); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Automattic/mcp-wordpress-remote'

If you have feedback or need assistance with the MCP directory API, please join our Discord server