-1

I have a React Client App where I am using msal-react to authenticate the user and generate an access token (API: /oauth2/v2.0/token) along with a bunch of other details like refresh_token, id, scope, etc.

I want to just use the access token and pass in the authorization header to my backend application which is a WebFlux application using Spring Security 5 and SpringBoot 3. The backend should validate the bearer token by hitting the MS endpoint (https://graph.microsoft.com/v1/me). Exception is the request to be authorized if it returns a valid user for the token.

But the SecurityWebFilterChain I am configuring is not working as expected.

Suppose I am trying to fetch this API:

@GetMapping("/secure-api")
  public Mono<String> getSecureResponse() {
    return Mono.just("Secure Response");
}

Following is my Security Configuration:

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

  private final ReactiveAuthenticationManager authenticationManager;

  public SecurityConfig(ReactiveAuthenticationManager authenticationManager) {
    this.authenticationManager = authenticationManager;
  }
  
  @Bean
  public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
    return http.authorizeExchange().anyExchange().authenticated().and()
            .authenticationManager(authenticationManager).build();
  }
}

AuthenticationManager:

  @Bean
  ReactiveAuthenticationManager customAuthenticationManager() {
    return authentication -> Mono.just(customAuthentication(authentication));
  }

  private Authentication customAuthentication(Authentication authentication) {
    String token = authentication.getCredentials().toString();
    MsAuthResponse authResponse = this.authenticateService.callMicrosoftGraphMeEndpoint(token);
    if (authResponse == null) {
      throw new BadCredentialsException("Authentication failure for token = " + token);
    }
    return new UsernamePasswordAuthenticationToken(authResponse, token, Collections.emptyList());
  }

callMicrosoftGraphMeEndpoint() is a simple rest call which will validate the token and return User details returned by MS.

I tried a few other things along with this but none seem to work for me it seems. When I put a breakpoint inside the authenticationManager code, it never gets hit.

dur
  • 15,689
  • 25
  • 79
  • 125

1 Answers1

0

Finally solved it using custom token introspector implementation.

@Bean
  public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
    return http.authorizeExchange().anyExchange()
        .authenticated().and().oauth2ResourceServer(oauth2 -> oauth2
            .opaqueToken(opaqueToken -> opaqueToken.introspector(msalTokenIntrospector())))
        .build();
  }

The msalTokenIntrospector() in the above snippet accepts the token and returns the OAuth2AuthenticatedPrincipal

private ReactiveOpaqueTokenIntrospector msalTokenIntrospector() {
    return token -> {
      MsAuthResponse authResponse = msAuthentication.callMicrosoftGraphMeEndpoint(token);
      if (authResponse == null) {
        throw new BadCredentialsException("Authentication failure for token = " + token);
      }
      return Mono.just(new OAuth2AuthenticatedPrincipal() {
        @Override
        public String getName() {
          return authResponse.getDisplayName();
        }

        @Override
        public Map<String, Object> getAttributes() {
          Map<String, Object> attributes = new HashMap<>();
          attributes.put("mail", authResponse.getMail());
          attributes.put("displayName", authResponse.getDisplayName());
          return attributes;
        }

        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
          return Collections.emptyList();
        }
      });
    };
  }