-
-
Notifications
You must be signed in to change notification settings - Fork 3
Description
Description
Currently, the library defaults to /callback as the redirect path. Many OAuth providers require specific redirect URIs to be registered exactly, and developers may need to use different paths. Add support for custom and multiple callback paths.
What needs to be done
-
Allow custom callback paths in configuration:
- Single custom path
- Multiple paths for different providers
- Path pattern matching (wildcards, regex)
-
Update server implementations to handle:
- Multiple registered paths
- Path validation
- Path-specific handlers
-
Enhance documentation to explain:
- How to configure custom paths
- Provider-specific requirements
- Security considerations
Why this matters
Redirect URI flexibility is essential:
- Provider requirements: Some providers enforce specific paths
- Multi-tenant apps: Different paths for different clients
- Legacy support: Maintaining backward compatibility
- Security: Path-based access control
Current limitations:
- Can't use library with providers requiring specific paths
- Can't handle multiple OAuth flows simultaneously
- No support for path-based routing
Implementation considerations
-
Security: How do we prevent redirect URI manipulation attacks? Should we whitelist paths?
-
Path matching: Exact match vs pattern matching? What about query parameters in the path?
-
Alternative approach: Instead of multiple paths, use a single path with provider identification in state parameter
-
Backward compatibility: How do we maintain compatibility with existing
/callbackusers? -
Provider detection: Should we auto-detect the provider from the callback path?
Suggested implementation
// Option 1: Simple custom path
interface GetAuthCodeOptions {
// ... existing options
callbackPath?: string; // Already exists but needs enhancement
callbackPaths?: string[]; // New: multiple paths
pathMatcher?: (path: string) => boolean; // New: custom matcher
}
// Option 2: Advanced path configuration
interface PathConfig {
path: string;
exact?: boolean; // Exact match vs prefix
provider?: string; // Associate with provider
handler?: (params: CallbackResult) => CallbackResult; // Custom handler
}
interface GetAuthCodeOptions {
// ... existing options
paths?: PathConfig[];
}
// Implementation in server
class CallbackServer {
private paths: Map<string, PathConfig> = new Map();
registerPath(config: PathConfig): void {
this.paths.set(config.path, config);
}
private handleRequest(request: Request): Response {
const url = new URL(request.url);
const pathname = url.pathname;
// Try exact match first
let config = this.paths.get(pathname);
// Try prefix match if no exact match
if (!config) {
for (const [path, pathConfig] of this.paths) {
if (!pathConfig.exact && pathname.startsWith(path)) {
config = pathConfig;
break;
}
}
}
// Try custom matcher
if (!config) {
for (const [, pathConfig] of this.paths) {
if (pathConfig.matcher?.(pathname)) {
config = pathConfig;
break;
}
}
}
if (!config) {
return new Response("Not Found", { status: 404 });
}
// Handle OAuth callback
const params = this.extractParams(url);
// Apply custom handler if provided
const result = config.handler ? config.handler(params) : params;
// Resolve the appropriate promise based on path
this.resolveCallback(config.path, result);
return new Response(/* success/error HTML */);
}
}
// Usage examples
// 1. Simple custom path
await getAuthCode({
authorizationUrl: "...",
callbackPath: "/auth/github/callback"
});
// 2. Multiple paths for different providers
const server = createCallbackServer();
await server.start({ port: 3000 });
// GitHub flow
const githubPromise = server.waitForCallback("/auth/github/callback", 30000);
// Google flow (concurrent)
const googlePromise = server.waitForCallback("/auth/google/callback", 30000);
// 3. Advanced configuration with patterns
await getAuthCode({
authorizationUrl: "...",
paths: [
{
path: "/auth/github/callback",
exact: true,
provider: "github"
},
{
path: "/auth/",
exact: false, // Prefix match for /auth/*
handler: (params) => {
// Custom processing
console.log("Auth callback:", params);
return params;
}
},
{
matcher: (path) => path.match(/^\/oauth\/\d+\/callback$/),
provider: "dynamic"
}
]
});
// 4. Backward compatible (default behavior)
await getAuthCode("https://..."); // Uses /callback
// 5. Provider-specific paths
const providers = {
github: "/auth/github/return",
google: "/signin-oidc",
microsoft: "/auth/microsoft/callback",
auth0: "/callback",
okta: "/authorization-code/callback"
};
await getAuthCode({
authorizationUrl: "...",
callbackPath: providers[selectedProvider]
});Path validation requirements
function validateCallbackPath(path: string): void {
// Must start with /
if (!path.startsWith('/')) {
throw new Error("Callback path must start with /");
}
// No double slashes
if (path.includes('//')) {
throw new Error("Callback path cannot contain //");
}
// No query parameters
if (path.includes('?')) {
throw new Error("Callback path cannot contain query parameters");
}
// No fragments
if (path.includes('#')) {
throw new Error("Callback path cannot contain fragments");
}
// Reasonable length
if (path.length > 256) {
throw new Error("Callback path too long");
}
// No path traversal
if (path.includes('../')) {
throw new Error("Callback path cannot contain ../");
}
}Testing requirements
- Test single custom path
- Test multiple concurrent paths
- Test path pattern matching
- Test path validation
- Test backward compatibility
- Test security (path traversal attempts)
- Test with real provider requirements
Documentation updates
Update README with:
- Custom path examples
- Provider-specific path requirements
- Security best practices for paths
- Migration guide from default path
Skills required
- TypeScript
- HTTP routing concepts
- URL path handling
- Security awareness
- API design
Difficulty
Easy - Straightforward enhancement with important design decisions