How To Redirect To Spring Security OAuth2 Behind a Gateway/Proxy — Part 2: Hiding The App Behind A Reverse-Proxy (Aka Gateway)

This post is part of a series of Mini-Howtos, that gather some help, to get you started, when switching from localhost to production with SSL and a reverse-proxy (aka gateway) in front of your app, that forwards the requests to your app that listens on a different name/IP, port and protocol.

In This Series We…

  1. Run the official Spring-Boot-OAuth2-Tutorial as a container in docker
  2. Simulate production by hiding the app behind a gateway (this part)
  3. Show how to debug the oauth2-flow for the whole crap!
  4. Enable SSL on our gateway
  5. Show how to do the same with Facebook, instead of GitHub

I will also give some advice for those of you, who are new to Docker – but just enough to enable you to follow.

This is part 2 of this series, that shows how to run a Spring-Boot OAuth2 App behind a gateway
Part 1 is linked above.

Our Plan: Simulating A Production-Setup

We will simulate a production-setup by adding the domain, that will be used in production – example.com in our case -, as an alias for localhost.

Additionally, we will start an NGINX as reverse-proxy alongside our app and put both containers into a virtual network.
This simulates a real-world secenario, where your app will be running behinde a gateway together with a bunch of other apps and will have to deal with forwarded requests.

Together, this enables you to test the production-setup of your oauth2-provider against a locally running development environment, including the configuration of the finally used URIs and nasty forwarding-errors.

To reach this goal we will have to:

  1. Reconfigure our oauth-provider for the new domain
  2. Add the domain as an alias for localhost
  3. Create a virtual network
  4. Move the app into the created virtual network
  5. Configure and start nginx as gateway in the virtual network

By the way:
Any other server, that can act as reverse proxy, or some real gateway,like Zuul would work as well, but we stick with good old NGINX, to keep it simple.

Switching The Setup Of Your OAuth2-Provider To Production

In our example we are using GitHub as oauth2-provider and example.com as the domain, where the app should be found after the release.
So, we will have to change the Authorization callback URL to
http://example.de/login/oauth2/code/github

O.k., that’s done.

But we haven’t released yet and nothing can be found on the reals server, that hosts example.com
But still, we really would like to test that production-setup to be sure that we configured all bits and pieces correctly!


In order to tackle this chicken-egg-problem, we will fool our locally running browser to belive, that example.com is our local development system.

Setting Up The Alias for example.com

On Linux/Unix this can be simply done by editing /etc/hosts.
You just have to add the domain (example.com) at the end of the line that starts with 127.0.0.1:

127.0.0.1	localhost example.com

Locally running programms – like your browser – will now resolve example.com as 127.0.0.1

Create A Virtual Network With Docker

Next, we have to create a virtual network, where we can put in both containers:

docker network create juplo

Yes, with Docker it is as simple as that.

Docker networks also come with some extra goodies.
Especially one, which is extremly handy for our use-case is: They are enabling automatic name-resolving for the connected containers.
Because of that, we do not need to know the IP-addresses of the participating containers, if we give each connected container a name.

Docker vs. Kubernetes vs. Docker-Compose

We are using Docker here on purpose.
Using Kubernetes just to test / experiment on a DevOp-box would be overkill.
Using Docker-Compose might be an option.
But we want to keep it as simple as possible for now, hence we stick with Docker.
Also, we are just experimenting here.


You might want to switch to Docker-Compose later.
Especially, if you plan to set up an environment, that you will frequently reuse for manual tests or such.

Move The App Into The Virtual Network

To move our app into the virtual network, we have to start it again with the additional parameter --network.
We also want to give it a name this time, by using --name, to be able to contact it by name.


You have to stop and remove the old container from part 1 of this HowTo-series with CTRL-C beforehand, if it is still running – Removing is done automatically, because we specified --rm
:

docker run \
  -d \
  --name app \
  --rm \
  --network juplo \
  juplo/social-logout:0.0.1 \
  --server.use-forward-headers=true \
  --spring.security.oauth2.client.registration.github.client-id=YOUR_GITHUB_ID \
  --spring.security.oauth2.client.registration.github.client-secret=YOUR_GITHUB_SECRET

Summary of the changes in comparison to the statement used in part 1:

  • We added -d to run the container in the background – See tips below…
  • We added --server.use-forward-headers=true, which is needed, because our app is running behind a gateway now – I will explain this in more detail later

  • And: Do not forget the --network juplo,
    which is necessary to put the app in our virtual network juplo, and --name app, which is necessary to enable DNS-resolving.
  • You do not need the port-mapping this time, because we will only talk to our app through the gateway.
    Remember: We are hiding our app behind the gateway!

Some quick tips to Docker-newbies

  • Since we are starting multiple containers, that shall run in parallel, you have to start each command in a separate terminal, because CTRL-C will stop (and in our case remove) the container again.
  • Alternatively, you can add the parameter -d (for daemonize) to start the container in the background.

  • Then, you can look at its output with docker logs -f NAME (safely disruptable with CTRL-C) and stop (and in our case remove) the container with docker stop NAME.
  • If you wonder, which containers are actually running, docker ps is your friend.

Starting the Reverse-Proxy Aka Gateway

Next, we will start NGINX alongside our app and configure it as reverse-proxy:

  1. Create a file proxy.conf with the following content:

    upstream upstream_a {
      server        app:8080;
    }
    
    server {
      listen        80;
      server_name   example.com;
    
      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;
      proxy_set_header     Host                $host;
      proxy_set_header     X-Forwarded-Host    $host;
      proxy_set_header     X-Forwarded-Port    $server_port;
    
      location / {
        proxy_pass  http://upstream_a;
      }
    }
    
    • We define a server, that listens to requests for the host example.com (server_name) on port 80.
    • With the location-directive we tell this server, that all requests shall be handled by the upstream-server upstream_a.
    • This server was defined in the upstream-block at the beginning of the configuration-file to be a forward to app:8080
    • app is simply the name of the container, that is running our oauth2-app – Rembember: the name is resolvable via DNS
    • 8080 is the port, our app listens on in that container.
    • The proxy_set_header-directives are needed by Spring-Boot Security, for dealing correctly with the circumstance, that it is running behind a reverse-proxy.

    In part 3, we will survey the proxy_set_header-directives in more detail.

  2. Start nginx in the virtual network and connect port 80 to localhost:

    docker run \
      --name proxy \
      --rm \
      --network juplo -p 80:80 \
      --volume $(pwd)/proxy.conf:/etc/nginx/conf.d/proxy.conf:ro \
      nginx:1.17
    

    This command has to be executed in the direcotry, where you have created the file proxy.conf.

    • I use NGINX here, because I want to demystify the work of a gateway
      traefik would have been easier to configure in this setup, but it would have disguised, what is going on behind the scene: with NGINX we have to configure all manually, which is more explicitly and hence, more informative
    • We can use port 80 on localhost, since the docker-daemon runs with root-privileges and hence, can use this privileged port – if you do not have another webserver running locally there.
    • $(pwd) resolves to your current working-directory – This is the most convenient way to produce the absolute path to proxy.conf, that is required by --volume to work correclty.
  3. If you have reproduced the receipt exacly, your app should be up and running now.
    That is:

    • Because we set the alias example.com to point at localhost you should now be able to open your app as http://example.com in a locally running browser
    • You then should be able to login/logount without errors
    • If you have configured everything correctly, neither your app nor GitHub should mutter at you during the redirect to GitHub and back to your app

    Whats next… is what can go wrong!

    In this simulated production-setup a lot of stuff can go wrong!
    You may face nearly any problem from configuration-mismatches considering the redirect-URIs to nasty and hidden redirect-issues due to forwarded requests.


    Do not mutter at me…
    Remember: That was the reason, we set up this simulated production-setup in the first place!

    In the next part of this series I will explain some of the most common problems in a production-setup with forwarded requests.
    I will also show, how you can debug the oauth2-flow in your simulated production-setup, to discover and solve these problems

How To Redirect To Spring Security OAuth2 Behind a Gateway/Proxy – Part 1: Running Your App In Docker

Switching From Tutorial-Mode (aka POC) To Production Is Hard

Developing Your first OAuth2-App on localhost with OAuth2 Boot may be easy, …

…but what about running it in real life?

Looking for the real life

This is the first post of a series of Mini-Howtos, that gather some help, to get you started, when switching from localhost to production with SSL and a reverse-proxy (aka gateway) in front of your app, that forwards the requests to your app that listens on a different name/IP, port and protocol.

In This Series We Will…

  1. Start with the fantastic official OAuth2-Tutorial from the Spring-Boot folks – love it! – and run it as a container in docker
  2. Hide that behind a reverse-proxy, like in production – nginx in our case, but could be any pice of software, that can act as a gateway
  3. Show how to debug the oauth2-flow for the whole crap!
  4. Enable SSL for our gateway – because oauth2-providers (like Facebook) are pressing us to do so
  5. Show how to do the same with Facebook, instead of GitHub

I will also give some advice for those of you, who are new to Docker – but just enough to enable you to follow.

This is Part 1 of this series, that shows how to package a Spring-Boot-App as Docker-Image and run it as a container

tut-spring-boot-oauth2/logout

As an example for a simple app, that uses OAuth2 for authentication, we will use the third step of the Spring-Boot OAuth2-Tutorial.

You should work through that tutorial up until that step – called logout -, if you have not done yet.
This will guide you through programming and setting up a simple app, that uses the GitHub-API to authenticate its users.

Especially, it explains, how to create and set up a OAuth2-App on GitHubDo not miss out on that part: You need your own app-ID and -secret and a correctly configured redirect URI.

You should be able to build the app as JAR and start that with the ID/secret of your GitHub-App without changing code or configuration-files as follows:

mvn package
java -jar target/social-logout-0.0.1-SNAPSHOT.jar \
  --spring.security.oauth2.client.registration.github.client-id=YOUR_GITHUB_APP_ID
  --spring.security.oauth2.client.registration.github.client-secret=YOUR_GITHUB_APP_SECRET

If the app is running corectly, you should be able to Login/Logout via http://localhost:8080/

The folks at Spring-Boot are keeping the guide and this repository up-to-date pretty well.
At the date of the writing of this article it is up to date with version 2.2.2.RELEASE of Spring-Boot.

You may as well use any other OAuth2-application here. For example your own POC, if you already have build one that works while running on localhost

Some Short Notes On OAuth2

I will only explain the protocol in very short words here, so that you can understand what goes wrong in case you stumble across one of the many pitfalls, when setting up oauth2.
You can read more about oauth2 elswhere

For authentication, oauth2 redirects the browser of your user to a server of your oauth2-provider.
This server authenticates the user and redirects the browser back to your server, providing additionally information and ressources, that lets your server know that the user was authenticated successfully and enables it to request more information in the name of the user.

Hence, when configuring oath2 one have to:

  1. Provide the URI of the server of your oauth2-provider, the browser will be redirected to for authentication
  2. Tell the server of the oauth2-provider the URL, the browser will be redirected to back after authentication
  3. Also, your app has to provide some identification – a client-ID and -secret – when redirecting to the server of your oauth2-provider, which it has to know

There are a lot more things, which can be configured in oauth2, because the protocol is designed to fit a wide range of use-cases.
But in our case, it usually boils down to the parameters mentioned above.

Considering our combination of spring-security-oauth2 with GitHub this means:

  1. The redirect-URIs of well known oauth2-providers like GitHub are build into the library and do not have to be configured explicitly.
  2. The URI, the provider has to redirect the browser back to after authenticating the user, is predefined by the library as well.

    But as an additional security measure, almost every oauth2-provider requires you, to also specify this redirect-URI in the configuration on the side of the oauth2-provider.

    This is a good and necessary protection against fraud, but at the same time the primary source for missconfiguration:
    If the specified URI in the configuration of your app and on the server of your oauth2-provider does not match, ALL WILL FAIL!
  3. The ID and secret of the client (your GitHub-app) always have to be specified explicitly by hand.

Again, everything can be manually overriden, if needed.
Configuration-keys starting with spring.security.oauth2.client.registration.github are choosing GitHub as the oauth2-provider and trigger a bunch of predifined default-configuration.
If you have set up your own oauth2-provider, you have to configure everything manually.

Running The App Inside Docker

To faciliate the debugging – and because this most probably will be the way you are deploying your app anyway – we will start by building a docker-image from the app

For this, you do not have to change a single character in the example project – all adjustments to the configuration will be done, when the image is started as a container.
Just change to the subdirectory logout of the checked out project and create the following Dockerfile there:

FROM openjdk:8-jre-buster

COPY  target/social-logout-0.0.1-SNAPSHOT.jar /opt/app.jar
EXPOSE 8080
ENTRYPOINT [ "/usr/local/openjdk-8/bin/java", "-jar", "/opt/app.jar" ]
CMD []

This defines a docker-image, that will run the app.

  • The image deduces from openjdk:8-jre-buster, which is an installation of the latest OpenJDK-JDK8 on a Debian-Buster
  • The app will listen on port 8080
  • By default, a container instanciated from this image will automatically start the Java-app
  • The CMD [] overwrites the default from the parent-image with an empty list – this enables us to pass command-line parameters to our spring-boot app which we will need to pass in our configuration

You can build and tag this image with the following commands:

mvn clean package
docker build -t juplo/social-logout:0.0.1 .

This will tag your image as juplo/social-logout:0.0.1 – you obviously will/should use your own tag here, for example: myfancytag

Do not miss out on the flyspeck (.) at the end of the last line!

You can run this new image with the follwing command – and you should do that, to test that everything works as expected:

docker run \
  --rm \
  -p 8080:8080 \
  juplo/social-logout:0.0.1 \
  --spring.security.oauth2.client.registration.github.client-id=YOUR_GITHUB_ID \
  --spring.security.oauth2.client.registration.github.client-secret=YOUR_GITHUB_SECRET
  • --rm removes this test-container automatically, once it is stopped again
  • -p 8080:8080 redirects port 8080 on localhost to the app

Everything after the specification of the image (here: juplo/social-logout:0.0.1) is handed as a command-line parameter to the started Spring-Boot app – That is, why we needed to declare CMD [] in our Dockerfile

We utilize this here to pass the ID and secret of your GitHub-app into the docker container — just like when we started the JAR directly

The app should behave exactly the same now lik in the test above, where we started it directly by calling the JAR.

That means, that you should still be able to login into and logout of your app, if you browse to http://localhost:8080
At least, if you correctly configured http://localhost:8080/login/oauth2/code/github as authorization callback URL in the settings of your OAuth App on GitHub.

Comming Next…

In the next part of this series, we will hide the app behind a proxy and simulate that the setup is running on our real server example.com.

Configure pac4j for a Social-Login along with a Spring-Security based Form-Login

The Problem – What will be explained

If you just want to enable your spring-based webapplication to let users log in with their social accounts, without changing anything else, pac4j should be your first choice.
But the provided example only shows, how to define all authentication mechanisms via pac4j.
If you already have set up your log-in via spring-security, you have to reconfigure it with the appropriate pac4j-mechanism.
That is a lot of unnecessary work, if you just want to supplement the already configured log in with the additionally possibility, to log in via a social provider.

In this short article, I will show you, how to set that up along with the normal form-based login of Spring-Security.
I will show this for a Login via Facabook along the Form-Login of Spring-Security.
The method should work as well for other social logins, that are supported by spring-security-pac4j, along other login-mechanisms provided by spring-security out-of-the-box.

In this article I will not explain, how to store the user-profile-data, that was retrieved during the social login.
Also, if you need more social interaction, than just a login and access to the default data in the user-profile you probably need spring-social. How to combine spring-social with spring-security for that purpose, is explained in this nice article about how to add social sign in to a spring-mvc weba-pplication.

Adding the Required Maven-Artifacts

In order to use spring-security-pac4j to login to facebook, you need the following maven-artifacts:


<dependency>
  <groupId>org.pac4j</groupId>
  <artifactId>spring-security-pac4j</artifactId>
  <version>1.2.5</version>
</dependency>
<dependency>
  <groupId>org.pac4j</groupId>
  <artifactId>pac4j-http</artifactId>
  <version>1.7.1</version>
</dependency>
<dependency>
  <groupId>org.pac4j</groupId>
  <artifactId>pac4j-oauth</artifactId>
  <version>1.7.1</version>
</dependency>

Configuration of Spring-Security (Without Social Login via pac4j)

This is a bare minimal configuration to get the form-login via Spring-Security working:


<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:security="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
    ">

  <security:http use-expressions="true">
    <security:intercept-url pattern="/**" access="permitAll"/>
    <security:intercept-url pattern="/home.html" access="isAuthenticated()"/>
    <security:form-login login-page="/login.html" authentication-failure-url="/login.html?failure"/>
    <security:logout/>
    <security:remember-me/>
  </security:http>

  <security:authentication-manager>
    <security:authentication-provider>
      <security:user-service>
  	<security:user name="user" password="user" authorities="ROLE_USER" />
      </security:user-service>
    </security:authentication-provider>
  </security:authentication-manager>

</beans>

The http defines, that the access to the url /home.html is restriced and must be authenticated via a form-login on url /login.html.
The authentication-manager defines an in-memory authentication-provider for testing purposes with just one user (username: user, password: user).
For more details, see the documentation of spring-security.

Enabling pac4j via spring-security-pac4j alongside

To enable pac4j alongside, you have to add/change the following:


<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:security="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
    ">

  <security:http use-expressions="true">
    <security:custom-filter position="OPENID_FILTER" ref="clientFilter"/>
    <security:intercept-url pattern="/**" access="permitAll()"/>
    <security:intercept-url pattern="/home.html" access="isAuthenticated()"/>
    <security:form-login login-page="/login.html" authentication-failure-url="/login.html?failure"/>
    <security:logout/>
  </security:http>

  <security:authentication-manager alias="authenticationManager">
    <security:authentication-provider>
      <security:user-service>
  	<security:user name="user" password="user" authorities="ROLE_USER" />
      </security:user-service>
    </security:authentication-provider>
    <security:authentication-provider ref="clientProvider"/>
  </security:authentication-manager>

  <!-- entry points -->
  <bean id="facebookEntryPoint" class="org.pac4j.springframework.security.web.ClientAuthenticationEntryPoint">
    <property name="client" ref="facebookClient"/>
  </bean>

  <!-- client definitions -->
  <bean id="facebookClient" class="org.pac4j.oauth.client.FacebookClient">
    <property name="key" value="145278422258960"/>
    <property name="secret" value="be21409ba8f39b5dae2a7de525484da8"/>
  </bean>
  <bean id="clients" class="org.pac4j.core.client.Clients">
    <property name="callbackUrl" value="http://localhost:8080/callback"/>
    <property name="clients">
      <list>
        <ref bean="facebookClient"/>
      </list>
    </property>
  </bean>

  <!-- common to all clients -->
  <bean id="clientFilter" class="org.pac4j.springframework.security.web.ClientAuthenticationFilter">
    <constructor-arg value="/callback"/>
    <property name="clients" ref="clients"/>
    <property name="sessionAuthenticationStrategy" ref="sas"/>
    <property name="authenticationManager" ref="authenticationManager"/>
  </bean>
  <bean id="clientProvider" class="org.pac4j.springframework.security.authentication.ClientAuthenticationProvider">
    <property name="clients" ref="clients"/>
  </bean>
  <bean id="httpSessionRequestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache"/>
  <bean id="sas" class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy"/>

</beans>

In short:

  1. You have to add an additional filter in http.
    I added this filter on position OPENID_FILTER, because pac4j introduces a unified way to handle OpenID and OAuth and so on.
    If you are using the OpenID-mechanism of spring-security, you have to use another position in the filter-chain (for example CAS_FILTER) or reconfigure OpenID to use the pac4j-mechanism, which should be fairly straight-forward.

    The new Filter has the ID clientFilter and needs a reference to the authenticationManager.
    Also, the callback-URL (here: /callback) must be mapped to your web-application!
  2. You have to add an additional authentication-provider to the authentication-manager, that references your newly defined pac4j-ClientProvider (clientProvider).
  3. You have to configure your entry-points as pac4j-clients.
    In the example above, only one pac4j-client, that authenticats the user via Facebook, is configured.
    You easily can add more clients: just copy the definitions from the spring-security-pac4j example.

That should be all, that is necessary, to enable a Facebook-Login in your Spring-Security web-application.

Do Not Forget To Use Your Own APP-ID!

The App-ID 145278422258960 and the accompanying secret be21409ba8f39b5dae2a7de525484da8 were taken from the spring-security-pac4j example for simplicity.
That works for a first test-run on localhost.
But you have to replace that with your own App-ID and -scecret, that you have to generate using your App Dashboard on Facebook!

More to come…

This short article does not show, how to save the retrieved user-profiles in your user-database, if you need that.
I hope, I will write a follow-up on that soon.
In short:
pac4j creates a Spring-Security UserDetails-Instance for every user, that was authenticated against it.
You can use this, to access the data in the retrieved user-profile (for example to write out the name of the user in a greeting or contact him via e-mail).

Log out from wrong Account with maven-appengine-plugin

Do you work with the maven-appengine-plugin and several google-accounts? If you do, or if you ever were logged in to the wrong google-account while executing mvn appengine:update, like me yesterday, you surely wondering, how to logout from maven-appengine-plugin.

maven-appengine-plugin somehow miracolously stores your credentials for you, when you attemp to upload an app for the first time. This comes in very handy, if you work with just one google-account. But it might get a “pain-in-the-ass”, if you work with several accounts. Because, if you once logged in into an account, there is no way (I mean: no goal of the maven-appengine-plugin) to log out, in order to change the account!

The solution: clear the credentials, that the maven-appengine-plugin stored on your behalf

Only after hard googling, i found a solution to this problem in a blog-post: maven-appengine-plugin stores its oauth2-credentials in the file .appcfg_oauth2_tokens_java in your home directory (on Linux – sorry Windows-folks, you have to figure out yourself, where the plugin stores the credentials on Windows).

Just delete the file .appcfg_oauth2_tokens_java and your logged out! The next time you call mvn appengine:upload you will be asked again to accept the request and, hence, can switch accounts. If you are not using oauth2, just look for .appcfg*-files in your home directory. I am sure, you will find another file with stored credentials, that you can delet to logout, like Radomir, who deleted .appcfg_cookiesy to log out.