quart-keycloak

Our OpenID Connect library

practices keycloak | 2023-09-07
1 бул. „Княз Александър Дондуков", Sofia Bulgaria - Kodak Gold 200, 2022

Companies often manage a collection of internal services/tools/platforms; hosted in various ways, managed in various ways, and relevant to this blogpost: each requiring a different set of user credentials. While most are familiar with a password manager, it would be nice to only have to deal with one account. This prevents from having to create and distribute a list of login accounts during the on-boarding process of new employees, as well as ease up termination by revoking access in one sweep.

What is an IdP?

An identity provider (IdP) is a service that authenticates users, generally via OpenID Connect - a flavor of OAuth2. Below is a list of software that are IdP's:

Users authenticate against an IdP rather than against individual applications. This means that applications don't have to deal with login forms, authentication, or storing users. They would all use a single IdP, effectively outsourcing their authentication and authorization business logic.

Benefits

Once logged-in to an IdP, users don't have to log in again to access a different application (single-sign-on). Centralizing user accounts in a single system means that properties related to authentication only need to be defined in one place, as opposed to individual applications having to re-invent the wheel, such as:

  • picking a hashing algorithm
  • enforcing password requirements
    • complexity, minimum length, etc.
  • enforcing 2FA
  • protecting against spam signups (bots et al.)
  • IP whitelisting
  • user session management
  • role delegation

This saves time (and mistakes) as the above is the responsibility of a single system that is designed for it.

Challenges

Not all applications, tools, and services support login via an IdP, and persuading software authors to implement OpenID Connect comes with challenges. The specification is extensive, and using it properly requires research.

Our own Keycloak client library

When writing web-applications in Python, we generally use one of the following frameworks:

  • FastAPI if we are providing an HTTP API
  • Quart if we'll be doing backend templating

(As you may notice; both are asyncio frameworks)

In order to provide login functionality to our Quart applications, we've created an extension called quart-keycloak. This extension puts an abstract layer over the complicated parts of OpenID Connect and makes a fair assumption that the user (developer) wants basic OpenID Connect features, mainly: login, and logout.

This allows us quickly develop web-applications where users can authenticate themselves, without the need for us to create login functionality for each project.

A minimal example of an Quart application that implements OIDC:

from quart import Quart, url_for, jsonify, session, redirect
from quart_session import Session
from quart_keycloak import Keycloak, KeycloakAuthToken, KeycloakLogoutRequest

app = Quart(__name__)
app.secret_key = 'changeme'
app.config['SESSION_TYPE'] = 'redis'
Session(app)

openid_keycloak_config = {
    "client_id": "",
    "client_secret": "",
    "configuration": "https://example.com/realms/master/.well-known/openid-configuration"
}

keycloak = Keycloak(app, **openid_keycloak_config)


@app.route("/")
async def root():
    # redirect the user after logout
    logout_url = url_for('logout', _external=True)

    # the login URL
    login_url_keycloak = url_for(keycloak.endpoint_name_login)

    # the logout URL, `redirect_uri` is required. `state` is optional.
    logout_url_keycloak = url_for(keycloak.endpoint_name_logout, redirect_uri=logout_url, state='bla')

    return f"""
    <b>token:</b> {session.get('auth_token')}<br><hr>
    Login via keycloak: <a href="{login_url_keycloak}">Login via Keycloak</a><br>
    Logout via keycloak: <a href="{logout_url_keycloak}">Logout via Keycloak</a>
    """

@keycloak.after_login()  # user was redirected back from Keycloak
async def handle_user_login(auth_token: KeycloakAuthToken):
    # user = await keycloak.user_info(auth_token.access_token)  # optionally call the userinfo endpoint for more info

    # set session
    session['auth_token'] = auth_token
    return redirect(url_for('root'))

@app.route("/logout")  # route that clears the session
async def logout():
    session.clear()
    return redirect(url_for('root'))

app.run("localhost", port=2700, debug=True, use_reloader=False)

Relevancy

Due to the "enterprise" nature of this technology you may be under the impression OpenID Connect is only applicable to large companies with exotic requirements, but we think otherwise. While complex, it can support any type of ecosystem that requires login functionality - even if it concerns managing some sort of informal community. By leveraging OIDC, you'll be prepared for future growth and save yourself growing pains when introducing new services to your ecosystem, as those services can immediately leverage your existing user management infrastructure.

Conclusion

To conclude, we have experience with:

  • OIDC client implementations
  • Hosting Keycloak in a high traffic environment (>1000 users)
  • Migrating Keycloak data between versions (starting from version 10 to recent)
  • in-depth knowledge of the public and confidential authentication flows
  • Customizing Keycloak theming/templates
  • Using Keycloak plugins like bcrypt, rabbitmq
  • Using alternative IdP's like Azure AD