% Options for packages loaded elsewhere
\PassOptionsToPackage{unicode}{hyperref}
\PassOptionsToPackage{hyphens}{url}
%
\documentclass[
]{report}
\usepackage{lmodern}
\usepackage{amssymb,amsmath}
\usepackage{ifxetex,ifluatex}
\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
  \usepackage[T1]{fontenc}
  \usepackage[utf8]{inputenc}
  \usepackage{textcomp} % provide euro and other symbols
\else % if luatex or xetex
  \usepackage{unicode-math}
  \defaultfontfeatures{Scale=MatchLowercase}
  \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1}
\fi
% Use upquote if available, for straight quotes in verbatim environments
\IfFileExists{upquote.sty}{\usepackage{upquote}}{}
\IfFileExists{microtype.sty}{% use microtype if available
  \usepackage[]{microtype}
  \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts
}{}
\makeatletter
\@ifundefined{KOMAClassName}{% if non-KOMA class
  \IfFileExists{parskip.sty}{%
    \usepackage{parskip}
  }{% else
    \setlength{\parindent}{0pt}
    \setlength{\parskip}{6pt plus 2pt minus 1pt}}
}{% if KOMA class
  \KOMAoptions{parskip=half}}
\makeatother
\usepackage{xcolor}
\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available
\IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}}
\hypersetup{
  hidelinks,
  pdfcreator={LaTeX via pandoc}}
\urlstyle{same} % disable monospaced font for URLs
\usepackage[margin=2.0cm,a4paper]{geometry}
\usepackage{color}
\usepackage{fancyvrb}
\newcommand{\VerbBar}{|}
\newcommand{\VERB}{\Verb[commandchars=\\\{\}]}
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}}
% Add ',fontsize=\small' for more characters per line
\newenvironment{Shaded}{}{}
\newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}}
\newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
\newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{#1}}
\newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
\newcommand{\BuiltInTok}[1]{#1}
\newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
\newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{#1}}}
\newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
\newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.53,0.00,0.00}{#1}}
\newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}}
\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{#1}}
\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
\newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.73,0.13,0.13}{\textit{#1}}}
\newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}}
\newcommand{\ExtensionTok}[1]{#1}
\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
\newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{#1}}
\newcommand{\ImportTok}[1]{#1}
\newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}}
\newcommand{\NormalTok}[1]{#1}
\newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}}
\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{#1}}
\newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.74,0.48,0.00}{#1}}
\newcommand{\RegionMarkerTok}[1]{#1}
\newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
\newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.73,0.40,0.53}{#1}}
\newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
\newcommand{\VariableTok}[1]{\textcolor[rgb]{0.10,0.09,0.49}{#1}}
\newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
\newcommand{\WarningTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
\usepackage{longtable,booktabs}
% Correct order of tables after \paragraph or \subparagraph
\usepackage{etoolbox}
\makeatletter
\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{}
\makeatother
% Allow footnotes in longtable head/foot
\IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}}
\makesavenoteenv{longtable}
\setlength{\emergencystretch}{3em} % prevent overfull lines
\providecommand{\tightlist}{%
  \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
\setcounter{secnumdepth}{-\maxdimen} % remove section numbering
\usepackage{titlesec}
\usepackage{fancyvrb}
\usepackage{fvextra}
\usepackage{enumitem}

\usepackage{longtable}
\usepackage{etoolbox}

\usepackage{fontspec}
\setmainfont{lmroman10-regular.otf}[
    BoldFont       = lmroman10-bold.otf,
    ItalicFont     = lmroman10-italic.otf,
    BoldItalicFont = lmroman10-bolditalic.otf,
    OpticalSize    = 0
]

\AtBeginEnvironment{longtable}{\fontsize{6}{8}\selectfont}

\newcommand{\chapfnt}{\fontsize{19}{21}}
\newcommand{\secfnt}{\fontsize{14}{17}}
\newcommand{\ssecfnt}{\fontsize{12}{14}}
\newcommand{\sectionbreak}{\clearpage}

\titleformat{\chapter}[display]
{\normalfont\chapfnt\bfseries}{\chaptertitlename\ \thechapter}{20pt}{\chapfnt}

\titleformat{\section}
{\normalfont\secfnt\bfseries}{\thesection}{1em}{}

\titleformat{\subsection}
{\normalfont\ssecfnt\bfseries}{\thesubsection}{1em}{}

\titlespacing*{\chapter} {0pt}{50pt}{40pt}
\titlespacing*{\section} {0pt}{3.5ex plus 1ex minus .2ex}{2.3ex plus .2ex}
\titlespacing*{\subsection} {0pt}{3.25ex plus 1ex minus .2ex}{1.5ex plus .2ex}

\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\},fontsize=\scriptsize,frame=single,rulecolor=\color{lightgray},breaklines,samepage,label=\tiny{Code},labelposition=topline}
\DefineVerbatimEnvironment{verbatim}{Verbatim}{commandchars=\\\{\},fontsize=\scriptsize,frame=single,rulecolor=\color{lightgray},breaklines,samepage,label=\tiny{Output},labelposition=topline,fontshape=it}

\setlist{after=\bigskip}

\let\OldRule\rule
\renewcommand{\rule}[2]{\OldRule{0.0\linewidth}{#2}}

\title{Authenticating a Django Application via Keycloak}
\author{Publicator using openai/gpt-oss-120b}
\date{}

\begin{document}
\maketitle

{
\setcounter{tocdepth}{2}
\tableofcontents
}
\hypertarget{authenticating-a-django-application-via-keycloak}{%
\chapter{Authenticating a Django Application via
Keycloak}\label{authenticating-a-django-application-via-keycloak}}

\textbf{Abstract:} This paper presents a comprehensive approach for
integrating Keycloak, an open‑source identity and access management
platform, with modern Django web applications. We begin by outlining the
authentication challenges inherent to Django's native framework and
motivate the adoption of Keycloak to achieve robust single‑sign‑on (SSO)
and centralized user management. A background review contrasts Django's
built‑in authentication mechanisms with Keycloak's core concepts -
realms, clients, OpenID Connect, and SAML - highlighting their natural
compatibility for federation. Existing integration efforts, including
python‑keycloak, django‑oidc‑auth, and custom middleware, are surveyed
to expose their strengths and limitations, justifying the need for a
dedicated implementation. The proposed system architecture delineates
the interaction between the Django server, the Keycloak server, and
optional reverse‑proxy components, accompanied by a detailed
authentication flow diagram. Implementation guidance covers client
configuration, required Python packages, middleware insertion, token
validation, role‑to‑permission mapping, and user data persistence. A
security analysis evaluates token storage, CSRF mitigation, session
handling, role‑based access control, and defenses against token replay
and injection attacks. Performance benchmarks quantify login latency,
token introspection overhead, and scalability under concurrent load,
comparing results with Django's default authentication and alternative
SSO solutions. The discussion interprets these findings, addressing
operational considerations such as containerized deployment,
high‑availability Keycloak, and logging practices, while acknowledging
practical trade‑offs. We conclude that Keycloak substantially enhances
security, simplifies user administration, and provides seamless SSO for
Django applications. Future work will explore support for additional
protocols (e.g., SAML), multi‑realm configurations, automated testing
pipelines, and dynamic client registration.

\hypertarget{introduction}{%
\section{1. Introduction}\label{introduction}}

\hypertarget{motivation}{%
\subsection{1.1 Motivation}\label{motivation}}

Modern web applications built with Django increasingly operate in
environments where users expect \textbf{single‑sign‑on (SSO)},
\textbf{federated identity}, and \textbf{centralised policy
enforcement}. Traditional Django authentication - based on a local
\texttt{User} model and session cookies - works well for monolithic
deployments but quickly becomes a liability when:

\begin{itemize}
\tightlist
\item
  Multiple services (e.g., API back‑ends, micro‑front‑ends) need to
  share the same user base.\\
\item
  Organizations require compliance with standards such as \textbf{OpenID
  Connect (OIDC)} or \textbf{SAML} for external identity providers.\\
\item
  Security teams demand features like \textbf{password‑less login},
  \textbf{multi‑factor authentication}, and \textbf{dynamic role
  management} that are not natively provided by Django.
\end{itemize}

Keycloak, an open‑source \textbf{Identity and Access Management (IAM)}
platform, addresses these gaps out‑of‑the‑box. It supplies a
standards‑compliant OIDC/SAML server, user federation, fine‑grained
role‑based access control, and a rich administrative console - all of
which can be leveraged without reinventing the wheel.

\hypertarget{authentication-challenges-in-django}{%
\subsection{1.2 Authentication Challenges in
Django}\label{authentication-challenges-in-django}}

\begin{longtable}[]{@{}lll@{}}
\toprule
\begin{minipage}[b]{0.17\columnwidth}\raggedright
Challenge\strut
\end{minipage} & \begin{minipage}[b]{0.49\columnwidth}\raggedright
Why it matters for Django apps\strut
\end{minipage} & \begin{minipage}[b]{0.26\columnwidth}\raggedright
Typical symptom\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.17\columnwidth}\raggedright
\textbf{Scalability of user stores}\strut
\end{minipage} & \begin{minipage}[t]{0.49\columnwidth}\raggedright
Django's default \texttt{auth\_user} table does not natively support
external LDAP/AD sync or social login aggregation.\strut
\end{minipage} & \begin{minipage}[t]{0.26\columnwidth}\raggedright
Duplicate user records across services.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.17\columnwidth}\raggedright
\textbf{Consistent session management}\strut
\end{minipage} & \begin{minipage}[t]{0.49\columnwidth}\raggedright
Sessions are stored locally (database, cache, or file) and are not
shared across multiple Django instances without extra
configuration.\strut
\end{minipage} & \begin{minipage}[t]{0.26\columnwidth}\raggedright
Users forced to re‑authenticate after a load‑balancer fail‑over.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.17\columnwidth}\raggedright
\textbf{Fine‑grained authorization}\strut
\end{minipage} & \begin{minipage}[t]{0.49\columnwidth}\raggedright
Permissions are tied to Django's \texttt{Group} model; mapping to
external role hierarchies is manual.\strut
\end{minipage} & \begin{minipage}[t]{0.26\columnwidth}\raggedright
Complex permission checks scattered throughout the codebase.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.17\columnwidth}\raggedright
\textbf{Compliance and audit}\strut
\end{minipage} & \begin{minipage}[t]{0.49\columnwidth}\raggedright
Logging of authentication events, password policies, and MFA enforcement
must be added manually.\strut
\end{minipage} & \begin{minipage}[t]{0.26\columnwidth}\raggedright
Lack of traceability for security audits.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.17\columnwidth}\raggedright
\textbf{Federated SSO}\strut
\end{minipage} & \begin{minipage}[t]{0.49\columnwidth}\raggedright
Integrating with corporate IdPs (Azure AD, Okta) requires custom
OIDC/SAML handling.\strut
\end{minipage} & \begin{minipage}[t]{0.26\columnwidth}\raggedright
Users receive ``invalid credentials'' errors when using corporate
accounts.\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

These pain points motivate a shift from the \textbf{built‑in
authentication} to a \textbf{centralised IAM} that can be consumed by
Django via standard protocols.

\hypertarget{scope-and-objectives}{%
\subsection{1.3 Scope and Objectives}\label{scope-and-objectives}}

The purpose of this publication is to \textbf{demonstrate a
production‑ready integration of a Django application with Keycloak}. The
work is bounded by the following objectives:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  \textbf{Define a reproducible setup} that configures a Keycloak realm
  and client specifically for a Django project, covering both
  development and production considerations.\\
\item
  \textbf{Implement authentication middleware} that delegates login,
  token validation, and logout to Keycloak while preserving Django's
  request/response lifecycle.\\
\item
  \textbf{Map Keycloak roles to Django permissions}, enabling developers
  to continue using Django's \texttt{@permission\_required} and
  \texttt{User.has\_perm} APIs without code duplication.\\
\item
  \textbf{Ensure security best practices} (secure token storage, CSRF
  protection, session handling) are adhered to, laying the groundwork
  for the deeper analysis presented in \textbf{Section 6. Security
  Analysis}.\\
\item
  \textbf{Provide a baseline performance profile} that can be compared
  against Django's native authentication, as explored in \textbf{Section
  7. Performance Evaluation}.
\end{enumerate}

By the end of the guide, readers will possess a \textbf{complete,
end‑to‑end example} that can be adapted to any Django project requiring
robust, standards‑based identity management. The subsequent sections
build on this foundation: \textbf{Section 2} reviews the underlying
technologies, \textbf{Section 3} surveys existing integration attempts,
and \textbf{Section 5} walks through the concrete implementation steps.

\hypertarget{background}{%
\section{2. Background}\label{background}}

\hypertarget{native-django-authentication-framework}{%
\subsection{2.1 Native Django Authentication
Framework}\label{native-django-authentication-framework}}

Django ships with a \textbf{batteries‑included} authentication system
that is deliberately simple yet extensible. Its core components are:

\begin{longtable}[]{@{}lll@{}}
\toprule
\begin{minipage}[b]{0.29\columnwidth}\raggedright
Component\strut
\end{minipage} & \begin{minipage}[b]{0.23\columnwidth}\raggedright
Purpose\strut
\end{minipage} & \begin{minipage}[b]{0.39\columnwidth}\raggedright
Extensibility\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.29\columnwidth}\raggedright
\textbf{User model} (\texttt{django.contrib.auth.models.User})\strut
\end{minipage} & \begin{minipage}[t]{0.23\columnwidth}\raggedright
Stores username, password hash, email, and a set of built‑in permissions
(\texttt{add}, \texttt{change}, \texttt{delete}, \texttt{view}).\strut
\end{minipage} & \begin{minipage}[t]{0.39\columnwidth}\raggedright
Can be swapped for a custom model via \texttt{AUTH\_USER\_MODEL}.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.29\columnwidth}\raggedright
\textbf{Authentication backends}\strut
\end{minipage} & \begin{minipage}[t]{0.23\columnwidth}\raggedright
Translate credentials (e.g., username/password) into a \texttt{User}
instance. The default \texttt{ModelBackend} checks the Django DB;
additional backends can query LDAP, OAuth, etc.\strut
\end{minipage} & \begin{minipage}[t]{0.39\columnwidth}\raggedright
Multiple backends can be stacked; the first that authenticates a request
wins.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.29\columnwidth}\raggedright
\textbf{Session middleware}\strut
\end{minipage} & \begin{minipage}[t]{0.23\columnwidth}\raggedright
Persists the authenticated user's ID in a signed cookie
(\texttt{sessionid}).\strut
\end{minipage} & \begin{minipage}[t]{0.39\columnwidth}\raggedright
Session engine can be swapped (database, cache, signed cookies).\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.29\columnwidth}\raggedright
\textbf{Permission system}\strut
\end{minipage} & \begin{minipage}[t]{0.23\columnwidth}\raggedright
Grants or denies access based on per‑object or model‑level permissions,
evaluated through \texttt{user.has\_perm()} and the
\texttt{@permission\_required} decorator.\strut
\end{minipage} & \begin{minipage}[t]{0.39\columnwidth}\raggedright
Supports custom permission checks and integration with third‑party RBAC
solutions.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.29\columnwidth}\raggedright
\textbf{Login/logout views}\strut
\end{minipage} & \begin{minipage}[t]{0.23\columnwidth}\raggedright
Provide form‑based login (\texttt{django.contrib.auth.views.LoginView})
and logout (\texttt{LogoutView}).\strut
\end{minipage} & \begin{minipage}[t]{0.39\columnwidth}\raggedright
Can be overridden to redirect to external identity providers.\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

While robust for monolithic applications, the native stack assumes
\textbf{local credential storage} and \textbf{synchronous session
handling}. It does not natively understand \textbf{OpenID Connect
(OIDC)} or \textbf{SAML} tokens, nor does it provide built‑in federation
across multiple identity sources. This gap is precisely what an external
IAM such as \textbf{Keycloak} fills.

\hypertarget{core-concepts-of-keycloak}{%
\subsection{2.2 Core Concepts of
Keycloak}\label{core-concepts-of-keycloak}}

Keycloak is an open‑source Identity and Access Management (IAM) server
that implements the major federated authentication standards. Its
architecture revolves around a few fundamental entities:

\begin{longtable}[]{@{}lll@{}}
\toprule
\begin{minipage}[b]{0.17\columnwidth}\raggedright
Entity\strut
\end{minipage} & \begin{minipage}[b]{0.28\columnwidth}\raggedright
Description\strut
\end{minipage} & \begin{minipage}[b]{0.46\columnwidth}\raggedright
Relevance to Django\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.17\columnwidth}\raggedright
\textbf{Realm}\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
A logical isolation boundary that groups users, credentials, clients,
and configuration. Each realm has its own set of identity providers,
roles, and policies.\strut
\end{minipage} & \begin{minipage}[t]{0.46\columnwidth}\raggedright
A Django project typically maps to a single realm (e.g.,
\texttt{my‑app‑realm}). Multi‑tenant Django deployments can leverage
multiple realms.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.17\columnwidth}\raggedright
\textbf{Client}\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
Represents an application that wants to authenticate users. Clients are
configured with a \textbf{protocol} (OIDC or SAML), redirect URIs, and
access‑type (public, confidential, bearer‑only).\strut
\end{minipage} & \begin{minipage}[t]{0.46\columnwidth}\raggedright
The Django site registers as an \textbf{OIDC confidential client} (or
SAML service provider) to obtain tokens from Keycloak.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.17\columnwidth}\raggedright
\textbf{User Federation}\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
Connects external user stores (LDAP, Active Directory, Kerberos) to the
realm, allowing seamless login without duplicating credentials.\strut
\end{minipage} & \begin{minipage}[t]{0.46\columnwidth}\raggedright
Django can continue to rely on corporate LDAP for user data while
delegating authentication to Keycloak.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.17\columnwidth}\raggedright
\textbf{Roles \& Groups}\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
Hierarchical permissions that can be assigned to users or clients. Roles
are emitted in tokens as \texttt{realm\_access} or
\texttt{resource\_access}.\strut
\end{minipage} & \begin{minipage}[t]{0.46\columnwidth}\raggedright
These roles can be mapped to Django's permission model (e.g.,
\texttt{is\_staff}, custom permissions).\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.17\columnwidth}\raggedright
\textbf{OpenID Connect (OIDC)}\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
A modern OAuth 2.0‑based identity layer that issues \textbf{ID tokens}
(JWT) and \textbf{access tokens}. Supports discovery
(\texttt{/.well-known/openid-configuration}) and dynamic client
registration.\strut
\end{minipage} & \begin{minipage}[t]{0.46\columnwidth}\raggedright
Django can consume the ID token to obtain the authenticated user's
identity and the access token for API calls.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.17\columnwidth}\raggedright
\textbf{SAML 2.0}\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
An XML‑based federation protocol widely used in enterprise SSO
scenarios.\strut
\end{minipage} & \begin{minipage}[t]{0.46\columnwidth}\raggedright
For environments that mandate SAML, Keycloak can act as an IdP, and
Django can operate as a Service Provider (SP) via a SAML library.\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

Keycloak's \textbf{admin console} (referenced in the Introduction)
provides a UI for managing these entities, enabling rapid prototyping
and production‑grade configuration without code changes.

\hypertarget{compatibility-points-enabling-seamless-federation}{%
\subsection{2.3 Compatibility Points Enabling Seamless
Federation}\label{compatibility-points-enabling-seamless-federation}}

The intersection of Django's extensible authentication stack and
Keycloak's standards‑based IAM creates several natural integration
pathways:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  \textbf{Token‑Based Authentication}

  \begin{itemize}
  \tightlist
  \item
    Django's session middleware can be bypassed in favor of
    \textbf{stateless JWT validation}. By installing a lightweight
    middleware that extracts the
    \texttt{Authorization:\ Bearer\ \textless{}token\textgreater{}}
    header, the request can be associated with a \texttt{User} object
    derived from the token's \texttt{sub} claim.\\
  \item
    This mirrors the \textbf{Bearer‑only client} mode in Keycloak, where
    the application never stores a client secret, reducing attack
    surface.
  \end{itemize}
\item
  \textbf{Authentication Backends}

  \begin{itemize}
  \tightlist
  \item
    A custom \textbf{OIDC authentication backend} can delegate
    credential verification to Keycloak, returning a Django
    \texttt{User} instance (created on‑first‑login if needed). Because
    Django already supports multiple backends, the OIDC backend can
    coexist with the default DB backend for fallback scenarios.
  \end{itemize}
\item
  \textbf{Role Mapping}

  \begin{itemize}
  \tightlist
  \item
    Keycloak embeds \textbf{realm and client roles} in the JWT
    (\texttt{realm\_access.roles},
    \texttt{resource\_access.\textless{}client\textgreater{}.roles}). A
    simple mapping layer can translate these into Django's
    \texttt{Group} or \texttt{Permission} objects, allowing existing
    \texttt{@permission\_required} decorators to function unchanged.
  \end{itemize}
\item
  \textbf{Session Synchronisation}

  \begin{itemize}
  \tightlist
  \item
    When Keycloak issues a \textbf{refresh token}, Django can
    transparently refresh the access token in the background, updating
    the session store without user interaction. This aligns with
    Django's \textbf{session expiration} settings, preserving the
    familiar user experience.
  \end{itemize}
\item
  \textbf{CSRF \& SameSite Integration}

  \begin{itemize}
  \tightlist
  \item
    Keycloak's OIDC flow uses \textbf{state parameters} and
    \textbf{PKCE} (Proof Key for Code Exchange) to mitigate CSRF.
    Django's built‑in CSRF middleware can be retained for POST requests,
    while the OIDC flow handles its own anti‑replay mechanisms,
    resulting in a layered defense.
  \end{itemize}
\item
  \textbf{SAML Compatibility}

  \begin{itemize}
  \tightlist
  \item
    For legacy enterprise portals that only support SAML, Keycloak can
    act as an IdP and issue \textbf{SAML assertions}. Django can consume
    these via a SAML SP library (e.g., \texttt{python3-saml}). The
    resulting user attributes are then mapped to Django's \texttt{User}
    model, preserving the same permission‑mapping pipeline described for
    OIDC.
  \end{itemize}
\end{enumerate}

These compatibility points mean that \textbf{no fundamental changes} are
required to Django's request‑handling pipeline; instead, the integration
is achieved by \textbf{plug‑in components} (middleware, authentication
backend, role mapper) that translate Keycloak's standards‑compliant
artifacts into Django's native objects. This design respects the
``complete, reproducible integration'' goals outlined in the
Introduction and sets the stage for the detailed implementation steps in
\textbf{Section 5 (Implementation Details)}.

\hypertarget{related-work}{%
\section{3. Related Work}\label{related-work}}

\hypertarget{existing-libraries-for-djangokeycloak-integration}{%
\subsection{3.1 Existing Libraries for Django‑Keycloak
Integration}\label{existing-libraries-for-djangokeycloak-integration}}

A number of open‑source projects already attempt to bridge Django with
Keycloak. The most frequently cited are:

\begin{longtable}[]{@{}llll@{}}
\toprule
\begin{minipage}[b]{0.14\columnwidth}\raggedright
Library\strut
\end{minipage} & \begin{minipage}[b]{0.22\columnwidth}\raggedright
Primary Goal\strut
\end{minipage} & \begin{minipage}[b]{0.25\columnwidth}\raggedright
Core Mechanism\strut
\end{minipage} & \begin{minipage}[b]{0.28\columnwidth}\raggedright
Typical Use‑Case\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.14\columnwidth}\raggedright
\textbf{python‑keycloak}\strut
\end{minipage} & \begin{minipage}[t]{0.22\columnwidth}\raggedright
General purpose Keycloak client\strut
\end{minipage} & \begin{minipage}[t]{0.25\columnwidth}\raggedright
Direct REST calls to the admin, token, and user‑management
endpoints\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
Scripts, CLI tools, and occasional server‑side token validation\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.14\columnwidth}\raggedright
\textbf{django‑oidc‑auth}\strut
\end{minipage} & \begin{minipage}[t]{0.22\columnwidth}\raggedright
OIDC‑based authentication for Django\strut
\end{minipage} & \begin{minipage}[t]{0.25\columnwidth}\raggedright
Implements an authentication backend that follows the OpenID Connect
Authorization Code flow, stores the ID token in the session, and creates
a Django \texttt{User} object on‑the‑fly\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
Quick SSO integration when the provider is any OIDC‑compatible IdP
(including Keycloak)\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.14\columnwidth}\raggedright
\textbf{Custom middleware examples} (e.g., community snippets on
GitHub)\strut
\end{minipage} & \begin{minipage}[t]{0.22\columnwidth}\raggedright
Plug‑in JWT validation into Django's request pipeline\strut
\end{minipage} & \begin{minipage}[t]{0.25\columnwidth}\raggedright
A thin WSGI/Django middleware that extracts the Bearer token, validates
it against the Keycloak introspection endpoint, and populates
\texttt{request.user}\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
Projects that already have a token‑centric architecture and need minimal
coupling\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

These projects share a common ambition: to let Django defer
authentication to an external IdP while preserving as much of Django's
native request lifecycle as possible.

\hypertarget{comparative-strengths-and-limitations}{%
\subsection{3.2 Comparative Strengths and
Limitations}\label{comparative-strengths-and-limitations}}

\begin{longtable}[]{@{}lll@{}}
\toprule
\begin{minipage}[b]{0.06\columnwidth}\raggedright
Library\strut
\end{minipage} & \begin{minipage}[b]{0.38\columnwidth}\raggedright
Strengths (as reported in documentation / community feedback)\strut
\end{minipage} & \begin{minipage}[b]{0.47\columnwidth}\raggedright
Limitations (relative to the goals outlined in \textbf{Section 1 -
Introduction})\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.06\columnwidth}\raggedright
\textbf{python‑keycloak}\strut
\end{minipage} & \begin{minipage}[t]{0.38\columnwidth}\raggedright
• Full coverage of Keycloak's admin API (realm, client, user
management). • Mature, well‑tested HTTP client with automatic token
refresh.\strut
\end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
• Not an authentication \emph{backend}; it leaves the integration of
tokens into Django's auth system to the developer. • Requires manual
handling of session cookies, CSRF, and role‑to‑permission mapping, which
contradicts the ``complete, reproducible integration'' goal.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.06\columnwidth}\raggedright
\textbf{django‑oidc‑auth}\strut
\end{minipage} & \begin{minipage}[t]{0.38\columnwidth}\raggedright
• Provides a ready‑made OIDC flow that works out‑of‑the‑box with any
compliant provider. • Handles token exchange, state management, and
basic user provisioning.\strut
\end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
• Assumes the IdP returns a \emph{single} ID token; it does not expose
the \texttt{realm\_access} or \texttt{resource\_access} claims needed
for fine‑grained role mapping described in \textbf{Section 2 -
Background}. • Lacks built‑in support for Keycloak‑specific features
such as client‑side logout, refresh‑token rotation, and dynamic role
synchronization.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.06\columnwidth}\raggedright
\textbf{Custom middleware}\strut
\end{minipage} & \begin{minipage}[t]{0.38\columnwidth}\raggedright
• Very lightweight; can be tailored to a project's exact token
validation strategy. • Easy to insert into Django's middleware stack
without altering settings dramatically.\strut
\end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
• Usually a \emph{proof‑of‑concept} rather than a production‑ready
solution: missing robust error handling, token revocation checks, and
comprehensive test coverage. • Role‑to‑permission translation is left to
ad‑hoc code, making it hard to maintain across multiple
applications.\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

Collectively, these tools address \textbf{some} of the authentication
challenges highlighted in the \textbf{Key Findings - Introduction}
(e.g., fragmented user stores, inconsistent session handling). However,
none simultaneously satisfies all of the following requirements that the
publication sets out:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  \textbf{Seamless token‑based authentication} that replaces Django's
  session cookie while still cooperating with Django's CSRF middleware
  (see \textbf{Key Findings - Background}).\\
\item
  \textbf{Automatic mapping of Keycloak roles} (\texttt{realm\_access},
  \texttt{resource\_access}) to Django
  \texttt{Group}/\texttt{Permission} objects, preserving the existing
  \texttt{@permission\_required} semantics.\\
\item
  \textbf{Full lifecycle management} (login, token refresh, logout,
  session synchronization) without requiring developers to stitch
  together disparate libraries.\\
\item
  \textbf{Security‑first defaults} (secure token storage, PKCE support,
  protection against token replay) that are explicitly called out in the
  publication's scope.
\end{enumerate}

\hypertarget{why-a-dedicated-implementation-is-warranted}{%
\subsection{3.3 Why a Dedicated Implementation Is
Warranted}\label{why-a-dedicated-implementation-is-warranted}}

Given the gaps identified above, the publication proposes a
\textbf{dedicated Django‑Keycloak integration} that builds on the
strengths of existing work while addressing their shortcomings:

\begin{itemize}
\item
  \textbf{Unified Middleware + Authentication Backend} - By combining a
  custom middleware that validates JWTs on every request with a Django
  authentication backend that creates/updates \texttt{User} objects, we
  achieve the ``complete, reproducible integration'' promised in
  \textbf{Section 1}. This eliminates the manual glue code required when
  using \texttt{python‑keycloak} alone.
\item
  \textbf{Explicit Role‑to‑Permission Mapper} - Leveraging the
  compatibility points enumerated in \textbf{Key Findings - Background}
  (e.g., mapping \texttt{realm\_access} claims to Django groups), the
  implementation provides a deterministic, configurable mapping layer.
  This resolves the ad‑hoc role handling seen in most community
  middleware snippets.
\item
  \textbf{Keycloak‑Specific Lifecycle Hooks} - The dedicated solution
  implements \textbf{front‑channel logout}, \textbf{refresh‑token
  rotation}, and \textbf{client‑side token revocation} using Keycloak's
  admin and token endpoints. These features are absent from
  \texttt{django‑oidc‑auth}, which treats the IdP as a black box.
\item
  \textbf{Security‑Hardening Out‑of‑the‑Box} - The implementation
  enforces PKCE for the authorization code flow, stores tokens in
  HttpOnly, SameSite‑strict cookies, and validates the \texttt{nonce}
  claim, directly addressing the security considerations discussed in
  \textbf{Section 6 - Security Analysis}.
\item
  \textbf{Reproducibility and Extensibility} - All configuration (realm,
  client, role mapping) is expressed declaratively in Django settings,
  enabling automated provisioning via infrastructure‑as‑code tools. This
  aligns with the publication's objective to provide a \textbf{baseline
  performance metric} and a \textbf{step‑by‑step guide} in
  \textbf{Section 5 - Implementation Details}.
\end{itemize}

In summary, while existing libraries provide valuable building blocks,
none delivers the \textbf{end‑to‑end, security‑focused, Django‑native
experience} required for production‑grade deployments. The dedicated
implementation presented in the subsequent sections therefore fills a
critical gap in the ecosystem, offering a reference architecture that
can be adopted, audited, and extended by practitioners.

\hypertarget{system-architecture}{%
\section{4. System Architecture}\label{system-architecture}}

\hypertarget{overview-of-the-deployment-landscape}{%
\subsection{4.1 Overview of the Deployment
Landscape}\label{overview-of-the-deployment-landscape}}

The authentication ecosystem for a Django application protected by
Keycloak consists of three logical layers (Figure 1):

\begin{longtable}[]{@{}lll@{}}
\toprule
\begin{minipage}[b]{0.11\columnwidth}\raggedright
Layer\strut
\end{minipage} & \begin{minipage}[b]{0.37\columnwidth}\raggedright
Primary Responsibility\strut
\end{minipage} & \begin{minipage}[b]{0.43\columnwidth}\raggedright
Typical Deployment Options\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.11\columnwidth}\raggedright
\textbf{Reverse‑proxy (optional)}\strut
\end{minipage} & \begin{minipage}[t]{0.37\columnwidth}\raggedright
TLS termination, HTTP‑header sanitisation, load‑balancing, and optional
caching of static assets.\strut
\end{minipage} & \begin{minipage}[t]{0.43\columnwidth}\raggedright
Nginx, Traefik, Envoy, or a cloud‑managed ingress controller.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.11\columnwidth}\raggedright
\textbf{Django Web Server}\strut
\end{minipage} & \begin{minipage}[t]{0.37\columnwidth}\raggedright
Handles business‑logic requests, renders HTML/JSON responses, and
delegates authentication/authorization to the Keycloak‑issued
tokens.\strut
\end{minipage} & \begin{minipage}[t]{0.43\columnwidth}\raggedright
\texttt{gunicorn}/\texttt{uvicorn} workers behind the proxy; can be
containerised (Docker) or run on a VM.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.11\columnwidth}\raggedright
\textbf{Keycloak Server}\strut
\end{minipage} & \begin{minipage}[t]{0.37\columnwidth}\raggedright
Centralised identity provider (IdP) that issues OpenID Connect (OIDC)
ID‑ and access‑tokens, manages user federation, client configuration,
and role‑based access control.\strut
\end{minipage} & \begin{minipage}[t]{0.43\columnwidth}\raggedright
Stand‑alone Keycloak instance, clustered deployment (HA) on Kubernetes,
or a managed Keycloak service.\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

\begin{quote}
\textbf{Why this layout?}\\
Section 3 (Related Work) highlighted that many community projects assume
a direct Django‑Keycloak coupling without a front‑end proxy. Introducing
an optional reverse‑proxy isolates TLS concerns, enables HTTP‑only
cookie handling, and aligns with the security best‑practices discussed
in Section 6 (Security Analysis).
\end{quote}

\hypertarget{component-interaction-diagram}{%
\subsection{4.2 Component Interaction
Diagram}\label{component-interaction-diagram}}

The following \textbf{Mermaid} flowchart visualises a typical
authentication round‑trip, token exchange, and user provisioning path.
It is deliberately abstracted to avoid implementation‑level details that
are covered in Section 5 (Implementation Details).

\begin{verbatim}
flowchart TD
    %% Actors
    A[Client Browser] -->|HTTPS Request| B[Reverse‑Proxy (optional)]
    B -->|Forward request| C[Django Application]
    
    %% Unauthenticated request path
    C -->|No valid session| D[Redirect to Keycloak Authorization Endpoint]
    D -->|User authenticates (login, MFA)| E[Keycloak Server]
    E -->|Redirect back with auth code| D
    
    %% Token exchange
    D -->|POST auth code| F[Keycloak Token Endpoint]
    F -->|Access + ID token (JWT)| G[Token Validation Middleware]
    
    %% Middleware actions
    G -->|Validate signature, expiry, nonce| H[User Provisioning Service]
    H -->|Create / update Django User, map roles| I[Database (auth_user, groups, permissions)]
    
    %% Final response
    I -->|Authenticated session cookie| C
    C -->|200 OK (protected resource)| A
    
    %% Logout flow (optional)
    A -->|GET /logout| C -->|Redirect to Keycloak logout| E
    E -->|Clear SSO session| A
\end{verbatim}

\textbf{Key points illustrated in the diagram}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  \textbf{Redirect‑based OIDC flow} - The Django app (via middleware)
  initiates an authorization request to Keycloak when no valid session
  exists.\\
\item
  \textbf{Token exchange} - The server‑side back‑channel POST to the
  token endpoint returns a signed JWT access token and an ID token.\\
\item
  \textbf{Middleware validation} - The custom middleware (see Section 5)
  validates the JWT signature against the Keycloak realm's public JWKS,
  checks \texttt{exp}, \texttt{nbf}, \texttt{nonce}, and optionally
  \texttt{azp}.\\
\item
  \textbf{User provisioning} - A lightweight service extracts the
  \texttt{sub} claim, looks up (or creates) a corresponding Django
  \texttt{User} record, and maps Keycloak roles
  (\texttt{realm\_access.roles},
  \texttt{resource\_access.\textless{}client\textgreater{}.roles}) to
  Django \texttt{Group}/\texttt{Permission} objects, preserving the
  native \texttt{@permission\_required} semantics described in Section 2
  (Background).\\
\item
  \textbf{Session cookie} - After successful provisioning, Django issues
  its own session cookie (HttpOnly, SameSite=Strict) that references the
  authenticated user; the original JWT is kept in a secure server‑side
  cache for token‑refresh operations.\\
\item
  \textbf{Logout propagation} - A logout request triggers a
  front‑channel redirect to Keycloak, ensuring the SSO session is
  terminated across all clients.
\end{enumerate}

\hypertarget{network-topology-and-deployment-variants}{%
\subsection{4.3 Network Topology and Deployment
Variants}\label{network-topology-and-deployment-variants}}

\hypertarget{singlenode-development-setup}{%
\subsubsection{4.3.1 Single‑Node Development
Setup}\label{singlenode-development-setup}}

\begin{verbatim}
localhost:8080  →  Nginx (TLS termination)  →  gunicorn → Django
localhost:8081  →  Keycloak (embedded H2 DB)
\end{verbatim}

\emph{All components run on a developer's workstation. The reverse‑proxy
is optional but useful for reproducing production TLS settings.}

\hypertarget{productiongrade-kubernetes-cluster}{%
\subsubsection{4.3.2 Production‑grade Kubernetes
Cluster}\label{productiongrade-kubernetes-cluster}}

\begin{verbatim}
[Ingress Controller] ──► [Service: django‑app] ──► [Pod: django] 
                         │
                         └─► [Service: keycloak] ──► [StatefulSet: keycloak] 
\end{verbatim}

\emph{The ingress controller (e.g., Traefik) terminates TLS, injects
\texttt{X-Forwarded-Proto} headers, and forwards traffic to the Django
service. Keycloak runs as a StatefulSet with an external PostgreSQL
backing store, enabling HA and rolling upgrades.}

\hypertarget{hybrid-cloudonprem-scenario}{%
\subsubsection{4.3.3 Hybrid Cloud‑On‑Prem
Scenario}\label{hybrid-cloudonprem-scenario}}

\begin{itemize}
\tightlist
\item
  \textbf{On‑prem Keycloak} (managed by the enterprise IdP team)\\
\item
  \textbf{Django app} hosted in a public cloud behind a cloud‑native API
  gateway
\end{itemize}

In this arrangement the reverse‑proxy is the cloud gateway, which also
enforces IP‑allow‑lists and rate‑limits before the request reaches the
Django service.

\begin{quote}
\textbf{Design rationale} - Section 3 emphasized that many community
solutions assume a monolithic deployment, which limits scalability and
operational flexibility. The architecture presented here decouples
identity management from the application tier, allowing independent
scaling, independent security hardening, and straightforward migration
to a multi‑realm or multi‑client topology (see Section 10 Future Work).
\end{quote}

\hypertarget{data-flow-summary}{%
\subsection{4.4 Data Flow Summary}\label{data-flow-summary}}

\begin{longtable}[]{@{}llll@{}}
\toprule
\begin{minipage}[b]{0.13\columnwidth}\raggedright
Phase\strut
\end{minipage} & \begin{minipage}[b]{0.13\columnwidth}\raggedright
Actor\strut
\end{minipage} & \begin{minipage}[b]{0.29\columnwidth}\raggedright
Data Exchanged\strut
\end{minipage} & \begin{minipage}[b]{0.34\columnwidth}\raggedright
Security Controls\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.13\columnwidth}\raggedright
\textbf{1. Authorization Request}\strut
\end{minipage} & \begin{minipage}[t]{0.13\columnwidth}\raggedright
Browser → Django → Keycloak\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
\texttt{client\_id}, \texttt{redirect\_uri},
\texttt{scope=openid\ profile\ email}, \texttt{state},
\texttt{nonce}\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
PKCE (code verifier/challenge) generated by Django middleware;
\texttt{state} stored in a short‑lived cookie.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.13\columnwidth}\raggedright
\textbf{2. Authentication}\strut
\end{minipage} & \begin{minipage}[t]{0.13\columnwidth}\raggedright
Browser ↔ Keycloak\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
Username/password, MFA factors\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
TLS 1.3, password hashing (bcrypt/argon2) managed by Keycloak, optional
LDAP/AD federation.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.13\columnwidth}\raggedright
\textbf{3. Token Issuance}\strut
\end{minipage} & \begin{minipage}[t]{0.13\columnwidth}\raggedright
Keycloak → Django (via browser redirect)\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
Authorization \texttt{code}\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
\texttt{code} is one‑time use; exchanged over server‑to‑server
TLS.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.13\columnwidth}\raggedright
\textbf{4. Token Exchange}\strut
\end{minipage} & \begin{minipage}[t]{0.13\columnwidth}\raggedright
Django → Keycloak\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
\texttt{code}, \texttt{code\_verifier}\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
Client authentication (\texttt{client\_secret\_basic} or
\texttt{private\_key\_jwt}).\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.13\columnwidth}\raggedright
\textbf{5. Token Validation}\strut
\end{minipage} & \begin{minipage}[t]{0.13\columnwidth}\raggedright
Django middleware\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
JWT access token, ID token\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
Signature verification against JWKS, \texttt{exp}/\texttt{nbf} checks,
audience (\texttt{aud}) validation, nonce verification.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.13\columnwidth}\raggedright
\textbf{6. User Provisioning}\strut
\end{minipage} & \begin{minipage}[t]{0.13\columnwidth}\raggedright
Middleware → Django DB\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
\texttt{sub}, \texttt{email}, \texttt{preferred\_username}, role
claims\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
Idempotent upsert; role‑to‑permission mapping performed
atomically.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.13\columnwidth}\raggedright
\textbf{7. Session Establishment}\strut
\end{minipage} & \begin{minipage}[t]{0.13\columnwidth}\raggedright
Django → Browser\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
Session cookie (\texttt{sessionid})\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
HttpOnly, Secure, SameSite=Strict, optional
\texttt{SESSION\_COOKIE\_AGE} aligned with token \texttt{exp}.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.13\columnwidth}\raggedright
\textbf{8. Logout}\strut
\end{minipage} & \begin{minipage}[t]{0.13\columnwidth}\raggedright
Browser → Django → Keycloak\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
Logout request, refresh token revocation\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
Front‑channel redirect to Keycloak
\texttt{/protocol/openid-connect/logout}; server‑side revocation of
refresh token in cache.\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

\hypertarget{alignment-with-publication-goals}{%
\subsection{4.5 Alignment with Publication
Goals}\label{alignment-with-publication-goals}}

\begin{itemize}
\tightlist
\item
  \textbf{Completeness} - The architecture satisfies the integration
  scope listed in Section 1 (Introduction) by covering \emph{realm \&
  client configuration}, \emph{middleware‑driven token handling},
  \emph{role‑to‑permission mapping}, and \emph{security best
  practices}.\\
\item
  \textbf{Extensibility} - Because the reverse‑proxy is optional, the
  same diagram applies to container‑native deployments (Docker/K8s) as
  well as to traditional VM‑based stacks, supporting the operational
  considerations discussed later in Section 8 (Discussion).\\
\item
  \textbf{Reproducibility} - All network endpoints, token flows, and
  provisioning steps are explicitly modelled, enabling readers to
  recreate the environment step‑by‑step in Section 5 (Implementation
  Details).
\end{itemize}

\hypertarget{takeaway-diagram-simplified}{%
\subsection{4.6 Take‑away Diagram
(Simplified)}\label{takeaway-diagram-simplified}}

For quick reference in later chapters, the following condensed diagram
captures the essential control flow:

\begin{verbatim}
sequenceDiagram
    participant B as Browser
    participant P as Reverse‑Proxy
    participant D as Django
    participant K as Keycloak

    B->>P: HTTPS request (protected URL)
    P->>D: Forward request
    D->>B: 302 → /auth/oidc/login
    B->>K: Authorization request (login)
    K->>B: Login UI / MFA
    B->>K: Credentials
    K->>B: 302 → redirect_uri?code=xyz&state=abc
    B->>D: GET redirect_uri (code)
    D->>K: POST /token (code, verifier)
    K->>D: access_token + id_token
    D->>D: Validate JWT, provision user
    D->>B: Set session cookie, 200 OK
\end{verbatim}

This sequence will be revisited in Section 5 when the concrete
middleware and view‑level code are introduced.

\emph{End of Section 4 - System Architecture.}

\hypertarget{implementation-details}{%
\section{5. Implementation Details}\label{implementation-details}}

\hypertarget{configure-a-keycloak-client-for-django}{%
\subsection{5.1 Configure a Keycloak client for
Django}\label{configure-a-keycloak-client-for-django}}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  \textbf{Create a new realm (or reuse an existing one).}

  \begin{itemize}
  \tightlist
  \item
    Follow the realm‑creation steps described in \textbf{Section 2
    (Background)} - a realm isolates the Django application from other
    services.
  \end{itemize}
\item
  \textbf{Add a confidential client} named \texttt{django-app}.

  \begin{itemize}
  \item
    \textbf{Client ID:} \texttt{django-app}\\
  \item
    \textbf{Client Protocol:} \texttt{openid-connect}\\
  \item
    \textbf{Access Type:} \texttt{confidential} (enables client‑secret
    authentication).\\
  \item
    \textbf{Valid Redirect URIs:}

\begin{verbatim}
https://<django-host>/oidc/callback/
http://localhost:8000/oidc/callback/
\end{verbatim}
  \item
    \textbf{Web Origins:} \texttt{+} (or explicitly list the Django
    host).
  \end{itemize}
\item
  \textbf{Enable PKCE and Standard Flow.}

  \begin{itemize}
  \tightlist
  \item
    In the client settings, toggle \textbf{Standard Flow Enabled} and
    \textbf{Proof Key for Code Exchange (PKCE) Code Challenge Method} to
    \texttt{S256}. This satisfies the security‑first defaults
    highlighted in \textbf{Section 6 (Security Analysis)}.
  \end{itemize}
\item
  \textbf{Define client scopes} (optional but recommended).

  \begin{itemize}
  \tightlist
  \item
    Create a scope \texttt{django-permissions} that includes the
    \texttt{roles} claim (\texttt{realm\_access},
    \texttt{resource\_access}).\\
  \item
    Assign the scope to the client so that the JWT contains the role
    information needed for mapping in \textbf{5.5}.
  \end{itemize}
\item
  \textbf{Generate client secret} and copy it; it will be stored in
  Django's settings (see \textbf{5.2}).
\item
  \textbf{Configure logout URL} so that a logout in Keycloak propagates
  to Django:

\begin{verbatim}
https://<django-host>/oidc/logout/
\end{verbatim}
\end{enumerate}

\begin{quote}
\textbf{Tip:} Export the client configuration (JSON) from the Keycloak
admin console and keep it under version control. This makes the setup
reproducible, as advocated in the Introduction.
\end{quote}

\hypertarget{install-required-python-packages}{%
\subsection{5.2 Install required Python
packages}\label{install-required-python-packages}}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# Core OIDC handling}
\ExtensionTok{pip}\NormalTok{ install django==4.2  \# or the version used in the project}
\ExtensionTok{pip}\NormalTok{ install python{-}keycloak==3.2.0}
\ExtensionTok{pip}\NormalTok{ install django{-}oidc{-}auth==0.5.0}

\CommentTok{\# Optional utilities}
\ExtensionTok{pip}\NormalTok{ install cryptography   \# for JWT signature verification}
\ExtensionTok{pip}\NormalTok{ install requests{-}oauthlib}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\tightlist
\item
  \texttt{python-keycloak} provides a thin wrapper around Keycloak's
  admin and token endpoints.\\
\item
  \texttt{django-oidc-auth} supplies the OIDC flow (authorization code,
  PKCE) and a ready‑made authentication backend that we will extend in
  \textbf{5.3}.
\end{itemize}

Add the packages to \texttt{requirements.txt} and lock them with
\texttt{pip\ freeze\ \textgreater{}\ requirements.txt} to guarantee
reproducibility across environments.

\hypertarget{add-authentication-backend-and-middleware}{%
\subsection{5.3 Add authentication backend and
middleware}\label{add-authentication-backend-and-middleware}}

\hypertarget{settings-settings.py}{%
\subsubsection{\texorpdfstring{5.3.1 Settings
(\texttt{settings.py})}{5.3.1 Settings (settings.py)}}\label{settings-settings.py}}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# OIDC configuration}
\NormalTok{OIDC\_RP\_CLIENT\_ID }\OperatorTok{=} \StringTok{"django{-}app"}
\NormalTok{OIDC\_RP\_CLIENT\_SECRET }\OperatorTok{=}\NormalTok{ os.getenv(}\StringTok{"KEYCLOAK\_CLIENT\_SECRET"}\NormalTok{)}
\NormalTok{OIDC\_OP\_AUTHORIZATION\_ENDPOINT }\OperatorTok{=} \StringTok{"https://keycloak.example.com/auth/realms/myrealm/protocol/openid{-}connect/auth"}
\NormalTok{OIDC\_OP\_TOKEN\_ENDPOINT }\OperatorTok{=} \StringTok{"https://keycloak.example.com/auth/realms/myrealm/protocol/openid{-}connect/token"}
\NormalTok{OIDC\_OP\_USER\_ENDPOINT }\OperatorTok{=} \StringTok{"https://keycloak.example.com/auth/realms/myrealm/protocol/openid{-}connect/userinfo"}
\NormalTok{OIDC\_OP\_JWKS\_ENDPOINT }\OperatorTok{=} \StringTok{"https://keycloak.example.com/auth/realms/myrealm/protocol/openid{-}connect/certs"}

\CommentTok{\# Enable the OIDC backend}
\NormalTok{AUTHENTICATION\_BACKENDS }\OperatorTok{=}\NormalTok{ [}
    \StringTok{"django\_oidc\_auth.auth.OIDCAuthenticationBackend"}\NormalTok{,}
    \StringTok{"django.contrib.auth.backends.ModelBackend"}\NormalTok{,  }\CommentTok{\# fallback for superusers}
\NormalTok{]}

\CommentTok{\# Middleware stack {-} insert our custom token validator after SessionMiddleware}
\NormalTok{MIDDLEWARE }\OperatorTok{=}\NormalTok{ [}
    \StringTok{"django.middleware.security.SecurityMiddleware"}\NormalTok{,}
    \StringTok{"django.contrib.sessions.middleware.SessionMiddleware"}\NormalTok{,}
    \StringTok{"django\_oidc\_auth.middleware.OIDCAuthenticationMiddleware"}\NormalTok{,}
    \StringTok{"myproject.middleware.KeycloakTokenValidatorMiddleware"}\NormalTok{,  }\CommentTok{\# \textless{}‑‑ defined in 5.4}
    \StringTok{"django.middleware.common.CommonMiddleware"}\NormalTok{,}
    \StringTok{"django.middleware.csrf.CsrfViewMiddleware"}\NormalTok{,}
    \StringTok{"django.contrib.auth.middleware.AuthenticationMiddleware"}\NormalTok{,}
    \StringTok{"django.contrib.messages.middleware.MessageMiddleware"}\NormalTok{,}
    \StringTok{"django.middleware.clickjacking.XFrameOptionsMiddleware"}\NormalTok{,}
\NormalTok{]}
\end{Highlighting}
\end{Shaded}

\hypertarget{custom-authentication-backend-optional}{%
\subsubsection{5.3.2 Custom authentication backend
(optional)}\label{custom-authentication-backend-optional}}

If you need to enrich the Django \texttt{User} model with additional
Keycloak attributes (e.g., \texttt{preferred\_username},
\texttt{email\_verified}), subclass the provided backend:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# myproject/auth\_backends.py}
\ImportTok{from}\NormalTok{ django\_oidc\_auth.auth }\ImportTok{import}\NormalTok{ OIDCAuthenticationBackend}

\KeywordTok{class}\NormalTok{ KeycloakExtendedBackend(OIDCAuthenticationBackend):}
    \KeywordTok{def}\NormalTok{ update\_user(}\VariableTok{self}\NormalTok{, user, claims):}
\NormalTok{        user }\OperatorTok{=} \BuiltInTok{super}\NormalTok{().update\_user(user, claims)}
\NormalTok{        user.full\_name }\OperatorTok{=}\NormalTok{ claims.get(}\StringTok{"name"}\NormalTok{, }\StringTok{""}\NormalTok{)}
\NormalTok{        user.save()}
        \ControlFlowTok{return}\NormalTok{ user}
\end{Highlighting}
\end{Shaded}

Add the new backend to \texttt{AUTHENTICATION\_BACKENDS} and reference
it in the OIDC settings
(\texttt{OIDC\_AUTHENTICATION\_BACKEND\ =\ "myproject.auth\_backends.KeycloakExtendedBackend"}).

\hypertarget{token-validation-middleware}{%
\subsection{5.4 Token validation
middleware}\label{token-validation-middleware}}

Create a middleware that runs on every request to verify the JWT stored
in the session (or in an HttpOnly cookie). This mirrors the
\textbf{middleware‑centric token handling} described in \textbf{Section
4 (System Architecture)}.

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# myproject/middleware.py}
\ImportTok{import}\NormalTok{ json}
\ImportTok{import}\NormalTok{ time}
\ImportTok{from}\NormalTok{ django.conf }\ImportTok{import}\NormalTok{ settings}
\ImportTok{from}\NormalTok{ django.http }\ImportTok{import}\NormalTok{ HttpResponseRedirect}
\ImportTok{from}\NormalTok{ django.urls }\ImportTok{import}\NormalTok{ reverse}
\ImportTok{from}\NormalTok{ jose }\ImportTok{import}\NormalTok{ jwt, JWTError}
\ImportTok{import}\NormalTok{ requests}

\KeywordTok{class}\NormalTok{ KeycloakTokenValidatorMiddleware:}
    \CommentTok{"""}
\CommentTok{    Validates the access token on each request.}
\CommentTok{    {-} Checks signature, expiration, audience, and nonce.}
\CommentTok{    {-} Refreshes the token silently if a refresh token is present.}
\CommentTok{    {-} Forces re‑authentication on failure.}
\CommentTok{    """}
    \KeywordTok{def} \FunctionTok{\_\_init\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{, get\_response):}
        \VariableTok{self}\NormalTok{.get\_response }\OperatorTok{=}\NormalTok{ get\_response}
        \VariableTok{self}\NormalTok{.jwks }\OperatorTok{=} \VariableTok{self}\NormalTok{.\_fetch\_jwks()}

    \KeywordTok{def} \FunctionTok{\_\_call\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{, request):}
\NormalTok{        token }\OperatorTok{=}\NormalTok{ request.session.get(}\StringTok{"oidc\_access\_token"}\NormalTok{)}
        \ControlFlowTok{if} \KeywordTok{not}\NormalTok{ token:}
            \ControlFlowTok{return} \VariableTok{self}\NormalTok{.\_redirect\_to\_login(request)}

        \ControlFlowTok{try}\NormalTok{:}
\NormalTok{            claims }\OperatorTok{=}\NormalTok{ jwt.decode(}
\NormalTok{                token,}
                \VariableTok{self}\NormalTok{.jwks,}
\NormalTok{                algorithms}\OperatorTok{=}\NormalTok{[}\StringTok{"RS256"}\NormalTok{],}
\NormalTok{                audience}\OperatorTok{=}\NormalTok{settings.OIDC\_RP\_CLIENT\_ID,}
\NormalTok{                issuer}\OperatorTok{=}\SpecialStringTok{f"https://}\SpecialCharTok{\{}\NormalTok{settings}\SpecialCharTok{.}\NormalTok{KEYCLOAK\_HOST}\SpecialCharTok{\}}\SpecialStringTok{/auth/realms/}\SpecialCharTok{\{}\NormalTok{settings}\SpecialCharTok{.}\NormalTok{KEYCLOAK\_REALM}\SpecialCharTok{\}}\SpecialStringTok{"}\NormalTok{,}
\NormalTok{            )}
            \CommentTok{\# Token is valid {-} attach claims to request for downstream use}
\NormalTok{            request.keycloak\_claims }\OperatorTok{=}\NormalTok{ claims}
        \ControlFlowTok{except}\NormalTok{ JWTError:}
            \CommentTok{\# Attempt silent refresh}
            \ControlFlowTok{if} \VariableTok{self}\NormalTok{.\_refresh\_token(request):}
                \ControlFlowTok{return} \VariableTok{self}\NormalTok{.}\FunctionTok{\_\_call\_\_}\NormalTok{(request)  }\CommentTok{\# retry with new token}
            \ControlFlowTok{return} \VariableTok{self}\NormalTok{.\_redirect\_to\_login(request)}

\NormalTok{        response }\OperatorTok{=} \VariableTok{self}\NormalTok{.get\_response(request)}
        \ControlFlowTok{return}\NormalTok{ response}

    \CommentTok{\# {-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}}
    \KeywordTok{def}\NormalTok{ \_fetch\_jwks(}\VariableTok{self}\NormalTok{):}
\NormalTok{        resp }\OperatorTok{=}\NormalTok{ requests.get(settings.OIDC\_OP\_JWKS\_ENDPOINT)}
\NormalTok{        resp.raise\_for\_status()}
        \ControlFlowTok{return}\NormalTok{ resp.json()}

    \KeywordTok{def}\NormalTok{ \_refresh\_token(}\VariableTok{self}\NormalTok{, request):}
\NormalTok{        refresh }\OperatorTok{=}\NormalTok{ request.session.get(}\StringTok{"oidc\_refresh\_token"}\NormalTok{)}
        \ControlFlowTok{if} \KeywordTok{not}\NormalTok{ refresh:}
            \ControlFlowTok{return} \VariableTok{False}
\NormalTok{        data }\OperatorTok{=}\NormalTok{ \{}
            \StringTok{"grant\_type"}\NormalTok{: }\StringTok{"refresh\_token"}\NormalTok{,}
            \StringTok{"client\_id"}\NormalTok{: settings.OIDC\_RP\_CLIENT\_ID,}
            \StringTok{"client\_secret"}\NormalTok{: settings.OIDC\_RP\_CLIENT\_SECRET,}
            \StringTok{"refresh\_token"}\NormalTok{: refresh,}
\NormalTok{        \}}
\NormalTok{        resp }\OperatorTok{=}\NormalTok{ requests.post(settings.OIDC\_OP\_TOKEN\_ENDPOINT, data}\OperatorTok{=}\NormalTok{data)}
        \ControlFlowTok{if}\NormalTok{ resp.status\_code }\OperatorTok{!=} \DecValTok{200}\NormalTok{:}
            \ControlFlowTok{return} \VariableTok{False}
\NormalTok{        tokens }\OperatorTok{=}\NormalTok{ resp.json()}
\NormalTok{        request.session[}\StringTok{"oidc\_access\_token"}\NormalTok{] }\OperatorTok{=}\NormalTok{ tokens[}\StringTok{"access\_token"}\NormalTok{]}
\NormalTok{        request.session[}\StringTok{"oidc\_refresh\_token"}\NormalTok{] }\OperatorTok{=}\NormalTok{ tokens.get(}\StringTok{"refresh\_token"}\NormalTok{, refresh)}
        \ControlFlowTok{return} \VariableTok{True}

    \KeywordTok{def}\NormalTok{ \_redirect\_to\_login(}\VariableTok{self}\NormalTok{, request):}
\NormalTok{        login\_url }\OperatorTok{=}\NormalTok{ reverse(}\StringTok{"oidc\_authentication\_init"}\NormalTok{)}
        \ControlFlowTok{return}\NormalTok{ HttpResponseRedirect(login\_url)}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\tightlist
\item
  The middleware respects \textbf{PKCE} and \textbf{nonce} validation
  because \texttt{django-oidc-auth} stores those values in the session
  during the initial authorization request.\\
\item
  By placing the validator after \texttt{SessionMiddleware}, we
  guarantee that the session is available but before
  \texttt{AuthenticationMiddleware}, ensuring that \texttt{request.user}
  is populated only after a successful token check.
\end{itemize}

\hypertarget{mapping-keycloak-roles-to-django-permissions}{%
\subsection{5.5 Mapping Keycloak roles to Django
permissions}\label{mapping-keycloak-roles-to-django-permissions}}

Keycloak roles are delivered in the JWT under
\texttt{realm\_access.roles} and
\texttt{resource\_access.\textless{}client\textgreater{}.roles}. The
mapping pipeline proceeds as follows:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  \textbf{Extract role claims} in the middleware (or in the backend's
  \texttt{update\_user}).\\
\item
  \textbf{Create or fetch Django \texttt{Group} objects} that mirror the
  Keycloak role names.\\
\item
  \textbf{Assign Django \texttt{Permission} objects} to those groups
  based on a static mapping defined in \texttt{settings.py}.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# myproject/role\_mapper.py}
\ImportTok{from}\NormalTok{ django.contrib.auth.models }\ImportTok{import}\NormalTok{ Group, Permission}
\ImportTok{from}\NormalTok{ django.conf }\ImportTok{import}\NormalTok{ settings}

\CommentTok{\# Example static mapping {-} can be externalised to a JSON/YAML file}
\NormalTok{ROLE\_PERMISSION\_MAP }\OperatorTok{=}\NormalTok{ \{}
    \StringTok{"admin"}\NormalTok{: [}\StringTok{"add\_user"}\NormalTok{, }\StringTok{"change\_user"}\NormalTok{, }\StringTok{"delete\_user"}\NormalTok{, }\StringTok{"view\_user"}\NormalTok{],}
    \StringTok{"editor"}\NormalTok{: [}\StringTok{"change\_article"}\NormalTok{, }\StringTok{"add\_article"}\NormalTok{, }\StringTok{"view\_article"}\NormalTok{],}
    \StringTok{"viewer"}\NormalTok{: [}\StringTok{"view\_article"}\NormalTok{],}
\NormalTok{\}}

\KeywordTok{def}\NormalTok{ sync\_keycloak\_roles(user, claims):}
    \CommentTok{"""}
\CommentTok{    Synchronises Keycloak roles with Django groups/permissions.}
\CommentTok{    """}
    \CommentTok{\# 1. Gather all role names from the token}
\NormalTok{    realm\_roles }\OperatorTok{=}\NormalTok{ claims.get(}\StringTok{"realm\_access"}\NormalTok{, \{\}).get(}\StringTok{"roles"}\NormalTok{, [])}
\NormalTok{    client\_roles }\OperatorTok{=}\NormalTok{ claims.get(}\StringTok{"resource\_access"}\NormalTok{, \{\}).get(settings.OIDC\_RP\_CLIENT\_ID, \{\}).get(}\StringTok{"roles"}\NormalTok{, [])}
\NormalTok{    all\_roles }\OperatorTok{=} \BuiltInTok{set}\NormalTok{(realm\_roles }\OperatorTok{+}\NormalTok{ client\_roles)}

    \CommentTok{\# 2. Ensure groups exist}
\NormalTok{    groups }\OperatorTok{=}\NormalTok{ []}
    \ControlFlowTok{for}\NormalTok{ role }\KeywordTok{in}\NormalTok{ all\_roles:}
\NormalTok{        group, \_ }\OperatorTok{=}\NormalTok{ Group.objects.get\_or\_create(name}\OperatorTok{=}\NormalTok{role)}
\NormalTok{        groups.append(group)}

        \CommentTok{\# 3. Attach permissions according to the static map}
\NormalTok{        perms }\OperatorTok{=}\NormalTok{ ROLE\_PERMISSION\_MAP.get(role, [])}
        \ControlFlowTok{for}\NormalTok{ codename }\KeywordTok{in}\NormalTok{ perms:}
            \ControlFlowTok{try}\NormalTok{:}
\NormalTok{                perm }\OperatorTok{=}\NormalTok{ Permission.objects.get(codename}\OperatorTok{=}\NormalTok{codename)}
\NormalTok{                group.permissions.add(perm)}
            \ControlFlowTok{except}\NormalTok{ Permission.DoesNotExist:}
                \CommentTok{\# Silently ignore missing permissions; log for devs}
                \ControlFlowTok{pass}

    \CommentTok{\# 4. Replace user\textquotesingle{}s groups with the freshly resolved set}
\NormalTok{    user.groups.}\BuiltInTok{set}\NormalTok{(groups)}
\NormalTok{    user.save()}
\end{Highlighting}
\end{Shaded}

Call
\texttt{sync\_keycloak\_roles(request.user,\ request.keycloak\_claims)}
from a post‑login signal
(\texttt{django\_oidc\_auth.signals.oidc\_user\_created}) or directly in
the custom backend's \texttt{update\_user} method.

\begin{quote}
\textbf{Why this matters:} The deterministic role‑to‑permission mapping
fulfills the requirement from \textbf{Section 4} and enables native
Django decorators such as \texttt{@permission\_required} to work without
modification.
\end{quote}

\hypertarget{persisting-and-synchronising-user-data}{%
\subsection{5.6 Persisting and synchronising user
data}\label{persisting-and-synchronising-user-data}}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  \textbf{User provisioning} - When a token is first seen, create a
  Django \texttt{User} object (or retrieve an existing one) using the
  \texttt{sub} claim as the unique identifier.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# myproject/auth\_backends.py (excerpt)}
\KeywordTok{def}\NormalTok{ create\_user(}\VariableTok{self}\NormalTok{, claims):}
\NormalTok{    username }\OperatorTok{=}\NormalTok{ claims[}\StringTok{"preferred\_username"}\NormalTok{]}
\NormalTok{    email }\OperatorTok{=}\NormalTok{ claims.get(}\StringTok{"email"}\NormalTok{, }\StringTok{""}\NormalTok{)}
\NormalTok{    user, created }\OperatorTok{=}\NormalTok{ User.objects.get\_or\_create(}
\NormalTok{        username}\OperatorTok{=}\NormalTok{username,}
\NormalTok{        defaults}\OperatorTok{=}\NormalTok{\{}\StringTok{"email"}\NormalTok{: email, }\StringTok{"is\_active"}\NormalTok{: }\VariableTok{True}\NormalTok{\},}
\NormalTok{    )}
    \CommentTok{\# Keep the Keycloak user ID for future reference}
\NormalTok{    user.profile.keycloak\_id }\OperatorTok{=}\NormalTok{ claims[}\StringTok{"sub"}\NormalTok{]}
\NormalTok{    user.profile.save()}
    \ControlFlowTok{return}\NormalTok{ user}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
  \textbf{Profile model} - Extend the default \texttt{User} with a
  one‑to‑one \texttt{Profile} that stores Keycloak‑specific attributes
  (e.g., \texttt{keycloak\_id}, \texttt{last\_login\_at\_keycloak}).
\item
  \textbf{Periodic sync} - Optionally run a management command
  (\texttt{python\ manage.py\ sync\_keycloak\_users}) that iterates over
  all active users, fetches their latest claims via the UserInfo
  endpoint, and updates groups/permissions. This addresses the
  \textbf{session synchronization} point from \textbf{Section 2
  (Background)}.
\item
  \textbf{Logout propagation} - Implement a view that forwards a logout
  request to Keycloak's end‑session endpoint and clears the Django
  session:
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# myproject/views.py}
\ImportTok{from}\NormalTok{ django.shortcuts }\ImportTok{import}\NormalTok{ redirect}
\ImportTok{from}\NormalTok{ django.conf }\ImportTok{import}\NormalTok{ settings}

\KeywordTok{def}\NormalTok{ oidc\_logout(request):}
\NormalTok{    request.session.flush()}
\NormalTok{    logout\_url }\OperatorTok{=}\NormalTok{ (}
        \SpecialStringTok{f"https://}\SpecialCharTok{\{}\NormalTok{settings}\SpecialCharTok{.}\NormalTok{KEYCLOAK\_HOST}\SpecialCharTok{\}}\SpecialStringTok{/auth/realms/}\SpecialCharTok{\{}\NormalTok{settings}\SpecialCharTok{.}\NormalTok{KEYCLOAK\_REALM}\SpecialCharTok{\}}\SpecialStringTok{"}
        \SpecialStringTok{f"/protocol/openid{-}connect/logout?redirect\_uri=}\SpecialCharTok{\{}\NormalTok{settings}\SpecialCharTok{.}\NormalTok{LOGOUT\_REDIRECT\_URI}\SpecialCharTok{\}}\SpecialStringTok{"}
\NormalTok{    )}
    \ControlFlowTok{return}\NormalTok{ redirect(logout\_url)}
\end{Highlighting}
\end{Shaded}

Add the URL pattern
\texttt{path(\textquotesingle{}oidc/logout/\textquotesingle{},\ oidc\_logout,\ name=\textquotesingle{}oidc\_logout\textquotesingle{})}.

\hypertarget{development-vs.-production-considerations}{%
\subsection{5.7 Development vs.~Production
considerations}\label{development-vs.-production-considerations}}

\begin{longtable}[]{@{}lll@{}}
\toprule
\begin{minipage}[b]{0.19\columnwidth}\raggedright
Aspect\strut
\end{minipage} & \begin{minipage}[b]{0.34\columnwidth}\raggedright
Development (local)\strut
\end{minipage} & \begin{minipage}[b]{0.39\columnwidth}\raggedright
Production (containerised / K8s)\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.19\columnwidth}\raggedright
\textbf{Keycloak URL}\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
\texttt{http://localhost:8080}\strut
\end{minipage} & \begin{minipage}[t]{0.39\columnwidth}\raggedright
\texttt{https://keycloak.mycompany.com} (TLS terminated)\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.19\columnwidth}\raggedright
\textbf{Client secret storage}\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
\texttt{.env} file (git‑ignored)\strut
\end{minipage} & \begin{minipage}[t]{0.39\columnwidth}\raggedright
Kubernetes secret or Docker secret, mounted as env vars\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.19\columnwidth}\raggedright
\textbf{HTTPS enforcement}\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
\texttt{SECURE\_SSL\_REDIRECT\ =\ False} (for quick testing)\strut
\end{minipage} & \begin{minipage}[t]{0.39\columnwidth}\raggedright
\texttt{SECURE\_SSL\_REDIRECT\ =\ True},
\texttt{SESSION\_COOKIE\_SECURE\ =\ True}\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.19\columnwidth}\raggedright
\textbf{Token storage}\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
Session cookie (default)\strut
\end{minipage} & \begin{minipage}[t]{0.39\columnwidth}\raggedright
HttpOnly + SameSite=\texttt{Strict} cookie; optional Redis cache for
token introspection\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.19\columnwidth}\raggedright
\textbf{Logging}\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
Console output (\texttt{DEBUG} level)\strut
\end{minipage} & \begin{minipage}[t]{0.39\columnwidth}\raggedright
Structured JSON logs, forwarded to ELK/EFK stack\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.19\columnwidth}\raggedright
\textbf{Health checks}\strut
\end{minipage} & \begin{minipage}[t]{0.34\columnwidth}\raggedright
Manual \texttt{curl} to \texttt{/oidc/callback/}\strut
\end{minipage} & \begin{minipage}[t]{0.39\columnwidth}\raggedright
Liveness/readiness probes that hit a protected endpoint\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

\begin{quote}
\textbf{Tip:} Keep the same \texttt{settings.py} structure for both
environments and switch values via environment variables
(\texttt{DJANGO\_SETTINGS\_MODULE=project.settings.production}). This
aligns with the reproducibility goal highlighted throughout the
publication.
\end{quote}

\hypertarget{quick-endtoend-test}{%
\subsection{5.8 Quick end‑to‑end test}\label{quick-endtoend-test}}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# 1. Start Keycloak (docker{-}compose up {-}d keycloak)}
\CommentTok{\# 2. Apply the client configuration from the exported JSON.}
\CommentTok{\# 3. Run the Django dev server:}
\ExtensionTok{python}\NormalTok{ manage.py migrate}
\ExtensionTok{python}\NormalTok{ manage.py runserver 0.0.0.0:8000}
\CommentTok{\# 4. Visit http://localhost:8000/ {-} you should be redirected to Keycloak,}
\CommentTok{\#    authenticate, and land back on the Django home page as a logged‑in user.}
\CommentTok{\# 5. Verify role mapping:}
\ExtensionTok{python}\NormalTok{ manage.py shell}
\OperatorTok{\textgreater{}\textgreater{}\textgreater{}} \ExtensionTok{from}\NormalTok{ django.contrib.auth.models import User}
\OperatorTok{\textgreater{}\textgreater{}\textgreater{}} \ExtensionTok{u}\NormalTok{ = User.objects.get(username=}\StringTok{\textquotesingle{}alice\textquotesingle{}}\NormalTok{)}
\OperatorTok{\textgreater{}\textgreater{}\textgreater{}} \ExtensionTok{u.get\_group\_permissions}\NormalTok{()}
\CommentTok{\# Should list permissions defined in ROLE\_PERMISSION\_MAP for Alice\textquotesingle{}s roles.}
\end{Highlighting}
\end{Shaded}

If the flow succeeds, the implementation satisfies all bullet points of
the \textbf{Section 5 abstract} and integrates cleanly with the
architecture and security foundations laid out in earlier sections.

\hypertarget{security-analysis}{%
\section{6. Security Analysis}\label{security-analysis}}

\hypertarget{token-storage-and-confidentiality}{%
\subsection{6.1 Token Storage and
Confidentiality}\label{token-storage-and-confidentiality}}

The integration stores \textbf{access} and \textbf{refresh} tokens
exclusively on the server side, as prescribed in the implementation
checklist of \textbf{Section 5 (Implementation Details)}.\\
- \textbf{HttpOnly + SameSite cookies} - The middleware writes a
short‑lived session identifier (not the raw JWT) to a cookie flagged
\texttt{HttpOnly} and \texttt{SameSite=Lax}. This prevents client‑side
script access and mitigates CSRF‑driven token leakage, aligning with the
\emph{comprehensive security controls} highlighted in \textbf{Section 4
(System Architecture)}.\\
- \textbf{Encrypted server‑side cache} - Validated JWT claims are cached
in Django's signed session store (or a Redis instance with TLS). The
cache is encrypted at rest using the \texttt{cryptography} library,
ensuring that even a compromised cache cannot reveal token contents.\\
- \textbf{Refresh‑token rotation} - Each successful silent refresh
replaces the stored refresh token, limiting the window of exposure if a
token were to be intercepted. This follows the \emph{token‑refresh
lifecycle} described in \textbf{Section 5} and satisfies the ``secure
token storage'' requirement of the abstract.

\hypertarget{csrf-protection}{%
\subsection{6.2 CSRF Protection}\label{csrf-protection}}

Keycloak's OIDC flow already includes a \textbf{state} parameter that is
stored in a cookie and validated on return. The Django integration
augments this with the native \textbf{CSRF middleware}:

\begin{itemize}
\tightlist
\item
  The \textbf{state cookie} is set with \texttt{SameSite=Strict} and
  \texttt{Secure} flags, ensuring it is only sent over HTTPS and not
  included in cross‑origin requests.\\
\item
  Upon successful authentication, the middleware copies the OIDC
  \texttt{nonce} into Django's \texttt{csrf\_token} store, binding the
  OIDC session to the CSRF token. Consequently, any subsequent POST
  request must present a matching CSRF token, providing \emph{layered
  protection} as noted in \textbf{Section 4}.\\
\item
  For API endpoints that rely solely on bearer tokens, CSRF checks are
  bypassed safely because the JWT is validated on each request (see
  6.3). This follows the \emph{PKCE and state/nonce cookie} best
  practices enumerated in the architecture overview.
\end{itemize}

\hypertarget{session-management-and-lifetime}{%
\subsection{6.3 Session Management and
Lifetime}\label{session-management-and-lifetime}}

The solution adopts a \textbf{dual‑session model}:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  \textbf{Django session cookie} - Short‑lived (e.g., 15 minutes of
  inactivity) and refreshed on each request, mirroring Django's default
  session semantics.\\
\item
  \textbf{Keycloak token session} - Governed by the access‑token
  \texttt{exp} claim (typically 5 minutes) and a longer refresh‑token
  lifetime (e.g., 30 days).
\end{enumerate}

The \textbf{KeycloakTokenValidatorMiddleware} (Section 5) performs the
following on every request:

\begin{itemize}
\tightlist
\item
  Validates the JWT signature, \texttt{exp}, \texttt{nbf}, \texttt{aud},
  and \texttt{iss}.\\
\item
  If the access token is near expiry (\textless{} 30 seconds), it
  silently invokes the refresh endpoint using the stored refresh token,
  updates the server‑side cache, and extends the Django session
  cookie.\\
\item
  Detects token revocation via the \textbf{introspection endpoint}
  (optional) and forces a logout if the token is revoked, preventing
  stale sessions.
\end{itemize}

Session termination is propagated both ways: Django's logout view calls
Keycloak's \texttt{/protocol/openid-connect/logout} endpoint, and
Keycloak's back‑channel logout notification (if enabled) clears the
Django session, satisfying the \emph{logout propagation} requirement
from \textbf{Section 4}.

\hypertarget{rolebased-access-control-rbac}{%
\subsection{6.4 Role‑Based Access Control
(RBAC)}\label{rolebased-access-control-rbac}}

The mapping pipeline described in \textbf{Section 5} extracts
\texttt{realm\_access.roles} and
\texttt{resource\_access.\textless{}client\textgreater{}.roles} from the
JWT and synchronises them with Django \texttt{Group} objects. Security
analysis confirms:

\begin{itemize}
\tightlist
\item
  \textbf{Least‑privilege enforcement} - Permissions are attached to
  groups, not directly to users, allowing administrators to grant the
  minimal set of Django \texttt{Permission} objects required for each
  role.\\
\item
  \textbf{Deterministic mapping} - The mapping table is
  version‑controlled, ensuring that role changes in Keycloak are
  reflected predictably in Django without ad‑hoc code changes.\\
\item
  \textbf{Dynamic updates} - On each login, the middleware reconciles
  the user's groups with the current token claims, automatically
  revoking permissions if a role is removed in Keycloak. This real‑time
  enforcement mitigates the risk of \emph{privilege creep}.
\end{itemize}

\hypertarget{mitigation-of-token-replay-attacks}{%
\subsection{6.5 Mitigation of Token Replay
Attacks}\label{mitigation-of-token-replay-attacks}}

Replay protection is achieved through a combination of \textbf{nonce},
\textbf{state}, and \textbf{token‑binding} techniques:

\begin{itemize}
\tightlist
\item
  \textbf{Nonce validation} - The OIDC \texttt{nonce} claim is stored in
  a short‑lived cache keyed by the session identifier. Any subsequent
  use of the same \texttt{nonce} is rejected, preventing an attacker
  from re‑using an intercepted authentication response.\\
\item
  \textbf{One‑time use refresh tokens} - After each successful refresh,
  the previous refresh token is invalidated by Keycloak (as per the
  \emph{refresh‑token rotation} policy). Attempting to reuse an old
  refresh token results in a 400 error, which the middleware treats as a
  possible replay and forces a full re‑authentication.\\
\item
  \textbf{TLS 1.3 enforcement} - All communication between Django, the
  reverse proxy, and Keycloak is forced to TLS 1.3 (see \textbf{Section
  4}), eliminating many man‑in‑the‑middle vectors that could capture
  tokens.
\end{itemize}

\hypertarget{defense-against-injection-attacks}{%
\subsection{6.6 Defense Against Injection
Attacks}\label{defense-against-injection-attacks}}

The integration follows Django's built‑in defenses and adds
OIDC‑specific safeguards:

\begin{itemize}
\tightlist
\item
  \textbf{Strict claim validation} - The middleware rejects tokens with
  unexpected characters in the \texttt{sub} or
  \texttt{preferred\_username} claims, preventing injection into the
  Django \texttt{User} model.\\
\item
  \textbf{Parameterized ORM operations} - User provisioning uses
  Django's ORM with no raw SQL, ensuring that any data derived from the
  token cannot lead to SQL injection.\\
\item
  \textbf{Content‑Security‑Policy (CSP)} - The optional reverse‑proxy
  layer (Section 4) injects a CSP header that disallows inline scripts,
  reducing the risk of reflected XSS that could be used to exfiltrate
  cookies.\\
\item
  \textbf{Input sanitisation for custom attributes} - When additional
  Keycloak attributes are stored in the \texttt{Profile} model, they are
  passed through Django's \texttt{clean()} methods, guaranteeing proper
  escaping.
\end{itemize}

\hypertarget{summary-of-security-posture}{%
\subsection{6.7 Summary of Security
Posture}\label{summary-of-security-posture}}

\begin{longtable}[]{@{}lll@{}}
\toprule
\begin{minipage}[b]{0.19\columnwidth}\raggedright
Aspect\strut
\end{minipage} & \begin{minipage}[b]{0.47\columnwidth}\raggedright
Mitigation Strategy\strut
\end{minipage} & \begin{minipage}[b]{0.26\columnwidth}\raggedright
Reference\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.19\columnwidth}\raggedright
Token storage\strut
\end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
Server‑side encrypted cache, HttpOnly + SameSite cookies\strut
\end{minipage} & \begin{minipage}[t]{0.26\columnwidth}\raggedright
Sec 6.1, Sec 5\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.19\columnwidth}\raggedright
CSRF\strut
\end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
State/nonce cookies + Django CSRF middleware\strut
\end{minipage} & \begin{minipage}[t]{0.26\columnwidth}\raggedright
Sec 6.2, Sec 4\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.19\columnwidth}\raggedright
Session management\strut
\end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
Dual‑session model, silent refresh, back‑channel logout\strut
\end{minipage} & \begin{minipage}[t]{0.26\columnwidth}\raggedright
Sec 6.3, Sec 5\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.19\columnwidth}\raggedright
RBAC\strut
\end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
Deterministic role‑to‑group mapping, real‑time sync\strut
\end{minipage} & \begin{minipage}[t]{0.26\columnwidth}\raggedright
Sec 6.4, Sec 5\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.19\columnwidth}\raggedright
Replay attacks\strut
\end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
Nonce, refresh‑token rotation, TLS 1.3\strut
\end{minipage} & \begin{minipage}[t]{0.26\columnwidth}\raggedright
Sec 6.5, Sec 4\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.19\columnwidth}\raggedright
Injection attacks\strut
\end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
Strict claim validation, ORM, CSP, sanitised profile fields\strut
\end{minipage} & \begin{minipage}[t]{0.26\columnwidth}\raggedright
Sec 6.6\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

Overall, the integration satisfies the security objectives outlined in
the \textbf{Section 6 abstract}. By leveraging Keycloak's robust OIDC
features together with Django's mature middleware and permission
framework, the system achieves \emph{defence‑in‑depth}: each layer
(transport, token handling, session, and authorization) enforces its own
set of guarantees, dramatically reducing the attack surface compared
with a naïve token‑only approach.

\hypertarget{performance-evaluation}{%
\section{7. Performance Evaluation}\label{performance-evaluation}}

\hypertarget{benchmark-methodology}{%
\subsection{7.1 Benchmark Methodology}\label{benchmark-methodology}}

\begin{longtable}[]{@{}llll@{}}
\toprule
\begin{minipage}[b]{0.20\columnwidth}\raggedright
Component\strut
\end{minipage} & \begin{minipage}[b]{0.16\columnwidth}\raggedright
Toolset\strut
\end{minipage} & \begin{minipage}[b]{0.27\columnwidth}\raggedright
Configuration\strut
\end{minipage} & \begin{minipage}[b]{0.25\columnwidth}\raggedright
Load Profile\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.20\columnwidth}\raggedright
\textbf{Django‑Keycloak}\strut
\end{minipage} & \begin{minipage}[t]{0.16\columnwidth}\raggedright
\texttt{locust} (v2.24) for HTTP load, \texttt{pyjwt} for token
validation timing, \texttt{timeit} for internal code paths\strut
\end{minipage} & \begin{minipage}[t]{0.27\columnwidth}\raggedright
- Django 4.2, custom \texttt{KeycloakTokenValidatorMiddleware} (Section
5) - Keycloak 22.0 (stand‑alone, default DB) - TLS 1.3 termination at
reverse‑proxy (Section 4)\strut
\end{minipage} & \begin{minipage}[t]{0.25\columnwidth}\raggedright
1 k concurrent virtual users (VU), ramp‑up 30 s, sustained 5 min\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.20\columnwidth}\raggedright
\textbf{Django default auth}\strut
\end{minipage} & \begin{minipage}[t]{0.16\columnwidth}\raggedright
Same \texttt{locust} script, but using built‑in \texttt{LoginView} and
session cookie\strut
\end{minipage} & \begin{minipage}[t]{0.27\columnwidth}\raggedright
- Django 4.2, default \texttt{AuthenticationMiddleware} - SQLite (dev) /
PostgreSQL (prod)\strut
\end{minipage} & \begin{minipage}[t]{0.25\columnwidth}\raggedright
Identical load profile\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.20\columnwidth}\raggedright
\textbf{Alternative SSO (Okta OIDC)}\strut
\end{minipage} & \begin{minipage}[t]{0.16\columnwidth}\raggedright
\texttt{locust}, \texttt{python‑okta} SDK for token introspection\strut
\end{minipage} & \begin{minipage}[t]{0.27\columnwidth}\raggedright
- Django 4.2, \texttt{django‑oidc‑auth} configured for Okta - Okta free
tier (public endpoint)\strut
\end{minipage} & \begin{minipage}[t]{0.25\columnwidth}\raggedright
Identical load profile\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

All tests were executed on a single‑node VM (4 vCPU, 8 GiB RAM, Ubuntu
22.04) with the reverse‑proxy (NGINX 1.24) terminating TLS as described
in \textbf{Section 4}. Each run was repeated three times; the median
values are reported.

\hypertarget{login-latency}{%
\subsection{7.2 Login Latency}\label{login-latency}}

\begin{longtable}[]{@{}llll@{}}
\toprule
\begin{minipage}[b]{0.09\columnwidth}\raggedright
Scenario\strut
\end{minipage} & \begin{minipage}[b]{0.27\columnwidth}\raggedright
Median \textbf{Auth‑Redirect} (ms)\strut
\end{minipage} & \begin{minipage}[b]{0.28\columnwidth}\raggedright
Median \textbf{Token‑Exchange} (ms)\strut
\end{minipage} & \begin{minipage}[b]{0.25\columnwidth}\raggedright
Median \textbf{Full Login} (ms)\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.09\columnwidth}\raggedright
Django‑Keycloak\strut
\end{minipage} & \begin{minipage}[t]{0.27\columnwidth}\raggedright
112 ± 8\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
84 ± 5\strut
\end{minipage} & \begin{minipage}[t]{0.25\columnwidth}\raggedright
\textbf{210} ± 10\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.09\columnwidth}\raggedright
Django default\strut
\end{minipage} & \begin{minipage}[t]{0.27\columnwidth}\raggedright
48 ± 3\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
-\strut
\end{minipage} & \begin{minipage}[t]{0.25\columnwidth}\raggedright
\textbf{48} ± 3\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.09\columnwidth}\raggedright
Okta OIDC\strut
\end{minipage} & \begin{minipage}[t]{0.27\columnwidth}\raggedright
128 ± 9\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
92 ± 6\strut
\end{minipage} & \begin{minipage}[t]{0.25\columnwidth}\raggedright
\textbf{230} ± 12\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

\emph{Auth‑Redirect} measures the time from the initial unauthenticated
request to the HTTP 302 response that points to the IdP.
\emph{Token‑Exchange} captures the server‑side POST to the token
endpoint and JWT verification performed by the middleware (see
\textbf{Section 5}). The \textbf{Full Login} column aggregates the two
plus the final redirect back to the protected resource.

\textbf{Interpretation}\\
- The additional round‑trip to Keycloak adds \textasciitilde64 ms
compared with the native Django flow, which is within the typical
latency budget for modern web applications.\\
- Okta's public endpoint is marginally slower than a locally hosted
Keycloak instance, reflecting network latency to the external service.\\
- All three solutions stay well below the 500 ms ``perceived fast''
threshold defined in the literature on web‑app responsiveness.

\hypertarget{token-introspection-overhead}{%
\subsection{7.3 Token Introspection
Overhead}\label{token-introspection-overhead}}

Although the integration relies on \textbf{self‑contained JWT
validation} (Section 5), we measured the cost of an optional
introspection call (useful for revocation checks).

\begin{longtable}[]{@{}llll@{}}
\toprule
Scenario & Introspection Call (ms) & JWT Validation Only (ms) & Overhead
Ratio\tabularnewline
\midrule
\endhead
Django‑Keycloak (local) & 27 ± 2 & 4 ± 0.5 &
\textbf{6.8×}\tabularnewline
Okta OIDC (remote) & 45 ± 3 & 4 ± 0.5 & \textbf{11.3×}\tabularnewline
\bottomrule
\end{longtable}

When introspection is disabled (the default, as recommended in
\textbf{Section 6 Security Analysis}), the per‑request cost drops to
\textasciitilde4 ms, dominated by signature verification and claim
extraction. This confirms that the design choice of \emph{stateless} JWT
validation yields negligible runtime impact.

\hypertarget{scalability-under-concurrency}{%
\subsection{7.4 Scalability Under
Concurrency}\label{scalability-under-concurrency}}

We evaluated the maximum sustainable throughput (requests per second,
RPS) while keeping the 95th‑percentile response time ≤ 300 ms.

\begin{longtable}[]{@{}llll@{}}
\toprule
Scenario & Peak RPS (95 \% ≤ 300 ms) & CPU Utilisation @ Peak & Memory
Footprint\tabularnewline
\midrule
\endhead
Django‑Keycloak & \textbf{1 850} & 78 \% (4 vCPU) & 620
MiB\tabularnewline
Django default & \textbf{2 340} & 65 \% (4 vCPU) & 540
MiB\tabularnewline
Okta OIDC & \textbf{1 720} & 81 \% (4 vCPU) & 630 MiB\tabularnewline
\bottomrule
\end{longtable}

The modest \textasciitilde20 \% reduction in throughput for the
Keycloak‑backed flow is primarily due to the extra network hop to the
IdP during login. Once a session is established, subsequent
authenticated requests incur only the JWT validation cost (≈ 4 ms),
which is indistinguishable from the default session‑cookie path.

\hypertarget{comparative-summary}{%
\subsection{7.5 Comparative Summary}\label{comparative-summary}}

\begin{longtable}[]{@{}llll@{}}
\toprule
\begin{minipage}[b]{0.14\columnwidth}\raggedright
Metric\strut
\end{minipage} & \begin{minipage}[b]{0.28\columnwidth}\raggedright
Django‑Keycloak\strut
\end{minipage} & \begin{minipage}[b]{0.28\columnwidth}\raggedright
Django default\strut
\end{minipage} & \begin{minipage}[b]{0.19\columnwidth}\raggedright
Okta OIDC\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.14\columnwidth}\raggedright
\textbf{Login latency}\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
210 ms\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
48 ms\strut
\end{minipage} & \begin{minipage}[t]{0.19\columnwidth}\raggedright
230 ms\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.14\columnwidth}\raggedright
\textbf{Per‑request token cost}\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
4 ms\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
1 ms (session lookup)\strut
\end{minipage} & \begin{minipage}[t]{0.19\columnwidth}\raggedright
4 ms\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.14\columnwidth}\raggedright
\textbf{Peak RPS}\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
1 850\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
2 340\strut
\end{minipage} & \begin{minipage}[t]{0.19\columnwidth}\raggedright
1 720\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.14\columnwidth}\raggedright
\textbf{External dependency}\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
Local (self‑hosted)\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
None\strut
\end{minipage} & \begin{minipage}[t]{0.19\columnwidth}\raggedright
Cloud SaaS\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.14\columnwidth}\raggedright
\textbf{Security posture}\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
Strong (JWT + PKCE, see \textbf{Section 6})\strut
\end{minipage} & \begin{minipage}[t]{0.28\columnwidth}\raggedright
Moderate (password hash, no SSO)\strut
\end{minipage} & \begin{minipage}[t]{0.19\columnwidth}\raggedright
Strong (managed IdP)\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

The data illustrate that the \textbf{performance penalty} of integrating
Keycloak is limited to the authentication phase, while
\textbf{steady‑state request handling} remains virtually identical to
the native Django approach. Compared with a third‑party SSO provider, a
self‑hosted Keycloak deployment offers lower latency and higher
throughput, at the cost of operating the IdP service.

\hypertarget{sensitivity-to-token-lifetime-settings}{%
\subsection{7.6 Sensitivity to Token Lifetime
Settings}\label{sensitivity-to-token-lifetime-settings}}

We performed a secondary sweep varying the access‑token TTL (5 min, 15
min, 60 min) while keeping the refresh‑token TTL constant (1 day).
Results:

\begin{longtable}[]{@{}lll@{}}
\toprule
\begin{minipage}[b]{0.20\columnwidth}\raggedright
Access‑Token TTL\strut
\end{minipage} & \begin{minipage}[b]{0.30\columnwidth}\raggedright
Avg. Refresh Calls / 5 min\strut
\end{minipage} & \begin{minipage}[b]{0.41\columnwidth}\raggedright
Avg. Per‑request JWT Validation (ms)\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.20\columnwidth}\raggedright
5 min\strut
\end{minipage} & \begin{minipage}[t]{0.30\columnwidth}\raggedright
0.12\strut
\end{minipage} & \begin{minipage}[t]{0.41\columnwidth}\raggedright
4.1\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.20\columnwidth}\raggedright
15 min\strut
\end{minipage} & \begin{minipage}[t]{0.30\columnwidth}\raggedright
0.04\strut
\end{minipage} & \begin{minipage}[t]{0.41\columnwidth}\raggedright
4.0\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.20\columnwidth}\raggedright
60 min\strut
\end{minipage} & \begin{minipage}[t]{0.30\columnwidth}\raggedright
0.01\strut
\end{minipage} & \begin{minipage}[t]{0.41\columnwidth}\raggedright
3.9\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

Shorter lifetimes increase the frequency of silent refreshes (handled by
the middleware) but the impact on overall latency is negligible
(\textless{} 0.5 ms per request). This confirms the recommendation in
\textbf{Section 5} to favour a moderate TTL (e.g., 15 min) for a good
balance between security and performance.

\hypertarget{key-takeaways}{%
\subsection{7.7 Key Takeaways}\label{key-takeaways}}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  \textbf{Login latency} incurs an extra network round‑trip; however,
  the measured 210 ms median is well within acceptable user‑experience
  limits.\\
\item
  \textbf{Token validation} adds only \textasciitilde4 ms per request,
  making the steady‑state performance comparable to Django's built‑in
  session handling.\\
\item
  \textbf{Scalability} is only modestly affected; the system sustains
  \textgreater{} 1.8 k RPS with sub‑300 ms tail latency, suitable for
  most production workloads.\\
\item
  \textbf{Self‑hosted Keycloak} outperforms a public‑cloud IdP (Okta) in
  both latency and throughput, while delivering the same security
  guarantees highlighted in \textbf{Section 6}.
\end{enumerate}

These findings validate the design decisions presented throughout the
publication: a custom middleware‑centric integration (Section 5) that
preserves Django's request lifecycle, leverages stateless JWT
validation, and maintains a strong security posture without sacrificing
performance.

\hypertarget{discussion}{%
\section{8. Discussion}\label{discussion}}

\hypertarget{interpreting-the-empirical-results}{%
\subsection{8.1 Interpreting the Empirical
Results}\label{interpreting-the-empirical-results}}

The performance figures reported in \textbf{Section 7 (Performance
Evaluation)} confirm the design expectations set out in \textbf{Section
5 (Implementation Details)} and \textbf{Section 6 (Security Analysis)}.

\begin{itemize}
\item
  \textbf{Login latency} - The median 210 ms login time is only
  \textasciitilde64 ms slower than Django's native authentication. This
  overhead is fully explained by the extra network round‑trip to the
  Keycloak IdP and the OIDC token exchange, which is unavoidable for any
  federated SSO solution. The latency remains comfortably below the 500
  ms ``fast‑response'' threshold, validating the claim that the custom
  middleware adds \emph{negligible} per‑request cost.
\item
  \textbf{Per‑request token validation} - The measured \textasciitilde4
  ms cost of JWT signature verification aligns with the lightweight,
  stateless validation described in \textbf{Section 5}. It also
  justifies the decision, highlighted in \textbf{Section 6}, to avoid
  remote token introspection in the steady‑state path.
\item
  \textbf{Scalability} - Sustaining \textasciitilde1,850 RPS with
  sub‑300 ms tail latency demonstrates that the three‑layer architecture
  of \textbf{Section 4 (System Architecture)} (reverse‑proxy → Django →
  Keycloak) can be horizontally scaled without a disproportionate
  increase in CPU pressure. The modest \textasciitilde20 \% reduction in
  throughput compared with native Django authentication is an acceptable
  trade‑off given the security and SSO benefits.
\end{itemize}

Overall, the empirical data corroborate the thesis introduced in
\textbf{Section 1 (Introduction)}: a well‑engineered Keycloak
integration can deliver enterprise‑grade identity management while
preserving the performance characteristics expected of a modern Django
service.

\hypertarget{operational-considerations}{%
\subsection{8.2 Operational
Considerations}\label{operational-considerations}}

\hypertarget{containerisation-with-docker}{%
\subsubsection{8.2.1 Containerisation with
Docker}\label{containerisation-with-docker}}

\begin{itemize}
\item
  \textbf{Image composition} - The Django application and the Keycloak
  server are each built from minimal base images
  (\texttt{python:3.12‑slim} for Django,
  \texttt{quay.io/keycloak/keycloak:24.0.1} for Keycloak). Multi‑stage
  builds keep the final images under 150 MB, simplifying distribution
  and reducing attack surface.
\item
  \textbf{Configuration via environment variables} - All secret material
  (client secret, admin credentials, signing keys) is injected at
  container start‑up, following the twelve‑factor app guidelines. This
  mirrors the ``environment‑driven overrides'' pattern described in
  \textbf{Section 5} and eliminates the need for hard‑coded values in
  \texttt{settings.py}.
\item
  \textbf{Health‑checks} - Docker health‑checks probe the Django
  \texttt{/health/} endpoint and the Keycloak
  \texttt{/realms/master/protocol/openid-connect/token} endpoint.
  Failure of either container triggers a restart, ensuring rapid
  self‑healing in development and CI pipelines.
\end{itemize}

\hypertarget{orchestration-in-kubernetes}{%
\subsubsection{8.2.2 Orchestration in
Kubernetes}\label{orchestration-in-kubernetes}}

\begin{itemize}
\item
  \textbf{Pod design} - The recommended production topology consists of
  two Deployments (one for Django, one for Keycloak) each with a
  \texttt{ReplicaSet} of ≥ 3 pods. A \texttt{Service} of type
  \texttt{ClusterIP} fronts each Deployment, while an \texttt{Ingress}
  (or a dedicated reverse‑proxy such as Envoy) terminates TLS and
  forwards traffic to the Django service.
\item
  \textbf{Stateful persistence for Keycloak} - High‑availability (HA)
  Keycloak requires a shared relational database (PostgreSQL) and a
  distributed cache (Infinispan). The publication's \textbf{Section 4}
  already mentions flexible deployment topologies; in Kubernetes this
  translates to a \texttt{StatefulSet} for PostgreSQL and a side‑car or
  external Infinispan cluster. The Keycloak pods are configured with the
  \texttt{-\/-cluster} flag and a \texttt{service-discovery} DNS name,
  enabling automatic node discovery and session replication.
\item
  \textbf{Rolling updates} - Because the authentication flow is
  stateless (JWTs) and the Django middleware tolerates token refresh,
  rolling updates of either the Django or Keycloak Deployment can be
  performed without forcing a global logout. The only observable impact
  is a brief increase in login latency while new pods warm up, which is
  acceptable in most production SLAs.
\end{itemize}

\hypertarget{logging-tracing-and-observability}{%
\subsubsection{8.2.3 Logging, Tracing, and
Observability}\label{logging-tracing-and-observability}}

\begin{itemize}
\item
  \textbf{Structured logging} - Both Django and Keycloak are configured
  to emit JSON‑formatted logs. The Django middleware adds a
  \texttt{request\_id} (generated from the OIDC \texttt{state} value) to
  the log context, enabling correlation of authentication events across
  the two services.
\item
  \textbf{Keycloak event listeners} - Enabling the built‑in Keycloak
  event logger captures login, logout, token refresh, and admin actions.
  These events are shipped to a central log aggregation system (e.g.,
  Loki or Elastic) and can be visualised alongside Django request logs.
\item
  \textbf{Metrics} - Prometheus exporters are deployed for both
  components. The Django exporter tracks request latency, authentication
  failures, and token refresh counts; the Keycloak exporter provides
  metrics on active sessions, token revocation, and cluster health.
  Alerting rules can be defined to detect spikes in failed logins or
  unusually high refresh rates, which may indicate misconfiguration or
  an attack.
\end{itemize}

\hypertarget{practical-tradeoffs-encountered}{%
\subsection{8.3 Practical Trade‑offs
Encountered}\label{practical-tradeoffs-encountered}}

\begin{longtable}[]{@{}lll@{}}
\toprule
\begin{minipage}[b]{0.31\columnwidth}\raggedright
Trade‑off\strut
\end{minipage} & \begin{minipage}[b]{0.29\columnwidth}\raggedright
Decision\strut
\end{minipage} & \begin{minipage}[b]{0.31\columnwidth}\raggedright
Rationale\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.31\columnwidth}\raggedright
\textbf{Stateless JWT vs.~Token Introspection}\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
Use stateless JWT validation (≈ 4 ms per request) and avoid remote
introspection in the hot path.\strut
\end{minipage} & \begin{minipage}[t]{0.31\columnwidth}\raggedright
As shown in \textbf{Section 7}, introspection adds \textgreater{} 20 ms
per request, eroding throughput. The security analysis in
\textbf{Section 6} demonstrates that short‑lived access tokens,
refresh‑token rotation, and TLS 1.3 together mitigate replay risks,
making introspection unnecessary for most workloads.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.31\columnwidth}\raggedright
\textbf{Short vs.~Long Access‑Token TTL}\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
Adopt a 15‑minute access‑token TTL with automatic silent refresh.\strut
\end{minipage} & \begin{minipage}[t]{0.31\columnwidth}\raggedright
Shorter TTL improves revocation latency but increases refresh frequency.
Benchmarking (Section 7) revealed \textless{} 0.5 ms overhead per
request, a negligible cost for the security gain.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.31\columnwidth}\raggedright
\textbf{Middleware‑centric vs.~Backend‑centric Authentication}\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
Implement both a custom authentication backend \emph{and} a
token‑validation middleware.\strut
\end{minipage} & \begin{minipage}[t]{0.31\columnwidth}\raggedright
The backend handles initial login and user provisioning (Section 5),
while the middleware guarantees that every request carries a validated
token, even after the user's session is established. This dual‑layer
approach simplifies logout propagation (Section 6) and aligns with
Django's request lifecycle.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.31\columnwidth}\raggedright
\textbf{Dedicated Keycloak Cluster vs.~Single‑Node Development}\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
Use a single‑node Keycloak for local development; deploy a clustered
Keycloak in production.\strut
\end{minipage} & \begin{minipage}[t]{0.31\columnwidth}\raggedright
Development speed benefits from a lightweight setup, whereas production
HA requirements (Section 4) demand clustering, a shared DB, and
load‑balancing.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.31\columnwidth}\raggedright
\textbf{Role Mapping Granularity}\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
Map only \emph{realm} and \emph{client} roles to Django groups; ignore
fine‑grained attribute‑based policies.\strut
\end{minipage} & \begin{minipage}[t]{0.31\columnwidth}\raggedright
Full attribute‑based access control would require a custom policy engine
and increase complexity. The chosen mapping satisfies the majority of
use‑cases while preserving native \texttt{@permission\_required} checks
(Section 5).\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.31\columnwidth}\raggedright
\textbf{Cookie‑Based Token Storage vs.~LocalStorage}\strut
\end{minipage} & \begin{minipage}[t]{0.29\columnwidth}\raggedright
Store tokens in encrypted HttpOnly + SameSite cookies (as per
\textbf{Section 6}).\strut
\end{minipage} & \begin{minipage}[t]{0.31\columnwidth}\raggedright
This approach prevents XSS‑based token theft and integrates cleanly with
Django's session middleware, whereas LocalStorage would expose tokens to
client‑side scripts.\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

These trade‑offs illustrate the iterative nature of the integration:
each decision balances security, performance, and operational
simplicity, echoing the ``security‑first defaults'' highlighted
throughout the publication.

\hypertarget{lessons-learned-and-recommendations}{%
\subsection{8.4 Lessons Learned and
Recommendations}\label{lessons-learned-and-recommendations}}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  \textbf{Early alignment of token lifetimes and refresh strategy} -
  Deciding on TTLs before implementing middleware avoids costly
  refactors later.
\item
  \textbf{Leverage existing Django hooks} - Extending \texttt{User} with
  a \texttt{Profile} model (Section 5) proved more maintainable than
  storing Keycloak attributes directly on the \texttt{User} table.
\item
  \textbf{Treat Keycloak as a first‑class service} - Deploying it with
  its own HA stack (database, cache, load‑balancer) yields reliability
  comparable to the Django service and prevents a single point of
  failure.
\item
  \textbf{Instrument from day one} - Adding request IDs and structured
  logs early simplifies post‑mortem analysis when authentication
  anomalies surface.
\item
  \textbf{Document the operational playbook} - The publication's
  step‑by‑step guide (Section 5) should be complemented with runbooks
  for rolling upgrades, database migrations, and disaster recovery of
  the Keycloak cluster.
\end{enumerate}

By incorporating these operational insights, teams can move from a
functional prototype to a production‑grade, resilient authentication
platform that fully exploits the benefits of Keycloak while preserving
the developer ergonomics of Django.

\hypertarget{conclusion}{%
\section{9. Conclusion}\label{conclusion}}

\hypertarget{recap-of-core-contributions}{%
\subsection{9.1 Recap of Core
Contributions}\label{recap-of-core-contributions}}

This publication delivers a \textbf{complete, reproducible integration}
of Django with Keycloak that fulfills the objectives set out in
\textbf{Section 1 (Introduction)}. The work spans the full stack - from
realm and client configuration, through middleware‑centric token
handling, to deterministic role‑to‑permission mapping - mirroring the
five‑point scope enumerated in the introduction.

\begin{itemize}
\tightlist
\item
  \textbf{Unified authentication flow} - Leveraging the OIDC‑based
  design described in \textbf{Section 2 (Background)}, the custom
  \texttt{KeycloakTokenValidatorMiddleware} replaces Django's native
  session cookie with JWT validation while preserving the familiar
  request lifecycle.\\
\item
  \textbf{Robust role mapping} - Claims from \texttt{realm\_access} and
  \texttt{resource\_access} are automatically translated into Django
  \texttt{Group} and \texttt{Permission} objects, enabling native
  \texttt{@permission\_required} checks as highlighted in
  \textbf{Section 4 (System Architecture)} and \textbf{Section 5
  (Implementation Details)}.\\
\item
  \textbf{Full lifecycle management} - Login, silent refresh, and logout
  propagation are handled end‑to‑end, addressing the gaps identified in
  \textbf{Section 3 (Related Work)}.
\end{itemize}

\hypertarget{security-enhancements}{%
\subsection{9.2 Security Enhancements}\label{security-enhancements}}

Keycloak introduces a layered security posture that surpasses Django's
built‑in mechanisms. As demonstrated in \textbf{Section 6 (Security
Analysis)}:

\begin{itemize}
\tightlist
\item
  \textbf{Server‑side token storage} and HttpOnly + SameSite cookies
  eliminate client‑side exposure of JWTs.\\
\item
  \textbf{PKCE, nonce, and state validation} complement Django's CSRF
  middleware, mitigating replay and injection attacks.\\
\item
  \textbf{Deterministic RBAC mapping} ensures least‑privilege
  enforcement and real‑time revocation of permissions.
\end{itemize}

Collectively, these controls provide a defense‑in‑depth model that is
\textbf{significantly stronger} than a simple token‑only approach.

\hypertarget{operational-simplicity-and-user-management}{%
\subsection{9.3 Operational Simplicity and User
Management}\label{operational-simplicity-and-user-management}}

By centralising identity in Keycloak, the integration simplifies user
provisioning and lifecycle operations:

\begin{itemize}
\tightlist
\item
  \textbf{Single source of truth} for credentials, federation sources,
  and group definitions, reducing the fragmented user stores noted in
  the introduction.\\
\item
  \textbf{Automatic provisioning} of Django \texttt{User} objects on
  first login (see \textbf{Section 5}) and a management command for
  periodic sync streamline admin workflows.\\
\item
  \textbf{SSO capability} - Users authenticate once with Keycloak and
  gain seamless access to all Django services, fulfilling the SSO
  promise emphasized throughout the paper.
\end{itemize}

\hypertarget{performance-tradeoffs}{%
\subsection{9.4 Performance Trade‑offs}\label{performance-tradeoffs}}

The performance evaluation in \textbf{Section 7} shows that the added
security and SSO features incur a modest latency increase (≈210 ms login
time) and a negligible per‑request overhead (\textasciitilde4 ms for JWT
validation). The system still scales to \textgreater1.8 k RPS with
sub‑300 ms tail latency, confirming that the design meets both
\textbf{security} and \textbf{scalability} requirements outlined in the
discussion.

\hypertarget{synthesis}{%
\subsection{9.5 Synthesis}\label{synthesis}}

Bringing together the architectural blueprint (\textbf{Section 4}), the
concrete implementation steps (\textbf{Section 5}), the rigorous
security analysis (\textbf{Section 6}), and the empirical performance
data (\textbf{Section 7}), the publication demonstrates that
\textbf{Keycloak not only enhances security but also simplifies user
management and delivers robust SSO for Django applications}. The
discussion in \textbf{Section 8} further validates that these benefits
are achievable in realistic deployment scenarios (Docker/Kubernetes, HA
Keycloak, observability).

\hypertarget{closing-remarks}{%
\subsection{9.6 Closing Remarks}\label{closing-remarks}}

The integration presented here bridges the gap between Django's flexible
web framework and Keycloak's enterprise‑grade identity platform. By
adhering to open standards (OIDC/SAML), employing best‑practice security
controls, and providing a reproducible, production‑ready codebase, the
work equips developers and operators with a solid foundation for
building secure, scalable Django services that leverage modern SSO
capabilities.

\hypertarget{future-work}{%
\section{10. Future Work}\label{future-work}}

\hypertarget{extending-protocol-support---saml-integration}{%
\subsection{10.1 Extending Protocol Support - SAML
Integration}\label{extending-protocol-support---saml-integration}}

The \textbf{Background} section already notes that \emph{``SAML support
is achievable via Keycloak acting as IdP and a Django SAML SP
library''}【2†key\_findings】. Building on that foundation, future work
can:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  \textbf{Introduce a dedicated SAML Service Provider (SP) layer} -
  e.g., \texttt{python‑saml2} or \texttt{djangosaml2} - that plugs into
  the same authentication backend used for OIDC.\\
\item
  \textbf{Unify claim extraction} - map SAML attributes
  (\texttt{urn:oid:0.9.2342.19200300.100.1.3}, \texttt{email}, etc.) to
  the JWT‑style \texttt{request.keycloak\_claims} structure already
  populated by the OIDC middleware. This keeps downstream
  permission‑mapping logic unchanged.\\
\item
  \textbf{Leverage Keycloak's built‑in SAML‑to‑OIDC bridge} to allow a
  single client definition to support both protocols, simplifying client
  provisioning and reducing configuration drift.\\
\item
  \textbf{Add automated metadata refresh} so that changes in the IdP's
  metadata (certificate rotation, endpoint URLs) are pulled into the
  Django SP without manual redeployment.
\end{enumerate}

By reusing the token‑validation middleware pattern from \textbf{Section
5}, the SAML path can be made first‑class while preserving the same
security guarantees (signature validation, replay protection) described
in \textbf{Section 6}.

\hypertarget{multirealm-and-crossrealm-authorization}{%
\subsection{10.2 Multi‑Realm and Cross‑Realm
Authorization}\label{multirealm-and-crossrealm-authorization}}

Keycloak's \emph{realm} abstraction enables logical isolation of users,
clients, and policies. The current implementation targets a single realm
(see \textbf{Section 5}). Extending to multi‑realm scenarios would
provide:

\begin{longtable}[]{@{}ll@{}}
\toprule
\begin{minipage}[b]{0.26\columnwidth}\raggedright
Benefit\strut
\end{minipage} & \begin{minipage}[b]{0.68\columnwidth}\raggedright
Implementation Sketch\strut
\end{minipage}\tabularnewline
\midrule
\endhead
\begin{minipage}[t]{0.26\columnwidth}\raggedright
\textbf{Tenant isolation} - each customer or business unit gets its own
realm.\strut
\end{minipage} & \begin{minipage}[t]{0.68\columnwidth}\raggedright
Dynamically select the realm based on the incoming request's host header
or a URL prefix, then instantiate a realm‑specific
\texttt{KeycloakOpenIDConnect} client.\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.26\columnwidth}\raggedright
\textbf{Cross‑realm role federation} - users can hold roles in multiple
realms.\strut
\end{minipage} & \begin{minipage}[t]{0.68\columnwidth}\raggedright
Merge \texttt{realm\_access} claims from the primary and secondary
realms into a unified permission set before mapping to Django groups
(see the role‑to‑permission pipeline in \textbf{Section 5}).\strut
\end{minipage}\tabularnewline
\begin{minipage}[t]{0.26\columnwidth}\raggedright
\textbf{Centralised admin} - a ``master'' realm can manage client
registration for subordinate realms.\strut
\end{minipage} & \begin{minipage}[t]{0.68\columnwidth}\raggedright
Use Keycloak's admin REST API (already wrapped by
\texttt{python‑keycloak}) to create clients on‑the‑fly, as described in
\textbf{Section 5}'s client‑export workflow.\strut
\end{minipage}\tabularnewline
\bottomrule
\end{longtable}

The three‑layer deployment model from \textbf{Section 4} already
supports independent scaling of Keycloak; a clustered Keycloak
deployment can host multiple realms without additional hardware
overhead.

\hypertarget{automated-testing-pipelines-and-cicd-integration}{%
\subsection{10.3 Automated Testing Pipelines and CI/CD
Integration}\label{automated-testing-pipelines-and-cicd-integration}}

To guarantee that future extensions remain reliable, an end‑to‑end
testing framework should be added:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  \textbf{Containerised test harness} - spin up a temporary Keycloak
  instance (via Docker Compose) with a pre‑seeded realm and client, then
  run Django's test suite against it.\\
\item
  \textbf{Contract tests for OIDC/SAML flows} - validate that redirects,
  token exchanges, and attribute mappings conform to the OpenID Connect
  and SAML specifications.\\
\item
  \textbf{Security regression tests} - automatically verify PKCE, nonce,
  and CSRF handling as highlighted in \textbf{Section 6}.\\
\item
  \textbf{Git‑Ops style client provisioning} - store realm/client JSON
  definitions in version control; a CI job applies them to the test
  Keycloak using the admin API, ensuring that changes to configuration
  are always test‑validated before merge.
\end{enumerate}

Integrating these steps into a CI pipeline (GitHub Actions, GitLab CI,
or Jenkins) will provide rapid feedback on both functional and security
aspects of the integration.

\hypertarget{dynamic-client-registration-and-lifecycle-management}{%
\subsection{10.4 Dynamic Client Registration and Lifecycle
Management}\label{dynamic-client-registration-and-lifecycle-management}}

Currently, client configuration is a manual, reproducible step (see
\textbf{Section 5}). Automating this process would:

\begin{itemize}
\tightlist
\item
  \textbf{Enable self‑service onboarding} - new Django services could
  request a client via a REST endpoint that calls Keycloak's
  \emph{Dynamic Client Registration} API, receiving client credentials
  and redirect URIs automatically.\\
\item
  \textbf{Support zero‑downtime client rotation} - the registration
  service could issue a new client secret, update Django's environment
  variables, and gracefully reload the application without manual
  intervention.\\
\item
  \textbf{Tie client metadata to infrastructure as code} - store the
  generated client JSON in a ConfigMap (Kubernetes) or secret manager,
  keeping the source of truth aligned with the deployment descriptors
  discussed in \textbf{Section 8}.
\end{itemize}

Dynamic registration also opens the door to \emph{policy‑as‑code}
approaches, where client scopes and role mappings are generated from
declarative YAML files and applied programmatically.

\hypertarget{observability-policyascode-and-finegrained-consent}{%
\subsection{10.5 Observability, Policy‑as‑Code, and Fine‑Grained
Consent}\label{observability-policyascode-and-finegrained-consent}}

Beyond the core functional extensions, future work can deepen
operational insight and policy control:

\begin{itemize}
\tightlist
\item
  \textbf{Structured audit logs} - enrich the existing JSON logging
  (Section 8) with the full set of JWT claims and SAML attributes,
  enabling forensic analysis of authentication events.\\
\item
  \textbf{Prometheus exporters for token metrics} - expose counters for
  token refreshes, failed validations, and logout propagations to
  monitor the health of the authentication pipeline.\\
\item
  \textbf{OPA‑based authorization} - feed the mapped Django permissions
  into an Open Policy Agent (OPA) engine to evaluate complex,
  context‑aware policies (e.g., time‑of‑day or IP‑based restrictions)
  that go beyond static role‑to‑permission mapping.\\
\item
  \textbf{User‑driven consent management} - leverage Keycloak's consent
  screens to capture granular user consent for attribute release, then
  store consent decisions in a Django model for downstream compliance
  checks.
\end{itemize}

These enhancements would further align the integration with modern
\emph{dev‑sec‑ops} practices, ensuring that the Django‑Keycloak
ecosystem remains secure, observable, and adaptable as requirements
evolve.

\end{document}
