fix: remove hardcoded URLs and add configurable post-login redirect

- Fixed hardcoded finalize URL to build from rpCallbackUrl
- Removed hardcoded /workspace redirect path
- Added postLoginPath config option (default: '/')
- Added allowContinueParam config option (default: true)
- Continue parameter now flows through signed state to session to final redirect

This fixes biblio-stats authentication flow and makes the package work for any RP.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
master
Guillermo Pages 3 months ago
parent c39b078628
commit c15b407e1c

@ -65,6 +65,10 @@ interface OidcStandardConfig {
// State signing secret (should be different from session secret)
stateSigningSecret: string;
// Post-login redirect configuration
postLoginPath?: string; // Default path to redirect after login (default: '/')
allowContinueParam?: boolean; // Allow ?continue= param to override (default: true)
// Optional hook invoked after id_token verification succeeds
onUserAuthenticated?: OnUserAuthenticatedHandler;
}
@ -104,7 +108,9 @@ export function createOidcStandardRoutes(config: OidcStandardConfig): Router {
rpCookieDomain,
rpFrontendUrl,
sessionCookieName,
stateSigningSecret
stateSigningSecret,
postLoginPath = '/',
allowContinueParam = true
} = config;
// Debug log the configuration
@ -140,12 +146,18 @@ export function createOidcStandardRoutes(config: OidcStandardConfig): Router {
const jti = crypto.randomBytes(16).toString('base64url');
const nonce = crypto.randomBytes(16).toString('base64url');
// Capture continue parameter for post-login redirect (if allowed)
const continueParam = allowContinueParam
? (req.query.continue as string | undefined)
: undefined;
// Create signed state token with all necessary data
const state = await new SignJWT({
jti, // Unique ID for replay prevention
nonce, // For id_token validation
client_id: swissoidClientId,
redirect_uri: rpCallbackUrl
redirect_uri: rpCallbackUrl,
continue: continueParam // Optional post-login redirect path
})
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
@ -359,13 +371,14 @@ export function createOidcStandardRoutes(config: OidcStandardConfig): Router {
// Step 6: Create opaque session (but don't set cookie yet)
const sessionId = crypto.randomBytes(24).toString('base64url');
// Store session data
// Store session data (including continue param for post-login redirect)
await sessionService.createSession(sessionId, {
sub: payload.sub,
iat: payload.iat,
exp: payload.exp,
email: payload.email,
name: payload.name
name: payload.name,
continue: statePayload.continue // Carry over from signed state
});
// Step 7: Create transit token and store in Redis
@ -389,7 +402,9 @@ export function createOidcStandardRoutes(config: OidcStandardConfig): Router {
// Step 8: Redirect to finalize endpoint with transit token
// This will set the cookie in first-party context
const finalizeUrl = `https://gateway.clockize.com/oidc/finalize?tx=${transitToken}`;
// Build finalize URL from rpCallbackUrl to support any RP
const callbackUrl = new URL(rpCallbackUrl);
const finalizeUrl = `${callbackUrl.protocol}//${callbackUrl.host}/oidc/finalize?tx=${transitToken}`;
// Set security headers for the redirect
res.setHeader('Cache-Control', 'no-store');
@ -464,8 +479,11 @@ export function createOidcStandardRoutes(config: OidcStandardConfig): Router {
res.cookie(sessionCookieName, sessionId, cookieOptions);
// Redirect to the app
const redirectUrl = `${rpFrontendUrl}/workspace`;
// Build redirect URL using continue parameter or default path
const continueValue = sessionData.continue || postLoginPath;
const redirectUrl = continueValue.startsWith('/')
? `${rpFrontendUrl}${continueValue}`
: rpFrontendUrl;
// Set security headers
res.setHeader('Cache-Control', 'no-store');

Loading…
Cancel
Save