Reputation: 559
I have created an angular app that serves as an oauth2 client. I have created my authorization server with spring oauth2 using the following security configs
@Bean
@Order(1)
public SecurityFilterChain jwtSecurityFilterChain(HttpSecurity http) throws Exception {
http
.addFilterBefore(corsFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable()
.headers().frameOptions().disable()
.and()
.antMatcher("/auth/account/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.logout()
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.and()
.oauth2ResourceServer().jwt();
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain standardSecurityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilterBefore(corsFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable()
.headers().frameOptions().disable()
.and()
.authorizeRequests()
.antMatchers("/management/**").permitAll()
.antMatchers("/h2-console/**").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.and()
.formLogin(withDefaults());
return http.build();
}
and here is my authorization server config
@Configuration
public class AuthServerConfig {
private final DataSource dataSource;
private final AuthProperties authProps;
private final PasswordEncoder encoder;
public AuthServerConfig(DataSource dataSource, AuthProperties authProps, PasswordEncoder encoder) {
this.dataSource = dataSource;
this.authProps = authProps;
this.encoder = encoder;
}
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
JdbcRegisteredClientRepository clientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
RegisteredClient webClient = RegisteredClient.withId("98a9104c-wertyuiop")
.clientId(authProps.getClientId())
.clientName(authProps.getClientName())
.clientSecret(encoder.encode(authProps.getClientSecret()))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:4200/xxxx/yyy")
.redirectUri("http://127.0.0.1:8000/xxxx/yyy")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("farmer:read")
.scope("farmer:write")
.tokenSettings(tokenSettings())
.build();
clientRepository.save(webClient);
return clientRepository;
}
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer(authProps.getIssuerUri())
.build();
}
@Bean
public TokenSettings tokenSettings() {
return TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofDays(1))
.refreshTokenTimeToLive(Duration.ofDays(1))
.build();
}
}
Here is my build.gradle file
plugins {
id 'org.springframework.boot' version '2.6.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'org.liquibase.gradle' version '2.1.0'
id 'java'
}
group = 'com.shamba.records'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/release' }
}
ext {
set('springCloudVersion', "2021.0.0")
set('liquibaseVersion', "4.6.1")
}
configurations {
liquibaseRuntime.extendsFrom runtimeClasspath
}
dependencies {
implementation 'tech.jhipster:jhipster-framework:7.4.0'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.security:spring-security-oauth2-authorization-server:0.2.1'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.security:spring-security-cas:5.6.1'
// mapstruct
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
// jackson
implementation 'com.fasterxml.jackson.module:jackson-module-jaxb-annotations'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hppc'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
implementation 'org.zalando:problem-spring-web:0.26.0'
// configure liquibase
implementation "org.liquibase:liquibase-core:${liquibaseVersion}"
liquibaseRuntime 'org.liquibase:liquibase-groovy-dsl:3.0.0'
liquibaseRuntime 'info.picocli:picocli:4.6.1'
liquibaseRuntime 'org.postgresql:postgresql'
liquibaseRuntime group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'
liquibaseRuntime 'org.liquibase.ext:liquibase-hibernate5:3.6'
liquibaseRuntime sourceSets.main.output
runtimeOnly 'com.h2database:h2'
runtimeOnly 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
test {
useJUnitPlatform()
}
and here is part of the properties, I have omitted other things because of brevity
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://${AUTH_SERVICE_HOST:127.0.0.1}:5000
jwk-set-uri: http://${AUTH_SERVICE_HOST:127.0.0.1}:5000/oauth2/jwks
I am able to sign in and sign out users using authorization code flow but the issue comes in after the first successful sign-in, when the users click on the sign in the user is automatically logged in by the auth server even after calling the /oauth2/revoke
endpoint and specifying the logout configs below in the auth server
.and()
.logout()
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
I also tried to implement a custom endpoint /auth/account/revoke
to manually log out users but nothing seems to work. here is the implementation
@RestController
@RequestMapping("auth/account")
public class AccountResource {
@GetMapping("/revoke")
public void revoke(HttpServletRequest request) {
Assert.notNull(request, "HttpServletRequest required");
HttpSession session = request.getSession(false);
if (!Objects.isNull(session)) {
session.removeAttribute("SPRING_SECURITY_CONTEXT");
session.invalidate();
}
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
}
}
what could be the issue? any help counts
---------updates-------------
After upgrading spring-security-oauth2-authorization-server
version 0.2.2
I updated this method
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
to this
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
authorizationServerConfigurer.tokenRevocationEndpoint(tokenRevocationEndpoint -> tokenRevocationEndpoint
.revocationResponseHandler((request, response, authentication) -> {
Assert.notNull(request, "HttpServletRequest required");
HttpSession session = request.getSession(false);
if (!Objects.isNull(session)) {
session.removeAttribute("SPRING_SECURITY_CONTEXT");
session.invalidate();
}
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
response.setStatus(HttpStatus.OK.value());
})
);
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
http
.requestMatcher(endpointsMatcher)
.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.apply(authorizationServerConfigurer);
return http.formLogin(Customizer.withDefaults()).build();
}
Upvotes: 3
Views: 14885
Reputation: 6158
There are two concepts in play that are somewhat confusingly related.
Regarding logging out of an application, this is necessary when a browser-based session is in use, which would usually be the case with the authorization_code
flow. Strictly speaking, terminating the session is all that is required to achieve your goal.
Regarding token revocation, this is more of an OAuth-related security concern and is distinct in that sense from traditional logout functionality. Typically, the most immediate need for token revocation is as a risk mitigation strategy when a refresh_token
(or to a lesser extent the associated access_token
) is stolen.
Update for clarification: It's worth mentioning that if the token is not stolen, then there is not normally a reason to revoke the token at this point. You could conceivably discard the token from memory on the client-side, and simply let it expire on the server. In most cases, the client should actually remain authorized (via the refresh token) even once the user is logged out since this is the purpose of obtaining authorization in the first place.
If you feel you need to revoke the token simply because the user logged out, you are probably attempting to use the access_token
and/or refresh_token
AS a session mechanism, which it is not.
If you still need to tie logout to revocation for some reason, the question is: How do we achieve both simultaneously, right?
Unfortunately, it's not built-in to either Spring Security or Spring Authorization Server at the moment (for the reasons explained above), though there are specifications that could be used as the basis of feature development.
In general, you will need to solve this on the backend, as there's not much a frontend can do. If you simply POST
to a /logout
endpoint from your UI, you can't manipulate the cookies stored for that host in the browser (when using CORS). I recommended using the backend-for-frontend pattern for your OAuth client. But even (and especially) if you don't, you will need to ensure you can terminate the session regardless of whether the cookie exists in the browser. This means storing the session in a database, associating it with the refresh token in some way, and making a secure call to the revocation endpoint using the refresh token to delete both from the database simultaneously.
You can achieve this by setting the AuthenticationSuccessHandler
on the OAuth2TokenRevocationEndpointFilter
, like so:
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
authorizationServerConfigurer.tokenRevocationEndpoint(tokenRevocationEndpoint -> tokenRevocationEndpoint
.revocationResponseHandler((request, response, authentication) -> {
/* delete session here... */
response.setStatus(HttpStatus.OK.value());
})
);
// ...
Hopefully that's enough to get you started. You may find some benefit in working through the specifics of how this might be achieved yourself (e.g. it's a good learning experience). If you're stuck, it could be a good opportunity to request a How-to guide, as we're fielding ideas for guides right now. See #499 for a list of existing how-to guides, and please feel free to submit your own!
Upvotes: 8
Reputation: 151
On defaultSecurityFilterChain set SessionCreationPolicy to NEVER and add a custom logOut Filter that invalidates the session and redirects to your redirect Uri.
And from your UI make a post to /logout?redirect_uri=
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, UserService userService) throws Exception {
http.csrf().disable();
http
.formLogin().loginPage("/login")
.and()
.logout(logout->{
logout.logoutUrl("/logout");
})
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.authorizeRequests()
.antMatchers("/login*").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(new CustomLogoutFilter(), SecurityContextPersistenceFilter.class);
return http.build();
}
public class CustomLogoutFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(request.getServletPath().equals("/logout")) {
HttpSession session = request.getSession(false);
if(session != null) {
log.info("Invalidating session");
session.invalidate();
}
SecurityContext context = SecurityContextHolder.getContext();
if(context != null) {
log.info("Clearing security context");
SecurityContextHolder.clearContext();
context.setAuthentication(null);
}
String redirectUri = request.getParameter("redirect_uri");
if(Utils.isEmpty(redirectUri)) {
redirectUri = request.getHeader("referer");
}
if(Utils.notEmpty(redirectUri)) {
response.sendRedirect(redirectUri);
return;
}
}
filterChain.doFilter(request, response);
}
}
Upvotes: 0
Reputation: 33
I faced the same issue. after spending five days of hard work, I found JSESSIONID to be removed from Server Side and Client Side.
I was trying the following things
revoked oauth2 token by calling authorization server's oauth2/revoke endpoint (both refresh and access tokens) but logout did not work.
I tried to call /logout endpoint of Authorization Server that also did not work out
I tried to remove JSESSIONID from client browser which also went in vein.
I tried to remove JSESSIONID from Server and Client side. that worked out. Now I am able to implement logout successfully
I added the following endpoint in Spring Authorization Server
@GetMapping("/do/logout")
@ResponseBody
public void doLogout(HttpSession session,HttpServletRequest request, HttpServletResponse response) {
System.out.println("entered logout point");
session.invalidate();
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
auth.setAuthenticated(false);
SecurityContextHolder.clearContext();
SecurityContextHolder.getContext().setAuthentication(null);
}
Cookie cookieWithSlash = new Cookie("JSESSIONID", null);
cookieWithSlash.setPath(request.getContextPath() + "/");
cookieWithSlash.setDomain("auth-server");
cookieWithSlash.setMaxAge(0);
response.addCookie(cookieWithSlash); // For Tomcat
}
and the following endpoint in client application
@PostMapping("/complete/logout/process")
public String testLogout(HttpSession session, HttpServletRequest request,
HttpServletResponse response,
@RegisteredOAuth2AuthorizedClient("messaging-client-oidc") OAuth2AuthorizedClient authorizedClient)
throws IOException {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
auth.setAuthenticated(false);
SecurityContextHolder.clearContext();
for (Cookie cookie : request.getCookies()) {
String cookieName = cookie.getName();
LOG.info("cookie name={}", cookieName);
Cookie cookieToDelete = new Cookie(cookieName, null);
cookieToDelete.setPath(request.getContextPath() + "/");
cookieToDelete.setMaxAge(0);
response.addCookie(cookieToDelete);
}
SecurityContextHolder.getContext().setAuthentication(null);
}
return "logout";
}
and in html I called two endpoints /server side logout and client side logout that cleared both the JSESSIONID cookie from the browser
<form method="post" id="clientLogout" th:action="@{/complete/logout/process}">
</form>
button class="btn btn-sm btn-outline-primary" type="button"
onclick="javascript:logout();">Logout</button>
<script>
function logout() {
// calling server side logout
var tmpwin = window.open("http://auth-server:9000/do/logout", "_blank");
// calling client side logout
document.getElementById("clientLogout").submit();
tmpwin.close();
}
</script>
logging out from server side and client side clears both the JSESSIONID cookies finally able to logout
if anything I am missing or doing wrong please educate me. Thanks
Additionally you may invalidate oauth2 access and refresh tokens if you want
@PostMapping("do/oauth2/token/revoke")
@ResponseBody
public void doOauth2TokenRevoke(@RegisteredOAuth2AuthorizedClient("messaging-client-oidc") OAuth2AuthorizedClient authorizedClient) {
String clientId = authorizedClient.getClientRegistration().getClientId();
String clientSecret = "secret";
String accesstoken = authorizedClient.getAccessToken().getTokenValue();
String refreshtoken = authorizedClient.getRefreshToken().getTokenValue();
LinkedMultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("token", refreshtoken);
map.add("token_type_hint", "refresh_token");
WebClient client3 = WebClient.builder().baseUrl("http://auth-server:9000").build();
client3.post()
.uri("/oauth2/revoke")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.headers(h -> h.setBasicAuth(clientId, clientSecret))
.body(BodyInserters.fromMultipartData(map))
.retrieve()
.bodyToMono(Void.class)
.block();
LinkedMultiValueMap<String, String> map2 = new LinkedMultiValueMap<String, String>();
map2.add("token", accesstoken);
map2.add("token_type_hint", "access_token");
WebClient client2 = WebClient.builder().baseUrl("http://auth-server:9000").build();
client2.post()
.uri("/oauth2/revoke")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.headers(h -> h.setBasicAuth(clientId, clientSecret))
.body(BodyInserters.fromMultipartData(map2))
.retrieve()
.bodyToMono(Void.class)
.block();
}
Upvotes: 1
Reputation: 3176
I created success handler for revocation endpoint like Steve pointed out and invalidated sessions manually. Revoke is called from another application as a part of logout success handler.
RevocationResponseHandler:
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, OAuth2AuthorizationService authorizationService, FindByIndexNameSessionRepository<?> sessions) throws Exception {
var authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer<HttpSecurity>();
authorizationServerConfigurer.tokenRevocationEndpoint(c -> c.revocationResponseHandler(sessionInvalidatingSuccessHandler(authorizationService, sessions)));
// ...
}
private AuthenticationSuccessHandler sessionInvalidatingSuccessHandler(OAuth2AuthorizationService authorizationService, FindByIndexNameSessionRepository<?> sessions) {
return (request, response, authentication) -> {
String token = request.getParameter(OAuth2ParameterNames.TOKEN);
if (token != null) {
OAuth2Authorization authorization = authorizationService.findByToken(token, null);
if (authorization != null) {
sessions.findByPrincipalName(authorization.getPrincipalName()).forEach((sessionId, session) -> sessions.deleteById(sessionId));
}
}
response.setStatus(HttpStatus.OK.value());
};
}
Upvotes: 0
Reputation: 99
I managed to create a filter named TokenValidationFilter
at resource server that calls authorization server's introspect endpoint resulting in the following:
/**
* Performs token introspection when
* an authenticated endpoint gets hit.
*/
@Component
public class TokenValidationFilter extends OncePerRequestFilter {
@Value( "${spring.security.oauth2.client.provider.authz-server.introspect}" )
private String introspectEndpoint;
@Value( "${spring.security.oauth2.client.registration.authz-server.client-id}" )
private String clientId;
@Value(
"${spring.security.oauth2.client.registration.authz-server.client-secret}" )
private String clientSecret;
@Override
public void doFilterInternal( HttpServletRequest request,
HttpServletResponse response, FilterChain chain )
throws ServletException {
try {
var token = request.getHeader( "authorization" );
// For endpoints that do not require Authorization header claim
if (token == null) {
chain.doFilter(request, response);
return;
}
var result = performTokenIntrospection( token );
// Parses authz-server http response into json
ObjectMapper mapper = new ObjectMapper();
JsonNode json = mapper.readTree( result.body() );
// Checks token validation
if ( json.get( "active" ).asBoolean( false ) ) {
chain.doFilter(request, response);
} else {
prepareRejectionResponse( response );
}
}
catch ( Exception ex ) {
throw new ServletException( ex );
}
}
private HttpResponse<String> performTokenIntrospection( String token )
throws IOException, InterruptedException,
NullPointerException, URISyntaxException {
var uri = new URI( introspectEndpoint );
// Prepares request to authz server introspection endpoint
var body = String.format( "token=%s&client_id=%s&client_secret=%s",
token.replaceAll( "[Bb][Ee][Aa][Rr][Ee][Rr] ", "" ),
clientId, clientSecret);
var req = HttpRequest
.newBuilder()
.uri(uri)
.headers(
"content-type", MediaType.APPLICATION_FORM_URLENCODED_VALUE,
"authorization", token
)
.POST( HttpRequest.BodyPublishers.ofString( body ) )
.build();
// Performs token introspection and returns its result
return HttpClient.newHttpClient()
.send( req, HttpResponse.BodyHandlers.ofString() );
}
private void prepareRejectionResponse( HttpServletResponse response )
throws IOException {
response.setStatus( HttpStatus.UNAUTHORIZED.value() );
response.setContentType( MediaType.APPLICATION_JSON_VALUE );
var writer = response.getWriter();
writer.print( "{\"message\": \"invalid token\"}" );
writer.close();
}
}
As you can see the filter attributes uses resource server's ( client-id and client-secret ) credentials, and i added token introspection's endpoint on my application.yml
file.
Finally, registering the filter in the configuration class my ResourceServerConfig
looked like:
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
SecurityFilterChain securityFilterChain( HttpSecurity http )
throws Exception {
//... omitted code above ...
http.addFilterBefore( tokenValidationFilter,
WebAsyncManagerIntegrationFilter.class );
return http.build();
}
// ... omitted code bellow ...
}
I just tested on development environment but yet to find its drawbacks, there might be better solutions but it works without spamming another application.
As far as i know (which is little btw), the solution presented backend-for-frontend
looks better in security perspective.
Obs: if you apply this solution remeber always to revoke by refresh_token as oauth2 framework specifies.
Upvotes: -1
Reputation: 559
For anyone facing the same issue, I was not able to make it work when using angular directly as a client, but following the advice of @SteveRiesenberg I recommended using the backend-for-frontend pattern for your OAuth client
I was able to make it work, therefore I advise anyone to use this pattern as it will help you avoid some of the pitfalls you would otherwise have faced as I did, plus it integrates seamlessly. For a head start you can refer to the sample project below created by the spring security team SpringOne 2021 repository Also here is a link to the presentation Spring Security 5.5 From Taxi to Takeoff
Upvotes: 2