@ -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' ) ;