How to setup OpenID to work with load balancer?

1.4k views Asked by At

The stack I'm currently using is: Keycloak flask flask-oidc nginx as load balancer

The set up I'm having is I have two instances of a service running (instance1, instance2). The issue I'm facing is:

  1. A user uses web browser to authenticate by going to https://mycompany.com/auth/login

  2. instance1 handles this request and redirects the user to Keycloak for authentication

  3. Keycloak redirects the user back to the app using redirect url (https://mycompany.com/auth/auth_callback)

  4. This time the load balancer routes the request to the redirect url to instance2. Here instance2 errors out with a response from Keycloak saying "{'error': 'invalid_grant', 'description': 'Incorrect redirect uri'}", which is very confusing because the redirect uri is correct.

    I am not entirely sure why this set up is not working. But after reading through how openID works, I kind of suspect it has to do with the state parameter (https://auth0.com/docs/protocols/oauth2/oauth-state). Again, I am not entirely sure. But it has to be something that's only local to instance1, which instance2 doesn't have.

How do people tackle this issue ? Is this set up even possible?

1

There are 1 answers

1
Sujan Adiga On BEST ANSWER

From the documentation

Note that you should probably provide the library with a place to store the credentials it has retrieved for the user. These need to be stored in a place where the user themselves or an attacker can not get to them. To provide this, give an object that has setitem and getitem dict APIs implemented as second argument to the init() call. Without this, the library will only work on a single thread, and only retain sessions until the server is restarted.

It is referring to credentials_store option in OpenIDConnect instantiation. To support persisted login via multiple application instances, you will need a persisted shared datastore for this use case. You could use a share redis or dynamodb instance.

Implementation of this credentials_store is fairly simple, you can try something like,

class RedisOpenIdCredStore:
    def __init__(self):
        # Handle Redis instance initialisation here
        pass

    def __setitem__(self, key, value):
        # Set item to redis
        pass

    def __getitem__(self, key):
        # Fetch and return item from redis if present
        pass

credential_store = RedisOpenIdCredStore()
oid_connect = OpenIDConnect(app, credential_store=credential_store, ...)