POC Django Rest Framework and Keycloak Integration
To learn and demonstrate the OAuth2 Authorization Code Flow using Keycloak.
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
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)
- 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
- 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=
- initial pyhon env and active environment
> poetry install
> eval $(poetry env activate)
- Run migration at first time and run app:
> python manage.py migrate
> python manage.py runserver 0.0.0.0:8000
- Test by Postman: collection or Frontend: next-oauth-code-poc