Goal:
Use OIDC from a Flask app running in a Docker container.
Background:
I'm building a web application with Flask and want to use Keycloak to provide access. For this I use the Python library flask_oidc.
All services are run locally with a docker-compose file:
- Gunicorn that runs the Flask application (port 5000)
- Keycloak (port 8080)
I followed the example of https://gist.github.com/thomasdarimont/145dc9aa857b831ff2eff221b79d179a and even reduced my app to just this.
Problem:
- Start all services
- Navigate to the service that requires login (
/private
in the example) - The user is rerouted to the Keycloud server prompting to log in.
- The user logs in.
- Keycloak routes the user back to the app (
/oidc_callback
) - !!! The Flask app crashes with an
OSError: [Errno 99] Cannot assign requested address
error.
This is caused deep down in flask_oidc
> oauth2client
> httplib2
when trying to connect to the Keycloak server.
What I think is happening is that the library tries to open a connection with the Keycloak server, but tries to bind this to localhost
. This probably fails, since inside the Docker container the applications are bound to 0.0.0.0
.
What I tried:
[WORKS] Run the Gunicorn/Flask app outside of a container, and run the Keylcoak inside a container.
This shows (to me) that all my settings and the code are fine, but that the problem is somewhere in the interaction between Docker<-->flask_oidc+.
Question: Can anyone explain this? I really hope that someone has a working setup (Flask inside docker with flask_oidc), and is willing to share this.
UPDATE [5-12-2018] I think I figured it out. I used PyOIDC to manually go through all the steps and be able to debug.
When running both services in Docker on your own computer (localhost
) you get a conflict:
- Users go to
localhost:8080
to find Keycloak andlocalhost:5000
to find the App. - The Flask app runs inside the container, and
localhost
doesn't resolve to the host but rather to itself inside the container. - You can let Flask connect to
http://keycloak/
using the network in the container, but then it returns all the configuration under this domain. Which is bad, because to the outside world it should belocalhost:8080
.
Now, if you actually have domain names (for example keycloak.awesome.app and app.awesome.app) I think it'll just work fine, since it'll use an outside DNS to resolve it to an IP address, which is the correct machine.
Bonus: PyOIDC can retrieve the provider configuration from the Keycloak, so no more manual typing for this. Yay!
New setup For local development I decided to make a little setup as follows:
(1) Add to /etc/hosts
:
127.0.0.1 keycloak.dev.local
127.0.0.1 app.dev.local
(2) Add to your Flask service in the docker-compose.yml
:
extra_hosts: #host.docker.internal is not accepted.
- "keycloak.dev.local:<YOUR IP ADRESS>"
- "app.dev.local:<YOUR IP ADRESS>"
(=) Now, both you and the Flask application can access keycloak.dev.local
and can proper reponses!
Note that I would still prefer a nicer solution. This setup fails as soon as my ip address changes.