kriasoft / kriasoft/oauth-callback

Add support for custom redirect URI paths

Open
#14 0 comments 0 reactions 0 assignees View on GitHub
enhancement good first issue
Dominant language
TypeScript
Stars
11
Forks
4
PR merge metrics
No merged PRs in 30d

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

1. **Allow custom callback paths** in configuration:
- Single custom path
- Multiple paths for different providers
- Path pattern matching (wildcards, regex)

2. **Update server implementations** to handle:
- Multiple registered paths
- Path validation
- Path-specific handlers

3. **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

**⚠️ Note:** This feature requires critical thinking during implementation. Consider:

1. **Security**: How do we prevent redirect URI manipulation attacks? Should we whitelist paths?

2. **Path matching**: Exact match vs pattern matching? What about query parameters in the path?

3. **Alternative approach**: Instead of multiple paths, use a single path with provider identification in state parameter

4. **Backward compatibility**: How do we maintain compatibility with existing `/callback` users?

5. **Provider detection**: Should we auto-detect the provider from the callback path?

## Suggested implementation

```typescript
// 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 = 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

```typescript
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

Contributor guide

Open the contributing guide

Research direction

Start by locating getAuthCode and the CallbackServer implementation, then review the existing callback handling and README examples. Define one settled path-configuration design before implementing it. Done means custom and multiple paths, validation, backward compatibility, the listed security and concurrency tests, and README updates.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, authentication
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.