← Back to list

Building a Secure Authentication System with Keycloak, React, and Flask

Hola

Ariel Parra · 2024-06-14 18:09 · 29 claps · 7.0 min read
#keycloak #keycloakjs #pyton #poc
Open on Medium ↗
Wiki topics: 🌐 · Web Development

Building a Secure Authentication System with Keycloak, React, and Flask

Hola

During my previous role, I had the opportunity to work with Auth0. Seeing how effortlessly we could leverage a third-party provider to authenticate users before granting them access to our system was a revelation. However, I also understood the potential cost implications, which, if not appropriately managed, could significantly impact your budget.

With that experience, when I heard about Keycloak, it caught my attention because, after the first check, it fit the most important features that Auth0 has or the ones I used the most.

What is Keycloak?

From their website

Open Source Identity and Access Management

Add authentication to applications and secure services with minimum effort. No need to deal with storing users or authenticating users.

In a nutshell, Keycloak provides user federation, strong authentication, user management, fine-grained authorization, and more.

I decided to do a simple proof of concept to check how keycloak could work as an auth service. Everything is set in this Keycloak PoC Repository.

This is the diagram of what we are going to build.

I used mermaid.js and in the README of the project is the code behind

I used mermaid.js and in the README of the project is the code behind

Setting Up Keycloak

I decided to go with the docker option using their image. The main instructions are here and it’s a simple docker-compose yaml.

version: '3.9'

services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: ${POSTGRESQL_DB}
      POSTGRES_USER: ${POSTGRESQL_USER}
      POSTGRES_PASSWORD: ${POSTGRESQL_PASS}
    volumes:
      - postgres_data:/var/lib/postgresql/data

  keycloak:
    image: quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}
    environment:
      KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN_USER}
      KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASS}
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres/${POSTGRESQL_DB}
      KC_DB_USERNAME: ${POSTGRESQL_USER}
      KC_DB_PASSWORD: ${POSTGRESQL_PASS}
    ports:
      - 8080:8080
    command:
      - start-dev

volumes:
  postgres_data:

As we could see there, I decided to use a Postgres database and a volume so we don’t lose the data after each docker restart. The basic information to set is defined in the .env.example and is the following:

KEYCLOAK_VERSION=20.0
PORT_KEYCLOAK=8080
POSTGRESQL_USER=keycloak
POSTGRESQL_PASS=keycloak
POSTGRESQL_DB=db-auth
KEYCLOAK_ADMIN_USER=admin
KEYCLOAK_ADMIN_PASS=change_me

If you just create a copy of it and name it .env, you will have a running version in port 8080 of version 20.0 with the credentials mentioned there.

Keycloak Setup

In order to set a basic environment in Keycloak, we will create a Realm which is a way to create isolated environments for managing users and applications, and then different clients, depending on the needs we we will have to allow connections through our authentication system

Realm Settings

Go to the realm section and create a new one. For this example, let’s called it “myrealm”

We will be using the default login provided by Keycloak, so we will also allow user registration. Go to Realm Settings and enable the User Registration option

Client Settings

For our Proof of concept, as we see in our initial design, we will have two clients: a Web Application and an API. The following instructions are the same for both cases, so go to the Clients section and create two new clients, one for the web and another for the api.

As a web example, let’s call it client-web

This will be the client for the web app, and we should provide the proper URLs allowed to interact with this client. For the sake of this test, we will use a wildcard (*), but in PROD, we should include the proper URLs here.

We will use wildcards (*) as testing purposes

We will use wildcards () as testing purposes*

In the Capability config, we will only set Standard Flow and Direct Access grants.

Creating the React Application

The web app will be a classic React application, with Typescript and using Vite as a development server. It is placed here in the repository.

Configuration

The main thing here is to properly set up the environment variables, to do that, I set an example there, so you have to create a .env file and fill in the following data, I’m going to explain how it will be if we are using the default values

VITE_KEYCLOAK_URL=http://0.0.0.0:8080/
VITE_KEYCLOAK_REALM=myrealm
VITE_KEYCLOAK_CLIENT=client-web

Keycloak Connection

In this example, I used the keycloak-js library and set everything in a context called KeycloakContext. When I click Login, I use the keycloak client created and provided by the context and call the login method.

About the KeycloakContext it’s being initialized at the beginning of the app because it acts as a provider (check App.tsx ) and the main part is defined here:

const initKeycloak = async () => {
  const keycloackConfig = {
    url: import.meta.env.VITE_KEYCLOAK_URL as string,
    realm: import.meta.env.VITE_KEYCLOAK_REALM as string,
    clientId: import.meta.env.VITE_KEYCLOAK_CLIENT as string,
  }
  const keycloakInstance: Keycloak = new Keycloak(keycloackConfig)

  keycloakInstance
    .init({
      onLoad: 'check-sso',
    })
    .then((authenticated: boolean) => {
      setAuthenticated(authenticated)
    })
    .catch((error) => {
      console.error('Keycloak initialization failed:', error)
      setAuthenticated(false)
    })
    .finally(() => {
      setKeycloak(keycloakInstance)
      console.log('keycloak', keycloakInstance)
    })
}

Using the configuration values, I’m making the connection with Keyloak using check-sso. The other option to use is login-required, and both do the same, except the first one redirects the user to the login page if it's not authenticated.

Running the App

By simply doing yarn run dev you will have a web page with a login button that will redirect you to the Keycloak default login screen (all of this is fully customisable, but I will let it for another post)

This is the flow you will see while doing login -> Register

This is the flow you will see while doing login -> Register

If you follow all the steps after logging in you will something similar like the following

Developing the Flask API

I’m using the word simple a lot, which is not the exception. To run an API, I created a basic Flask application that will have three endpoints

  • /api/public -> to test that the API is accessible without a token
  • /api/private -> to test that the API is only accessible with a valid token
  • /api/users/<string:user_id>/items -> to test a valid use case of getting items from a user

For this example, you could create a .env file and setting with the following values (if you are following the default ones). I'm also using a SQLite version to store the data.

KEYCLOAK_SERVER_URL=http://localhost:8080
KEYCLOAK_REALM_NAME=myrealm
KEYCLOAK_CLIENT_ID=client-api
SQLALCHEMY_DATABASE_URI=sqlite:///project.db

After that the main point here is how to validate the token, and to do that I’m using authlib library for the token validation. All the “magic” is defined in auth.py

import json
import os
from urllib.request import urlopen

from authlib.integrations.flask_oauth2 import ResourceProtector
from authlib.jose.rfc7517.jwk import JsonWebKey
from authlib.oauth2.rfc7523 import JWTBearerTokenValidator

from api.models import verify_user

KEYCLOAK_SERVER_URL = os.getenv("KEYCLOAK_SERVER_URL")
KEYCLOAK_REALM_NAME = os.getenv("KEYCLOAK_REALM_NAME")

KEYCLOAK_ISSUER = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM_NAME}"

class ClientCredsTokenValidator(JWTBearerTokenValidator):
    def __init__(self, issuer):
        jsonurl = urlopen(f"{issuer}/protocol/openid-connect/certs")
        public_key = JsonWebKey.import_key_set(json.loads(jsonurl.read()))
        super(ClientCredsTokenValidator, self).__init__(public_key)
        self.claims_options = {
            "exp": {"essential": True},
            "iss": {"essential": True, "value": issuer},
        }

    def validate_token(self, token, scopes, request):
        super(ClientCredsTokenValidator, self).validate_token(token, scopes, request)

        verify_user(token)

require_auth = ResourceProtector()
validator = ClientCredsTokenValidator(KEYCLOAK_ISSUER)
require_auth.register_token_validator(validator)

We are getting the open ID certs provided by our Keycloak instance (for the specific realm), and the library validates that the provided token is valid and has not expired.

As an extra point here, the user data we are seeing in the token lives in Keycloak. For this example, I decided to also have an API instance of the user where I will be storing extra information there (this is optional and will depend on how are you defining your architecture). So for that reason, I override validate_token method in the JWTBearerTokenValidator because I am checking if the user token is valid, and I do not have a record in my db, so I create a new one with the information provided by the JWT.

Integrating React with Flask

Now that our Web App and API are running with proper connections to Keycloak, let's move on to how both clients interact with each other.

All about this part is located in MyItems.tsx in the web project and we are using the keycloak context only when is authenticated and we will call to our api by using the bearer token provided.

useEffect(() => {
  const fetchItems = async () => {
    try {
      if (!keycloak) {
        return;
      }

      const userId = keycloak.idTokenParsed?.sub;

      setLoading(true);
      setError(null);

      const response = await fetch(`/api/users/${userId}/items`, {
        headers: {
          Authorization: `Bearer ${keycloak.token}`
        }
      });

      if (!response.ok) {
        setError('Error fetching items.');
        return;
      }

      const data = await response.json();
      setItems(data.items);
    } catch (error) {
      console.error('Error fetching items:', error);
      setError('Unexpected error occurred.');
    } finally {
      setLoading(false);
    }
  };

  if (authenticated) {
    fetchItems();
  }
}, [keycloak, authenticated]);

More specifically in the following part we use the user token

const response = await fetch(`/api/users/${userId}/items`, {
  headers: {
    Authorization: `Bearer ${keycloak.token}`
  }
});

If we go go the My Items section and open the dev tools you will see how the connection is made and we are getting a mock data

Conclusions

Keycloak works great as an auth service and provides a solid place to control and expand the clients you want to access your information. In this example, upcoming points should be to start creating real data (items), so that when you call the user’s items endpoint, you will get the proper info, but the main point of this article is to show the general overview.

This will work great if you have mobile apps, too, so you can control login access and the token lifecycle in a centralized place (and go beyond that and allow other developers to start connecting with your data by creating specific clients with more strict permissions for them).

Thank you for reading here, and I hope it likes you, the guide and explanation of the Keycloak usage.


메타데이터
post_id
35aeee04e37a
slug
building-a-secure-authentication-system-with-keycloak-react-and-flask-35aeee04e37a
url
https://medium.com/@darkaico/building-a-secure-authentication-system-with-keycloak-react-and-flask-35aeee04e37a
canonical_url
https://medium.com/@darkaico/building-a-secure-authentication-system-with-keycloak-react-and-flask-35aeee04e37a
author_url
https://medium.com/@darkaico
status
ok
fetched_at
2026-06-18 00:10:23