1
votes

For my Spring Boot 2.4 application, I've configured OAuth2 Login (OpenID Connect) that uses the OAuth 2.0 Authorization Code Grant flow internally.

The OAuth 2.0 specification for this flow defines the state parameter as recommended, mostly for CSRF-protection issues. A sample request looks like the following:

https://authorization-server.com/oauth/authorize
?client_id=a17c21ed
&response_type=code
&state=5ca75bd30
&redirect_uri=https%3A%2F%2Fexample-app.com%2Fauth
&scope=photos

As the application should run with multiple instances (at least two), I'm running into issues as this state parameter is randomly generated with different values for each Spring Boot instance. So the callback has to arrive at the same instance the request was initiated, as otherwise, the validation of this state field fails.

The relevant Spring Security code part I found is the following (resolve method of DefaultOAuth2AuthorizationRequestResolver)

builder.clientId(clientRegistration.getClientId())
                .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
                .redirectUri(redirectUriStr)
                .scopes(clientRegistration.getScopes())
                .state(this.stateGenerator.generateKey()) // this line generates it
                .attributes(attributes);

If I enable sticky sessions on the load balancer, everything works as expected. But then I wouldn't describe the application setup as fully stateless.

Is there are way to persist the used state parameter e.g. in a database to share it among several Spring Boot instances to verify the callback at every instance? Or do I have to manually provide a custom StringKeyGenerator and somehow tweak the state validation?

1

1 Answers

2
votes

i looked a bit into it and as you say, it seems like it is non configurable, and your demand is definitely not unreasonable.

I think this is worthy of an issue to be opened at Spring Security Github Issues The stategenerator should at least be an interface with a default implementation that you can override, because the state generator is not only used as a form of CSRF, it should also be useable to send any form of custom stateful parameter if needed.

Im afraid that as of now the workaround you have is to extend the default DefaultOAuth2AuthorizationRequestResolver and override the DefaultOAuth2AuthorizationRequestResolver#Resolve function and copy over the default implementation.

Or implement a custom OAuth2AuthorizationRequestResolver using the interface OAuth2AuthorizationRequestResolver and copy over the relevant code from the default one.

And when you have a default implementation you can customise i would implement a shared in memory cache between the services, for instance Hazelcast or a Redis, with a short eviction time so you don't have to deal with databases.

But i do think you should open an issue for them to add the customisation.

I also found this link that customises some things.