10

I'm using spring stack (Spring Boot 2.0.1.RELEASE) for creating a site that delegues user authentication/registration to Facebook via OAuth2. When I click the "login with facebook" button I get redirected to Facebook, but Spring Security OAuth2 is creating the redirect_uri parameter using http instead of https. The application uses https and I can't figure out where this "http" is coming from.

So, how can I make Spring create the redirect_uri parameter correctly?

UPDATE

Sorry for the original post. It was late and I wanted to have the question posted before going sleep :-)

Well, my application uses Spring Boot 2.0.1.RELEASE, which comes with Spring Security 2.0.1.RELEASE and Spring Security OAuth2 5.0.4.RELEASE. My application uses Facebook for registering and authenticating users. Currently I have a test environment running in AWS (Beanstalk) and using Amazon's SSL certificate.

When I first wrote the post my issue was that the redirect_uri parameter sent by my application (by SS actually) to Facebook had a http prefix, instead of https. This was causing an error in Facebook, which only accepts https redirect urls.

Reading the docs I found the spring.security.oauth2.client.registration.facebook.redirect-uri-template property, which I set to https://[my domain]/login/oauth2/code/{registrationId}. Now Facebook processes my authentication requests and posts back to my application.

However, with the previous parameter set, now the problem has changed. Now when the Facebook's callback hits my application at AWS I get the following exception (from the logs):

2018-04-11 10:51:23 [http-nio-5000-exec-5] DEBUG o.s.s.o.c.w.OAuth2LoginAuthenticationFilter - Request is to process authentication
2018-04-11 10:51:23 [http-nio-5000-exec-5] DEBUG o.s.s.authentication.ProviderManager - Authentication attempt using org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider
2018-04-11 10:51:23 [http-nio-5000-exec-5] DEBUG o.s.s.authentication.ProviderManager - Authentication attempt using org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider
2018-04-11 10:51:23 [http-nio-5000-exec-5] DEBUG o.s.s.o.c.w.OAuth2LoginAuthenticationFilter - Authentication request failed: org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_redirect_uri_parameter] 
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_redirect_uri_parameter] 
    at org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider.authenticate(OAuth2LoginAuthenticationProvider.java:117)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
    at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:159)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:128)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

Taking a look at the sources I found that the issue seems to be in the following test in the org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider class:

if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) {
    OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE);
    throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}

To check why this comparison is failing I checked the requests and responses using Chrome's developer tools. So, this is the call to Facebook:

https://www.facebook.com/v2.8/dialog/oauth?response_type=code&client_id=[REMOVED]&scope=public_profile%20email&state=[REMOVED]&redirect_uri=https://[REMOVED]/login/oauth2/code/facebook

Everything seems to be ok, the redirect_uri parameter is using https as expected and the complete redirect_uri seems correct.

And this is Facebook's callback:

https://[REMOVED]/login/oauth2/code/facebook?code=[REMOVED]

Once again, everything seems ok. However, SS is rejecting the user authentication because request and response redirect_uris are not matching.

And this is the issue. Any idea of what is going wrong here? Am I missing something?

Loreno Oliveira
  • 337
  • 1
  • 8
  • 19
  • it would be nice if you could share your configuration and properties file. It is hard to guess any thing with this information. – Agam Apr 11 '18 at 03:50
  • I posted an answer here https://stackoverflow.com/a/61929017/4950185 in a simlar question. – Gayan Kavirathne May 21 '20 at 07:06
  • Does this answer your question? [Spring OAuth redirect\_uri not using https](https://stackoverflow.com/questions/33812471/spring-oauth-redirect-uri-not-using-https) – Gayan Kavirathne May 21 '20 at 07:07

8 Answers8

13

I encountered the same error when I m setting up a Spring Boot application to authenticate users using Facebook OAuth2 implementation. Nginx (functions as reverse proxy) is configured to front the web app and also to offload the SSL cert.

Initially, I tried to customize the property: redirect-uri-template so that the redirect uri can be hard-coded with https://{domain}/login/oauth2/code/facebook (this is because Facebook only accepts HTTPS protocol for valid OAuth Redirect URI). It didnt work as I encountered the same error: OAuth2AuthenticationException: [invalid_redirect_uri_parameter]

Then, I found the proposed solution in link, which works for me. So, it is basically to set the OAuth2 Login Application with server.use-forward-headers=true and remove the customized property: redirect-uri-template.

Hope it helps :)

mengjiann
  • 275
  • 3
  • 12
  • Just to sum up, here in the official [docs](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-web-servers.html#howto-use-tomcat-behind-a-proxy-server) – Aritz Jun 27 '19 at 19:59
  • @mengjiann how did you debug which exactly redirectUri you received? – Anatolii Stepaniuk Jun 28 '19 at 06:38
  • 3
    In my case, I also had to add this to my nginx config: `proxy_set_header Host $host;` `proxy_set_header X-Real-IP $remote_addr;` `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;` `proxy_set_header X-Forwarded-Proto $scheme;` – Anatolii Stepaniuk Jun 28 '19 at 09:54
  • Great man! it worked for me. – Bruno Lee Jan 10 '22 at 22:52
9

I faced with exact the same problem but with Google.

Having the following architecture of microservices

Google Auth Server


  Zuul Gateway (:8080)
     /   \
    /     \
   /       \
Other      OAuth2Client (:5000)

while running at local machine everything works fine, but in AWS Elastic Beanstalk I catch the very same exception.

After debugging, I found out that in my case, when OAuth2Client is behind Zuul proxy (they implemented in separate microservices) I really get different redirect_uri values in the check inside OAuth2LoginAuthenticationProvider:

if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) {
    OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE);
    throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}

So in my case in AWS I have following values:

authorizationResponse.getRedirectUri()
http://[INNER_AWS_ESB_IP]:5000/auth/login/oauth2/code/google

authorizationRequest.getRedirectUri()
https://[MY_PROJECT_DOMAIN_NAME]/auth/login/oauth2/code/google

where [INNER_AWS_ESB_IP] is an IP address of inner network in AWS Elastic Beanstalk and [MY_PROJECT_DOMAIN_NAME] is a domain name of my project, which is hardcoded in application.yml as redirect-uri-template parameter.

I have the following config in application.yml of my OAuth2Client microservice

server:
  port: 5000
  servlet:
     contextPath: /auth
  use-forward-headers: true

spring:
  security:
    oauth2:
      resource:
        filter-order: 3
      client:
        registration:
          google:
            client-id:  [REMOVED]
            client-secret: [REMOVED]
            redirect-uri-template: ${MY_PROJECT_DOMAIN_NAME:http://localhost:8080}/auth/login/oauth2/code/google
            scope: profile,email

Loreno, what kind of architecture do you have? Can you share your config?

UPDATE

Seems that problem is connected directly with implementation of Spring Security Oauth2 Client in version science 5.0

Problem can be reproduced, if launch Zuul Gateway microservice on some separate virtual machine and other microservices should be launched at local machine ☝️ So Google should be called from the browser on VM.

The solution which helps me to avoid this problem is to add custom Filter with custom HttpServletRequestWrapper which can override method and return "right" URL to satisfy the check in OAuth2LoginAuthenticationProvider.java:115

  1. In the application.yml of the Oauth2 client

    myCloudPath: ${MY_PROJECT_DOMAIN_NAME:http://localhost:8080}

  2. In the SecurityConfig

    @Value("${myCloudPath}")
    private String myCloudPath;
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.
             addFilterBefore(new MyCustomFilter(myCloudPath), OAuth2LoginAuthenticationFilter.class).
             ...
    
  3. Filter

    public class MyCustomFilter implements Filter {
    
        private static final Logger logger = LogManager.getLogger(MyCustomFilter.class);
        private String myCloudPath;
    
    
        public MyCustomFilter(String myCloudPath) {
            this.myCloudPath= myCloudPath;
        }
    
        @Override
        public void init(FilterConfig filterConfiguration) throws ServletException {
            logger.info("MyCustomFilter init");
        }
    
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
    
            request = new MyHttpServletRequestWrapper((HttpServletRequest) request, myCloudPath);
    
            chain.doFilter(request, response);
        }
    
        @Override
        public void destroy() {
            logger.info("MyCustomFilter destroy");
        }
    }
    
  4. HttpServletRequestWrapper

    public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
    
        public final String redirectUrl;
    
        public MyHttpServletRequestWrapper(HttpServletRequest request, String myCloudPath) {
            super(request);
            this.redirectUrl = myCloudPath + request.getRequestURI();
        }
    
        @Override
        public StringBuffer getRequestURL() {
            return new StringBuffer(redirectUrl);
        }
    }
    
Andrew
  • 409
  • 5
  • 17
  • 1
    Hi Andrew, sorry for the late response. The problem was the SSL offloading at AWS' environment. The Facebook callback is made using https but, due to the Elastic Load Balancer SSL offloading, the request that actually hits my application is http, instead of https. Including `use-forward-headers: true` in my configuration solved the problem. – Loreno Oliveira Jun 13 '18 at 10:28
  • @Andrew how did you debug redirect uri in authorizationRequest and authorizationResponse ? – Anatolii Stepaniuk Jun 27 '19 at 19:09
5

We faced the same issue while running in OpenShift and authenticating against Microsoft Azure. Filtering seemed like hacking, the *.redirect-uri-template properties are now deprecated, and after returning from Azure the outgoing and incoming redirect URIs did not match.

After much searching, this simple entry in application.properties solved the issue:

server.forward-headers-strategy=framework
Michael Piefel
  • 18,660
  • 9
  • 81
  • 112
1

I also faced this problem and nothing worked after trying all solutions available on google and stack overflow. This resolved the problem for me:

  1. I upgraded the Spring-boot version to "2.3.1.RELEASE" and rebuilt the application. Then added this property in application.yml (talking about google here but i think it will work for facebook, okta and other identity provider platforms) :

  security:
    oauth2:
      client: 
        registration:
          google:
            clientId: 'your google client id'
            clientSecret: 'your google client secret'
            redirect-uri: https://www.yourdomain.com/login/oauth2/code/google # important

Defining redirect-uri , did the work and that was only possible if you are working on a spring-boot 2.3.x and above release. Don't waste your time on "redirect-uri-template" as you will be facing problem when google redirects the user-agent back to your application. Spring will still be authenticating the redirect-uri internally.

  1. If you are working behind a reverse proxy (may be nginx) the this should be your server configuration :

     location / {
         absolute_redirect off;
         proxy_ssl_server_name on;
         proxy_set_header X-Forwarded-Proto $scheme;
         proxy_set_header Host $http_host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_redirect off;
         proxy_pass http://127.0.0.1:8080;
     }
    

These settings on ngnix will make sure that Spring gets requests header which are coming in the request. I was using tomcat on port 8080 hence defined this in proxy_pass. You can change this for your application.

I hope this helps. Cheers.

Hemant Nagpal
  • 624
  • 6
  • 14
0

For me works this. I have set

redirect-uri-template: "{baseUrl}/login/oauth2/code/{registrationId}"

and wrote custom filter which chnge http to https

@Slf4j
public class LinkedInRewriteFilter extends OncePerRequestFilter {

private static final String GET_PROTOCOL = "://.*";
private static final String LINKED_IN = "linkedin";
private static final String HTTPS = "https";

@Value("${base-url}")
private String baseUrl;

@Value("${server.port}")
private int serverPort;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    if (request.getRequestURL().toString().contains(LINKED_IN)) {
        request = new LinkedInHttpServletRequestWrapper(request);
    }
    filterChain.doFilter(request, response);
}

public class LinkedInHttpServletRequestWrapper extends HttpServletRequestWrapper {

    public LinkedInHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public String getScheme() {
        return baseUrl.replaceFirst(GET_PROTOCOL, StringUtils.EMPTY);
    }

    @Override
    public int getServerPort() {
        return HTTPS.equals(getScheme()) ? 443 : serverPort;
    }
}

}

Jakub Krhovják
  • 136
  • 1
  • 4
  • 1
    Hi, Jakub! How did you debug that you receive redirectUri with http instead of https ? – Anatolii Stepaniuk Jun 28 '19 at 06:41
  • Actually yes. Our apps use HTTP but we operate behind Apache (443) So I installed apache locally and then debug and found out UriComponentsBuilder set HTTP or https based on which your application use. But I find out that you can set properties like X-Forwarded-SSL or similar to change this behavior but I did not try. You can check UriComponentsBuilder In my case OAuth client uses `UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)) – Jakub Krhovják Jun 29 '19 at 18:55
  • But if you want to use my solution you should set LinkedIn the filter like `.addFilterBefore(linkedInRewriteFilter(), OAuth2AuthorizationRequestRedirectFilter.class)` Probably it is possible to solve it by settling right properties to change UriComponentsBuilder behavior but you now for me it is working and have no time ... – Jakub Krhovják Jun 29 '19 at 18:55
0

For me, I had to do these:

Step 1: Change your config file

If using application.properties

server.port=8081
server.use-forward-headers: true

If using yml

server:
port: 8081
use-forward-headers : true

Step 2: Update proxy

If using httpd

RequestHeader set X-Forwarded-Proto https
RequestHeader set X-Forwarded-Port 443

ProxyPreserveHost On
ProxyPass / http://localhost:8081/
ProxyPassReverse / http://localhost:8081/

If using nginx

location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Will add the user's ip to the request, some apps need this
        proxy_set_header X-Forwarded-Proto $scheme; # will forward the protocole i.e. http, https
        proxy_set_header X-Forwarded-Port $server_port; # Will forward the port 
        proxy_set_header Host $host;                    # !Important will forward the host address
        proxy_pass http://localhost:8081/;
}

Step 3: Restart server and test

VK321
  • 5,768
  • 3
  • 44
  • 47
0

I think the new version of Spring Security just added new support for .redirect-uri replacing .redirect-uri-template.

Dependencies

I am using org.springframework.boot:spring-boot-starter-web:2.3.2.RELEASE,org.springframework.boot:spring-boot-starter-oauth2-client:2.3.2.RELEASE,org.springframework.boot:spring-boot-starter-security:2.3.2.RELEASE.

Configuration File, e.g., .properties

In the configuration file, I could specify:

spring.security.oauth2.client.registration.google.redirect-uri=https://example.com/api/login/oauth2/code/google

Note that I did not use .redirect-uri-template

OAuth2 Flow

Then, Spring OAuth2 guides requests to the Google server with the URI like:

https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?response_type=...&
redirect_uri=https%3A%2F%2Fexample.com%2Fapi%2Flogin%2Foauth2%2Fcode%2Fgoogle

Note that redirect_uri=https%3A%2F%2Fexample.com has been encoded, where https:// protocol is used.

justthink
  • 439
  • 3
  • 6
0

If you're using container apps or web apps contained over Linux, refer this answer. It could be caused by authentication redirecting to provider handle that's not served over HTTPS.

Ashique Razak
  • 487
  • 3
  • 8