Skip to content

POC of Integration Between Django Rest Framework and Keycloak using OAuth 2.0 Authorization Code Grant Flow.

Notifications You must be signed in to change notification settings

nuttapat-swd/drf-keycloak-poc

Repository files navigation

POC Django Rest Framework and Keycloak Integration

🎯 What is it?

POC Django Rest Framework and Keycloak Integration

📚 Purpose

To learn and demonstrate the OAuth2 Authorization Code Flow using Keycloak.

🛠️ Features

KeycloakAuthentication class

sequenceDiagram
  participant Web
  participant App
  participant Auth
  Web->>App: Request
  alt Auth Class
  App->>Auth: Check Auth
  Auth-->>+Keycloak: Check AccessToken
  Keycloak-->>-Auth: Reponse
  Auth->>App: Return Result
  end
  App->>Resoure: Access Reponse
  Resoure->>Web: Return Reponse
Loading

setting.py

...
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'apis.authorization.auth.KeycloakAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
}
...
class KeycloakAuthentication(BaseAuthentication):

    def authenticate(self, request):
        auth = request.headers.get('Authorization', None)
        if not auth or not auth.startswith("Bearer "):
            return None

        token = auth.split(" ")[1]
        try:
            # Validate the token
            token_info = keycloak_openid.introspect(token)
            if not token_info.get('active'):
                return None
                # raise AuthenticationFailed("Token is not active")

            # Optionally decode token to get claims
            userinfo = keycloak_openid.userinfo(token)
            username = userinfo.get('preferred_username')
            email = userinfo.get('email', '')

            if not username:
                return None
                # raise AuthenticationFailed("Username missing in token claims")

        except Exception as e:
            raise AuthenticationFailed(f"Token validation failed: {str(e)}")

        # Create or get Django user
        user, created = User.objects.get_or_create(
            username=username,
            defaults={'email': email}
        )
        if created:
            user.set_unusable_password()
            user.save()

        return (user, None)

🚀 Getting Started

  1. Deploy and Setup Keycloak
services:
  # PostgreSQL Database Service
  # - Stores all Keycloak data (users, realms, clients)
  # - Must be started before Keycloak
  # - Connected to Keycloak via keycloak-network
  # - Data persisted through postgres_data volume
  postgres:
    image: postgres:15
    container_name: keycloak-postgres
    environment:
      POSTGRES_DB: ${KC_DB_NAME}
      POSTGRES_USER: ${KC_DB_USERNAME}
      POSTGRES_PASSWORD: ${KC_DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "keycloak"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - keycloak-network

  # Keycloak Authentication Server
  # - Provides OAuth2/OpenID Connect functionality
  # - Depends on PostgreSQL for data storage
  # - Accessible on port 8080
  # - Connected to PostgreSQL via keycloak-network
  # - Configured for development mode with HTTP enabled
  keycloak:
    image: quay.io/keycloak/keycloak:latest
    container_name: keycloak
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/${KC_DB_NAME}
      KC_DB_USERNAME: ${KC_DB_USERNAME}
      KC_DB_PASSWORD: ${KC_DB_PASSWORD}
      KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}
      KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
      KC_HOSTNAME_STRICT: false
      KC_HOSTNAME_STRICT_HTTPS: false
      KC_HTTP_ENABLED: true
    command:
      - start-dev
    ports:
      - "8880:8080"
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - keycloak-network

volumes:
  postgres_data:
    name: keycloak_postgres_data

networks:
  keycloak-network:
    name: keycloak-network
  1. Create a .env file with the following variables:
# ============ main settings ============
# run the following command to generate a random secret key:
# python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
DJANGO_SECRET_KEY=
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1

# ============ CORS settings ============
CORS_ALLOW_CREDENTIALS=
CORS_ALLOWED_ORIGINS=
CSRF_TRUSTED_ORIGINS=

# ============ Keycloak settings ============
KEYCLOAK_URL=
KEYCLOAK_REALM=
KEYCLOAK_CLIENT_ID=
KEYCLOAK_CLIENT_SECRET=
KEYCLOAK_ADMIN_USERNAME=
KEYCLOAK_ADMIN_PASSWORD=
  1. initial pyhon env and active environment
> poetry install
> eval $(poetry env activate)
  1. Run migration at first time and run app:
> python manage.py migrate
> python manage.py runserver 0.0.0.0:8000
  1. Test by Postman: collection or Frontend: next-oauth-code-poc

About

POC of Integration Between Django Rest Framework and Keycloak using OAuth 2.0 Authorization Code Grant Flow.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages