3

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:

  1. Start all services
  2. Navigate to the service that requires login (/private in the example)
  3. The user is rerouted to the Keycloud server prompting to log in.
  4. The user logs in.
  5. Keycloak routes the user back to the app (/oidc_callback)
  6. !!! 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 and localhost: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 be localhost: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.

Rubenz
  • 46
  • 3
  • It looks to me that this problem relies in `flask-oidc`. – Filnor Dec 04 '18 at 13:30
  • Thank you Rubenz!!! You saved my day! I had the same problem (ok with gunicorn and docker but not from docker). Pointing my flask-oidc url to http://keycloak.dev.local:$port and using the docker-compose with extrahost like you pointed worked for me too. – Mr Bonjour Dec 09 '18 at 15:39

1 Answers1

2

flask-oidc gets token endpoint configuration from the client secrets file.

I managed to make it work by making the following changes:

  1. Created a docker network for the flask app and keycloak containers;
  2. Edited the attributes userinfo_uri, token_uri and token_introspection_uri, replacing the hostname with the keycloak container hostname (i'm using docker-compose, in this case the keycloak container hostname is the service name).

Example:

{
  "web": {
    "auth_uri": "http://localhost:8080/auth/realms/REMOVED/protocol/openid-connect/auth",
    "client_id": "flask-client",
    "client_secret": "REMOVED",
    "redirect_uris": ["http://localhost:5000/oidc_callback"],
    "userinfo_uri": "http://keycloak:8080/auth/realms/REMOVED/protocol/openid-connect/userinfo",
    "token_uri": "http://keycloak:8080/auth/realms/REMOVED/protocol/openid-connect/token",
    "token_introspection_uri": "http://keycloak:8080/auth/realms/REMOVED/protocol/openid-connect/token/introspect"
  }
}

Now it connects through the docker network to exchange token info.