@@ -18,6 +18,7 @@ import { UserService } from "../user/user-service";
1818import { AuthFlow , AuthProvider } from "./auth-provider" ;
1919import { HostContextProvider } from "./host-context-provider" ;
2020import { SignInJWT } from "./jwt" ;
21+ import { getFeatureFlagEnforceAuthorizeStateValidation } from "../util/featureflags" ;
2122
2223@injectable ( )
2324export class Authenticator {
@@ -121,6 +122,60 @@ export class Authenticator {
121122 return state ;
122123 }
123124
125+ private async validateOAuthState (
126+ userId : string ,
127+ stateParam : string | undefined ,
128+ host : string | undefined ,
129+ returnTo : string | undefined ,
130+ ) : Promise < boolean > {
131+ // Check feature flag for OAuth state validation
132+ const enforceStateValidation = await getFeatureFlagEnforceAuthorizeStateValidation ( userId ) ;
133+
134+ if ( ! enforceStateValidation ) {
135+ log . info ( `OAuth state validation disabled by feature flag` , {
136+ host,
137+ returnTo,
138+ "authorize-flow" : true ,
139+ } ) ;
140+ return true ; // Allow request to proceed
141+ }
142+
143+ // Validate JWT state parameter when feature flag is enabled
144+ if ( ! stateParam ) {
145+ log . warn ( `Missing state parameter in authorize request` , {
146+ host,
147+ returnTo,
148+ "authorize-flow" : true ,
149+ } ) ;
150+ return false ;
151+ }
152+
153+ try {
154+ const flowState = await this . parseState ( stateParam ) ;
155+
156+ if ( flowState . host !== host || flowState . returnTo !== returnTo ) {
157+ log . warn ( `State parameter mismatch in authorize request` , {
158+ stateHost : flowState . host ,
159+ requestHost : host ,
160+ stateReturnTo : flowState . returnTo ,
161+ requestReturnTo : returnTo ,
162+ "authorize-flow" : true ,
163+ } ) ;
164+ return false ;
165+ }
166+
167+ log . info ( `Valid state parameter in authorize request` , { host, "authorize-flow" : true } ) ;
168+ return true ; // Validation passed
169+ } catch ( error ) {
170+ log . warn ( `Invalid state parameter in authorize request` , error , {
171+ host,
172+ returnTo,
173+ "authorize-flow" : true ,
174+ } ) ;
175+ return false ;
176+ }
177+ }
178+
124179 protected async getAuthProviderForHost ( host : string ) : Promise < AuthProvider | undefined > {
125180 const hostContext = this . hostContextProvider . get ( host ) ;
126181 return hostContext && hostContext . authProvider ;
@@ -232,6 +287,15 @@ export class Authenticator {
232287 const host : string | undefined = req . query . host ?. toString ( ) ;
233288 const scopes : string = req . query . scopes ?. toString ( ) || "" ;
234289 const override = req . query . override === "true" ;
290+ const stateParam = req . query . state ?. toString ( ) ;
291+
292+ // Validate OAuth state parameter (feature-flagged)
293+ const isStateValid = await this . validateOAuthState ( user . id , stateParam , host , returnTo ) ;
294+ if ( ! isStateValid ) {
295+ res . redirect ( this . getSorryUrl ( `Invalid authorization request.` ) ) ;
296+ return ;
297+ }
298+
235299 const authProvider = host && ( await this . getAuthProviderForHost ( host ) ) ;
236300 if ( ! returnTo || ! host || ! authProvider ) {
237301 log . info ( `Bad request: missing parameters.` , { "authorize-flow" : true } ) ;
0 commit comments