Spring-security-oauth: Token endpoint should permit all HTTP OPTIONS requests to support CORS.

Created on 9 Dec 2014  Â·  20Comments  Â·  Source: spring-projects/spring-security-oauth

This is the same issue documented in https://github.com/spring-projects/spring-security-oauth/issues/184. That ticket was closed and marked as fixed, but I believe this fix was removed in subsequent updates. The fix was supposedly committed in 8d1e947545adad7524f7a5b173a3067efad6c52f, yet removed in d3819e2e5414019ab9e5cb1207fc6a54f239dd55. Was this intentional? If so, why?

enhancement

Most helpful comment

+1 to solve this problem

All 20 comments

The change that "fixed" #184 was reverted because the solution isn't ideal and at the time I was aware that Spring MVC is getting CORS support [SPR-9278 (although at the time it looked like the issue would be resolved sooner than it has been). I think your best bet is to install a custom Filter that handles the CORS concerns - that's independent of Spring OAuth and there are plenty of examples out there if you google around.

Thanks @dsyer. A custom filter for CORS does do the trick. For anybody reading this issue and wanting a resolution, check out http://spring.io/guides/gs/rest-service-cors/. Implementing a filter similar to that should do it. I actually extended OncePerRequestFilter and handled it that way:

@Component
public class CORSFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
            final FilterChain filterChain)
            throws ServletException, IOException {
        // Add CORS headers here...
        filterChain.doFilter(request, response);
    }
}

FYI the solutions in http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html do not work, because the token endpoint is intercepted before it reaches the MVC handler.

The workaround I found using Spring Boot 1.3 and Spring Security OAuth2 2.0.8 would be to remove @EnableAuthorizationServer, and supply a overridden AuthorizationServerSecurityConfiguration as below:

//workaround for https://github.com/spring-projects/spring-security-oauth/issues/330
@Configuration
@Import(AuthorizationServerEndpointsConfiguration.class)
public class CorsEnabledAuthorizationServerSecurityConfiguration extends AuthorizationServerSecurityConfiguration {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        CorsConfigurationSource source = corsConfigurationSource();
        http.addFilterBefore(new CorsFilter(source), ChannelProcessingFilter.class);
    }

    private CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("POST");
        //more config
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

Yea I noticed that Spring Boot 1.3 broke CORs support for Spring OAuth2. I
too have to implement a custom authorization configuration. Would be nice
if this ticket was reopened and reevaluated.
On Feb 22, 2016 6:43 AM, "greyfairer" [email protected] wrote:

FYI the solutions in
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html
do not work, because the token endpoint is intercepted before it reaches
the MVC handler.

The workaround I found using Spring Boot 1.3 and Spring Security OAuth2
2.0.8 would be to remove @EnableAuthorizationServer, and supply a
overridden AuthorizationServerSecurityConfiguration as below:

//workaround for https://github.com/spring-projects/spring-security-oauth/issues/330
@Configuration
@Import(AuthorizationServerEndpointsConfiguration.class)
public class CorsEnabledAuthorizationServerSecurityConfiguration extends AuthorizationServerSecurityConfiguration {

@Override
protected void configure(HttpSecurity http) throws Exception {
    super.configure(http);
    CorsConfigurationSource source = corsConfigurationSource();
    http.addFilterBefore(new CorsFilter(source), ChannelProcessingFilter.class);
}

private CorsConfigurationSource corsConfigurationSource() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("POST");
    //more config
    source.registerCorsConfiguration("/**", config);
    return source;
}

}

—
Reply to this email directly or view it on GitHub
https://github.com/spring-projects/spring-security-oauth/issues/330#issuecomment-187136113
.

This ticket is still open, but has a pretty low priority. I don't think you need any custom security configuration (the CORS filter is not a Security filter, and I already remarked in an earlier comment). You can just add one with a path that suits you (e.g. matching only the token endpoint).

@dsyer you mean something like this:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/oauth/token").allowedOrigins("*");
    }
}

I tried that first, but the AuthorizationServer endpoints don't seem to be affected by this WebConfig. That's probably because the security filter has precedence over the Cors Interceptor.

I'm actually surprised that didn't work but it's not what I meant. You can just add a @Bean of type CorsFilter and map it to /oauth/token.

I have a custom OncePerRequstFilter (basically CorsFilter, except this was written before it existed in Spring Boot), and I have it registered as a bean. The issue was that the Spring Security filter chain was placed before the CorsFilter, so HTTP OPTIONS to any of the OAuth endpoints would just 401 without passing the client credentials. I ended up doing an @Order(-1000) on the filter, and that seemed to do the trick. Kind of a hack though.

It wouldn't be a hack if you used the CorsFilter from Spring with a more targeted URL path. So at least we agree that there is an avalaible solution (and since we don't in general want to encourage browsers to use the token endpoint, I might leave it at that).

So I've tested what you described above, and unless I'm doing something wrong, this does not work (I still get 401 when doing an HTTP OPTIONS request to any endpoint).

The code can be seen here: https://github.com/ccampo133/spring-boot-oauth2-demo/tree/cors

I have registered a CorsFilter bean in Application.java. Everything else is just standard OAuth2 configuration boilerplate. In the test ApplicationTests.java you'll notice I do a simple integration test requesting HTTP OPTIONS to the token endpoint, and asserting that the status is 200 and the Access-Control-Allow-Origin is not null. Unfortunately, this test is failing with the most recent versions of Spring Boot and Spring Security/Spring Security OAuth.

@dsyer, could you please take a quick glance at this?

Regarding the remark about browsers: In the case of a highly trusted client (lets say your main page is a Javascript app), and it's using the resource owner password credentials grant type, they will need access to the token endpoint with CORS support. I understand your sentiment, but I wouldn't be so quick to write it off.

By definition practically a JavaScript client is not trusted, so it's still a corner case from my point of view.

If your filter is not firing you probably have the security filter registered with a lower order?

Yeah, so you added a filter with no order, meaning unordered, meaning it runs after all the ordered filters. You need to regained it with an order before the security filter (whose order is defined by spring boot using a constant, by default).

Right which is why my in my previous answer, setting @Order(-1000) (https://github.com/spring-projects/spring-security-oauth/issues/330#issuecomment-187227642) worked since it injected the filter at the front of the chain. Just seems to me like a weird solution, considering CORS support via an unordered filter worked back when I posted this issue (Dec 2014; https://github.com/spring-projects/spring-security-oauth/issues/330#issuecomment-66471162). I'm not sure if this is a Spring Boot issue of Spring Security OAuth issue honestly, but a breaking change happened somewhere along the line.

In Dec 2014 the security filter was probably unordered too, so it was random which one came first. At least now you have some control (i.e. nothing is broken).

+1 to solve this problem

We have a micro service architecture with a Spring oAuth2 service and an api-service infront. The API service process all incoming requests (except for the once to the oAuth-service) and redirects them to the correct micro service if access is granted.Our own webpage and apps uses a password grant flow and the frontend framwork sends unauthorised OPTIONS-requests prior to the POST to oauth/token/

If I try the "hacks" described above (Having my SecurityConfig extending AuthorizationServerSecurityConfiguration instead of WebSecurityConfigurerAdapter) in order to allow unauthorised OPTIONS-requests, our custom AuthenticationProvider is no longer registered (the AuthenticationManager does no longer register the ResourceOwnerPasswordTokenGranter). Thus these methods have side effects.

Are we using this all wrong, or why else is there a resistance from Spring developers side to allow for configuration of unauthorised OPTIONS-requests?

+1
Why this issue is still opened? Is spring-security-oauth not developed anymore?
P.S. I have the same issue, as I understand server doesn't handle OPTIONS request properly and I cannot find a working solution, so server is not responding with allow origins *.

EDIT
I've found out that the problem was not having a class that extends ResourceServerConfigurerAdapter and not configuring app properly. (e.g. to see what path in tomcat settings or to check if app has corsFilter in class extending AuthorizationServerConfigurerAdapter)

I am using Swagger UI in a dev environment to authorize with the Resource Owner Password Flow against a protected resource server. Under these circumstances, Swagger UI actually is a "highly trusted client" for me and the unsupported CORS preflight gave me some headaches. So I want to share my workaround and give this issue some more context.

I placed a Spring Security filter chain before the AuthorizationServerSecurityConfiguration and let it match only for OPTIONS /oauth/token requests to permit them.

@EnableWebSecurity
@Order(-1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.requestMatcher(new ELOAuthRequestMatcher("isCorsPreflightRequest()"))
        .authorizeRequests().anyRequest().permitAll();
  }

  @Bean
  public FilterRegistrationBean<SimpleCorsFilter> someFilterRegistration() {
      FilterRegistrationBean<SimpleCorsFilter> registration = new FilterRegistrationBean<>();
      registration.setFilter(new SimpleCorsFilter());
      registration.addUrlPatterns("/oauth/token");
      return registration;
  }

  // [...]

}
public class ELOAuthRequestMatcher extends ELRequestMatcher {

  public ELOAuthRequestMatcher(String el) {
    super(el);
  }

  @Override
  public EvaluationContext createELContext(HttpServletRequest request) {
    return new StandardEvaluationContext(new ELOAuthRequestMatcherContext(request));
  }

}
public class ELOAuthRequestMatcherContext {

  private final HttpServletRequest request;

  public ELOAuthRequestMatcherContext(HttpServletRequest request) {
        this.request = request;
    }

  public boolean isCorsPreflightRequest() {
    return "OPTIONS".equalsIgnoreCase(request.getMethod())
        && StringUtils.endsWithIgnoreCase(request.getRequestURI(), "/oauth/token");
  }

}

The Access-Control headers are added by a CorsFilter:

    @Bean
    public FilterRegistrationBean<CorsFilter> someFilterRegistration() {
      UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
      CorsConfiguration config = new CorsConfiguration();
      config.setAllowCredentials(true);
      config.addAllowedOrigin("*");
      config.addAllowedHeader("*");
      config.addAllowedMethod("*");
      source.registerCorsConfiguration("/oauth/token", config);
      return new FilterRegistrationBean<>(new CorsFilter(source));
    }

A built-in CORS preflight support would have made things easier for me.

@dsyer I think this is not that much of a corner-case, since the implicit grant type seems to have been mostly replaced by an authorization code grant, with a call without a client secret. Even for browser clients.

Some info:

https://oauth.net/2/grant-types/implicit/

_"It is generally not recommended to use the implicit flow (and some servers prohibit this flow entirely). In the time since the spec was originally written, the industry best practice has changed to recommend that public clients should use either the authorization code flow without the client secret, or use the PKCE extension instead."_

Or:

https://stackoverflow.com/questions/13387698/why-is-there-an-authorization-code-flow-in-oauth2-when-implicit-flow-works-s

Was this page helpful?
0 / 5 - 0 ratings