<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Howto on I_AM Fabio</title>
    <link>https://iam.fabiograsso.net/howto/</link>
    <description>Recent content in Howto on I_AM Fabio</description>
    <generator>Hugo</generator>
    <language>en</language>
    <atom:link href="https://iam.fabiograsso.net/howto/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Opaflix - Session Replay Viewer for Okta Privileged Access (OPA)</title>
      <link>https://iam.fabiograsso.net/howto/okta-opaflix-session-replay-tool/</link>
      <pubDate>Fri, 03 Apr 2026 04:00:00 +0000</pubDate>
      <guid>https://iam.fabiograsso.net/howto/okta-opaflix-session-replay-tool/</guid>
      <description>Opaflix is an open-source tool to browse and replay Okta Privileged Access (OPA) SSH and RDP session recordings from AWS S3. Supports single-tenant and multi-tenant deployments, advanced search, infrastructure graph, and OIDC Authentication.</description>
      <content:encoded>&lt;![CDATA[
<h2 class="relative group">Introduction
    <div id="introduction" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#introduction" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>If you work with <strong>Okta Privileged Access (OPA)</strong>, you know that <strong>session recording</strong> is one of the most powerful features of the platform. It gives you a full audit trail of every SSH terminal session and every RDP desktop connection performed by your <em>privileged users</em>.</p>
<p>The problem? Out of the box, there is no simple way to <em>browse and replay</em> those recordings without digging into raw files stored in the <strong>Okta Privileged Access Access Gateway</strong>. This is one of the most common pain points I hear from <strong>Okta PAM</strong> customers in the field.</p>
<p>Today I&rsquo;m releasing <strong><a href="https://github.com/fabiograsso/okta-opaflix"  target="_blank" rel="noreferrer">Opaflix</a></strong>: an open-source web application that lets you browse and replay converted OPA session recordings stored in AWS S3, protected by Okta OIDC authentication. Like Netflix 🍿 but for your PAM recordings playback.</p>
<div class="admonition relative overflow-hidden rounded-lg border-l-4 my-3 px-4 py-3 shadow-sm" data-type="caution">
      <div class="flex items-center gap-2 font-semibold text-inherit">
        <div class="flex shrink-0 h-5 w-5 items-center justify-center text-lg"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="currentColor"  d="M159.3 5.4c7.8-7.3 19.9-7.2 27.7 .1c27.6 25.9 53.5 53.8 77.7 84c11-14.4 23.5-30.1 37-42.9c7.9-7.4 20.1-7.4 28 .1c34.6 33 63.9 76.6 84.5 118c20.3 40.8 33.8 82.5 33.8 111.9C448 404.2 348.2 512 224 512C98.4 512 0 404.1 0 276.5c0-38.4 17.8-85.3 45.4-131.7C73.3 97.7 112.7 48.6 159.3 5.4zM225.7 416c25.3 0 47.7-7 68.8-21c42.1-29.4 53.4-88.2 28.1-134.4c-2.8-5.6-5.6-11.2-9.8-16.8l-50.6 58.8s-81.4-103.6-87.1-110.6C133.1 243.8 112 273.2 112 306.8C112 375.4 162.6 416 225.7 416z"/></svg></span></div>
        <div class="grow">
          Not an Official Okta Product
        </div>
      </div><div class="admonition-content mt-3 text-base leading-relaxed text-inherit"><p><strong>Opaflix</strong> is an open-source community project, and it is not officially supported by Okta. Always test in a non-production environment first.</p></div></div>
<h3 class="relative group">GitHub Repository
    <div id="github-repository" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#github-repository" aria-label="Anchor">#</a>
    </span>
    
</h3>

  <div class="github-card-wrapper">
    <a id="github-014fc1ae7ca45871f4c31ee56fd84026" target="_blank" href="https://github.com/fabiograsso/okta-opaflix" class="cursor-pointer">
      <div
        class="w-full md:w-auto p-0 m-0 border border-neutral-200 dark:border-neutral-700 border rounded-md shadow-2xl">
        <div class="w-full md:w-auto pt-3 p-5">
          <div class="flex items-center">
            <span class="text-2xl text-neutral-800 dark:text-neutral me-2">
              <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
</span>
            </span>
            <div
              id="github-014fc1ae7ca45871f4c31ee56fd84026-full_name"
              class="m-0 font-bold text-xl text-neutral-800 decoration-primary-500 hover:underline hover:underline-offset-2 dark:text-neutral">
              fabiograsso/okta-opaflix
            </div>
          </div>
          <p id="github-014fc1ae7ca45871f4c31ee56fd84026-description" class="m-0 mt-2 text-md text-neutral-800 dark:text-neutral">
            View this repository on GitHub
          </p>
        </div>
      </div>
      
      
      <script
        async
        type="text/javascript"
        src="/js/fetch-repo.min.dc5533c50cefd50405344b235937142271f26229fe39cbee27fd4960e8bb897a0beebfad77a1091ca91cd0d1fb14e70fc37cc114dd9674fb2c32e0ab512ec8a4.js"
        integrity="sha512-3FUzxQzv1QQFNEsjWTcUInHyYin&#43;OcvuJ/1JYOi7iXoL7r&#43;td6EJHKkc0NH7FOcPw3zBFN2WdPssMuCrUS7IpA=="
        data-repo-url="https://api.github.com/repos/fabiograsso/okta-opaflix"
        data-repo-id="github-014fc1ae7ca45871f4c31ee56fd84026"></script>
    </a>
  </div>

<h3 class="relative group">Sample Video
    <div id="sample-video" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sample-video" aria-label="Anchor">#</a>
    </span>
    
</h3>
<figure>
  <video
    class="w-full rounded-md aspect-video object-contain"preload="metadata" loop muted controls playsinline>
    <source src="/howto/okta-opaflix-session-replay-tool/opaflix-demo.mp4">
    <p>Your browser cannot play this video. <a href="/howto/okta-opaflix-session-replay-tool/opaflix-demo.mp4">Download video</a>.</p>
  </video>
</figure>


<h3 class="relative group">Why Opaflix?
    <div id="why-opaflix" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-opaflix" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Reviewing session recordings is a critical workflow for <strong>security teams</strong>, <strong>auditors</strong>, and <strong>PAM administrators</strong>. Whether you&rsquo;re investigating a security incident, validating compliance, or simply verifying that a change was performed correctly, you need a fast and intuitive way to find the right session and play it back.</p>
<p>The inspiration for Opaflix came from <strong><a href="https://github.com/mrdanielmh/opa-utils"  target="_blank" rel="noreferrer">opa-utils</a></strong>, a similar project by my former colleague Daniel Harris. Since that project is no longer maintained, I decided to build a new one from scratch with a more modern tech stack.</p>
<div class="admonition relative overflow-hidden rounded-lg border-l-4 my-3 px-4 py-3 shadow-sm" data-type="info">
      <div class="flex items-center gap-2 font-semibold text-inherit">
        <div class="flex shrink-0 h-5 w-5 items-center justify-center text-lg"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span></div>
        <div class="grow">
          Vibe Coding Disclaimer
        </div>
      </div><div class="admonition-content mt-3 text-base leading-relaxed text-inherit"><p>I&rsquo;ll also be transparent about something: Opaflix was built with <strong>heavy use of Vibe Coding powered by Claude Code</strong>. The code has been reviewed and tested, but I&rsquo;m not a professional developer, so I can&rsquo;t exclude bugs or security issues. Use with caution.</p></div></div><hr>

<h2 class="relative group">Key Features
    <div id="key-features" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#key-features" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Here is what Opaflix offers out of the box:</p>

<h3 class="relative group">Dashboard and Sessions List
    <div id="dashboard-and-sessions-list" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#dashboard-and-sessions-list" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The dashboard provides an overview of session activity, including total sessions, sessions by team, and sessions by project. This gives you quick insights into usage patterns and helps identify anomalies.</p>
<table>
  <thead>
      <tr>
          <th>Dashboard with Statistics</th>
          <th>Sessions List</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Opaflix Dashboard showing session statistics by team and project"
    srcset="
      /howto/okta-opaflix-session-replay-tool/opaflix-dashboard_hu_1f01c1f69eb6c98a.webp  330w,
      /howto/okta-opaflix-session-replay-tool/opaflix-dashboard_hu_59e322560033d8b.webp  660w,
      /howto/okta-opaflix-session-replay-tool/opaflix-dashboard_hu_262c420ca0033fa8.webp  960w,
      /howto/okta-opaflix-session-replay-tool/opaflix-dashboard_hu_c9c681293665d7c1.webp 1280w,
      /howto/okta-opaflix-session-replay-tool/opaflix-dashboard_hu_6a2ae2b38c618df.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-opaflix-session-replay-tool/opaflix-dashboard.png"
    src="/howto/okta-opaflix-session-replay-tool/opaflix-dashboard.png">


  
</figure>
</td>
          <td>








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Opaflix Sessions List with filtering and sorting options"
    srcset="
      /howto/okta-opaflix-session-replay-tool/opaflix-sessions_hu_b554e925a8ba74df.webp  330w,
      /howto/okta-opaflix-session-replay-tool/opaflix-sessions_hu_2d46020634e2bad4.webp  660w,
      /howto/okta-opaflix-session-replay-tool/opaflix-sessions_hu_c906293c74795593.webp  960w,
      /howto/okta-opaflix-session-replay-tool/opaflix-sessions_hu_8b8d17029978b732.webp 1280w,
      /howto/okta-opaflix-session-replay-tool/opaflix-sessions_hu_ba450ee56a3c108b.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-opaflix-session-replay-tool/opaflix-sessions.png"
    src="/howto/okta-opaflix-session-replay-tool/opaflix-sessions.png">


  
</figure>
</td>
      </tr>
  </tbody>
</table>

<h3 class="relative group">SSH &amp; RDP Session Playback
    <div id="ssh--rdp-session-playback" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ssh--rdp-session-playback" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>SSH terminal sessions are replayed using the Asciinema player, with support for play/pause, speed control, and seeking. RDP desktop sessions are played back through an HTML5 video player. Both support direct download from S3 for offline access.</p>
<table>
  <thead>
      <tr>
          <th>SSH Playback</th>
          <th>RDP Playback</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Opaflix SSH session playback with Asciinema player showing play/pause and speed controls"
    srcset="
      /howto/okta-opaflix-session-replay-tool/opaflix-playback-ssh_hu_d4fae5690bd8e7db.webp  330w,
      /howto/okta-opaflix-session-replay-tool/opaflix-playback-ssh_hu_65091fac4abab0e2.webp  660w,
      /howto/okta-opaflix-session-replay-tool/opaflix-playback-ssh_hu_dd1526f18c6a2a94.webp  960w,
      /howto/okta-opaflix-session-replay-tool/opaflix-playback-ssh_hu_daca814a902e469c.webp 1280w,
      /howto/okta-opaflix-session-replay-tool/opaflix-playback-ssh_hu_1f1e64f44aca6599.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-opaflix-session-replay-tool/opaflix-playback-ssh.png"
    src="/howto/okta-opaflix-session-replay-tool/opaflix-playback-ssh.png">


  
</figure>
</td>
          <td>








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Opaflix RDP session playback with HTML player showing play/pause and speed controls"
    srcset="
      /howto/okta-opaflix-session-replay-tool/opaflix-playback_hu_1d08ccb3c2f24822.webp  330w,
      /howto/okta-opaflix-session-replay-tool/opaflix-playback_hu_d6615db8f6b3f53d.webp  660w,
      /howto/okta-opaflix-session-replay-tool/opaflix-playback_hu_1c1d027a97cf3578.webp  960w,
      /howto/okta-opaflix-session-replay-tool/opaflix-playback_hu_170824cce97f0afa.webp 1280w,
      /howto/okta-opaflix-session-replay-tool/opaflix-playback_hu_30d86fea7542b842.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-opaflix-session-replay-tool/opaflix-playback.png"
    src="/howto/okta-opaflix-session-replay-tool/opaflix-playback.png">


  
</figure>
</td>
      </tr>
  </tbody>
</table>

<h3 class="relative group">Single-Tenant and Multi-Tenant Modes
    <div id="single-tenant-and-multi-tenant-modes" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#single-tenant-and-multi-tenant-modes" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Opaflix supports two deployment modes.</p>
<ul>
<li>In <strong>single-tenant mode</strong> (default), everything is configured via environment variables with no database required: you can be up and running in under five minutes.</li>
<li>In <strong>multi-tenant mode</strong>, a PostgreSQL database backs an isolated configuration per team, with a web UI for managing settings. This makes it suitable for centralized deployments serving multiple OPA teams.</li>
</ul>
<p>A configuration UI help managing settings in multi-tenant mode.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Opaflix Configuration UI for managing tenant settings in multi-tenant mode"
    srcset="
      /howto/okta-opaflix-session-replay-tool/opaflix-configuration_hu_c44ba389e339aa3f.webp  330w,
      /howto/okta-opaflix-session-replay-tool/opaflix-configuration_hu_c8596f13f1af9df4.webp  660w,
      /howto/okta-opaflix-session-replay-tool/opaflix-configuration_hu_b3d0f6a99b94b5a4.webp  960w,
      /howto/okta-opaflix-session-replay-tool/opaflix-configuration_hu_2f7a185f652febc2.webp 1280w,
      /howto/okta-opaflix-session-replay-tool/opaflix-configuration_hu_fe3874c2df29b610.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-opaflix-session-replay-tool/opaflix-configuration.png"
    src="/howto/okta-opaflix-session-replay-tool/opaflix-configuration.png">


  
</figure>

<h3 class="relative group">Advanced Search and Filtering:
    <div id="advanced-search-and-filtering" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#advanced-search-and-filtering" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Sessions can be searched and filtered by server, username, project, team, and date range. Filter dropdowns are populated with real-time data from the OPA API. Sortable and resizable columns make it easy to navigate large session archives.</p>
<table>
  <thead>
      <tr>
          <th>Simple Search</th>
          <th>Advanced Search</th>
          <th>Sorting</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Opaflix simple text search across all session fields"
    srcset="
      /howto/okta-opaflix-session-replay-tool/opaflix-search_simple_hu_e1c0641a34a76cf5.webp  330w,
      /howto/okta-opaflix-session-replay-tool/opaflix-search_simple_hu_9e546900298a553e.webp  660w,
      /howto/okta-opaflix-session-replay-tool/opaflix-search_simple_hu_c7ddb09b5c6e3e7f.webp  960w,
      /howto/okta-opaflix-session-replay-tool/opaflix-search_simple_hu_81cccf54e624e5f4.webp 1280w,
      /howto/okta-opaflix-session-replay-tool/opaflix-search_simple_hu_e95812882119e108.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-opaflix-session-replay-tool/opaflix-search_simple.png"
    src="/howto/okta-opaflix-session-replay-tool/opaflix-search_simple.png">


  
</figure>
</td>
          <td>








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Opaflix advanced search with server, username, project, team, and date filters"
    srcset="
      /howto/okta-opaflix-session-replay-tool/opaflix-search_advanced_hu_11c1f0ba57fe8212.webp  330w,
      /howto/okta-opaflix-session-replay-tool/opaflix-search_advanced_hu_42bf33e7d5115042.webp  660w,
      /howto/okta-opaflix-session-replay-tool/opaflix-search_advanced_hu_b8526cf903a1b939.webp  960w,
      /howto/okta-opaflix-session-replay-tool/opaflix-search_advanced_hu_b46e456095dcad10.webp 1280w,
      /howto/okta-opaflix-session-replay-tool/opaflix-search_advanced_hu_9d656b0281140007.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-opaflix-session-replay-tool/opaflix-search_advanced.png"
    src="/howto/okta-opaflix-session-replay-tool/opaflix-search_advanced.png">


  
</figure>
</td>
          <td>








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Opaflix sortable columns for organizing session results"
    srcset="
      /howto/okta-opaflix-session-replay-tool/opaflix-search_sorting_hu_649ff8099e9cac08.webp  330w,
      /howto/okta-opaflix-session-replay-tool/opaflix-search_sorting_hu_a58dedf53b25916c.webp  660w,
      /howto/okta-opaflix-session-replay-tool/opaflix-search_sorting_hu_4835f0ec866c0f8.webp  960w,
      /howto/okta-opaflix-session-replay-tool/opaflix-search_sorting_hu_bc640a64d65c3555.webp 1280w,
      /howto/okta-opaflix-session-replay-tool/opaflix-search_sorting_hu_6d5700e88902b66e.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-opaflix-session-replay-tool/opaflix-search_sorting.png"
    src="/howto/okta-opaflix-session-replay-tool/opaflix-search_sorting.png">


  
</figure>
</td>
      </tr>
  </tbody>
</table>

<h3 class="relative group">Infrastructure Graph
    <div id="infrastructure-graph" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#infrastructure-graph" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>A visual topology view shows the relationships between Gateways, Projects, and Servers, populated with live data from the OPA API. This helps you understand your PAM infrastructure at a glance and quickly navigate to relevant sessions.</p>
<table>
  <thead>
      <tr>
          <th>Graph Page</th>
          <th>Graph Details</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Opaflix Infrastructure Graph showing topology of Gateways, Projects, and Servers"
    srcset="
      /howto/okta-opaflix-session-replay-tool/opaflix-graph2_hu_4c9647e3022270d5.webp  330w,
      /howto/okta-opaflix-session-replay-tool/opaflix-graph2_hu_fa89be74b3ce57a0.webp  660w,
      /howto/okta-opaflix-session-replay-tool/opaflix-graph2_hu_16e8a10eab82784b.webp  960w,
      /howto/okta-opaflix-session-replay-tool/opaflix-graph2_hu_734fa78fb9129305.webp 1280w,
      /howto/okta-opaflix-session-replay-tool/opaflix-graph2_hu_826e5ecceeec655d.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-opaflix-session-replay-tool/opaflix-graph2.png"
    src="/howto/okta-opaflix-session-replay-tool/opaflix-graph2.png">


  
</figure>
</td>
          <td>








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Opaflix Infrastructure Graph showing detwil of a server"
    srcset="
      /howto/okta-opaflix-session-replay-tool/opaflix-graph4_hu_26ed563508fcefef.webp  330w,
      /howto/okta-opaflix-session-replay-tool/opaflix-graph4_hu_e20399661dd31264.webp  660w,
      /howto/okta-opaflix-session-replay-tool/opaflix-graph4_hu_9e1cdd730115918b.webp  960w,
      /howto/okta-opaflix-session-replay-tool/opaflix-graph4_hu_19897b2adfeefaef.webp 1280w,
      /howto/okta-opaflix-session-replay-tool/opaflix-graph4_hu_56d5e64e40f5d04e.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-opaflix-session-replay-tool/opaflix-graph4.png"
    src="/howto/okta-opaflix-session-replay-tool/opaflix-graph4.png">


  
</figure>
</td>
      </tr>
  </tbody>
</table>

<h3 class="relative group">Okta OIDC Authentication
    <div id="okta-oidc-authentication" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#okta-oidc-authentication" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>All routes are protected by Okta OIDC. Session cookies are <code>httpOnly</code>, <code>secure</code>, and <code>sameSite</code>. Rate limiting, input validation, and security headers via Helmet.js are included by default.</p>
<div class="admonition relative overflow-hidden rounded-lg border-l-4 my-3 px-4 py-3 shadow-sm" data-type="warning">
      <div class="flex items-center gap-2 font-semibold text-inherit">
        <div class="flex shrink-0 h-5 w-5 items-center justify-center text-lg"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span></div>
        <div class="grow">
          Security Considerations
        </div>
      </div><div class="admonition-content mt-3 text-base leading-relaxed text-inherit"><p>While basic security measures are implemented, Opaflix is a v1 release and has not undergone a formal security audit. Use with caution, especially in production environments.
I warmly suggest deploying behind a WAF and/or VPN, and not exposing it directly to the internet without proper protections in place.</p></div></div>
<h3 class="relative group">IAM Roles Anywhere Support
    <div id="iam-roles-anywhere-support" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#iam-roles-anywhere-support" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>For deployments outside AWS (e.g., Vercel, on-premises), Opaflix supports certificate-based authentication via IAM Roles Anywhere, so you don&rsquo;t need to rely on static access keys.
Static Access Keys are supported as well for simplicity, but using IAM Roles Anywhere is the recommended approach for AWS security best practice.</p>
<div class="admonition relative overflow-hidden rounded-lg border-l-4 my-3 px-4 py-3 shadow-sm" data-type="info">
      <div class="flex items-center gap-2 font-semibold text-inherit">
        <div class="flex shrink-0 h-5 w-5 items-center justify-center text-lg"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span></div>
        <div class="grow">
          AWS Documentation
        </div>
      </div><div class="admonition-content mt-3 text-base leading-relaxed text-inherit"><p>Detailed instructions for setting up IAM Roles Anywhere and configuring Opaflix to use it are available in <a href="https://github.com/fabiograsso/okta-opaflix/blob/main/docs/AWS.md"  target="_blank" rel="noreferrer">docs/AWS.md</a> for general configuration and <a href="https://github.com/fabiograsso/okta-opaflix/blob/main/scripts/aws/README.md"  target="_blank" rel="noreferrer">scripts/aws/README.md</a> for AWS CLI and CloudFormation commands.</p></div></div><hr>

<h2 class="relative group">How it works
    <div id="how-it-works" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-it-works" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">High-Level Data Flow
    <div id="high-level-data-flow" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#high-level-data-flow" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The following is an high-level overview of the Opaflix data flow:</p>
<pre class="not-prose mermaid">
---
config:
  layout: dagre
---
flowchart LR
 subgraph User["1️⃣ &nbsp; Privileged Access"]
        Admin["👤 &nbsp; Administrator"]
  end
 subgraph Gateway["2️⃣ &nbsp; OPA Gateway"]
        Broker["SFT Broker"]
        Session["Session Recording<br>/var/log/sft/sessions/*.asa"]
  end
 subgraph Target["3️⃣ &nbsp; Target"]
        Server["🖥️ &nbsp; Server"]
  end
 subgraph SyncService["4️⃣ &nbsp; opaflix-sync"]
        Convert["Session Conversion<br>.asa → .cast / .mkv"]
  end
 subgraph Storage["5️⃣ &nbsp; AWS S3"]
        S3["Converted Sessions<br>.cast / .mkv"]
  end
 subgraph Playback["6️⃣ &nbsp; Opaflix"]
        Web["Web Application"]
        Browser["🌐 &nbsp; Browser"]
  end
    Broker --> Session & Server
    Session --> Convert
    Convert -- Upload --> S3
    S3 --> Web
    Web --> Browser
    Admin -.-> Server
    Admin --> Broker

    S3@{ shape: disk}
    Session@{ shape: disk}
    style Admin fill:#ffffff
    style Broker fill:#ffffff
    style Session fill:#ffffff
    style Server fill:#ffffff
    style Convert fill:#ffffff
    style S3 fill:#ffffff
    style Web fill:#ffffff
    style Browser fill:#ffffff
    style User fill:#BBDEFB
    style Gateway fill:#C8E6C9
    style Storage fill:#FFF9C4
    style Playback fill:#FFCDD2
    style SyncService fill:#E1BEE7
</pre>

<table>
  <thead>
      <tr>
          <th>Step</th>
          <th>Actor</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>1</strong></td>
          <td>Administrator</td>
          <td>Connects to target server via SSH or RDP using OPA Gateway as bastion host</td>
      </tr>
      <tr>
          <td><strong>2</strong></td>
          <td>OPA Gateway</td>
          <td>Proxies the connection to the target server and records the session in proprietary <code>.asa</code> format</td>
      </tr>
      <tr>
          <td><strong>3</strong></td>
          <td>Target Server</td>
          <td>The actual server being accessed (recorded by the gateway)</td>
      </tr>
      <tr>
          <td><strong>4</strong></td>
          <td>opaflix-sync</td>
          <td>Converts <code>.asa</code> → <code>.cast</code> (SSH) or <code>.mkv</code> (RDP), uploads to S3</td>
      </tr>
      <tr>
          <td><strong>5</strong></td>
          <td>AWS S3</td>
          <td>Stores converted session recordings</td>
      </tr>
      <tr>
          <td><strong>6</strong></td>
          <td>Opaflix</td>
          <td>Web interface for browsing and playing back sessions</td>
      </tr>
  </tbody>
</table>
<div class="admonition relative overflow-hidden rounded-lg border-l-4 my-3 px-4 py-3 shadow-sm" data-type="note">
      <div class="flex items-center gap-2 font-semibold text-inherit">
        <div class="flex shrink-0 h-5 w-5 items-center justify-center text-lg"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span></div>
        <div class="grow">
          Note
        </div>
      </div><div class="admonition-content mt-3 text-base leading-relaxed text-inherit"><p>The opaflix-sync script may be installed on the same server as the OPA Gateway or on a separate server with access to the <code>.asa</code> files. See <a href="/howto/okta-opaflix-session-replay-tool/#session-conversion" >Session Conversion</a> for details.</p></div></div>
<h3 class="relative group">Application Architecture
    <div id="application-architecture" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#application-architecture" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The application is built with <strong>Node.js</strong>, <strong>Express</strong>, and <strong>Handlebars</strong> for the backend. Session recordings are served directly from S3 via presigned URLs: no server bandwidth is consumed for playback. Presigned URLs are short-lived (1 hour by default) for security, ensuring recordings cannot be accessed via stale links.</p>
<p>The following diagram illustrates the architecture of the Opaflix application, including its interactions with Okta for authentication, AWS S3 for storage, and the OPA API for real-time data:</p>
<pre class="not-prose mermaid">
---
config:
  layout: dagre
---
flowchart LR
 subgraph Client["Client"]
        Browser["🌐 Browser"]
  end
 subgraph Opaflix["Opaflix"]
        App["Express.js App"]
        Auth["Okta OIDC"]
  end
 subgraph AWS["AWS"]
        S3[("AWS S3")]
  end
  subgraph OktaCloud["Okta Cloud"]
        OPA["OPA API"]
        Okta["Okta IdP"]
  end
  subgraph Database["Database"]
        PG[("PostgreSQL")]
  end
    Browser --> App
    App --> Auth & S3 & OPA
    App -.-> PG
    Auth --> Okta
</pre>

<hr>

<h2 class="relative group">Quick Start
    <div id="quick-start" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#quick-start" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Get Opaflix running in single-tenant mode in a few steps:</p>
<ol>
<li>
<p>Create an Okta Web App (OIDC) and configure the redirect URIs</p>
</li>
<li>
<p>Create an AWS S3 bucket and upload converted session recordings</p>
</li>
<li>
<p>Clone the Opaflix repository and set up the environment variables</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Clone the repository</span>
</span></span><span class="line"><span class="cl">git clone https://github.com/fabiograsso/okta-opaflix.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> okta-opaflix
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create and edit the environment file</span>
</span></span><span class="line"><span class="cl">cp .env.example .env</span></span></code></pre></div></div>
</li>
<li>
<p>Fill in the required values in <code>.env</code>. The minimum required configuration in <code>.env</code> for single-tenant mode:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-env" data-lang="env"><span class="line"><span class="cl"><span class="c1"># Application</span>
</span></span><span class="line"><span class="cl"><span class="nv">BASE_URI</span><span class="o">=</span>http://localhost:3000
</span></span><span class="line"><span class="cl"><span class="nv">SESSION_SECRET</span><span class="o">=</span>your-secure-secret-minimum-32-characters-long
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Okta Authentication</span>
</span></span><span class="line"><span class="cl"><span class="nv">OKTA_ISSUER</span><span class="o">=</span>https://your-tenant.okta.com
</span></span><span class="line"><span class="cl"><span class="nv">OKTA_CLIENT_ID</span><span class="o">=</span>your-client-id
</span></span><span class="line"><span class="cl"><span class="nv">OKTA_CLIENT_SECRET</span><span class="o">=</span>your-client-secret
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># AWS S3</span>
</span></span><span class="line"><span class="cl"><span class="nv">AWS_REGION</span><span class="o">=</span>us-east-1
</span></span><span class="line"><span class="cl"><span class="nv">AWS_S3_BUCKET</span><span class="o">=</span>your-bucket
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Option 1: Static Access Keys</span>
</span></span><span class="line"><span class="cl"><span class="nv">AWS_ACCESS_KEY_ID</span><span class="o">=</span>your-access-key
</span></span><span class="line"><span class="cl"><span class="nv">AWS_SECRET_ACCESS_KEY</span><span class="o">=</span>your-secret-key
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Option 2: IAM Roles Anywhere</span>
</span></span><span class="line"><span class="cl"><span class="c1"># AWS_ROLES_ANYWHERE_TRUST_ANCHOR_ARN=arn:aws:rolesanywhere:us-east-1:123456789012:trust-anchor/abc123</span>
</span></span><span class="line"><span class="cl"><span class="c1"># AWS_ROLES_ANYWHERE_PROFILE_ARN=arn:aws:rolesanywhere:us-east-1:123456789012:profile/def456</span>
</span></span><span class="line"><span class="cl"><span class="c1"># AWS_ROLES_ANYWHERE_ROLE_ARN=arn:aws:iam::123456789012:role/OpaflixS3Access</span>
</span></span><span class="line"><span class="cl"><span class="c1"># AWS_ROLES_ANYWHERE_CERTIFICATE=&#34;-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># AWS_ROLES_ANYWHERE_PRIVATE_KEY=&#34;-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----&#34;</span></span></span></code></pre></div></div>
</li>
<li>
<p>Then start the application:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Option 1: Local Node.js</span>
</span></span><span class="line"><span class="cl">npm install <span class="o">&amp;&amp;</span> npm start
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Option 2: Docker Compose</span>
</span></span><span class="line"><span class="cl">make start</span></span></code></pre></div></div>
<p>Open <code>http://localhost:3000</code> and authenticate with Okta.</p>
</li>
</ol>
<div class="admonition relative overflow-hidden rounded-lg border-l-4 my-3 px-4 py-3 shadow-sm" data-type="info">
      <div class="flex items-center gap-2 font-semibold text-inherit">
        <div class="flex shrink-0 h-5 w-5 items-center justify-center text-lg"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span></div>
        <div class="grow">
          Multi-Tenant Mode and other features
        </div>
      </div><div class="admonition-content mt-3 text-base leading-relaxed text-inherit"><p>In the <a href="https://github.com/fabiograsso/okta-opaflix"  target="_blank" rel="noreferrer">GitHub Repository</a> you can find detailed documentation on how to set up multi-tenant mode, configure IAM Roles Anywhere, and automate session conversion and upload to S3.</p></div></div><hr>

<h2 class="relative group">Prerequisites and Setup
    <div id="prerequisites-and-setup" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#prerequisites-and-setup" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Before deploying Opaflix, you need:</p>
<table>
  <thead>
      <tr>
          <th>Component</th>
          <th>Required</th>
          <th>Notes</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Node.js 18+</td>
          <td>Yes</td>
          <td>Or Docker</td>
      </tr>
      <tr>
          <td>Okta Web App (OIDC)</td>
          <td>Yes</td>
          <td>For authentication</td>
      </tr>
      <tr>
          <td>AWS S3 Bucket</td>
          <td>Yes</td>
          <td>Stores converted session recordings</td>
      </tr>
      <tr>
          <td>OPA API Credentials</td>
          <td>No</td>
          <td>Optional, used to populate filter dropdowns and graph</td>
      </tr>
      <tr>
          <td>PostgreSQL</td>
          <td>Only for multi-tenant</td>
          <td>Neon serverless recommended</td>
      </tr>
  </tbody>
</table>

<h3 class="relative group">Okta OIDC Setup
    <div id="okta-oidc-setup" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#okta-oidc-setup" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>Create a new <strong>Web Application</strong> in your Okta Admin Console</li>
<li>Set the <strong>Sign-in redirect URI</strong> to <code>http://localhost:3000/authorization-code/callback</code> (or your custom domain)</li>
<li>Set the <strong>Sign-out redirect URI</strong> to <code>http://localhost:3000/login</code></li>
<li>Copy the <strong>Client ID</strong> and <strong>Client Secret</strong> to your <code>.env</code> file</li>
</ol>

<h3 class="relative group">OPA Gateway Configuration
    <div id="opa-gateway-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#opa-gateway-configuration" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>For Opaflix to correctly parse session metadata, recordings must follow a specific filename format. Edit <code>/etc/sft/sft-gatewayd.yaml</code> on your OPA Gateway:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">LogFileNameFormats</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">SSHRecording</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{.Protocol}}~{{.StartTime}}~{{.TeamName}}~{{.ProjectName}}~{{.ServerName}}~{{.Username}}~&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">RDPRecording</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{.Protocol}}~{{.StartTime}}~{{.TeamName}}~{{.ProjectName}}~{{.ServerName}}~{{.Username}}~&#34;</span></span></span></code></pre></div></div>
<p>Restart the gateway with <code>sudo systemctl restart sft-gatewayd</code>.</p>
<p>This produces filenames like:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ssh~2024-01-15T10:30:00Z~acme-team~production~web-server-01~admin~.asa</span></span></code></pre></div></div>

<h3 class="relative group">Session Conversion
    <div id="session-conversion" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#session-conversion" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>OPA session recordings (<code>.asa</code> files) must be converted before uploading to S3:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># SSH: .asa → .cast (Asciinema format)</span>
</span></span><span class="line"><span class="cl">sft session-logs <span class="nb">export</span> --insecure --format asciinema /path/source.asa --output /path/output.cast
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># RDP: .asa → .mkv</span>
</span></span><span class="line"><span class="cl">sft session-logs <span class="nb">export</span> --insecure --format mkv --output /path/ /path/source.asa</span></span></code></pre></div></div>
<p>Two conversion utilities are provided in <code>scripts/convert-sessions/</code>:</p>
<table>
  <thead>
      <tr>
          <th>Script</th>
          <th>Type</th>
          <th>Use Case</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>convert-sessions.sh</code></td>
          <td>Bash</td>
          <td>Manual or cron-based batch conversion</td>
      </tr>
      <tr>
          <td><code>opaflix-sync.py</code></td>
          <td>Python</td>
          <td>Automated service with file system monitoring</td>
      </tr>
  </tbody>
</table>
<p><strong>Bash Script (<code>convert-sessions.sh</code>)</strong>: A straightforward batch converter. Point it at a directory of <code>.asa</code> files, and it will convert them to the appropriate format (<code>.cast</code> for SSH, <code>.mkv</code> for RDP). Ideal for one-off conversions and tests. You have then to manually upload the converted files to S3 (e.g. using <code>aws s3 cp</code> or manually uploading the files in the AWS Management Console).</p>
<p><strong>Python Service (<code>opaflix-sync.py</code>)</strong>: A more sophisticated option that runs as a background service. It uses <strong>file system monitoring</strong> (via <code>watchdog</code>) to detect new <code>.asa</code> files as they arrive, converts them automatically, and uploads them to the S3 bucket. It includes duplicate detection, configurable cleanup of old source files, and a systemd service file for production deployments. This is the recommended approach for production environments where you want near real-time availability of session recordings in Opaflix.</p>
<p>You can read more details in <a href="https://github.com/fabiograsso/okta-opaflix/blob/main/scripts/convert-sessions/README.md"  target="_blank" rel="noreferrer">scripts/convert-sessions/README.md</a> on GitHub.</p>

<h3 class="relative group">AWS S3 Bucket Setup
    <div id="aws-s3-bucket-setup" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#aws-s3-bucket-setup" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Opaflix requires an S3 bucket to store converted session recordings. The bucket needs appropriate IAM policies to allow Opaflix to generate presigned URLs for playback.</p>
<p>You can find the full S3 setup documentation on the GitHub repository:</p>
<ul>
<li><a href="https://github.com/fabiograsso/okta-opaflix/blob/main/docs/AWS.md"  target="_blank" rel="noreferrer">docs/AWS.md</a> for general AWS configuration</li>
<li><a href="https://github.com/fabiograsso/okta-opaflix/blob/main/scripts/aws/README.md"  target="_blank" rel="noreferrer">scripts/aws/README.md</a> for AWS CLI and CloudFormation commands</li>
</ul>
<p>A <strong>CloudFormation template</strong> is provided to automate the entire AWS setup, including:</p>
<ul>
<li>S3 bucket with proper CORS configuration for browser-based playback</li>
<li>IAM policy with least-privilege permissions for Opaflix</li>
<li>Optional IAM Roles Anywhere trust anchor and profile (for deployments outside AWS)</li>
</ul>
<hr>

<h2 class="relative group">Known Limitations
    <div id="known-limitations" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#known-limitations" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Opaflix is a v1 release. There are a few intentional constraints worth knowing:</p>
<ul>
<li><strong>No permission management.</strong> All authenticated users can access all recordings. The primary audience is PAM Admins and Auditors, adding granular permission management is on the long-term roadmap, but keeping things simple was the priority for this release.</li>
<li><strong>S3 only.</strong> Other storage backends may be evaluated in the future based on feedback.</li>
<li><strong>Vibe Coded.</strong> As mentioned above, this project was built with heavy AI assistance. It&rsquo;s been tested, but if you find a bug, please <a href="https://github.com/fabiograsso/okta-opaflix/issues"  target="_blank" rel="noreferrer">open an issue</a>.</li>
</ul>
<hr>

<h2 class="relative group">What&rsquo;s Next
    <div id="whats-next" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#whats-next" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The project is live and open for contributions. The immediate roadmap includes:</p>
<ul>
<li>Improve the initial tenant creation process in multi-tenant mode with a dedicated UI</li>
<li>Evaluate granular permission management based on community feedback</li>
<li>Evaluate additional storage backends beyond S3</li>
</ul>
<hr>

<h2 class="relative group">Documentation Links
    <div id="documentation-links" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#documentation-links" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The complete documentation is available in the <a href="https://github.com/fabiograsso/okta-opaflix"  target="_blank" rel="noreferrer">GitHub Repository</a>, here are some quick links to get you started:</p>
<ul>
<li><a href="https://github.com/fabiograsso/okta-opaflix"  target="_blank" rel="noreferrer">README.md - Main Documentation</a></li>
<li><a href="https://github.com/fabiograsso/okta-opaflix/blob/main/docs/AWS.md"  target="_blank" rel="noreferrer">docs/AWS.md - General AWS Configuration</a></li>
<li><a href="https://github.com/fabiograsso/okta-opaflix/blob/main/scripts/aws/README.md"  target="_blank" rel="noreferrer">scripts/aws/README.md - AWS CLI and CloudFormation Commands</a></li>
<li><a href="https://github.com/fabiograsso/okta-opaflix/blob/main/scripts/convert-sessions/README.md"  target="_blank" rel="noreferrer">scripts/convert-sessions/README.md - Session Conversion Scripts</a></li>
</ul>
<div class="admonition relative overflow-hidden rounded-lg border-l-4 my-3 px-4 py-3 shadow-sm" data-type="info">
      <div class="flex items-center gap-2 font-semibold text-inherit">
        <div class="flex shrink-0 h-5 w-5 items-center justify-center text-lg"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span></div>
        <div class="grow">
          Feedback Welcome
        </div>
      </div><div class="admonition-content mt-3 text-base leading-relaxed text-inherit"><p>If you have suggestions for features, improvements, or if you find any issues, please don&rsquo;t hesitate to <a href="/howto/okta-opaflix-session-replay-tool/#comments" >comment here</a> or <a href="https://github.com/fabiograsso/okta-opaflix/issues"  target="_blank" rel="noreferrer">open an issue on GitHub</a>.</p></div></div><hr>

<h2 class="relative group">Conclusion
    <div id="conclusion" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#conclusion" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Opaflix fills a practical gap for anyone running Okta Privileged Access at scale. If your security team needs to review session recordings regularly, having a searchable, authenticated, browser-based player dramatically reduces friction, whether you&rsquo;re responding to an incident or running a quarterly compliance review.</p>
<p>The project is available now at <strong><a href="https://github.com/fabiograsso/okta-opaflix"  target="_blank" rel="noreferrer">github.com/fabiograsso/okta-opaflix</a></strong> under the Apache 2.0 license.</p>

  <div class="github-card-wrapper">
    <a id="github-06272ebb539cbaf509c071168fdd1dc1" target="_blank" href="https://github.com/fabiograsso/okta-opaflix" class="cursor-pointer">
      <div
        class="w-full md:w-auto p-0 m-0 border border-neutral-200 dark:border-neutral-700 border rounded-md shadow-2xl">
        <div class="w-full md:w-auto pt-3 p-5">
          <div class="flex items-center">
            <span class="text-2xl text-neutral-800 dark:text-neutral me-2">
              <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
</span>
            </span>
            <div
              id="github-06272ebb539cbaf509c071168fdd1dc1-full_name"
              class="m-0 font-bold text-xl text-neutral-800 decoration-primary-500 hover:underline hover:underline-offset-2 dark:text-neutral">
              fabiograsso/okta-opaflix
            </div>
          </div>
          <p id="github-06272ebb539cbaf509c071168fdd1dc1-description" class="m-0 mt-2 text-md text-neutral-800 dark:text-neutral">
            View this repository on GitHub
          </p>
        </div>
      </div>
      
      
      <script
        async
        type="text/javascript"
        src="/js/fetch-repo.min.dc5533c50cefd50405344b235937142271f26229fe39cbee27fd4960e8bb897a0beebfad77a1091ca91cd0d1fb14e70fc37cc114dd9674fb2c32e0ab512ec8a4.js"
        integrity="sha512-3FUzxQzv1QQFNEsjWTcUInHyYin&#43;OcvuJ/1JYOi7iXoL7r&#43;td6EJHKkc0NH7FOcPw3zBFN2WdPssMuCrUS7IpA=="
        data-repo-url="https://api.github.com/repos/fabiograsso/okta-opaflix"
        data-repo-id="github-06272ebb539cbaf509c071168fdd1dc1"></script>
    </a>
  </div>
<p><em>Questions or feedback?</em> If you try it, feel free to <a href="https://github.com/fabiograsso/okta-opaflix/issues"  target="_blank" rel="noreferrer">open issues</a>, submit PRs, or leave a <a href="/howto/okta-opaflix-session-replay-tool/#comments" >comment below</a>. Feedback from the field is always welcome.</p>

<h3 class="relative group">Special Thanks
    <div id="special-thanks" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#special-thanks" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><strong>Pascale Kik</strong> - For her invaluable feedback and testing efforts during the early stages of development. Her insights helped shape the user experience and identify critical issues.</li>
<li><strong>Okta Community</strong> - For their support and engagement, which inspired the creation of Opaflix as a tool to benefit PAM administrators and auditors.</li>
</ul>
]]></content:encoded>
      <category>Okta</category>
      <category>PAM</category>
      <category>Privileged Access</category>
      <category>Okta Privileged Access</category>
      <category>OPA</category>
      <category>AWS</category>
      <category>S3</category>
      <category>Open Source</category>
      <category>Node.js</category>
      <category>Docker</category>
    </item>
    <item>
      <title>Okta On-Prem SCIM Server: Technical Deep Dive and Architecture</title>
      <link>https://iam.fabiograsso.net/howto/okta-scim-server-deep-dive/</link>
      <pubDate>Mon, 02 Mar 2026 01:00:00 +0100</pubDate>
      <guid>https://iam.fabiograsso.net/howto/okta-scim-server-deep-dive/</guid>
      <description>Technical deep dive into the Okta On-Prem SCIM Server architecture, REST endpoints, authentication mechanisms, and internal workings. Learn how the SCIM server translates provisioning requests into database operations through reverse-engineered analysis for educational purposes.</description>
      <content:encoded>&lt;![CDATA[
<h2 class="relative group">Introduction
    <div id="introduction" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#introduction" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>You&rsquo;ve deployed the Okta Generic Database Connector and configured provisioning workflows, but have you ever wondered what happens behind the scenes? How does the SCIM Server translate those high-level provisioning requests from Okta into actual database operations? What REST endpoints does it expose, and how does the authentication actually work?</p>
<p>This article provides a technical deep dive into the <strong>Okta On-Prem SCIM Server</strong>—the critical component that sits between the Okta Provisioning Agent and your database, translating SCIM protocol calls into JDBC operations. Through reverse engineering and analysis of the SCIM Server JAR file, we&rsquo;ll explore its internal architecture, REST controllers, authentication mechanisms, and the mysterious <code>X-OKTA-ONPREM-DATA</code> header that makes multi-tenancy possible.</p>
<p><strong>Why This Matters:</strong></p>
<p>Understanding the SCIM Server&rsquo;s internals helps you:</p>
<ul>
<li><strong>Troubleshoot issues</strong> more effectively by knowing exactly where requests flow</li>
<li><strong>Optimize performance</strong> by understanding connection pooling and configuration</li>
<li><strong>Debug provisioning failures</strong> by interpreting log patterns and error messages</li>
<li><strong>Appreciate the architecture</strong> of Okta&rsquo;s on-premises provisioning solution</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855809607264 {
    background: #FFFAE6;
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855809607264 .panel-icon {
    color: rgb(224,108,0);
  }
  html.dark #panel-1778753855809607264 {
    background: rgb(51,46,27);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855809607264 .panel-icon {
    color: rgb(251,200,40);
  }
</style>

<div id="panel-1778753855809607264" class="flex px-4 py-3 rounded-md shadow panel-warning ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>
  <div class="panel-text"><p><strong>IMPORTANT DISCLAIMER: This document is NOT official Okta documentation.</strong></p>
<p>The information in this article has been obtained through <strong>reverse engineering and analysis of the Okta On-Prem SCIM Server JAR file</strong>. The content is provided for <strong>educational and learning purposes only</strong>.</p>
<ul>
<li>
<p><strong>No Official Documentation</strong>: Okta does not publish official API documentation or technical specifications for the On-Prem SCIM Server&rsquo;s internal workings.</p>
</li>
<li>
<p><strong>Unsupported Use</strong>: The <strong>ONLY officially supported way</strong> to use the Okta On-Prem SCIM Server is:</p>
<ul>
<li>Through the <strong>Okta Provisioning Agent (OPP Agent)</strong></li>
<li>Via configuration in the <strong>Okta Admin Console</strong></li>
<li>Following official Okta documentation and guidelines</li>
</ul>
</li>
<li>
<p><strong>Direct API Access Not Supported</strong>: Directly calling the SCIM Server APIs documented here is <strong>not supported by Okta</strong> and should only be done for:</p>
<ul>
<li>Educational purposes</li>
<li>Testing and debugging in lab environments</li>
<li>Understanding the system architecture</li>
<li>Troubleshooting with Okta Support guidance</li>
</ul>
</li>
<li>
<p><strong>Use at Your Own Risk</strong>: Any use of this information outside of the officially supported methods is at your own risk and may:</p>
<ul>
<li>Void support agreements</li>
<li>Cause unexpected behavior</li>
<li>Break with future updates</li>
<li>Introduce security vulnerabilities</li>
</ul>
</li>
</ul>

<h3 class="relative group">Official Documentation:
    <div id="official-documentation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#official-documentation" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Always refer to official Okta documentation:</p>
<ul>
<li><a href="https://help.okta.com/oie/en-us/content/topics/provisioning/opp/on-prem-scim-install.htm"  target="_blank" rel="noreferrer">Install the Okta On-prem SCIM Server</a></li>
<li><a href="https://help.okta.com/oie/en-us/content/topics/provisioning/opc/connectors/on-prem-connector-generic-db.htm"  target="_blank" rel="noreferrer">On-premises Connector for Generic Databases</a></li>
</ul>
<p><strong>By reading this article, you acknowledge that this information is for learning purposes only and that you will use the Okta On-Prem SCIM Server only through officially supported methods in production environments.</strong></p>

  </div>
</div>
<hr>

<h2 class="relative group">Overview
    <div id="overview" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#overview" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The Okta On-Prem SCIM Server is a <strong>Spring Boot 3.5.0</strong> application that implements the SCIM 2.0 protocol for provisioning users, groups, and entitlements to on-premises databases. It acts as a bridge between <strong>Okta&rsquo;s cloud</strong>, the <em>On-Premise Provisioning (OPP) Agent</em>, and your <em>local database</em> infrastructure.</p>

<h3 class="relative group">Key Characteristics
    <div id="key-characteristics" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#key-characteristics" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><strong>Application Type</strong>: Spring Boot 3.5.0 JAR (executable with embedded Tomcat)</li>
<li><strong>Main Class</strong>: <code>com.okta.server.scim.ScimServerApplication</code></li>
<li><strong>Launcher</strong>: <code>org.springframework.boot.loader.launch.JarLauncher</code> (Spring Boot fat JAR)</li>
<li><strong>JAR Location</strong>: <code>data/okta-scim/conf/OktaOnPremScimServer-&lt;version&gt;.jar</code></li>
<li><strong>Default Port</strong>: <code>1443</code> (HTTPS)</li>
<li><strong>Base Context Path</strong>: <code>/ws/rest</code></li>
<li><strong>Protocol</strong>: HTTPS only (TLS 1.2, TLS 1.3)</li>
</ul>
<hr>

<h2 class="relative group">Architecture
    <div id="architecture" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#architecture" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Application Stack
    <div id="application-stack" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#application-stack" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The SCIM Server is built on a modern Spring Boot stack with embedded Tomcat, JDBC connectivity, and REST controllers that implement the SCIM 2.0 specification.</p>
<pre class="not-prose mermaid">
flowchart TB
 subgraph SCIM["<b>Okta On-Prem SCIM Server</b>"]
    direction TB
        E["Spring Boot 3.5.0"]
        F["Embedded Tomcat 10.x<br>- HTTPS/TLS<br>- Port 1443"]
        H["JDBC Connector:<br>- HikariCP Pool<br>- Stored Proc Executor"]
        G["REST Controllers:<br>- UserController<br>- GroupController<br>- EntitlementController<br>- StatusController"]
  end
 subgraph DB["<b>Database</b>"]
    direction TB
        I["Table USERS<br>Table ENTITLEMENTS<br>Table USERENTITLEMENTS<br>Stored Procedures"]
  end
    E -.->|manages| F & H & G
    OC["<b>Okta Cloud</b>"] -- HTTPS --> OPP["<b>Okta Provisioning Agent</b>"]
    SCIM -- JDBC Connection --> DB
    OPP -- "HTTPS + Bearer Token +<br>X-OKTA-ONPREM-DATA header" --> SCIM

    style OC fill:#e1f5ff
    style OPP fill:#fff4e6
    style SCIM fill:#e8f5e9
    style DB fill:#fce4ec
</pre>


<h3 class="relative group">Core Components
    <div id="core-components" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#core-components" aria-label="Anchor">#</a>
    </span>
    
</h3>

<h4 class="relative group">1. Embedded Tomcat Server
    <div id="1-embedded-tomcat-server" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#1-embedded-tomcat-server" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li><strong>Version</strong>: Apache Tomcat 10.x</li>
<li><strong>Protocol</strong>: HTTPS with NIO connector</li>
<li><strong>Bundled with</strong>: Spring Boot 3.5.0</li>
<li><strong>Port</strong>: 1443 (configurable)</li>
<li><strong>TLS Support</strong>: TLS 1.2 and TLS 1.3</li>
</ul>
<p>The embedded Tomcat server handles all HTTPS connections, TLS handshakes, and HTTP request processing. Since it uses self-signed certificates by default, you&rsquo;ll see TLS handshake warnings in logs—this is expected behavior.</p>

<h4 class="relative group">2. REST Controllers
    <div id="2-rest-controllers" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#2-rest-controllers" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Located in <code>BOOT-INF/classes/com/okta/server/scim/controller/</code>:</p>
<table>
  <thead>
      <tr>
          <th>Controller</th>
          <th>Purpose</th>
          <th>Base Path</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>UserController.java</code></td>
          <td>User CRUD operations</td>
          <td><code>/{app}/scim/v2/Users</code></td>
      </tr>
      <tr>
          <td><code>GroupController.java</code></td>
          <td>Group operations (not used for Database Connector)</td>
          <td><code>/{app}/scim/v2/Groups</code></td>
      </tr>
      <tr>
          <td><code>EntitlementController.java</code></td>
          <td>Entitlement management</td>
          <td><code>/{app}/scim/v2/Entitlements</code></td>
      </tr>
      <tr>
          <td><code>StatusController.java</code></td>
          <td>Health check</td>
          <td><code>/{app}/scim/v2/Status</code></td>
      </tr>
      <tr>
          <td><code>ServiceProviderConfigController.java</code></td>
          <td>SCIM capabilities</td>
          <td><code>/{app}/scim/v2/ServiceProviderConfig</code></td>
      </tr>
      <tr>
          <td><code>SchemaImportController.java</code></td>
          <td>SCIM schema definitions</td>
          <td><code>/{app}/scim/v2/Schemas</code></td>
      </tr>
      <tr>
          <td><code>ResourceTypeController.java</code></td>
          <td>SCIM resource types</td>
          <td><code>/{app}/scim/v2/ResourceTypes</code></td>
      </tr>
      <tr>
          <td><code>ScimExceptionHandler.java</code></td>
          <td>Global exception handling</td>
          <td>N/A</td>
      </tr>
  </tbody>
</table>
<p>These controllers implement the SCIM 2.0 REST API specification, handling JSON payloads and translating them into database operations.</p>

<h4 class="relative group">3. JDBC Connector
    <div id="3-jdbc-connector" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#3-jdbc-connector" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Located in <code>BOOT-INF/classes/com/okta/server/scim/connector/jdbc/</code>:</p>
<ul>
<li><strong>DataSource Management</strong>: HikariCP connection pooling</li>
<li><strong>Executor</strong>: Stored procedure and SQL queries execution engine</li>
<li><strong>Service Layer</strong>: Workflow orchestration for SCIM operations</li>
<li><strong>Configuration</strong>: Dynamic connector configuration via properties</li>
</ul>
<p>The JDBC connector is responsible for all database interactions, managing connection pools, executing stored procedures, and handling transaction boundaries.</p>

<h4 class="relative group">4. Security Layer
    <div id="4-security-layer" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#4-security-layer" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Located in <code>BOOT-INF/classes/com/okta/server/scim/security/</code>:</p>
<ul>
<li><strong>Bearer Token Authentication</strong>: Simple token-based auth</li>
<li><strong>SSL/TLS</strong>: Server-side certificate authentication</li>
<li><strong>No mTLS</strong>: Client certificate authentication disabled (<code>server.ssl.client-auth=NONE</code>)</li>
</ul>
<p>The security layer validates the <code>Authorization</code> header on every request and enforces HTTPS-only communication.</p>
<hr>

<h2 class="relative group">Authentication
    <div id="authentication" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#authentication" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Bearer Token Authentication
    <div id="bearer-token-authentication" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#bearer-token-authentication" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The SCIM server uses <strong>Bearer token authentication</strong> for all SCIM API requests. This is a simple but effective authentication mechanism that requires each request to include a valid bearer token in the <code>Authorization</code> header.</p>

<h4 class="relative group">Configuration of the Token
    <div id="configuration-of-the-token" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuration-of-the-token" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Bearer token is configured in the properties file:</p>
<p><strong>File</strong>: <code>data/okta-scim/conf/config-{CUSTOMER_ID}.properties</code></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="na">scim.security.bearer.token</span><span class="o">=</span><span class="s">d5307740c879491cedecf70c2225776b</span></span></span></code></pre></div></div>
<p>This token is <strong>auto-generated</strong> during first startup by the RPM installer (or in the Docker Compose lab, the Docker entrypoint script).</p>

<h4 class="relative group">Usage
    <div id="usage" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#usage" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p><strong>Header Format</strong>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">Authorization: Bearer &lt;token&gt;</span></span></code></pre></div></div>
<p><strong>Example</strong>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -k -H <span class="s2">&#34;Authorization: Bearer d5307740c879491cedecf70c2225776b&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  https://localhost:1443/ws/rest/jdbc_on_prem/scim/v2/Status</span></span></code></pre></div></div>

<h4 class="relative group">Important Notes
    <div id="important-notes" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#important-notes" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ol>
<li><strong>Case Sensitive</strong>: The word <code>Bearer</code> must be capitalized</li>
<li><strong>Space Required</strong>: There must be exactly one space between <code>Bearer</code> and the token</li>
<li><strong>Length</strong>: 32 characters (hex string)</li>
<li><strong>Rotation</strong>: To change the token, modify the properties file and restart the SCIM server. You will also need to update the configuration of the Generic Database Connector Application in Okta</li>
</ol>
<hr>

<h2 class="relative group">SCIM Endpoints
    <div id="scim-endpoints" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#scim-endpoints" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Endpoint URL Pattern
    <div id="endpoint-url-pattern" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#endpoint-url-pattern" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>All SCIM endpoints follow this pattern:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">https://{host}:{port}/ws/rest/{app}/scim/v2/{resource}</span></span></code></pre></div></div>
<p>Where:</p>
<ul>
<li><code>{host}</code>: SCIM server hostname (e.g., <code>okta-scim</code> or <code>localhost</code>)</li>
<li><code>{port}</code>: SCIM server port (default: <code>1443</code>)</li>
<li><code>{app}</code>: Application/connector name (e.g., <code>jdbc_on_prem</code>)</li>
<li><code>{resource}</code>: SCIM resource type (<code>Users</code>, <code>Entitlements</code>, etc.)</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855810300848 {
    background: rgb(248,238,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855810300848 .panel-icon {
    color: rgb(175,89,225);
  }
  html.dark #panel-1778753855810300848 {
    background: rgb(53,36,63);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855810300848 .panel-icon {
    color: rgb(191,99,243);
  }
</style>

<div id="panel-1778753855810300848" class="flex px-4 py-3 rounded-md shadow panel-note ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M421.7 220.3L188.5 453.4L154.6 419.5L158.1 416H112C103.2 416 96 408.8 96 400V353.9L92.51 357.4C87.78 362.2 84.31 368 82.42 374.4L59.44 452.6L137.6 429.6C143.1 427.7 149.8 424.2 154.6 419.5L188.5 453.4C178.1 463.8 165.2 471.5 151.1 475.6L30.77 511C22.35 513.5 13.24 511.2 7.03 504.1C.8198 498.8-1.502 489.7 .976 481.2L36.37 360.9C40.53 346.8 48.16 333.9 58.57 323.5L291.7 90.34L421.7 220.3zM492.7 58.75C517.7 83.74 517.7 124.3 492.7 149.3L444.3 197.7L314.3 67.72L362.7 19.32C387.7-5.678 428.3-5.678 453.3 19.32L492.7 58.75z"/></svg>
</span>
  </span>
  <div class="panel-text">In addition to the <strong>Authentication header (Bearer Token)</strong>, these endpoints require the <strong>X-OKTA-ONPREM-DATA header</strong>, as described in the dedicated section below. The Status endpoint is the only exception—it does not require the X-OKTA-ONPREM-DATA header.
  </div>
</div>

<h3 class="relative group">User Operations
    <div id="user-operations" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#user-operations" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p><strong>Base Path</strong>: <code>{app}/scim/v2/Users</code></p>

<h4 class="relative group">List Users
    <div id="list-users" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#list-users" aria-label="Anchor">#</a>
    </span>
    
</h4>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">GET /{app}/scim/v2/Users?startIndex=1&amp;count=100
</span></span><span class="line"><span class="cl">Authorization: Bearer {token}
</span></span><span class="line"><span class="cl">X-OKTA-ONPREM-DATA: {base64_encoded_config}</span></span></code></pre></div></div>
<p><strong>Query Parameters</strong>:</p>
<ul>
<li><code>startIndex</code> (optional): Starting index for pagination (1-based)</li>
<li><code>count</code> (optional): Number of results to return</li>
<li><code>filter</code> (optional): SCIM filter expression</li>
</ul>
<p><strong>Response</strong>: SCIM ListResponse with user resources</p>

<h4 class="relative group">Get User by ID
    <div id="get-user-by-id" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#get-user-by-id" aria-label="Anchor">#</a>
    </span>
    
</h4>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">GET /{app}/scim/v2/Users/{userId}
</span></span><span class="line"><span class="cl">Authorization: Bearer {token}
</span></span><span class="line"><span class="cl">X-OKTA-ONPREM-DATA: {base64_encoded_config}</span></span></code></pre></div></div>
<p><strong>Path Parameters</strong>:</p>
<ul>
<li><code>{userId}</code>: User identifier (typically email or username)</li>
</ul>
<p><strong>Response</strong>: SCIM User resource</p>

<h4 class="relative group">Create User
    <div id="create-user" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#create-user" aria-label="Anchor">#</a>
    </span>
    
</h4>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">POST /{app}/scim/v2/Users
</span></span><span class="line"><span class="cl">Authorization: Bearer {token}
</span></span><span class="line"><span class="cl">X-OKTA-ONPREM-DATA: {base64_encoded_config}
</span></span><span class="line"><span class="cl">Content-Type: application/json
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">{
</span></span><span class="line"><span class="cl">  &#34;schemas&#34;: [&#34;urn:ietf:params:scim:schemas:core:2.0:User&#34;],
</span></span><span class="line"><span class="cl">  &#34;userName&#34;: &#34;john.doe&#34;,
</span></span><span class="line"><span class="cl">  &#34;name&#34;: {
</span></span><span class="line"><span class="cl">    &#34;givenName&#34;: &#34;John&#34;,
</span></span><span class="line"><span class="cl">    &#34;familyName&#34;: &#34;Doe&#34;
</span></span><span class="line"><span class="cl">  },
</span></span><span class="line"><span class="cl">  &#34;emails&#34;: [
</span></span><span class="line"><span class="cl">    {
</span></span><span class="line"><span class="cl">      &#34;value&#34;: &#34;john.doe@example.com&#34;,
</span></span><span class="line"><span class="cl">      &#34;primary&#34;: true
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">  ],
</span></span><span class="line"><span class="cl">  &#34;active&#34;: true
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div>
<p><strong>Response</strong>: 201 Created with SCIM User resource</p>

<h4 class="relative group">Update User
    <div id="update-user" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#update-user" aria-label="Anchor">#</a>
    </span>
    
</h4>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">PUT /{app}/scim/v2/Users/{userId}
</span></span><span class="line"><span class="cl">Authorization: Bearer {token}
</span></span><span class="line"><span class="cl">X-OKTA-ONPREM-DATA: {base64_encoded_config}
</span></span><span class="line"><span class="cl">Content-Type: application/json
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">{
</span></span><span class="line"><span class="cl">  &#34;schemas&#34;: [&#34;urn:ietf:params:scim:schemas:core:2.0:User&#34;],
</span></span><span class="line"><span class="cl">  &#34;id&#34;: &#34;{userId}&#34;,
</span></span><span class="line"><span class="cl">  &#34;userName&#34;: &#34;john.doe&#34;,
</span></span><span class="line"><span class="cl">  &#34;name&#34;: {
</span></span><span class="line"><span class="cl">    &#34;givenName&#34;: &#34;John&#34;,
</span></span><span class="line"><span class="cl">    &#34;familyName&#34;: &#34;Doe&#34;
</span></span><span class="line"><span class="cl">  },
</span></span><span class="line"><span class="cl">  &#34;active&#34;: false
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div>
<p><strong>Response</strong>: 200 OK with updated SCIM User resource</p>

<h3 class="relative group">Entitlement Operations
    <div id="entitlement-operations" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#entitlement-operations" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p><strong>Base Path</strong>: <code>{app}/scim/v2/Entitlements</code></p>

<h4 class="relative group">List Entitlements
    <div id="list-entitlements" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#list-entitlements" aria-label="Anchor">#</a>
    </span>
    
</h4>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">GET /{app}/scim/v2/Entitlements
</span></span><span class="line"><span class="cl">Authorization: Bearer {token}
</span></span><span class="line"><span class="cl">X-OKTA-ONPREM-DATA: {base64_encoded_config}</span></span></code></pre></div></div>
<p><strong>Response</strong>: SCIM ListResponse with entitlement resources</p>

<h4 class="relative group">Get Entitlement by ID
    <div id="get-entitlement-by-id" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#get-entitlement-by-id" aria-label="Anchor">#</a>
    </span>
    
</h4>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">GET /{app}/scim/v2/Entitlements/{id}
</span></span><span class="line"><span class="cl">Authorization: Bearer {token}
</span></span><span class="line"><span class="cl">X-OKTA-ONPREM-DATA: {base64_encoded_config}</span></span></code></pre></div></div>
<p><strong>Response</strong>: SCIM Entitlement resource</p>

<h3 class="relative group">SCIM Metadata Endpoints
    <div id="scim-metadata-endpoints" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#scim-metadata-endpoints" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>These endpoints provide metadata about the SCIM server&rsquo;s capabilities, schemas, and supported resource types.</p>

<h4 class="relative group">Service Provider Configuration
    <div id="service-provider-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#service-provider-configuration" aria-label="Anchor">#</a>
    </span>
    
</h4>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">GET /{app}/scim/v2/ServiceProviderConfig
</span></span><span class="line"><span class="cl">Authorization: Bearer {token}
</span></span><span class="line"><span class="cl">X-OKTA-ONPREM-DATA: {base64_encoded_config}</span></span></code></pre></div></div>
<p>Returns SCIM server capabilities (supported operations, bulk operations, filtering, etc.)</p>

<h4 class="relative group">Schemas
    <div id="schemas" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#schemas" aria-label="Anchor">#</a>
    </span>
    
</h4>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">GET /{app}/scim/v2/Schemas
</span></span><span class="line"><span class="cl">Authorization: Bearer {token}
</span></span><span class="line"><span class="cl">X-OKTA-ONPREM-DATA: {base64_encoded_config}</span></span></code></pre></div></div>
<p>Returns SCIM schema definitions for User, Group, and custom resources.</p>

<h4 class="relative group">Resource Types
    <div id="resource-types" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#resource-types" aria-label="Anchor">#</a>
    </span>
    
</h4>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">GET /{app}/scim/v2/ResourceTypes
</span></span><span class="line"><span class="cl">Authorization: Bearer {token}
</span></span><span class="line"><span class="cl">X-OKTA-ONPREM-DATA: {base64_encoded_config}</span></span></code></pre></div></div>
<p>Returns available SCIM resource types (User, Group, Entitlement).</p>
<hr>

<h2 class="relative group">Health Check
    <div id="health-check" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#health-check" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Status Endpoint
    <div id="status-endpoint" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#status-endpoint" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The Status endpoint is the <strong>only endpoint that does NOT require the <code>X-OKTA-ONPREM-DATA</code> header</strong>. It&rsquo;s designed for health checks and monitoring systems.</p>
<p><strong>Endpoint</strong>: <code>{app}/scim/v2/Status</code></p>
<p><strong>Method</strong>: <code>GET</code></p>
<p><strong>Authentication</strong>: Bearer token only</p>
<p><strong>Example</strong>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -k -H <span class="s2">&#34;Authorization: Bearer d5307740c879491cedecf70c2225776b&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  https://localhost:1443/ws/rest/jdbc_on_prem/scim/v2/Status</span></span></code></pre></div></div>
<p><strong>Success Response</strong>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">HTTP/1.1 200 OK
</span></span><span class="line"><span class="cl">Content-Type: text/plain;charset=UTF-8
</span></span><span class="line"><span class="cl">Content-Length: 27
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">✅ Scim Server is running.</span></span></code></pre></div></div>
<p><strong>Use Cases</strong>:</p>
<ul>
<li>Docker health checks (<code>HEALTHCHECK</code> directive)</li>
<li>Monitoring systems (Nagios, Prometheus, Zabbix, etc.)</li>
<li>Load balancer health checks</li>
<li>Startup validation scripts</li>
</ul>
<p>This simple endpoint confirms the SCIM server is running and accepting HTTPS connections. It doesn&rsquo;t validate database connectivity—it only confirms the web server is operational.</p>
<hr>

<h2 class="relative group">X-OKTA-ONPREM-DATA Header
    <div id="x-okta-onprem-data-header" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#x-okta-onprem-data-header" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Purpose
    <div id="purpose" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#purpose" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The <code>X-OKTA-ONPREM-DATA</code> header is a <strong>custom HTTP header</strong> that contains the <strong>connector configuration</strong> required for SCIM operations. It&rsquo;s automatically added by the <strong>Okta Provisioning Agent</strong> when forwarding requests to the SCIM server.</p>
<p>This header is what enables a single SCIM Server to manage multiple database systems simultaneously—each request carries its own configuration payload.</p>

<h3 class="relative group">Contents
    <div id="contents" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#contents" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The header contains a <strong>Base64-encoded JSON payload</strong> with:</p>
<ul>
<li><strong>Database connection details</strong>: JDBC URL, username, password</li>
<li><strong>Stored procedure mappings</strong>: Which stored procedures to call for each SCIM operation</li>
<li><strong>Attribute mappings</strong>: How SCIM attributes map to database columns/parameters</li>
<li><strong>Connector metadata</strong>: Connector type, version, configuration version</li>
</ul>

<h3 class="relative group">Why It&rsquo;s Required
    <div id="why-its-required" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-its-required" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li><strong>Multi-tenancy</strong>: The SCIM server can serve multiple connectors simultaneously</li>
<li><strong>Dynamic Configuration</strong>: Each request includes its own configuration, allowing runtime changes</li>
<li><strong>Security</strong>: Configuration is passed per-request rather than stored on the SCIM server</li>
<li><strong>Flexibility</strong>: Different Okta apps can use different database configurations</li>
</ol>
<p>This architecture means you can have one SCIM server managing up to 8 different databases, each with its own connection details, stored procedures, and attribute mappings—all determined dynamically by the header content.</p>

<h3 class="relative group">Flow Diagram
    <div id="flow-diagram" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#flow-diagram" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The following diagram illustrates how the <code>X-OKTA-ONPREM-DATA</code> header flows through the provisioning request lifecycle:</p>
<pre class="not-prose mermaid">
sequenceDiagram
    participant OC as Okta Cloud
    participant OPP as Okta Provisioning Agent
    participant SCIM as Okta On-Prem<br/>SCIM Server
    participant DB as On-prem<br/>Database

    Note over OC: 1. User creates<br/>provisioning request

    OC->>OPP: 2. Send request to Agent

    Note over OPP: 3. Retrieves<br/>DB config and Bearer Token<br/>from Okta
    Note over OPP: 4. Encodes config<br/>as Base64
    Note over OPP: 5. Adds headers:<br/>'X-OKTA-ONPREM-DATA'<br/>and 'Authorization'

    OPP->>SCIM: 6. Forward to SCIM Server<br/>(HTTPS + headers)

    Note over SCIM: 7. Check Authorization Token
    Note over SCIM: 8. Decode headers
    Note over SCIM: 9. Extract from DB config:<br/>- JDBC URL<br/>- Credentials<br/>- Stored procs
    Note over SCIM: 10. Execute operation

    SCIM->>DB: 11. JDBC call

    Note over DB: 12. Execute Stored Procedure

    DB-->>SCIM: Result
    SCIM-->>OPP: SCIM Response
    OPP-->>OC: Provisioning Result
</pre>


<h3 class="relative group">Error Response
    <div id="error-response" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#error-response" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>If the header is missing, the SCIM server returns:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;schemas&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;urn:ietf:params:scim:api:messages:2.0:Error&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scimType&#34;</span><span class="p">:</span> <span class="s2">&#34;MISSING_ATTRIBUTE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;detail&#34;</span><span class="p">:</span> <span class="s2">&#34;Missing X-OKTA-ONPREM-DATA header&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;status&#34;</span><span class="p">:</span> <span class="mi">400</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p><strong>HTTP Status</strong>: <code>400 Bad Request</code></p>

<h3 class="relative group">Testing Without Agent
    <div id="testing-without-agent" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#testing-without-agent" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>For <strong>development and debugging</strong>, you can manually construct the header:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Example connector config (JSON)</span>
</span></span><span class="line"><span class="cl"><span class="nv">CONFIG</span><span class="o">=</span><span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;jdbcUrl&#34;: &#34;jdbc:mysql://db:3306/oktademo&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;username&#34;: &#34;oktademo&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;password&#34;: &#34;oktademo&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;procedures&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;listUsers&#34;: &#34;GET_ACTIVEUSERS&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;getUser&#34;: &#34;GET_USER_BY_ID&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;createUser&#34;: &#34;CREATE_USER&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;updateUser&#34;: &#34;UPDATE_USER&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">  }
</span></span></span><span class="line"><span class="cl"><span class="s1">}&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Base64 encode</span>
</span></span><span class="line"><span class="cl"><span class="nv">ENCODED</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> -n <span class="s2">&#34;</span><span class="nv">$CONFIG</span><span class="s2">&#34;</span> <span class="p">|</span> base64<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Make request with header</span>
</span></span><span class="line"><span class="cl">curl -k <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Authorization: Bearer d5307740c879491cedecf70c2225776b&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;X-OKTA-ONPREM-DATA: </span><span class="nv">$ENCODED</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  https://localhost:1443/ws/rest/jdbc_on_prem/scim/v2/Users</span></span></code></pre></div></div>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855810593055 {
    background: #FFFAE6;
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855810593055 .panel-icon {
    color: rgb(224,108,0);
  }
  html.dark #panel-1778753855810593055 {
    background: rgb(51,46,27);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855810593055 .panel-icon {
    color: rgb(251,200,40);
  }
</style>

<div id="panel-1778753855810593055" class="flex px-4 py-3 rounded-md shadow panel-warning ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>
  <div class="panel-text">The actual header format and schema may vary by connector version. Always refer to Okta documentation and support for the correct configuration when using the agent in production. Direct API testing should only be done in lab environments for educational purposes.
  </div>
</div>
<hr>

<h2 class="relative group">Configuration
    <div id="configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuration" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Configuration Files
    <div id="configuration-files" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuration-files" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>All configuration is stored in: <code>data/okta-scim/conf/</code></p>

<h4 class="relative group">1. Application Properties
    <div id="1-application-properties" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#1-application-properties" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p><strong>File</strong>: <code>config-{CUSTOMER_ID}.properties</code></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="c1"># HTTPS Configuration</span>
</span></span><span class="line"><span class="cl"><span class="na">server.port</span><span class="o">=</span><span class="s">1443</span>
</span></span><span class="line"><span class="cl"><span class="na">server.servlet.context-path</span><span class="o">=</span><span class="s">/ws/rest</span>
</span></span><span class="line"><span class="cl"><span class="na">server.ssl.enabled</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># SSL/TLS Certificate</span>
</span></span><span class="line"><span class="cl"><span class="na">server.ssl.key-store-type</span><span class="o">=</span><span class="s">PKCS12</span>
</span></span><span class="line"><span class="cl"><span class="na">server.ssl.key-store</span><span class="o">=</span><span class="s">/etc/pki/tls/private/OktaOnPremScimServer-{CUSTOMER_ID}.p12</span>
</span></span><span class="line"><span class="cl"><span class="na">server.ssl.key-store-password</span><span class="o">=</span><span class="s">{auto_generated}</span>
</span></span><span class="line"><span class="cl"><span class="na">server.ssl.key-alias</span><span class="o">=</span><span class="s">okscimservercert</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># TLS Protocols</span>
</span></span><span class="line"><span class="cl"><span class="na">server.ssl.enabled-protocols</span><span class="o">=</span><span class="s">TLSv1.2,TLSv1.3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Client Authentication (disabled)</span>
</span></span><span class="line"><span class="cl"><span class="na">server.ssl.client-auth</span><span class="o">=</span><span class="s">NONE</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Request Size</span>
</span></span><span class="line"><span class="cl"><span class="na">server.max-http-request-header-size</span><span class="o">=</span><span class="s">10KB</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Bearer Token Authentication</span>
</span></span><span class="line"><span class="cl"><span class="na">scim.security.bearer.token</span><span class="o">=</span><span class="s">{auto_generated}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># HikariCP Database Connection Pool</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.maximumPoolSize</span><span class="o">=</span><span class="s">10</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.minimumIdle</span><span class="o">=</span><span class="s">0</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.connectionTimeout</span><span class="o">=</span><span class="s">30000</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.validationTimeout</span><span class="o">=</span><span class="s">3000</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.idleTimeout</span><span class="o">=</span><span class="s">90000</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.keepaliveTime</span><span class="o">=</span><span class="s">60000</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.maxLifetime</span><span class="o">=</span><span class="s">180000</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.initializationFailTimeout</span><span class="o">=</span><span class="s">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Logging Configuration</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.level.root</span><span class="o">=</span><span class="s">INFO</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.level.org.springframework.web</span><span class="o">=</span><span class="s">WARN</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.level.org.apache.catalina</span><span class="o">=</span><span class="s">WARN</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.level.com.okta.server.scim</span><span class="o">=</span><span class="s">INFO</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.level.org.springframework.jdbc</span><span class="o">=</span><span class="s">INFO</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="na">logging.pattern.file</span><span class="o">=</span><span class="s">%d{yyyy-MM-dd&#39;T&#39;HH:mm:ss.SSSXXX}  [pid:${PID:-unknown}] [%thread] %class{36}:%line - %msg%n</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.pattern.console</span><span class="o">=</span><span class="s">1</span></span></span></code></pre></div></div>

<h4 class="relative group">2. Customer ID Configuration
    <div id="2-customer-id-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#2-customer-id-configuration" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p><strong>File</strong>: <code>customer-id.conf</code></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">CUSTOMER_ID</span><span class="o">=</span>myorg</span></span></code></pre></div></div>
<p>Used to namespace configuration and certificates for multi-instance deployments.</p>

<h4 class="relative group">3. JVM Configuration
    <div id="3-jvm-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#3-jvm-configuration" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p><strong>File</strong>: <code>jvm.conf</code></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">JAVA_OPTS</span><span class="o">=</span><span class="s2">&#34;-Xmx2048m -Xms1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200&#34;</span></span></span></code></pre></div></div>
<p>Java memory and garbage collection settings.</p>

<h3 class="relative group">Certificate Files
    <div id="certificate-files" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#certificate-files" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Stored in: <code>data/okta-scim/certs/</code></p>

<h4 class="relative group">Auto-Generated Certificates
    <div id="auto-generated-certificates" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#auto-generated-certificates" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>The entrypoint script automatically generates:</p>
<ol>
<li>
<p><strong>Public Certificate</strong>: <code>OktaOnPremScimServer-{CUSTOMER_ID}.crt</code></p>
<ul>
<li>4096-bit RSA</li>
<li>10-year validity</li>
<li>Self-signed</li>
<li>Format: PEM (X.509)</li>
</ul>
</li>
<li>
<p><strong>Private Key</strong>: <code>OktaOnPremScimServer-{CUSTOMER_ID}.key</code></p>
<ul>
<li>RSA private key</li>
<li>Format: PEM (PKCS#8)</li>
</ul>
</li>
<li>
<p><strong>PKCS12 Keystore</strong>: <code>OktaOnPremScimServer-{CUSTOMER_ID}.p12</code></p>
<ul>
<li>Contains certificate + private key</li>
<li>Password: Auto-generated (stored in properties file)</li>
<li>Alias: <code>okscimservercert</code></li>
</ul>
</li>
</ol>
<hr>

<h2 class="relative group">Logging and Debugging
    <div id="logging-and-debugging" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#logging-and-debugging" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Log Files
    <div id="log-files" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#log-files" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p><strong>Location</strong>: <code>/var/log/OktaOnPremScimServer/</code> (mapped to <code>data/okta-scim/logs/</code> on host in Docker Compose setup)</p>
<p><strong>Files</strong>:</p>
<ul>
<li><code>application.log</code> - Main application log</li>
</ul>

<h3 class="relative group">Log Levels
    <div id="log-levels" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#log-levels" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Configured in <code>config-{CUSTOMER_ID}.properties</code>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="na">logging.level.root</span><span class="o">=</span><span class="s">INFO</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.level.org.springframework.web</span><span class="o">=</span><span class="s">WARN           # HTTP requests/responses</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.level.org.apache.catalina</span><span class="o">=</span><span class="s">WARN               # Tomcat server</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.level.org.apache.coyote</span><span class="o">=</span><span class="s">WARN                 # HTTP connector</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.level.org.apache.tomcat</span><span class="o">=</span><span class="s">WARN                 # Tomcat internals</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.level.com.zaxxer.hikari</span><span class="o">=</span><span class="s">WARN                 # Connection pool</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.level.com.okta.server.scim</span><span class="o">=</span><span class="s">INFO              # SCIM operations</span>
</span></span><span class="line"><span class="cl"><span class="na">logging.level.org.springframework.jdbc</span><span class="o">=</span><span class="s">INFO          # JDBC operations</span></span></span></code></pre></div></div>
<p><strong>Available Levels</strong>: <code>TRACE</code>, <code>DEBUG</code>, <code>INFO</code>, <code>WARN</code>, <code>ERROR</code>, <code>FATAL</code>, <code>OFF</code></p>

<h3 class="relative group">Log Format
    <div id="log-format" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#log-format" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p><strong>File Pattern</strong>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">%d{yyyy-MM-dd&#39;T&#39;HH:mm:ss.SSSXXX}  [pid:${PID:-unknown}] [%thread] %class{36}:%line - %msg%n</span></span></code></pre></div></div>
<p><strong>Example</strong>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">2026-02-17T11:54:32.123+00:00  [pid:42] [https-jsse-nio-1443-exec-1] c.o.s.s.controller.UserController:87 - Creating user: john.doe@example.com</span></span></code></pre></div></div>

<h3 class="relative group">Debugging SCIM Operations
    <div id="debugging-scim-operations" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#debugging-scim-operations" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Enable detailed JDBC logging to see SQL queries:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="na">logging.level.org.springframework.jdbc.core</span><span class="o">=</span><span class="s">TRACE</span></span></span></code></pre></div></div>
<p>This logs:</p>
<ul>
<li>SQL statements before execution</li>
<li>Parameter values</li>
<li>Result sets</li>
<li>Transaction boundaries</li>
</ul>

<h3 class="relative group">Common Log Patterns
    <div id="common-log-patterns" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#common-log-patterns" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p><strong>TLS Handshake Failures</strong>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">Handshake failed for client connection from IP address [192.168.65.1]</span></span></code></pre></div></div>
<p><strong>Cause</strong>: Client doesn&rsquo;t trust the self-signed certificate (expected behavior)</p>
<p><strong>Missing Header</strong>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">Missing X-OKTA-ONPREM-DATA header</span></span></code></pre></div></div>
<p><strong>Cause</strong>: Direct API call without Okta Provisioning Agent</p>
<p><strong>JDBC Connection Issues</strong>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">HikariPool - Exception during pool initialization</span></span></code></pre></div></div>
<p><strong>Cause</strong>: Database not reachable or credentials incorrect</p>
<p><strong>Stored Procedure Errors</strong>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">CallableStatementCallback; ERROR 1305 (42000): PROCEDURE oktademo.CREATE_USER does not exist</span></span></code></pre></div></div>
<p><strong>Cause</strong>: Stored procedure not created in database</p>
<hr>

<h2 class="relative group">Database Connectivity
    <div id="database-connectivity" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#database-connectivity" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Connection Pooling
    <div id="connection-pooling" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#connection-pooling" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The SCIM server uses <strong>HikariCP</strong> for database connection pooling:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="na">app.datasource.hikari.maximumPoolSize</span><span class="o">=</span><span class="s">10              # Max connections</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.minimumIdle</span><span class="o">=</span><span class="s">0                   # Min idle connections</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.connectionTimeout</span><span class="o">=</span><span class="s">30000         # 30 seconds</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.validationTimeout</span><span class="o">=</span><span class="s">3000          # 3 seconds</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.idleTimeout</span><span class="o">=</span><span class="s">90000               # 90 seconds</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.keepaliveTime</span><span class="o">=</span><span class="s">60000             # 60 seconds</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.maxLifetime</span><span class="o">=</span><span class="s">180000              # 180 seconds</span>
</span></span><span class="line"><span class="cl"><span class="na">app.datasource.hikari.initializationFailTimeout</span><span class="o">=</span><span class="s">0     # Fail immediately</span></span></span></code></pre></div></div>
<p>HikariCP is known for being the fastest, most reliable connection pool for JDBC. The SCIM Server leverages it to maintain a pool of database connections that can be reused across requests, significantly improving performance.</p>

<h3 class="relative group">JDBC Drivers
    <div id="jdbc-drivers" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#jdbc-drivers" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p><strong>Location</strong>: <code>/opt/OktaOnPremScimServer/userlib/</code></p>
<p><strong>Copied from</strong>: <code>docker/okta-scim/packages/*.jar</code> (in Docker Compose setup)</p>
<p><strong>Supported Drivers</strong>:</p>
<ul>
<li>MySQL Connector/J: <code>mysql-connector-j-9.6.0.jar</code></li>
<li>PostgreSQL: <code>postgresql-*.jar</code></li>
<li>Oracle JDBC: <code>ojdbc*.jar</code></li>
<li>SQL Server: <code>mssql-jdbc-*.jar</code></li>
</ul>

<h3 class="relative group">Connection Configuration
    <div id="connection-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#connection-configuration" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Database connection details are provided in the <code>X-OKTA-ONPREM-DATA</code> header on each request:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;jdbcUrl&#34;</span><span class="p">:</span> <span class="s2">&#34;jdbc:mysql://db:3306/oktademo&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;username&#34;</span><span class="p">:</span> <span class="s2">&#34;oktademo&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;password&#34;</span><span class="p">:</span> <span class="s2">&#34;oktademo&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;driverClassName&#34;</span><span class="p">:</span> <span class="s2">&#34;com.mysql.cj.jdbc.Driver&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>This per-request configuration approach enables the multi-database capability—each request can target a different database by including different connection details in its header.</p>
<hr>

<h2 class="relative group">Conclusion
    <div id="conclusion" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#conclusion" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Understanding the Okta On-Prem SCIM Server&rsquo;s architecture and internals provides valuable insight into how modern provisioning solutions bridge cloud identity platforms with on-premises infrastructure. Through this reverse-engineered analysis, you now understand:</p>
<ul>
<li><strong>The application stack</strong>: Spring Boot 3.5.0 with embedded Tomcat handling HTTPS on port 1443</li>
<li><strong>REST Controllers</strong>: How SCIM 2.0 operations map to Java controllers and ultimately database operations</li>
<li><strong>Authentication mechanisms</strong>: Bearer token validation and the role of the <code>X-OKTA-ONPREM-DATA</code> header</li>
<li><strong>Connection pooling</strong>: HikariCP managing database connections efficiently</li>
<li><strong>Logging patterns</strong>: How to interpret logs for troubleshooting</li>
<li><strong>Multi-tenancy architecture</strong>: How one SCIM server handles multiple databases</li>
</ul>
<p>While this knowledge is powerful for debugging, troubleshooting, and understanding your provisioning infrastructure, remember that <strong>the only supported way to interact with the SCIM Server in production is through the Okta Provisioning Agent and Admin Console</strong>. Direct API access should remain in the lab environment for educational exploration.</p>

<h3 class="relative group">Next Steps
    <div id="next-steps" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#next-steps" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><strong>Implement the full lab environment</strong>: Follow the <a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc"  target="_blank" rel="noreferrer">main Generic Database Connector guide</a> to deploy your own test environment</li>
<li><strong>Monitor your SCIM Server</strong>: Set up logging and health checks based on the patterns discussed</li>
<li><strong>Optimize performance</strong>: Tune HikariCP connection pool settings for your workload</li>
<li><strong>Explore the JAR</strong>: Extract and examine the Spring Boot JAR structure yourself for deeper learning</li>
</ul>
<p>Questions or want to explore more? Check out the <a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc"  target="_blank" rel="noreferrer">GitHub repository</a> for the complete lab environment and additional technical documentation.</p>
<hr>

<h2 class="relative group">Additional Resources
    <div id="additional-resources" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#additional-resources" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Official Okta Documentation
    <div id="official-okta-documentation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#official-okta-documentation" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><a href="https://help.okta.com/oie/en-us/content/topics/provisioning/opp/on-prem-scim-install.htm"  target="_blank" rel="noreferrer">Install the Okta On-prem SCIM Server</a></li>
<li><a href="https://help.okta.com/oie/en-us/content/topics/provisioning/opc/connectors/on-prem-connector-generic-db.htm"  target="_blank" rel="noreferrer">On-premises Connector for Generic Databases</a></li>
<li><a href="https://help.okta.com/oie/en-us/content/topics/provisioning"  target="_blank" rel="noreferrer">Okta Lifecycle Management</a></li>
</ul>

<h3 class="relative group">SCIM Protocol Resources
    <div id="scim-protocol-resources" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#scim-protocol-resources" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><a href="https://datatracker.ietf.org/doc/html/rfc7644"  target="_blank" rel="noreferrer">SCIM 2.0 RFC 7644</a> - Official SCIM specification</li>
<li><a href="https://datatracker.ietf.org/doc/html/rfc7643"  target="_blank" rel="noreferrer">SCIM 2.0 Core Schema</a> - User and Group schemas</li>
<li><a href="https://developer.okta.com/docs/concepts/scim/"  target="_blank" rel="noreferrer">Okta SCIM Documentation</a> - Okta&rsquo;s SCIM implementation guide</li>
</ul>

<h3 class="relative group">Related Articles
    <div id="related-articles" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#related-articles" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><a href="/posts/howto/okta-generic-jdbc-connector/" >On-premises Connector for Generic Databases with Docker Compose: A Complete Guide</a> - Full deployment and configuration guide</li>
</ul>

<h3 class="relative group">Project Resources
    <div id="project-resources" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#project-resources" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc"  target="_blank" rel="noreferrer">GitHub Repository</a> - Complete Docker Compose lab environment</li>
<li><a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc/blob/main/doc/Okta_SCIM_Server.md"  target="_blank" rel="noreferrer">Okta SCIM Server Technical Documentation</a> - Original reverse-engineered documentation</li>
<li><a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc/blob/main/doc/Okta_Provisioning_Configuration.md"  target="_blank" rel="noreferrer">Okta Provisioning Configuration Guide</a> - Detailed Admin Console setup</li>
</ul>
<hr>

<h2 class="relative group">Disclaimer
    <div id="disclaimer" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#disclaimer" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>This article contains technical information obtained through reverse engineering for educational purposes only. The content is provided &ldquo;as is&rdquo; without warranty of any kind. Always use official Okta documentation and support channels for production deployments.</p>
<p><strong>Remember</strong>: Use the Okta On-Prem SCIM Server only through officially supported methods—the Okta Provisioning Agent and Okta Admin Console—in production environments.</p>
]]></content:encoded>
      <category>Okta</category>
      <category>SCIM</category>
      <category>Architecture</category>
      <category>API</category>
      <category>Reverse Engineering</category>
      <category>Spring Boot</category>
      <category>JDBC</category>
      <category>Technical</category>
      <category>Deep Dive</category>
      <category>Provisioning</category>
    </item>
    <item>
      <title>Okta On-premises Connector for Generic Databases: A Complete Guide</title>
      <link>https://iam.fabiograsso.net/howto/okta-generic-jdbc-connector/</link>
      <pubDate>Sun, 01 Mar 2026 12:00:00 +0100</pubDate>
      <guid>https://iam.fabiograsso.net/howto/okta-generic-jdbc-connector/</guid>
      <description>Comprehensive guide to deploying Okta&amp;rsquo;s On-Premises Provisioning Agent, SCIM Server, and Generic Database Connector using Docker Compose. Covers architecture, setup, configuration, stored procedures, entitlement management, and testing workflows for bridging Okta with on-premises databases.</description>
      <content:encoded>&lt;![CDATA[
<h2 class="relative group">Introduction
    <div id="introduction" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#introduction" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Your organization likely runs critical business applications backed by custom databases—systems that have been reliable workhorses for years but don&rsquo;t speak the language of modern identity protocols. While integrating cloud applications with Okta using <a href="https://www.okta.com/identity-101/what-is-saml/"  target="_blank" rel="noreferrer">SAML</a>, <a href="https://developer.okta.com/docs/concepts/oidc/"  target="_blank" rel="noreferrer">OIDC (OpenID Connect)</a>, and <a href="https://developer.okta.com/docs/concepts/scim/"  target="_blank" rel="noreferrer">SCIM 2.0</a> is straightforward, bringing these legacy database-driven applications into your unified identity fabric has historically required complex custom scripting or middleware solutions.</p>
<p>Okta recently released the <strong>On-premises Connector for Generic Databases</strong>, a powerful Early Access feature that changes the game. This connector enables direct provisioning and lifecycle management to on-premises databases via JDBC, bringing automated joiner/mover/leaver (JML) workflows and entitlement management to applications that were previously difficult to integrate. No more custom scripts, no more manual account management—just standards-based automation using the SCIM protocol.</p>
<p>This guide provides a complete deep dive into deploying and configuring the Generic Database Connector. You&rsquo;ll build a fully functional test environment using Docker Compose with MariaDB (MySQL-compatible), configure every aspect of the integration—from user provisioning to entitlement management—and test real-world scenarios including user creation, attribute updates, entitlement assignments, and deactivation workflows.</p>
<p><strong>Why Docker for This Lab?</strong></p>
<p>Docker Compose provides the fastest path to a working environment, letting you spin up the entire stack—Okta Provisioning Agent, SCIM Server, MariaDB database, and a web-based management interface—in minutes. While Okta doesn&rsquo;t officially support Docker for production deployments, it&rsquo;s ideal for demonstrations, proof-of-concepts, and learning the integration workflow.</p>
<p>Importantly, <strong>every configuration step in this guide applies equally to manual installations</strong> on production systems. Whether you&rsquo;re testing in Docker or deploying on dedicated Linux servers, the Okta configuration, stored procedures, and integration patterns remain identical. Think of the Docker environment as an accelerator for understanding—once you&rsquo;ve mastered the concepts here, translating them to a production deployment is straightforward.</p>

<h2 class="relative group">Quick Instructions
    <div id="quick-instructions" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#quick-instructions" aria-label="Anchor">#</a>
    </span>
    
</h2>
<ol>
<li>Clone the repository: <a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc"  target="_blank" rel="noreferrer">https://github.com/fabiograsso/okta-lab-onprem-jdbc</a></li>
<li>Download the required RPM files from Okta Help Center:
<ul>
<li>Okta Provisioning Agent: <code>OktaProvisioningAgent-*.rpm</code> → copy to <code>./docker/okta-opp/packages/</code></li>
<li>Okta SCIM Server: <code>OktaOnPremScimServer-*.rpm</code> → copy to <code>./docker/okta-scim/packages/</code></li>
</ul>
</li>
<li>Configure the environment: <code>cp .env-sample .env</code> (edit if needed)</li>
<li>Build and start: <code>make build &amp;&amp; make start-logs</code></li>
<li>Configure the OPP Agent: <code>make configure</code> (once you see &ldquo;Waiting for configuration files&rdquo; message)</li>
<li>Retrieve SCIM credentials from <code>./data/okta-scim/conf/config-*.properties</code></li>
<li>Configure the Generic Database Connector application in Okta Admin Console</li>
</ol>

<h2 class="relative group">Prerequisites
    <div id="prerequisites" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#prerequisites" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">System Requirements
    <div id="system-requirements" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#system-requirements" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><strong>Docker</strong> and <strong>Docker Compose</strong> (v2+) are installed
<ul>
<li>If you are using Ubuntu 24.04 Linux server: <code>apt-get install -y docker docker-compose</code></li>
<li>You can also use <a href="https://docs.docker.com/desktop/"  target="_blank" rel="noreferrer">Docker Desktop</a></li>
</ul>
</li>
<li><strong>Okta Organization</strong> with administrative access</li>
<li><strong>Git</strong> (to clone the repository)</li>
</ul>

<h3 class="relative group">Okta Early Access Feature
    <div id="okta-early-access-feature" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#okta-early-access-feature" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Since the <strong>Okta Generic Database Connector</strong> is currently an Early Access feature, you need to enable it in the Okta Admin Panel:</p>
<ol>
<li>Navigate to <strong>Settings</strong> → <strong>Features</strong></li>
<li>Enable <strong>&ldquo;OPP Agent with SCIM 2.0 support&rdquo;</strong></li>
<li>Enable <strong>&ldquo;On-prem Connector for Generic Databases&rdquo;</strong></li>
</ol>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Enable Early Access features for OPP Agent with SCIM 2.0 and Generic Database Connector"
    srcset="
      /howto/okta-generic-jdbc-connector/on-prem-connector-feature_hu_60a0515847ec95be.webp  330w,
      /howto/okta-generic-jdbc-connector/on-prem-connector-feature_hu_54737e1366422384.webp  660w,
      /howto/okta-generic-jdbc-connector/on-prem-connector-feature_hu_52e841f18425b5cd.webp  960w,
      /howto/okta-generic-jdbc-connector/on-prem-connector-feature_hu_507eb9f70e9feec4.webp 1280w,
      /howto/okta-generic-jdbc-connector/on-prem-connector-feature_hu_36238cfbc0b02152.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/on-prem-connector-feature.png"
    src="/howto/okta-generic-jdbc-connector/on-prem-connector-feature.png">


  
</figure>

<h3 class="relative group">Required Files from Okta
    <div id="required-files-from-okta" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#required-files-from-okta" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>You&rsquo;ll need to download installer packages from the Okta Admin Console.</p>
<p><strong>For OPP Agent</strong> - Place in <code>./docker/okta-opp/packages/</code>:</p>
<table>
  <thead>
      <tr>
          <th>File</th>
          <th>Required</th>
          <th>Description</th>
          <th>Download</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>OktaProvisioningAgent-&lt;version&gt;.x86_64.rpm</code></td>
          <td>Yes</td>
          <td>OPP Agent installer</td>
          <td>From the Okta Admin Console → Settings → Downloads → <strong>Okta Provisioning Agent x64 RPM</strong></td>
      </tr>
      <tr>
          <td><code>*.pem</code> or <code>*.crt</code> (certificates)</td>
          <td>No</td>
          <td>Custom VPN certificates</td>
          <td>Copy your VPN provider root CA</td>
      </tr>
  </tbody>
</table>
<p><strong>For SCIM Server</strong> - Place in <code>./docker/okta-scim/packages/</code>:</p>
<table>
  <thead>
      <tr>
          <th>File</th>
          <th>Required</th>
          <th>Description</th>
          <th>Download</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>OktaOnPremScimServer-&lt;version&gt;.rpm</code></td>
          <td>Yes</td>
          <td>SCIM Server installer</td>
          <td>From the Okta Admin Console → Settings → Downloads → <strong>Okta On-prem SCIM Server</strong></td>
      </tr>
      <tr>
          <td><code>*.jar</code> (JDBC drivers)</td>
          <td>No</td>
          <td>Additional database drivers</td>
          <td>MySQL Connector/J is auto-downloaded. For other databases: <a href="https://jdbc.postgresql.org/"  target="_blank" rel="noreferrer">PostgreSQL</a>, <a href="https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html"  target="_blank" rel="noreferrer">Oracle</a>, <a href="https://learn.microsoft.com/en-us/sql/connect/jdbc/download-microsoft-jdbc-driver-for-sql-server"  target="_blank" rel="noreferrer">SQL Server</a></td>
      </tr>
      <tr>
          <td><code>*.pem</code> or <code>*.crt</code> (certificates)</td>
          <td>No</td>
          <td>Custom VPN certificates</td>
          <td>Copy your VPN provider root CA</td>
      </tr>
  </tbody>
</table>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855850748257 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855850748257 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855850748257 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855850748257 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855850748257" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text">Certificate files are only needed if you&rsquo;re connecting through a VPN with custom CA certificates (e.g., Palo Alto GlobalProtect, Prisma Access). If you don&rsquo;t have custom VPN certificates, you can ignore the warnings during startup.
  </div>
</div>
<hr>

<h2 class="relative group">Understanding the Architecture
    <div id="understanding-the-architecture" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#understanding-the-architecture" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Before diving into the setup, it&rsquo;s essential to understand how the components work together.</p>

<h3 class="relative group">What is the Okta Generic Database Connector (JDBC)?
    <div id="what-is-the-okta-generic-database-connector-jdbc" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-is-the-okta-generic-database-connector-jdbc" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The Okta Generic Database Connector allows Okta to connect to on-premises databases using JDBC drivers. It enables comprehensive user lifecycle management and entitlement operations by executing SQL queries or stored procedures against the database. This is particularly valuable for organizations with legacy applications or custom databases that don&rsquo;t support modern provisioning protocols but can be accessed via JDBC.</p>

<h3 class="relative group">What is the Okta On-Premises Provisioning Agent?
    <div id="what-is-the-okta-on-premises-provisioning-agent" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-is-the-okta-on-premises-provisioning-agent" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The Okta On-Premises Provisioning (OPP) Agent acts as a secure bridge between Okta&rsquo;s cloud identity platform and your on-premises applications. Key capabilities include:</p>
<ul>
<li><strong>Automated User Provisioning</strong>: Create, update, and deactivate user accounts in on-premises databases</li>
<li><strong>SCIM Protocol Support</strong>: Industry-standard protocol for user identity management</li>
<li><strong>Secure Communication</strong>: Outbound-only HTTPS connections from your network to Okta (no inbound firewall rules required)</li>
<li><strong>Real-time Synchronization</strong>: Push user changes from Okta to on-premises systems instantly</li>
</ul>

<h3 class="relative group">What is the Okta On-Prem SCIM Server?
    <div id="what-is-the-okta-on-prem-scim-server" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-is-the-okta-on-prem-scim-server" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The Okta On-prem SCIM Server is a Java application that translates SCIM API calls from the OPP Agent into database operations using JDBC. It serves as the intermediary layer that allows Okta to manage user accounts in your on-premises database through the SCIM protocol.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855851093494 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855851093494 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855851093494 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855851093494 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855851093494" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>Deployment Flexibility:</strong> The OPP Agent and SCIM Server can be installed on the <strong>same server</strong> (recommended for most deployments) or on <strong>separate servers</strong> for enhanced isolation. This lab uses separate Docker containers to simulate a production-like architecture and demonstrate component interaction.
  </div>
</div>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855851313052 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855851313052 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855851313052 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855851313052 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855851313052" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>Deep Dive Available:</strong> For a comprehensive technical exploration of the SCIM Server&rsquo;s internal architecture, REST endpoints, authentication mechanisms, and multi-tenancy support, see <strong><a href="/posts/howto/okta-scim-server-deep-dive/" >Okta On-Prem SCIM Server: Technical Deep Dive and Architecture</a></strong>.
  </div>
</div>

<h3 class="relative group">How They Work Together
    <div id="how-they-work-together" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-they-work-together" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The complete provisioning flow involves four key stages:</p>
<ol>
<li>
<p><strong>Okta Cloud → OPP Agent</strong>: The Provisioning Agent maintains a secure, outbound HTTPS connection from your network to Okta using long polling. Since the connection originates from within your network perimeter, no inbound firewall rules are required.</p>
</li>
<li>
<p><strong>OPP Agent → SCIM Server</strong>: When lifecycle management or provisioning requests arrive from Okta, the Agent relays them to the SCIM Server for processing.</p>
</li>
<li>
<p><strong>SCIM Server → Database</strong>: The SCIM Server interprets incoming SCIM requests and executes the corresponding database operations—creating users, modifying attributes, deactivating accounts, or managing entitlements—directly against your on-premises database.</p>
</li>
<li>
<p><strong>Response Flow</strong>: Data and operation results return through the same path in reverse: from the database to the SCIM Server, then to the Provisioning Agent, which securely transmits the results back to Okta.</p>
</li>
</ol>
<pre class="not-prose mermaid">
---
config:
 layout: dagre
---
flowchart TB
 subgraph subGraph0["**Okta Cloud**"]
 A["Okta Org"]
 end
 subgraph subGraph1["**Docker Environment**"]
 B["OPP Agent"]
 C["SCIM Server"]
 D["MariaDB Database"]
 E["DBGate UI"]
 end
 B -- Outbound HTTPS --> A
 B -- SCIM Protocol --> C
 C -- JDBC Connection --> D
 E -- Management --> D

 style B fill:#FFE0B2
 style C fill:#FFE0B2
 style D fill:#FFCDD2
 style E fill:#FFF9C4
 style subGraph0 stroke:#757575,fill:#C8E6C9
 style subGraph1 fill:#BBDEFB,stroke:#2962FF
</pre>


<h3 class="relative group">Lab Components
    <div id="lab-components" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#lab-components" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>This Docker Compose environment includes four separate containers:</p>
<ol>
<li><strong>Okta Provisioning Agent (okta-opp)</strong>: CentOS based container running the OPP Agent</li>
<li><strong>Okta On-Prem SCIM Server (okta-scim)</strong>: CentOS 9-based container running the SCIM Server</li>
<li><strong>MariaDB (db)</strong>: Database server pre-populated with test data and stored procedures</li>
<li><strong>DBGate</strong>: Web-based database management interface for easy data inspection</li>
</ol>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855851556030 {
    background: rgb(248,238,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855851556030 .panel-icon {
    color: rgb(175,89,225);
  }
  html.dark #panel-1778753855851556030 {
    background: rgb(53,36,63);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855851556030 .panel-icon {
    color: rgb(191,99,243);
  }
</style>

<div id="panel-1778753855851556030" class="flex px-4 py-3 rounded-md shadow panel-note ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M421.7 220.3L188.5 453.4L154.6 419.5L158.1 416H112C103.2 416 96 408.8 96 400V353.9L92.51 357.4C87.78 362.2 84.31 368 82.42 374.4L59.44 452.6L137.6 429.6C143.1 427.7 149.8 424.2 154.6 419.5L188.5 453.4C178.1 463.8 165.2 471.5 151.1 475.6L30.77 511C22.35 513.5 13.24 511.2 7.03 504.1C.8198 498.8-1.502 489.7 .976 481.2L36.37 360.9C40.53 346.8 48.16 333.9 58.57 323.5L291.7 90.34L421.7 220.3zM492.7 58.75C517.7 83.74 517.7 124.3 492.7 149.3L444.3 197.7L314.3 67.72L362.7 19.32C387.7-5.678 428.3-5.678 453.3 19.32L492.7 58.75z"/></svg>
</span>
  </span>
  <div class="panel-text">This lab uses <strong>separate containers</strong> for the OPP Agent and SCIM Server to demonstrate a distributed architecture. In production, you can install both on the <strong>same server</strong>, which is the typical deployment model and simplifies infrastructure management.
  </div>
</div>

<h3 class="relative group">Multi-Database Support
    <div id="multi-database-support" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#multi-database-support" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>A powerful feature of this solution is that a single OPP Agent and SCIM Server deployment can manage <strong>up to 8 databases simultaneously</strong>. This enables:</p>
<ul>
<li>Management of users across multiple applications</li>
<li>Support for different database types (MySQL, PostgreSQL, Oracle, SQL Server)</li>
<li>Separation of production and staging environments</li>
<li>Organizational unit isolation</li>
</ul>
<p>Each database requires a separate Generic Database Connector application instance in Okta, but all share the same on-premises infrastructure. This means you only need one OPP Agent and one SCIM Server installation to provision to multiple databases—just ensure all required JDBC drivers are available in the SCIM Server before building the container.</p>
<hr>

<h2 class="relative group">Docker Compose Setup
    <div id="docker-compose-setup" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#docker-compose-setup" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Docker provides the fastest path to a working environment. This section covers the complete setup process using Docker Compose.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855851787079 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855851787079 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855851787079 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855851787079 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855851787079" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>Okta does not officially support Docker for running the OPP Agent and SCIM Server in production environments. While it&rsquo;s a fantastic way to quickly set up a full environment for demos and POCs, production deployments should follow the <a href="https://help.okta.com/oie/en-us/content/topics/provisioning/opp/opp-install-agent.htm"  target="_blank" rel="noreferrer">official installation guide</a> on supported operating systems.</p>
<p>However, Docker is the optimal solution for:</p>
<ul>
<li>Demonstrations and presentations</li>
<li>Proof of concepts (POCs)</li>
<li>Development and testing</li>
<li>Learning the integration workflow</li>
</ul>

  </div>
</div>

<h3 class="relative group">Initial Setup
    <div id="initial-setup" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#initial-setup" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>
<p><strong>Clone the Repository</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/fabiograsso/okta-lab-onprem-jdbc
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> okta-lab-onprem-jdbc</span></span></code></pre></div></div>
</li>
<li>
<p><strong>Prepare Required Files</strong></p>
<p>Download the RPM packages from the Okta Help Center and organize them:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Copy OPP Agent RPM</span>
</span></span><span class="line"><span class="cl">cp /path/to/OktaProvisioningAgent-*.rpm ./docker/okta-opp/packages/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Copy SCIM Server RPM</span>
</span></span><span class="line"><span class="cl">cp /path/to/OktaOnPremScimServer-*.rpm ./docker/okta-scim/packages/</span></span></code></pre></div></div>
</li>
<li>
<p><strong>Configure Environment Variables</strong></p>
<p>The <code>.env</code> file contains all configuration parameters:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cp .env-sample .env</span></span></code></pre></div></div>
<p>You can customize the database settings if needed (default values work for testing):</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Database Configuration</span>
</span></span><span class="line"><span class="cl"><span class="nv">MARIADB_PORT</span><span class="o">=</span><span class="m">3306</span>
</span></span><span class="line"><span class="cl"><span class="nv">MARIADB_DATABASE</span><span class="o">=</span>oktademo
</span></span><span class="line"><span class="cl"><span class="nv">MARIADB_USER</span><span class="o">=</span>oktademo
</span></span><span class="line"><span class="cl"><span class="nv">MARIADB_PASSWORD</span><span class="o">=</span>oktademo
</span></span><span class="line"><span class="cl"><span class="nv">MARIADB_ROOT_PASSWORD</span><span class="o">=</span>oktademo
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># SCIM Server Logging (for troubleshooting)</span>
</span></span><span class="line"><span class="cl"><span class="nv">LOG_LEVEL_ROOT</span><span class="o">=</span>INFO
</span></span><span class="line"><span class="cl"><span class="nv">LOG_LEVEL_OKTA_SCIM</span><span class="o">=</span>INFO
</span></span><span class="line"><span class="cl"><span class="nv">LOG_LEVEL_SPRING_JDBC</span><span class="o">=</span>INFO</span></span></code></pre></div></div>
</li>
</ol>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855852018647 {
    background: #FFFAE6;
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855852018647 .panel-icon {
    color: rgb(224,108,0);
  }
  html.dark #panel-1778753855852018647 {
    background: rgb(51,46,27);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855852018647 .panel-icon {
    color: rgb(251,200,40);
  }
</style>

<div id="panel-1778753855852018647" class="flex px-4 py-3 rounded-md shadow panel-warning ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>Before proceeding, verify:</p>
<ul>
<li>OPP Agent RPM exists in <code>./docker/okta-opp/packages/</code></li>
<li>SCIM Server RPM exists in <code>./docker/okta-scim/packages/</code></li>
<li><code>.env</code> file is configured (default values are fine for testing)</li>
</ul>
  </div>
</div>

<h3 class="relative group">Build and Start Services
    <div id="build-and-start-services" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#build-and-start-services" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The project includes a Makefile with convenient commands for managing the Docker environment:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Build Docker images (includes prerequisite checks)</span>
</span></span><span class="line"><span class="cl">make build
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start all services (detached mode with log following)</span>
</span></span><span class="line"><span class="cl">make start-logs
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Alternative: Start in detached mode without logs</span>
</span></span><span class="line"><span class="cl"><span class="c1"># make start</span></span></span></code></pre></div></div>
<p>The <code>make start</code> command will:</p>
<ul>
<li>Run prerequisite checks to verify RPM files are present</li>
<li>Start all four containers (db, dbgate, okta-scim, okta-opp)</li>
<li>Initialize the database with schema and test data</li>
<li>Generate SCIM Server certificates and configuration</li>
</ul>
<p>You&rsquo;ll see output similar to this:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[+] Running 4/4
</span></span><span class="line"><span class="cl"> ✔ Container lab-db-1          Started
</span></span><span class="line"><span class="cl"> ✔ Container lab-dbgate-1      Started
</span></span><span class="line"><span class="cl"> ✔ Container lab-okta-scim-1   Started
</span></span><span class="line"><span class="cl"> ✔ Container lab-okta-opp-1    Started</span></span></code></pre></div></div>

<h3 class="relative group">Configure the OPP Agent
    <div id="configure-the-opp-agent" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configure-the-opp-agent" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>After the containers start, the OPP Agent will wait for configuration. You&rsquo;ll see this message in the logs:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">⏳ Waiting for configuration files. Please configure the Agent...</span></span></code></pre></div></div>
<p>Now run the interactive configuration script:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make configure</span></span></code></pre></div></div>
<p>Follow the prompts:</p>
<ol>
<li><strong>Okta Subdomain</strong>: Enter your Okta org URL (e.g., <code>https://myorg.okta.com</code>)</li>
<li><strong>Proxy Server</strong>: Press Enter to skip (unless you&rsquo;re behind a proxy)</li>
<li><strong>LDAP Server</strong>: Not applicable for database connector - press Enter to skip</li>
<li><strong>SCIM Server</strong>: Not applicable - configuration is automatic via Docker networking</li>
</ol>
<p>The script will display an activation URL and code:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Please visit the URL: https://myorg.okta.com/activate
</span></span><span class="line"><span class="cl">and enter the following code: MSJPKGRP
</span></span><span class="line"><span class="cl">before Fri Mar 02 15:30:00 UTC 2026 to authenticate and continue agent registration.</span></span></code></pre></div></div>
<p>Open the provided URL in your browser, enter the activation code, and click &ldquo;Allow&rdquo; to complete the registration.</p>
<p>You&rsquo;ll see a success message:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[INFO] - Register Mode successfully finished
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Configuration successful.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Service can now be started by typing
</span></span><span class="line"><span class="cl">systemctl start OktaProvisioningAgent.service
</span></span><span class="line"><span class="cl">as root.</span></span></code></pre></div></div>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855852209915 {
    background: rgb(248,238,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855852209915 .panel-icon {
    color: rgb(175,89,225);
  }
  html.dark #panel-1778753855852209915 {
    background: rgb(53,36,63);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855852209915 .panel-icon {
    color: rgb(191,99,243);
  }
</style>

<div id="panel-1778753855852209915" class="flex px-4 py-3 rounded-md shadow panel-note ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M421.7 220.3L188.5 453.4L154.6 419.5L158.1 416H112C103.2 416 96 408.8 96 400V353.9L92.51 357.4C87.78 362.2 84.31 368 82.42 374.4L59.44 452.6L137.6 429.6C143.1 427.7 149.8 424.2 154.6 419.5L188.5 453.4C178.1 463.8 165.2 471.5 151.1 475.6L30.77 511C22.35 513.5 13.24 511.2 7.03 504.1C.8198 498.8-1.502 489.7 .976 481.2L36.37 360.9C40.53 346.8 48.16 333.9 58.57 323.5L291.7 90.34L421.7 220.3zM492.7 58.75C517.7 83.74 517.7 124.3 492.7 149.3L444.3 197.7L314.3 67.72L362.7 19.32C387.7-5.678 428.3-5.678 453.3 19.32L492.7 58.75z"/></svg>
</span>
  </span>
  <div class="panel-text">You don&rsquo;t need to run the <code>systemctl</code> command since the agent is already running in the container. The message indicates that the configuration files have been generated successfully.
  </div>
</div>

<h3 class="relative group">Retrieve SCIM Server Credentials
    <div id="retrieve-scim-server-credentials" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#retrieve-scim-server-credentials" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The SCIM Server automatically generates a bearer token and self-signed certificate on first startup. These credentials are needed to configure the Okta application.</p>
<p><strong>Option 1 - From Host Filesystem:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Get the bearer token (look for scim.security.bearer.token property)</span>
</span></span><span class="line"><span class="cl">cat ./data/okta-scim/conf/config-*.properties <span class="p">|</span> grep bearer.token
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Output example:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># scim.security.bearer.token=da655feabd8ec0c3f89c1fb6e9f0ad39</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get the public certificate</span>
</span></span><span class="line"><span class="cl">cat ./data/okta-scim/certs/OktaOnPremScimServer-*.crt</span></span></code></pre></div></div>
<p><strong>Option 2 - From Container:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Display all credentials</span>
</span></span><span class="line"><span class="cl">docker compose <span class="nb">exec</span> okta-scim /opt/OktaOnPremScimServer/bin/Get-OktaOnPremScimServer-Credentials.sh
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get just the certificate</span>
</span></span><span class="line"><span class="cl">docker compose <span class="nb">exec</span> okta-scim bash -c <span class="s1">&#39;cat /opt/OktaOnPremScimServer/certs/OktaOnPremScimServer-*.crt&#39;</span></span></span></code></pre></div></div>
<p><strong>Save these credentials</strong> - you&rsquo;ll need them when configuring the Okta application:</p>
<ul>
<li><strong>SCIM Hostname</strong>: <code>okta-scim</code> (container name for internal Docker networking)</li>
<li><strong>Bearer Token</strong>: The value from <code>scim.security.bearer.token</code> (will be prefixed with <code>Bearer </code> in Okta)</li>
<li><strong>Certificate</strong>: The <code>.crt</code> file contents</li>
</ul>

<h3 class="relative group">Verify the Setup
    <div id="verify-the-setup" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#verify-the-setup" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Check that all containers are running properly:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Check container status</span>
</span></span><span class="line"><span class="cl">docker compose ps
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Expected output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># NAME               STATUS        PORTS</span>
</span></span><span class="line"><span class="cl"><span class="c1"># lab-db-1           Up (healthy)  33060/tcp</span>
</span></span><span class="line"><span class="cl"><span class="c1"># lab-dbgate-1       Up            0.0.0.0:8090-&gt;3000/tcp</span>
</span></span><span class="line"><span class="cl"><span class="c1"># lab-okta-scim-1    Up (healthy)  0.0.0.0:1443-&gt;1443/tcp</span>
</span></span><span class="line"><span class="cl"><span class="c1"># lab-okta-opp-1     Up</span></span></span></code></pre></div></div>
<p>You can also access the DBGate web interface to inspect the database:</p>
<ul>
<li>Open http://localhost:8090 in your browser</li>
<li>Username: <code>oktademo</code></li>
<li>Password: <code>oktademo</code></li>
</ul>

<h3 class="relative group">Other Useful Commands
    <div id="other-useful-commands" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#other-useful-commands" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The Makefile provides additional commands for managing the environment:</p>
<table>
  <thead>
      <tr>
          <th>Command</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>make help</code></td>
          <td>Display all available commands</td>
      </tr>
      <tr>
          <td><code>make stop</code></td>
          <td>Stop and remove all containers</td>
      </tr>
      <tr>
          <td><code>make restart</code></td>
          <td>Restart all services</td>
      </tr>
      <tr>
          <td><code>make restart-logs</code></td>
          <td>Restart and follow logs</td>
      </tr>
      <tr>
          <td><code>make logs</code></td>
          <td>Follow container logs (last 500 lines)</td>
      </tr>
      <tr>
          <td><code>make rebuild</code></td>
          <td>Force rebuild from scratch (no cache)</td>
      </tr>
      <tr>
          <td><code>make kill</code></td>
          <td>Kill containers and remove orphans</td>
      </tr>
  </tbody>
</table>
<hr>

<h2 class="relative group">Database Schema and Test Data
    <div id="database-schema-and-test-data" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#database-schema-and-test-data" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The MariaDB database is automatically initialized with a comprehensive schema designed for realistic user provisioning scenarios.</p>

<h3 class="relative group">Database Tables
    <div id="database-tables" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#database-tables" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The database includes three core tables:</p>
<p><strong>USERS Table</strong>:</p>
<ul>
<li><strong>Identity</strong>: <code>USER_ID</code> (PRIMARY KEY), <code>USERNAME</code> (UNIQUE), <code>EMAIL</code></li>
<li><strong>Personal</strong>: <code>FIRSTNAME</code>, <code>LASTNAME</code>, <code>MIDDLENAME</code>, <code>DISPLAYNAME</code>, <code>NICKNAME</code></li>
<li><strong>Contact</strong>: <code>MOBILEPHONE</code>, <code>STREETADDRESS</code>, <code>CITY</code>, <code>STATE</code>, <code>ZIPCODE</code>, <code>COUNTRYCODE</code>, <code>TIMEZONE</code></li>
<li><strong>Work</strong>: <code>TITLE</code>, <code>ORGANIZATION</code>, <code>DEPARTMENT</code>, <code>EMPLOYEENUMBER</code>, <code>MANAGER</code>, <code>MANAGERID</code></li>
<li><strong>Dates</strong>: <code>HIREDATE</code>, <code>TERMINATIONDATE</code></li>
<li><strong>Security</strong>: <code>PASSWORD_HASH</code>, <code>IS_ACTIVE</code> (BOOLEAN, default TRUE)</li>
</ul>
<p><strong>ENTITLEMENTS Table</strong></p>
<ul>
<li><code>ENT_ID</code> (INT PRIMARY KEY, values 1-10)</li>
<li><code>ENT_NAME</code> (VARCHAR 100, UNIQUE)</li>
<li><code>ENT_DESCRIPTION</code> (VARCHAR 255)</li>
</ul>
<p><strong>USERENTITLEMENTS Table</strong> (Junction Table):</p>
<ul>
<li><code>USERENTITLEMENT_ID</code> (INT PRIMARY KEY, AUTO_INCREMENT)</li>
<li><code>USER_ID</code> (FOREIGN KEY to USERS)</li>
<li><code>ENT_ID</code> (FOREIGN KEY to ENTITLEMENTS)</li>
<li><code>ASSIGNEDDATE</code> (DATETIME, auto-populated)</li>
<li>Unique constraint on (USER_ID, ENT_ID) combination</li>
</ul>
<pre class="not-prose mermaid">
erDiagram
    USERS ||--o{ USERENTITLEMENTS : "has"
    ENTITLEMENTS ||--o{ USERENTITLEMENTS : "assigned_to"

    USERS {
        VARCHAR(100) *USER_ID PK "User identifier (email format) - REQUIRED"
        VARCHAR(100) USERNAME UK "Login username (unique) - REQUIRED"
        VARCHAR(100) EMAIL "Email address - REQUIRED"
        VARCHAR(100) FIRSTNAME "First name - REQUIRED"
        VARCHAR(100) LASTNAME "Last name - REQUIRED"
        VARCHAR(100) OTHERFIELDS "...Other Fields..."
        DATE HIREDATE "Date of hire"
        DATE TERMINATIONDATE "Date of termination"
        VARCHAR(255) PASSWORD_HASH "Password hash"
        BOOLEAN IS_ACTIVE "Account status (default TRUE)"
    }

    ENTITLEMENTS {
        INT ENT_ID PK "Entitlement identifier"
        VARCHAR(100) ENT_NAME UK "Entitlement name (unique)"
        TEXT ENT_DESCRIPTION "Description of entitlement"
    }

    USERENTITLEMENTS {
        INT USERENTITLEMENT_ID PK "Auto-increment ID"
        VARCHAR(100) USER_ID FK "Foreign key to USERS"
        INT ENT_ID FK "Foreign key to ENTITLEMENTS"
        DATETIME ASSIGNEDDATE "When entitlement was assigned"
    }
</pre>


<h3 class="relative group">Test Data
    <div id="test-data" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-data" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The database comes pre-populated with <strong>15 Star Wars characters</strong> as test users:</p>
<table>
  <thead>
      <tr>
          <th>User ID</th>
          <th>Name</th>
          <th>Organization</th>
          <th>Title</th>
          <th>Entitlements</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="mailto:luke.skywalker@galaxy.local" >luke.skywalker@galaxy.local</a></td>
          <td>Luke Skywalker</td>
          <td>Jedi</td>
          <td>Jedi Knight</td>
          <td>VPN Access, GitHub Admin, Confluence Edit</td>
      </tr>
      <tr>
          <td><a href="mailto:leia.organa@galaxy.local" >leia.organa@galaxy.local</a></td>
          <td>Leia Organa</td>
          <td>Resistance</td>
          <td>General</td>
          <td>VPN Access, AWS Console, Jira Admin</td>
      </tr>
      <tr>
          <td><a href="mailto:han.solo@galaxy.local" >han.solo@galaxy.local</a></td>
          <td>Han Solo</td>
          <td>Resistance</td>
          <td>Smuggler</td>
          <td>VPN Access, Database Read</td>
      </tr>
      <tr>
          <td><a href="mailto:obiwan.kenobi@galaxy.local" >obiwan.kenobi@galaxy.local</a></td>
          <td>Obi-Wan Kenobi</td>
          <td>Jedi</td>
          <td>Jedi Master</td>
          <td>VPN Access, GitHub Admin, AWS Console</td>
      </tr>
      <tr>
          <td><a href="mailto:yoda@galaxy.local" >yoda@galaxy.local</a></td>
          <td>Yoda</td>
          <td>Jedi</td>
          <td>Grand Master</td>
          <td>VPN Access, GitHub Admin, AWS Console, Jira Admin</td>
      </tr>
      <tr>
          <td>&hellip;</td>
          <td>&hellip;</td>
          <td>&hellip;</td>
          <td>&hellip;</td>
          <td>&hellip;</td>
      </tr>
  </tbody>
</table>
<p><strong>10 Pre-configured Entitlements:</strong></p>
<ol>
<li>VPN Access</li>
<li>GitHub Admin</li>
<li>AWS Console</li>
<li>Jira Admin</li>
<li>Confluence Edit</li>
<li>Database Read</li>
<li>Database Write</li>
<li>Slack Admin</li>
<li>Office 365</li>
<li>Salesforce</li>
</ol>
<p>You can inspect the test data using DBGate or command-line queries:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># View all users</span>
</span></span><span class="line"><span class="cl">docker compose <span class="nb">exec</span> db mariadb -u oktademo -poktademo oktademo <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="s2">&#34;SELECT USER_ID, FIRSTNAME, LASTNAME, TITLE, ORGANIZATION FROM USERS LIMIT 10;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># View all entitlements</span>
</span></span><span class="line"><span class="cl">docker compose <span class="nb">exec</span> db mariadb -u oktademo -poktademo oktademo <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="s2">&#34;SELECT * FROM ENTITLEMENTS;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># View user-entitlement assignments</span>
</span></span><span class="line"><span class="cl">docker compose <span class="nb">exec</span> db mariadb -u oktademo -poktademo oktademo <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="s2">&#34;SELECT * FROM USERENTITLEMENTS LIMIT 10;&#34;</span></span></span></code></pre></div></div>

<h3 class="relative group">Operational Views
    <div id="operational-views" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#operational-views" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The database also includes 6 operational views for convenient monitoring and reporting queries:</p>
<ul>
<li><code>V_USERENTITLEMENTS</code> - Comprehensive view joining users with their entitlements</li>
<li><code>V_ACTIVE_USERS</code> - Quick access to active users only</li>
<li><code>V_INACTIVE_USERS</code> - Quick access to inactive/deactivated users</li>
<li><code>V_ENTITLEMENT_USAGE</code> - Statistics on entitlement assignments</li>
<li><code>V_USER_HIERARCHY</code> - User reporting structure and management chains</li>
<li><code>V_INACTIVE_USERENTITLEMENTS</code> - Entitlements assigned to inactive users (for cleanup)</li>
</ul>
<p>These views provide simplified access to common queries without writing complex joins, making it easier to monitor your identity data.</p>

<h3 class="relative group">Stored Procedures
    <div id="stored-procedures" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#stored-procedures" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The database includes stored procedures that provide a clean abstraction layer for SCIM operations:</p>
<p><strong>Import Operations (To Okta):</strong></p>
<ol>
<li><code>GET_ACTIVEUSERS()</code> - Retrieve all active users (WHERE IS_ACTIVE = 1)</li>
<li><code>GET_USER_BY_ID(p_user_id)</code> - Get specific user by USER_ID</li>
<li><code>GET_ALL_ENTITLEMENTS()</code> - List all available entitlements</li>
<li><code>GET_USER_ENTITLEMENT(p_user_id)</code> - Get user&rsquo;s assigned entitlements</li>
</ol>
<p><strong>Provisioning Operations (To App):</strong></p>
<ol start="5">
<li><code>CREATE_USER(...)</code> - Create new user with 24 parameters (5 mandatory, 19 optional)</li>
<li><code>UPDATE_USER(...)</code> - Update user attributes with 24 parameters (5 mandatory, 19 optional)</li>
<li><code>ACTIVATE_USER(p_user_id)</code> - Set IS_ACTIVE = TRUE</li>
<li><code>DEACTIVATE_USER(p_user_id)</code> - Set IS_ACTIVE = FALSE</li>
<li><code>ADD_ENTITLEMENT_TO_USER(p_user_id, p_ent_id)</code> - Assign entitlement</li>
<li><code>REMOVE_ENTITLEMENT_FROM_USER(p_user_id, p_ent_id)</code> - Revoke entitlement</li>
</ol>
<p>You can test stored procedures directly:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Test GET_ACTIVEUSERS</span>
</span></span><span class="line"><span class="cl">docker compose <span class="nb">exec</span> db mariadb -u oktademo -poktademo oktademo <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="s2">&#34;CALL GET_ACTIVEUSERS();&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Test GET_USER_ENTITLEMENT</span>
</span></span><span class="line"><span class="cl">docker compose <span class="nb">exec</span> db mariadb -u oktademo -poktademo oktademo <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="s2">&#34;CALL GET_USER_ENTITLEMENT(&#39;yoda@galaxy.local&#39;);&#34;</span></span></span></code></pre></div></div>

<h3 class="relative group">SQL Queries vs Stored Procedures
    <div id="sql-queries-vs-stored-procedures" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sql-queries-vs-stored-procedures" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The Generic Database Connector supports two approaches for configuring database operations:</p>
<ol>
<li><strong>SQL Statements</strong>: Direct SQL queries (e.g. <code>SELECT</code>, <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>)</li>
<li><strong>Stored Procedures</strong>: Pre-compiled database procedures that encapsulate business logic</li>
</ol>
<p><strong>This guide provides both options</strong> for each operation, allowing you to choose the approach that best fits your requirements and database architecture.
Stored procedures are pre-configured in <a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc/blob/main/sql/stored_proc.sql"  target="_blank" rel="noreferrer">sql/stored_proc.sql</a> and are the recommended approach.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855852398374 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855852398374 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855852398374 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855852398374 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855852398374" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><p><strong>Stored Procedures</strong> are pre-compiled SQL code blocks stored in the database that can be executed with a single call. They act as reusable functions that encapsulate complex queries and business logic.<br>
<strong>Key Benefits:</strong></p>
<ul>
<li><strong>Security</strong>: Parameters are automatically handled, preventing SQL injection attacks</li>
<li><strong>Performance</strong>: Pre-compiled and optimized by the database engine</li>
<li><strong>Maintainability</strong>: Centralized logic makes updates easier without changing Okta configuration</li>
<li><strong>Abstraction</strong>: Hides database complexity from the provisioning layer</li>
<li><strong>Consistency</strong>: Ensures the same logic is applied across all operations</li>
<li><strong>Portability</strong>: Easier to migrate to different databases by just rewriting the stored procedures without changing Okta configuration</li>
</ul>
<p><strong>Example:</strong><br>
Instead of writing: <code>SELECT * FROM USERS WHERE USER_ID = ?</code><br>
You call: <code>CALL GET_USER_BY_ID(?)</code><br>
The procedure internally handles the query, any data transformations, and - eventually - error handling.</p>

  </div>
</div>
<p>The following diagram illustrates how all stored procedures interact with the database tables:</p>
<pre class="not-prose mermaid">
---
config:
  layout: elk
---
flowchart TB
 subgraph s1["Database Tables"]
        USERENTITLEMENTS[("USERENTITLEMENTS<br>Junction Table")]
        ENTITLEMENTS[("ENTITLEMENTS<br>ENT_ID, ENT_NAME, ENT_DESCRIPTION")]
        USERS[("USERS")]
  end
 subgraph s2["Read Operations"]
        GET_USER_ENTITLEMENT["GET_USER_ENTITLEMENT<br>Input: p_user_id"]
        GET_ALL_ENTITLEMENTS["GET_ALL_ENTITLEMENTS<br>Returns all entitlements"]
        GET_USER_BY_ID["GET_USER_BY_ID<br>Input: p_user_id"]
        GET_ACTIVEUSERS["GET_ACTIVEUSERS<br>Returns all active users"]
  end
 subgraph s3["User Lifecycle Operations"]
        DEACTIVATE_USER["DEACTIVATE_USER<br>Input: p_user_id"]
        ACTIVATE_USER["ACTIVATE_USER<br>Input: p_user_id"]
        UPDATE_USER["UPDATE_USER<br>29 Parameters<br>5 mandatory + 24 optional"]
        CREATE_USER["CREATE_USER<br>29 Parameters<br>5 mandatory + 24 optional"]
  end
 subgraph s4["Entitlement Management"]
        REMOVE_ENTITLEMENT["REMOVE_ENTITLEMENT_FROM_USER<br>Inputs: p_user_id, p_ent_id"]
        ADD_ENTITLEMENT["ADD_ENTITLEMENT_TO_USER<br>Inputs: p_user_id, p_ent_id"]
  end
    GET_ACTIVEUSERS -- "SELECT WHERE IS_ACTIVE=1" --> USERS
    GET_USER_BY_ID -- "SELECT WHERE USER_ID=?" --> USERS
    GET_ALL_ENTITLEMENTS -- SELECT * --> ENTITLEMENTS
    GET_USER_ENTITLEMENT -- JOIN --> USERENTITLEMENTS & USERS & ENTITLEMENTS
    CREATE_USER -- INSERT --> USERS
    UPDATE_USER -- "UPDATE WHERE USER_ID=?" --> USERS
    ACTIVATE_USER -- "UPDATE IS_ACTIVE=1" --> USERS
    DEACTIVATE_USER -- "UPDATE IS_ACTIVE=0" --> USERS
    ADD_ENTITLEMENT -- INSERT --> USERENTITLEMENTS
    ADD_ENTITLEMENT -. Validates .-> USERS & ENTITLEMENTS
    REMOVE_ENTITLEMENT -- DELETE --> USERENTITLEMENTS

     USERS:::tableStyle
     ENTITLEMENTS:::tableStyle
     USERENTITLEMENTS:::tableStyle
     GET_ACTIVEUSERS:::readStyle
     GET_USER_BY_ID:::readStyle
     GET_ALL_ENTITLEMENTS:::readStyle
     GET_USER_ENTITLEMENT:::readStyle
     CREATE_USER:::lifecycleStyle
     UPDATE_USER:::lifecycleStyle
     ACTIVATE_USER:::lifecycleStyle
     DEACTIVATE_USER:::lifecycleStyle
     ADD_ENTITLEMENT:::entitlementStyle
     REMOVE_ENTITLEMENT:::entitlementStyle
    classDef tableStyle fill:#e1f5ff,stroke:#0066cc,stroke-width:2px
    classDef readStyle fill:#d4edda,stroke:#28a745,stroke-width:2px
    classDef lifecycleStyle fill:#fff3cd,stroke:#ffc107,stroke-width:2px
    classDef entitlementStyle fill:#f8d7da,stroke:#dc3545,stroke-width:2px
</pre>

<hr>

<h2 class="relative group">Configuring Okta Integration
    <div id="configuring-okta-integration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuring-okta-integration" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Now that the infrastructure is running, it&rsquo;s time to configure the Okta side of the integration. This section covers the complete setup process in the Okta Admin Console.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855852637401 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855852637401 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855852637401 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855852637401 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855852637401" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>Multi-Database Support:</strong> A single OPP Agent and SCIM Server can connect to <strong>up to 8 different databases</strong> simultaneously. This allows you to manage users and entitlements across multiple database systems from a single on-premises infrastructure. Each database connection is configured as a <strong>separate Generic Database Connector application instance</strong> in Okta.
  </div>
</div>

<h3 class="relative group">Create the Generic Database Connector Application
    <div id="create-the-generic-database-connector-application" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#create-the-generic-database-connector-application" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>
<p><strong>Navigate to Applications</strong></p>
<ul>
<li>Log on to <strong>Okta Admin Console</strong></li>
<li>Navigate to <strong>Applications</strong> → <strong>Browse App Catalog</strong></li>
</ul>
</li>
<li>
<p><strong>Search for Generic Database Connector</strong></p>
<ul>
<li>In the search box, type <strong>&ldquo;Generic Database&rdquo;</strong></li>
<li>Select <strong>&ldquo;On-prem connector for Generic Databases&rdquo;</strong> from the results</li>
</ul>
</li>
<li>
<p><strong>Add the Integration</strong></p>
<ul>
<li>Click <strong>Add Integration</strong></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Click Add Integration button to add the Generic Database Connector application"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-add-integration-button_hu_20b937aab4bc47d1.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-add-integration-button_hu_80eb9ff66269e463.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-add-integration-button_hu_dc1493a23ec97c84.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-add-integration-button_hu_8a3ce02aafe5b29f.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-add-integration-button_hu_92ec79d7ab7fdc44.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-add-integration-button.png"
    src="/howto/okta-generic-jdbc-connector/okta-add-integration-button.png">


  
</figure>
</li>
<li>
<p><strong>Configure Application Label</strong></p>
<ul>
<li>Provide a name for the application in the <strong>Application Label</strong> field (Default name: &ldquo;Generic Database Connector&rdquo;)</li>
<li>Check &ldquo;Do not display application icon to users&rdquo;</li>
<li>Click <strong>Next</strong></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Configure application label and visibility settings for Generic Database Connector"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-application-label-configuration_hu_e3781ca2b2d1656d.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-application-label-configuration_hu_2f17426dc9b9a405.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-application-label-configuration_hu_ca4d820d7d2cb57.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-application-label-configuration_hu_9f83a1609d448145.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-application-label-configuration_hu_33d63facab753a7b.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-application-label-configuration.png"
    src="/howto/okta-generic-jdbc-connector/okta-application-label-configuration.png">


  
</figure>
</li>
<li>
<p><strong>Complete Application Setup</strong></p>
<ul>
<li>Leave all the other fields as default and click <strong>Done</strong></li>
</ul>
</li>
<li>
<p><strong>Enable Entitlement Management</strong></p>
<ul>
<li>In the <strong>General</strong> tab, scroll down to the <strong>Entitlement Management</strong> section</li>
<li>Click <strong>Edit</strong></li>
<li>From the dropdown menu, select <strong>Enable</strong></li>
<li>Click <strong>Save</strong></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Enable entitlement management setting in Okta Generic Database Connector application"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-enable-entitlement-management_hu_8780c9ca28f1b13c.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-enable-entitlement-management_hu_6a65fcd5d36274e7.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-enable-entitlement-management_hu_e22bc0a1d4c5a938.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-enable-entitlement-management_hu_de2b42296b09c448.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-enable-entitlement-management_hu_15b28d10011945e3.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-enable-entitlement-management.png"
    src="/howto/okta-generic-jdbc-connector/okta-enable-entitlement-management.png">


  
</figure>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855852807000 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855852807000 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855852807000 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855852807000 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855852807000" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text">Once entitlement management is enabled, you&rsquo;ll notice that the <strong>Governance</strong> tab now shows additional sub-tabs such as <strong>Entitlements</strong>, <strong>Bundles</strong>, etc.
  </div>
</div>
</li>
</ol>

<h3 class="relative group">Enable and Configure Provisioning
    <div id="enable-and-configure-provisioning" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#enable-and-configure-provisioning" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>
<p><strong>Enable Provisioning</strong></p>
<ul>
<li>Navigate to the <strong>Provisioning</strong> tab</li>
<li>Click <strong>Enable Provisioning</strong></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Click Enable Provisioning button to start provisioning configuration"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-enable-provisioning-button_hu_6577092eeb164055.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-enable-provisioning-button_hu_69cb5b82a6193550.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-enable-provisioning-button_hu_cc68c653f2afebf7.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-enable-provisioning-button_hu_1960f8a5e07ecd82.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-enable-provisioning-button_hu_6cdc3eea2935eb37.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-enable-provisioning-button.png"
    src="/howto/okta-generic-jdbc-connector/okta-enable-provisioning-button.png">


  
</figure>
</li>
<li>
<p><strong>Select OPP Agent</strong></p>
<ul>
<li>This page displays all available Okta Provisioning Agents (both active and inactive) in your Okta org</li>
<li>Select your registered Okta Provisioning Agent <code>okta-opp</code> from the list</li>
<li>Click <strong>Next</strong></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Select the okta-opp provisioning agent from available agents list"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-select-opp-agent_hu_bc150ee47e974900.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-select-opp-agent_hu_90b30bd6d32e82ae.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-select-opp-agent_hu_e5ceec93c8b90e20.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-select-opp-agent_hu_c115bac2b093289d.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-select-opp-agent_hu_e8c3007ef0e5c750.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-select-opp-agent.png"
    src="/howto/okta-generic-jdbc-connector/okta-select-opp-agent.png">


  
</figure>
</li>
<li>
<p><strong>Configure SCIM Server Connection</strong></p>
<ul>
<li>
<p>Enter the <strong>SCIM Hostname</strong>: <code>okta-scim</code> (must match the container name for internal connectivity)</p>
</li>
<li>
<p>Enter the <strong>API Token</strong> with the <code>Bearer</code> prefix (from SCIM Server credentials)</p>
<ul>
<li>Example: <code>Bearer d5307740c879491cedecf70c2225776b</code>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855852997968 {
    background: #FFFAE6;
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855852997968 .panel-icon {
    color: rgb(224,108,0);
  }
  html.dark #panel-1778753855852997968 {
    background: rgb(51,46,27);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855852997968 .panel-icon {
    color: rgb(251,200,40);
  }
</style>

<div id="panel-1778753855852997968" class="flex px-4 py-3 rounded-md shadow panel-warning ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>IMPORTANT</strong>: When configuring the Okta application, you <strong>MUST</strong> add the <code>Bearer</code> prefix before the token value, with a space between <code>Bearer</code> and the token.
  </div>
</div></li>
</ul>
</li>
<li>
<p>Click <strong>Add Files</strong> under <strong>Public Key</strong></p>
</li>
<li>
<p>Upload the certificate file (<code>.crt</code>) from the host system <code>./data/okta-scim/certs/OktaOnPremScimServer-*.crt</code></p>
<ul>
<li>
<p>Or save in a <code>.crt</code> or <code>.pem</code> file the certificate extacted with the command:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose <span class="nb">exec</span> okta-scim bash -c <span class="s1">&#39;cat /opt/OktaOnPremScimServer/certs/OktaOnPremScimServer-*.crt&#39;</span></span></span></code></pre></div></div>
</li>
</ul>
</li>
<li>
<p>Click <strong>Next</strong></p>
</li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Configure SCIM server hostname, bearer token, and upload public key certificate"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-scim-server-connection-config_hu_f2eda888265c0574.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-scim-server-connection-config_hu_f0b0d47588753f92.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-scim-server-connection-config_hu_a82119fa9d8fa77f.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-scim-server-connection-config_hu_e59a753c501302a1.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-scim-server-connection-config_hu_bee0087c9530ece2.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-scim-server-connection-config.png"
    src="/howto/okta-generic-jdbc-connector/okta-scim-server-connection-config.png">


  
</figure>
</li>
<li>
<p><strong>Configure Database Connection</strong></p>
<ul>
<li>Provide the database connection details (change them if you are not using the default values in your <code>.env</code> file):
<ul>
<li><strong>Username</strong>: <code>oktademo</code></li>
<li><strong>Password</strong>: <code>oktademo</code></li>
<li><strong>Type of Database</strong>: Select <code>MySQL</code></li>
<li><strong>IP/Domain Name</strong>: <code>db</code> (must match the container name for internal connectivity)</li>
<li><strong>Port</strong>: <code>3306</code></li>
<li><strong>Database Name</strong>: <code>oktademo</code></li>
<li>Add the following additional key/value pair in the <strong>Database Property: Configuration of Key-Value Pairs</strong> section:
<ul>
<li>Key: <code>allowMultiQueries</code></li>
<li>Value: <code>true</code></li>
</ul>
</li>
</ul>
</li>
<li>Click <strong>Setup Complete</strong></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Configure MySQL database connection details including credentials and allowMultiQueries property"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-database-connection-config_hu_9663900a8c16465f.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-database-connection-config_hu_363a3c4420f74529.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-database-connection-config_hu_30d2cc5b6ea3109a.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-database-connection-config_hu_428e6cda4b9f691f.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-database-connection-config_hu_424026fc5893173c.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-database-connection-config.png"
    src="/howto/okta-generic-jdbc-connector/okta-database-connection-config.png">


  
</figure>
</li>
<li>
<p>You will see a <strong>Connecting agents&hellip;</strong> pop-up for a few seconds.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Connecting agents loading popup during provisioning setup"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-connecting-agents-popup_hu_668ce8aae290fea0.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-connecting-agents-popup_hu_b87d9c8cd53e27c5.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-connecting-agents-popup_hu_2b5a171666133da6.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-connecting-agents-popup_hu_9de6e019b4fb622e.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-connecting-agents-popup_hu_bb56001439f043c4.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-connecting-agents-popup.png"
    src="/howto/okta-generic-jdbc-connector/okta-connecting-agents-popup.png">


  
</figure>
</li>
<li>
<p><strong>Connection Success</strong>: Once the connection is successful, you&rsquo;ll be directed to the <strong>Integration</strong> tab of the <strong>Provisioning</strong> section. From here, you can proceed to configure Schema Discovery &amp; Import and Provisioning operations.</p>
</li>
</ol>

<h3 class="relative group">Configure Import Operations (To Okta)
    <div id="configure-import-operations-to-okta" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configure-import-operations-to-okta" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Import operations retrieve data from the database and bring it into Okta.</p>

<h4 class="relative group">Navigate to Import Settings
    <div id="navigate-to-import-settings" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#navigate-to-import-settings" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ol>
<li>Go to <strong>Okta Admin Console</strong> → <strong>Applications</strong> → <strong>Generic Database Connector</strong></li>
<li>Navigate to the <strong>Provisioning</strong> tab</li>
<li>Go to <strong>Integration</strong> → <strong>To Okta</strong></li>
<li>Click <strong>Edit</strong> next to <strong>Schema discovery &amp; Import</strong></li>
</ol>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Navigate to Provisioning Integration To Okta import settings"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-import-settings-to-okta_hu_b5d3251840236f9d.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-import-settings-to-okta_hu_d9f6a88e1d1f89d9.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-import-settings-to-okta_hu_bfff5a55ac6a5e87.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-import-settings-to-okta_hu_bed6c5cb9f32701d.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-import-settings-to-okta_hu_2e05c255381b7429.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-import-settings-to-okta.png"
    src="/howto/okta-generic-jdbc-connector/okta-import-settings-to-okta.png">


  
</figure>

<h4 class="relative group">1. Get Users
    <div id="1-get-users" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#1-get-users" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Import all active users from the database.</p>
<p><strong>Configuration:</strong></p>
<ul>
<li>
<p>✅ Check <strong>Enabled</strong></p>
</li>
<li>
<p>Option 1 - Select <strong>SQL Statement</strong>, and enter the SQL query:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">USER_ID</span><span class="p">,</span><span class="w"> </span><span class="n">USERNAME</span><span class="p">,</span><span class="w"> </span><span class="n">FIRSTNAME</span><span class="p">,</span><span class="w"> </span><span class="n">LASTNAME</span><span class="p">,</span><span class="w"> </span><span class="n">MIDDLENAME</span><span class="p">,</span><span class="w"> </span><span class="n">EMAIL</span><span class="p">,</span><span class="w"> </span><span class="n">DISPLAYNAME</span><span class="p">,</span><span class="w"> </span><span class="n">NICKNAME</span><span class="p">,</span><span class="w"> </span><span class="n">MOBILEPHONE</span><span class="p">,</span><span class="w"> </span><span class="n">STREETADDRESS</span><span class="p">,</span><span class="w"> </span><span class="n">CITY</span><span class="p">,</span><span class="w"> </span><span class="k">STATE</span><span class="p">,</span><span class="w"> </span><span class="n">ZIPCODE</span><span class="p">,</span><span class="w"> </span><span class="n">COUNTRYCODE</span><span class="p">,</span><span class="w"> </span><span class="n">TIMEZONE</span><span class="p">,</span><span class="w"> </span><span class="n">ORGANIZATION</span><span class="p">,</span><span class="w"> </span><span class="n">DEPARTMENT</span><span class="p">,</span><span class="w"> </span><span class="n">MANAGERID</span><span class="p">,</span><span class="w"> </span><span class="n">MANAGER</span><span class="p">,</span><span class="w"> </span><span class="n">TITLE</span><span class="p">,</span><span class="w"> </span><span class="n">EMPLOYEENUMBER</span><span class="p">,</span><span class="w"> </span><span class="n">HIREDATE</span><span class="p">,</span><span class="w"> </span><span class="n">TERMINATIONDATE</span><span class="p">,</span><span class="w"> </span><span class="n">PASSWORD_HASH</span><span class="p">,</span><span class="w"> </span><span class="n">IS_ACTIVE</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">USERS</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">IS_ACTIVE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span></span></span></code></pre></div></div>
</li>
<li>
<p>Option 2 - Select <strong>Stored Procedure</strong> (Recommended), and enter the stored procedure call:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CALL</span><span class="w"> </span><span class="n">GET_ACTIVEUSERS</span><span class="p">()</span></span></span></code></pre></div></div>
</li>
<li>
<p><strong>User ID Column:</strong> <code>USER_ID</code></p>
</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855853250296 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855853250296 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855853250296 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855853250296 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855853250296" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>What it does:</strong> Retrieves all active users (where <code>IS_ACTIVE = 1</code>) with all fields from the USERS table.
  </div>
</div>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Configure Get Users import operation with stored procedure or SQL query"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-get-users-configuration_hu_f6282fc70543cea8.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-get-users-configuration_hu_161db3492b87a4a7.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-get-users-configuration_hu_9ad2f322e771b279.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-get-users-configuration_hu_58082907f041f6e9.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-get-users-configuration_hu_94643cb08cb891a7.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-get-users-configuration.png"
    src="/howto/okta-generic-jdbc-connector/okta-get-users-configuration.png">


  
</figure>
<hr>

<h4 class="relative group">2. Get All Entitlements
    <div id="2-get-all-entitlements" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#2-get-all-entitlements" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Import all available entitlements from the database.</p>
<p><strong>Configuration:</strong></p>
<ul>
<li>
<p>✅ Check <strong>Enabled</strong></p>
</li>
<li>
<p>Option 1 - Select <strong>SQL Statement</strong>, and enter the SQL query:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">ENT_ID</span><span class="p">,</span><span class="w"> </span><span class="n">ENT_NAME</span><span class="p">,</span><span class="w"> </span><span class="n">ENT_DESCRIPTION</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">ENTITLEMENTS</span></span></span></code></pre></div></div>
</li>
<li>
<p>Option 2 - Select <strong>Stored Procedure</strong> (Recommended), and enter the stored procedure call:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CALL</span><span class="w"> </span><span class="n">GET_ALL_ENTITLEMENTS</span><span class="p">()</span></span></span></code></pre></div></div>
</li>
<li>
<p><strong>Entitlement ID Column:</strong> <code>ENT_ID</code></p>
</li>
<li>
<p><strong>Entitlement Display Column:</strong> <code>ENT_NAME</code></p>
</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855853514754 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855853514754 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855853514754 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855853514754 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855853514754" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>What it does:</strong> Retrieves all entitlements from the ENTITLEMENTS table (e.g., VPN Access, GitHub Admin, AWS Console).
  </div>
</div>
<hr>

<h4 class="relative group">3. Get User by ID
    <div id="3-get-user-by-id" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#3-get-user-by-id" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Retrieve specific user details by their USER_ID.</p>
<p><strong>Configuration:</strong></p>
<ul>
<li>
<p>✅ Check <strong>Enabled</strong></p>
</li>
<li>
<p>Option 1 - Select <strong>SQL Statement</strong>, and enter the SQL query:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">USER_ID</span><span class="p">,</span><span class="w"> </span><span class="n">USERNAME</span><span class="p">,</span><span class="w"> </span><span class="n">FIRSTNAME</span><span class="p">,</span><span class="w"> </span><span class="n">LASTNAME</span><span class="p">,</span><span class="w"> </span><span class="n">MIDDLENAME</span><span class="p">,</span><span class="w"> </span><span class="n">EMAIL</span><span class="p">,</span><span class="w"> </span><span class="n">DISPLAYNAME</span><span class="p">,</span><span class="w"> </span><span class="n">NICKNAME</span><span class="p">,</span><span class="w"> </span><span class="n">MOBILEPHONE</span><span class="p">,</span><span class="w"> </span><span class="n">STREETADDRESS</span><span class="p">,</span><span class="w"> </span><span class="n">CITY</span><span class="p">,</span><span class="w"> </span><span class="k">STATE</span><span class="p">,</span><span class="w"> </span><span class="n">ZIPCODE</span><span class="p">,</span><span class="w"> </span><span class="n">COUNTRYCODE</span><span class="p">,</span><span class="w"> </span><span class="n">TIMEZONE</span><span class="p">,</span><span class="w"> </span><span class="n">ORGANIZATION</span><span class="p">,</span><span class="w"> </span><span class="n">DEPARTMENT</span><span class="p">,</span><span class="w"> </span><span class="n">MANAGERID</span><span class="p">,</span><span class="w"> </span><span class="n">MANAGER</span><span class="p">,</span><span class="w"> </span><span class="n">TITLE</span><span class="p">,</span><span class="w"> </span><span class="n">EMPLOYEENUMBER</span><span class="p">,</span><span class="w"> </span><span class="n">HIREDATE</span><span class="p">,</span><span class="w"> </span><span class="n">TERMINATIONDATE</span><span class="p">,</span><span class="w"> </span><span class="n">PASSWORD_HASH</span><span class="p">,</span><span class="w"> </span><span class="n">IS_ACTIVE</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">USERS</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">USER_ID</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span></span></span></code></pre></div></div>
</li>
<li>
<p>Option 2 - Select <strong>Stored Procedure</strong> (Recommended), and enter the stored procedure call:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CALL</span><span class="w"> </span><span class="n">GET_USER_BY_ID</span><span class="p">(</span><span class="o">?</span><span class="p">)</span></span></span></code></pre></div></div>
</li>
<li>
<p><strong>Map Parameters to Fields:</strong></p>
<ul>
<li>Parameter 1: <code>DATABASE_FIELD</code> → <code>USER_ID</code></li>
</ul>
</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855853736502 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855853736502 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855853736502 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855853736502 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855853736502" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>What it does:</strong> Queries a specific user from the USERS table using their USER_ID, returning all fields.
  </div>
</div>
<hr>

<h4 class="relative group">4. Get User Entitlements
    <div id="4-get-user-entitlements" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#4-get-user-entitlements" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Retrieve all entitlements assigned to a specific user.</p>
<p><strong>Configuration:</strong></p>
<ul>
<li>
<p>✅ Check <strong>Enabled</strong></p>
</li>
<li>
<p>Option 1 - Select <strong>SQL Statement</strong>, and enter the SQL query:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">UE</span><span class="p">.</span><span class="n">USERENTITLEMENT_ID</span><span class="p">,</span><span class="w"> </span><span class="n">UE</span><span class="p">.</span><span class="n">USER_ID</span><span class="p">,</span><span class="w"> </span><span class="n">U</span><span class="p">.</span><span class="n">USERNAME</span><span class="p">,</span><span class="w"> </span><span class="n">U</span><span class="p">.</span><span class="n">EMAIL</span><span class="p">,</span><span class="w"> </span><span class="n">UE</span><span class="p">.</span><span class="n">ENT_ID</span><span class="p">,</span><span class="w"> </span><span class="n">E</span><span class="p">.</span><span class="n">ENT_NAME</span><span class="p">,</span><span class="w"> </span><span class="n">E</span><span class="p">.</span><span class="n">ENT_DESCRIPTION</span><span class="p">,</span><span class="w"> </span><span class="n">UE</span><span class="p">.</span><span class="n">ASSIGNEDDATE</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="n">USERENTITLEMENTS</span><span class="w"> </span><span class="n">UE</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">JOIN</span><span class="w"> </span><span class="n">USERS</span><span class="w"> </span><span class="n">U</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">UE</span><span class="p">.</span><span class="n">USER_ID</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">U</span><span class="p">.</span><span class="n">USER_ID</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">JOIN</span><span class="w"> </span><span class="n">ENTITLEMENTS</span><span class="w"> </span><span class="n">E</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">UE</span><span class="p">.</span><span class="n">ENT_ID</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">E</span><span class="p">.</span><span class="n">ENT_ID</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">WHERE</span><span class="w"> </span><span class="n">UE</span><span class="p">.</span><span class="n">USER_ID</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span></span></span></code></pre></div></div>
</li>
<li>
<p>Option 2 - Select <strong>Stored Procedure</strong> (Recommended), and enter the stored procedure call:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CALL</span><span class="w"> </span><span class="n">GET_USER_ENTITLEMENT</span><span class="p">(</span><span class="o">?</span><span class="p">)</span></span></span></code></pre></div></div>
</li>
<li>
<p><strong>Map Parameters to Fields:</strong></p>
<ul>
<li>Parameter 1: <code>DATABASE_FIELD</code> → <code>USER_ID</code></li>
</ul>
</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855853978760 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855853978760 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855853978760 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855853978760 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855853978760" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>What it does:</strong> Queries the USERENTITLEMENTS table to retrieve all entitlements for a user with JOIN to USERS and ENTITLEMENTS tables.
  </div>
</div>
<hr>

<h3 class="relative group">Configure Provisioning Operations (To App)
    <div id="configure-provisioning-operations-to-app" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configure-provisioning-operations-to-app" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Provisioning operations push changes from Okta to the database.</p>

<h4 class="relative group">Navigate to Provisioning Settings
    <div id="navigate-to-provisioning-settings" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#navigate-to-provisioning-settings" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ol>
<li>Stay in the <strong>Provisioning</strong> tab</li>
<li>Go to <strong>Integration</strong> → <strong>To App</strong></li>
<li>Click <strong>Edit</strong> next to <strong>Provisioning</strong></li>
</ol>
<hr>

<h4 class="relative group">5. Create User
    <div id="5-create-user" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#5-create-user" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Create a new user in the database when assigned in Okta.</p>
<p><strong>Configuration:</strong></p>
<ul>
<li>
<p>✅ Check <strong>Enabled</strong></p>
</li>
<li>
<p>Option 1 - Select <strong>SQL Statement</strong>, and enter the SQL query:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">USERS</span><span class="w"> </span><span class="p">(</span><span class="n">USER_ID</span><span class="p">,</span><span class="w"> </span><span class="n">USERNAME</span><span class="p">,</span><span class="w"> </span><span class="n">FIRSTNAME</span><span class="p">,</span><span class="w"> </span><span class="n">LASTNAME</span><span class="p">,</span><span class="w"> </span><span class="n">EMAIL</span><span class="p">,</span><span class="w"> </span><span class="n">MIDDLENAME</span><span class="p">,</span><span class="w"> </span><span class="n">DISPLAYNAME</span><span class="p">,</span><span class="w"> </span><span class="n">NICKNAME</span><span class="p">,</span><span class="w"> </span><span class="n">MOBILEPHONE</span><span class="p">,</span><span class="w"> </span><span class="n">STREETADDRESS</span><span class="p">,</span><span class="w"> </span><span class="n">CITY</span><span class="p">,</span><span class="w"> </span><span class="k">STATE</span><span class="p">,</span><span class="w"> </span><span class="n">ZIPCODE</span><span class="p">,</span><span class="w"> </span><span class="n">COUNTRYCODE</span><span class="p">,</span><span class="w"> </span><span class="n">TIMEZONE</span><span class="p">,</span><span class="w"> </span><span class="n">ORGANIZATION</span><span class="p">,</span><span class="w"> </span><span class="n">DEPARTMENT</span><span class="p">,</span><span class="w"> </span><span class="n">MANAGERID</span><span class="p">,</span><span class="w"> </span><span class="n">MANAGER</span><span class="p">,</span><span class="w"> </span><span class="n">TITLE</span><span class="p">,</span><span class="w"> </span><span class="n">EMPLOYEENUMBER</span><span class="p">,</span><span class="w"> </span><span class="n">HIREDATE</span><span class="p">,</span><span class="w"> </span><span class="n">TERMINATIONDATE</span><span class="p">,</span><span class="w"> </span><span class="n">PASSWORD_HASH</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">)</span></span></span></code></pre></div></div>
</li>
<li>
<p>Option 2 - Select <strong>Stored Procedure</strong> (Recommended), and enter the stored procedure call:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CALL</span><span class="w"> </span><span class="n">CREATE_USER</span><span class="p">(</span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">)</span></span></span></code></pre></div></div>
</li>
<li>
<p><strong>Map Parameters to Fields:</strong></p>
<ul>
<li>Parameter 1: <code>DATABASE_FIELD</code> → <code>USER_ID</code> <strong>(required)</strong></li>
<li>Parameter 2: <code>DATABASE_FIELD</code> → <code>USERNAME</code> <strong>(required)</strong></li>
<li>Parameter 3: <code>DATABASE_FIELD</code> → <code>FIRSTNAME</code> <strong>(required)</strong></li>
<li>Parameter 4: <code>DATABASE_FIELD</code> → <code>LASTNAME</code> <strong>(required)</strong></li>
<li>Parameter 5: <code>DATABASE_FIELD</code> → <code>EMAIL</code> <strong>(required)</strong></li>
<li>Parameter 6: <code>DATABASE_FIELD</code> → <code>MIDDLENAME</code></li>
<li>Parameter 7: <code>DATABASE_FIELD</code> → <code>DISPLAYNAME</code></li>
<li>Parameter 8: <code>DATABASE_FIELD</code> → <code>NICKNAME</code></li>
<li>Parameter 9: <code>DATABASE_FIELD</code> → <code>MOBILEPHONE</code></li>
<li>Parameter 10: <code>DATABASE_FIELD</code> → <code>STREETADDRESS</code></li>
<li>Parameter 11: <code>DATABASE_FIELD</code> → <code>CITY</code></li>
<li>Parameter 12: <code>DATABASE_FIELD</code> → <code>STATE</code></li>
<li>Parameter 13: <code>DATABASE_FIELD</code> → <code>ZIPCODE</code></li>
<li>Parameter 14: <code>DATABASE_FIELD</code> → <code>COUNTRYCODE</code></li>
<li>Parameter 15: <code>DATABASE_FIELD</code> → <code>TIMEZONE</code></li>
<li>Parameter 16: <code>DATABASE_FIELD</code> → <code>ORGANIZATION</code></li>
<li>Parameter 17: <code>DATABASE_FIELD</code> → <code>DEPARTMENT</code></li>
<li>Parameter 18: <code>DATABASE_FIELD</code> → <code>MANAGERID</code></li>
<li>Parameter 19: <code>DATABASE_FIELD</code> → <code>MANAGER</code></li>
<li>Parameter 20: <code>DATABASE_FIELD</code> → <code>TITLE</code></li>
<li>Parameter 21: <code>DATABASE_FIELD</code> → <code>EMPLOYEENUMBER</code></li>
<li>Parameter 22: <code>DATABASE_FIELD</code> → <code>HIREDATE</code></li>
<li>Parameter 23: <code>DATABASE_FIELD</code> → <code>TERMINATIONDATE</code></li>
<li>Parameter 24: <code>DATABASE_FIELD</code> → <code>PASSWORD_HASH</code></li>
</ul>
</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855854217808 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855854217808 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855854217808 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855854217808 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855854217808" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>What it does:</strong> Inserts a new row into the USERS table with all user attributes. Only USER_ID, USERNAME, FIRSTNAME, LASTNAME, and EMAIL are mandatory; all other fields are optional and can be NULL.
  </div>
</div>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855854465496 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855854465496 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855854465496 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855854465496 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855854465496" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text">You can use less parameters if you don&rsquo;t want to populate all fields during user creation. For example, you can choose to only pass the 5 mandatory fields and leave the rest as NULL.
  </div>
</div>
<hr>

<h4 class="relative group">6. Update User
    <div id="6-update-user" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#6-update-user" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Update existing user attributes in the database.</p>
<p><strong>Configuration:</strong></p>
<ul>
<li>
<p>✅ Check <strong>Enabled</strong></p>
</li>
<li>
<p>Option 1 - Select <strong>SQL Statement</strong>, and enter the SQL query:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">UPDATE</span><span class="w"> </span><span class="n">USERS</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">SET</span><span class="w"> </span><span class="n">USERNAME</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">FIRSTNAME</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">LASTNAME</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">EMAIL</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">MIDDLENAME</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">DISPLAYNAME</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">NICKNAME</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">MOBILEPHONE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">STREETADDRESS</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">CITY</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="k">STATE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">ZIPCODE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">COUNTRYCODE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">TIMEZONE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">ORGANIZATION</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">DEPARTMENT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">MANAGERID</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">MANAGER</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">TITLE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">EMPLOYEENUMBER</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">HIREDATE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">TERMINATIONDATE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="n">PASSWORD_HASH</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">WHERE</span><span class="w"> </span><span class="n">USER_ID</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span></span></span></code></pre></div></div>
</li>
<li>
<p>Option 2 - Select <strong>Stored Procedure</strong> (Recommended), and enter the stored procedure call:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CALL</span><span class="w"> </span><span class="n">UPDATE_USER</span><span class="p">(</span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">)</span></span></span></code></pre></div></div>
</li>
<li>
<p><strong>Map Parameters to Fields:</strong></p>
<ul>
<li>Parameter 1: <code>DATABASE_FIELD</code> → <code>USER_ID</code> <strong>(required)</strong></li>
<li>Parameter 2: <code>DATABASE_FIELD</code> → <code>USERNAME</code> <strong>(required)</strong></li>
<li>Parameter 3: <code>DATABASE_FIELD</code> → <code>FIRSTNAME</code> <strong>(required)</strong></li>
<li>Parameter 4: <code>DATABASE_FIELD</code> → <code>LASTNAME</code> <strong>(required)</strong></li>
<li>Parameter 5: <code>DATABASE_FIELD</code> → <code>EMAIL</code> <strong>(required)</strong></li>
<li>Parameter 6: <code>DATABASE_FIELD</code> → <code>MIDDLENAME</code></li>
<li>Parameter 7: <code>DATABASE_FIELD</code> → <code>DISPLAYNAME</code></li>
<li>Parameter 8: <code>DATABASE_FIELD</code> → <code>NICKNAME</code></li>
<li>Parameter 9: <code>DATABASE_FIELD</code> → <code>MOBILEPHONE</code></li>
<li>Parameter 10: <code>DATABASE_FIELD</code> → <code>STREETADDRESS</code></li>
<li>Parameter 11: <code>DATABASE_FIELD</code> → <code>CITY</code></li>
<li>Parameter 12: <code>DATABASE_FIELD</code> → <code>STATE</code></li>
<li>Parameter 13: <code>DATABASE_FIELD</code> → <code>ZIPCODE</code></li>
<li>Parameter 14: <code>DATABASE_FIELD</code> → <code>COUNTRYCODE</code></li>
<li>Parameter 15: <code>DATABASE_FIELD</code> → <code>TIMEZONE</code></li>
<li>Parameter 16: <code>DATABASE_FIELD</code> → <code>ORGANIZATION</code></li>
<li>Parameter 17: <code>DATABASE_FIELD</code> → <code>DEPARTMENT</code></li>
<li>Parameter 18: <code>DATABASE_FIELD</code> → <code>MANAGERID</code></li>
<li>Parameter 19: <code>DATABASE_FIELD</code> → <code>MANAGER</code></li>
<li>Parameter 20: <code>DATABASE_FIELD</code> → <code>TITLE</code></li>
<li>Parameter 21: <code>DATABASE_FIELD</code> → <code>EMPLOYEENUMBER</code></li>
<li>Parameter 22: <code>DATABASE_FIELD</code> → <code>HIREDATE</code></li>
<li>Parameter 23: <code>DATABASE_FIELD</code> → <code>TERMINATIONDATE</code></li>
<li>Parameter 24: <code>DATABASE_FIELD</code> → <code>PASSWORD_HASH</code></li>
</ul>
</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855854723184 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855854723184 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855854723184 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855854723184 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855854723184" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>What it does:</strong> Updates the USERS table record matching the USER_ID with new attribute values. Only USER_ID, USERNAME, FIRSTNAME, LASTNAME, and EMAIL are mandatory; all other fields are optional and can be NULL.
  </div>
</div>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855854982072 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855854982072 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855854982072 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855854982072 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855854982072" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text">You can use less parameters if you don&rsquo;t want to populate all fields during user updates. For example, you can choose to only pass the 5 mandatory fields and leave the rest as NULL.
  </div>
</div>
<hr>

<h4 class="relative group">7. Activate User
    <div id="7-activate-user" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#7-activate-user" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Activate a user account.</p>
<p><strong>Configuration:</strong></p>
<ul>
<li>
<p>✅ Check <strong>Enabled</strong></p>
</li>
<li>
<p>Option 1 - Select <strong>SQL Statement</strong>, and enter the SQL query:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">UPDATE</span><span class="w"> </span><span class="n">USERS</span><span class="w"> </span><span class="k">SET</span><span class="w"> </span><span class="n">IS_ACTIVE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">USER_ID</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span></span></span></code></pre></div></div>
</li>
<li>
<p>Option 2 - Select <strong>Stored Procedure</strong> (Recommended), and enter the stored procedure call:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CALL</span><span class="w"> </span><span class="n">ACTIVATE_USER</span><span class="p">(</span><span class="o">?</span><span class="p">)</span></span></span></code></pre></div></div>
</li>
<li>
<p><strong>Map Parameters to Fields:</strong></p>
<ul>
<li>Parameter 1: <code>DATABASE_FIELD</code> → <code>USER_ID</code></li>
</ul>
</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855855273819 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855855273819 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855855273819 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855855273819 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855855273819" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>What it does:</strong> Sets <code>IS_ACTIVE = TRUE</code> for the specified user in the USERS table.
  </div>
</div>
<hr>

<h4 class="relative group">8. Deactivate User
    <div id="8-deactivate-user" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#8-deactivate-user" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Deactivate a user account.</p>
<p><strong>Configuration:</strong></p>
<ul>
<li>
<p>✅ Check <strong>Enabled</strong></p>
</li>
<li>
<p>Option 1 - Select <strong>SQL Statement</strong>, and enter the SQL query:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">UPDATE</span><span class="w"> </span><span class="n">USERS</span><span class="w"> </span><span class="k">SET</span><span class="w"> </span><span class="n">IS_ACTIVE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">USER_ID</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span></span></span></code></pre></div></div>
</li>
<li>
<p>Option 2 - Select <strong>Stored Procedure</strong> (Recommended), and enter the stored procedure call:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CALL</span><span class="w"> </span><span class="n">DEACTIVATE_USER</span><span class="p">(</span><span class="o">?</span><span class="p">)</span></span></span></code></pre></div></div>
</li>
<li>
<p><strong>Map Parameters to Fields:</strong></p>
<ul>
<li>Parameter 1: <code>DATABASE_FIELD</code> → <code>USER_ID</code></li>
</ul>
</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855855522097 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855855522097 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855855522097 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855855522097 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855855522097" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>What it does:</strong> Sets <code>IS_ACTIVE = FALSE</code> for the specified user in the USERS table.
  </div>
</div>
<hr>

<h4 class="relative group">9. Add Entitlement to User
    <div id="9-add-entitlement-to-user" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#9-add-entitlement-to-user" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Assign an entitlement to a user.</p>
<p><strong>Configuration:</strong></p>
<ul>
<li>
<p>✅ Check <strong>Enabled</strong></p>
</li>
<li>
<p>Option 1 - Select <strong>SQL Statement</strong>, and enter the SQL query:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">USERENTITLEMENTS</span><span class="w"> </span><span class="p">(</span><span class="n">USER_ID</span><span class="p">,</span><span class="w"> </span><span class="n">ENT_ID</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">)</span></span></span></code></pre></div></div>
</li>
<li>
<p>Option 2 - Select <strong>Stored Procedure</strong> (Recommended), and enter the stored procedure call:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CALL</span><span class="w"> </span><span class="n">ADD_ENTITLEMENT_TO_USER</span><span class="p">(</span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">)</span></span></span></code></pre></div></div>
</li>
<li>
<p><strong>Map Parameters to Fields:</strong></p>
<ul>
<li>Parameter 1: <code>DATABASE_FIELD</code> → <code>USER_ID</code></li>
<li>Parameter 2: <code>DATABASE_FIELD</code> → <code>ENT_ID</code></li>
</ul>
</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855855784395 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855855784395 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855855784395 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855855784395 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855855784395" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>What it does:</strong> Inserts a new row into the USERENTITLEMENTS table, creating a user-entitlement mapping.
  </div>
</div>
<hr>

<h4 class="relative group">10. Remove Entitlement from User
    <div id="10-remove-entitlement-from-user" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#10-remove-entitlement-from-user" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Revoke an entitlement from a user.</p>
<p><strong>Configuration:</strong></p>
<ul>
<li>
<p>✅ Check <strong>Enabled</strong></p>
</li>
<li>
<p>Option 1 - Select <strong>SQL Statement</strong>, and enter the SQL query:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">DELETE</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">USERENTITLEMENTS</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">USER_ID</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="n">ENT_ID</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span></span></span></code></pre></div></div>
</li>
<li>
<p>Option 2 - Select <strong>Stored Procedure</strong> (Recommended), and enter the stored procedure call:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CALL</span><span class="w"> </span><span class="n">REMOVE_ENTITLEMENT_FROM_USER</span><span class="p">(</span><span class="o">?</span><span class="p">,</span><span class="w"> </span><span class="o">?</span><span class="p">)</span></span></span></code></pre></div></div>
</li>
<li>
<p><strong>Map Parameters to Fields:</strong></p>
<ul>
<li>Parameter 1: <code>DATABASE_FIELD</code> → <code>USER_ID</code></li>
<li>Parameter 2: <code>DATABASE_FIELD</code> → <code>ENT_ID</code></li>
</ul>
</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855856028283 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855856028283 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855856028283 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855856028283 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855856028283" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>What it does:</strong> Deletes the row from the USERENTITLEMENTS table matching the user and entitlement.
  </div>
</div>
<hr>

<h3 class="relative group">Configure User Profile Attributes
    <div id="configure-user-profile-attributes" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configure-user-profile-attributes" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Before enabling provisioning features, you need to configure attribute mappings between Okta and the database.</p>
<ol>
<li>
<p><strong>Navigate to Profile Editor</strong></p>
<ul>
<li>In Okta Admin Console, go to <strong>Directory</strong> → <strong>Profile Editor</strong></li>
</ul>
</li>
<li>
<p><strong>Select Generic Database Connector User Profile</strong></p>
<ul>
<li>Search for <strong>&ldquo;Generic Database Connector&rdquo;</strong></li>
<li>Individuate the profile named <strong>&ldquo;Generic Database Connector User&rdquo;</strong> and click <strong>Mappings</strong>.</li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Select Generic Database Connector User profile in Okta Profile Editor"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-profile-editor-selection_hu_278785e1e081ad63.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-profile-editor-selection_hu_ca05b12089403f85.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-profile-editor-selection_hu_1013cd183c3a4278.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-profile-editor-selection_hu_c274790187e45db2.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-profile-editor-selection_hu_5754f7b0e5fedcba.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-profile-editor-selection.png"
    src="/howto/okta-generic-jdbc-connector/okta-profile-editor-selection.png">


  
</figure>
</li>
<li>
<p><strong>Add Attributes from Database</strong></p>
<ul>
<li>Under <strong>Attributes</strong>, you&rsquo;ll see that only the <strong>Username</strong> attribute is present</li>
<li>Click <strong>+ Add Attribute</strong></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Click Add Attribute button to import database fields"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-add-attribute-button_hu_82a7218e20ca2784.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-add-attribute-button_hu_e9bd1319cb825c3a.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-add-attribute-button_hu_d641791375bca64e.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-add-attribute-button_hu_cfc0e73bbdbf59d7.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-add-attribute-button_hu_bf620bae978489c3.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-add-attribute-button.png"
    src="/howto/okta-generic-jdbc-connector/okta-add-attribute-button.png">


  
</figure>
</li>
<li>
<p><strong>Import Database Attributes</strong></p>
<ul>
<li>The next page displays all attributes imported from the database</li>
<li>Check all the required attributes:
<ul>
<li><code>ext_USER_ID</code></li>
<li><code>ext_FIRSTNAME</code></li>
<li><code>ext_LASTNAME</code></li>
<li><code>ext_EMAIL</code></li>
<li><code>ext_MANAGER</code></li>
<li><code>ext_TITLE</code></li>
<li>And any other custom attributes (you can click the first checkbox to select all)</li>
<li>You can click the first box at the top to select all</li>
</ul>
</li>
<li>Click <strong>Save</strong></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Select and import database attributes into application user profile"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-import-database-attributes_hu_3300146db3a4c342.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-import-database-attributes_hu_caf5c60babb2f389.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-import-database-attributes_hu_95fa9c75094268a3.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-import-database-attributes_hu_b18f29b9dcbf2172.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-import-database-attributes_hu_a8ba59d61a277f8a.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-import-database-attributes.png"
    src="/howto/okta-generic-jdbc-connector/okta-import-database-attributes.png">


  
</figure>
</li>
<li>
<p><strong>Verify Attributes</strong></p>
<ul>
<li>The Generic Database Connector user profile now contains all attributes needed for provisioning operations</li>
</ul>
</li>
</ol>
<hr>

<h4 class="relative group">Generic Database Connector User to Okta User
    <div id="generic-database-connector-user-to-okta-user" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#generic-database-connector-user-to-okta-user" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>This mapping governs how user accounts from the database are imported into Okta.</p>
<p><strong>Configuration Steps:</strong></p>
<ol>
<li>
<p><strong>Navigate to Mappings</strong></p>
<ul>
<li>
<p>Within the <strong>Generic Database Connector User</strong> profile, click <strong>Mappings</strong></p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Click Mappings button to configure attribute mapping between profiles"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-mappings-button_hu_72d19b5a1ec03330.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-mappings-button_hu_432ac1e417fd3b5d.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-mappings-button_hu_56b0469b35ec1bcb.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-mappings-button_hu_cc83c2198b390abe.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-mappings-button_hu_de400dc135f597ca.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-mappings-button.png"
    src="/howto/okta-generic-jdbc-connector/okta-mappings-button.png">


  
</figure>
</li>
</ul>
</li>
<li>
<p><strong>Configure Import Mappings</strong></p>
<ul>
<li>By default, <strong>Generic Database Connector User to Okta User</strong> is selected</li>
<li>You&rsquo;ll see that mapping for <code>login</code> is present, but others are empty</li>
<li>Set up mappings for additional attributes as needed</li>
</ul>
</li>
<li>
<p><strong>Map Attributes</strong></p>
<ul>
<li>Under <strong>Okta User Profile</strong>, click the dropdown in <strong>Choose an attribute or enter an expression</strong></li>
<li>Select the corresponding attribute from <strong>Generic Database Connector User Profile</strong></li>
</ul>
<p>Example mappings:</p>
<ul>
<li><code>appuser.ext_FIRSTNAME</code> → <code>firstName</code></li>
<li><code>appuser.ext_LASTNAME</code> → <code>lastName</code></li>
<li><code>appuser.ext_EMAIL</code> → <code>email</code></li>
<li><code>appuser.ext_TITLE</code> → <code>title</code></li>
<li><code>appuser.ext_MANAGER</code> → <code>managerId</code></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Configure attribute mappings from Generic Database Connector to Okta User profile"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-attribute-mapping-to-okta-user_hu_828e5e6c118ea0ab.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-attribute-mapping-to-okta-user_hu_1deffb2b12ecc306.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-attribute-mapping-to-okta-user_hu_62f11ac839af29e8.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-attribute-mapping-to-okta-user_hu_d5e46a914ea3d84b.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-attribute-mapping-to-okta-user_hu_2e51f5071d125611.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-attribute-mapping-to-okta-user.png"
    src="/howto/okta-generic-jdbc-connector/okta-attribute-mapping-to-okta-user.png">


  
</figure>
</li>
<li>
<p><strong>Save Mappings</strong></p>
<ul>
<li>Click <strong>Save</strong></li>
<li>Click <strong>Apply updates</strong></li>
</ul>
</li>
</ol>

<h4 class="relative group">Okta User to Generic Database Connector User
    <div id="okta-user-to-generic-database-connector-user" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#okta-user-to-generic-database-connector-user" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>This mapping dictates how user attributes in Okta correlate with the database user profile, facilitating user creation or modification in the database.</p>
<p><strong>Configuration Steps:</strong></p>
<ol>
<li>
<p><strong>Navigate to Mappings</strong></p>
<ul>
<li>Within the <strong>Generic Database Connector User</strong> profile, click <strong>Mappings</strong></li>
</ul>
</li>
<li>
<p><strong>Select Okta User to Generic Database Connector User</strong></p>
<ul>
<li>Click <strong>Okta User to Generic Database Connector User</strong></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Select Okta User to Generic Database Connector User mapping direction"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-user-to-db-connector-mapping_hu_c9eadf6ea9b5e79a.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-user-to-db-connector-mapping_hu_4d8568e35ddb82e.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-user-to-db-connector-mapping_hu_82a6baba039c1950.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-user-to-db-connector-mapping_hu_c46fd4a564aa9c04.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-user-to-db-connector-mapping_hu_655913b93105f7fb.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-user-to-db-connector-mapping.png"
    src="/howto/okta-generic-jdbc-connector/okta-user-to-db-connector-mapping.png">


  
</figure>
</li>
<li>
<p><strong>Map Attributes</strong></p>
<ul>
<li>Click the dropdown under <strong>Choose an attribute or enter an expression</strong></li>
<li>Select the appropriate attributes from <strong>Okta User Profile</strong></li>
</ul>
<p>Example mappings:</p>
<ul>
<li><code>login</code> → <code>ext_USER_ID</code></li>
<li><code>login</code> → <code>ext_USERNAME</code></li>
<li><code>firstName</code> → <code>ext_FIRSTNAME</code></li>
<li><code>lastName</code> → <code>ext_LASTNAME</code></li>
<li><code>email</code> → <code>ext_EMAIL</code></li>
<li><code>managerId</code> → <code>ext_MANAGER</code></li>
<li><code>title</code> → <code>ext_TITLE</code></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Configure attribute mappings from Okta User profile to database fields"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-attribute-mapping-details_hu_1366b8f49a2a2d3d.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-attribute-mapping-details_hu_4675d8a501f691f8.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-attribute-mapping-details_hu_3822ae214921fa93.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-attribute-mapping-details_hu_ca00e9559b26d28a.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-attribute-mapping-details_hu_6e3dd82508de3b4a.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-attribute-mapping-details.png"
    src="/howto/okta-generic-jdbc-connector/okta-attribute-mapping-details.png">


  
</figure>
</li>
<li>
<p><strong>Save Mappings</strong></p>
<ul>
<li>Click <strong>Save Mappings</strong></li>
</ul>
</li>
</ol>
<hr>

<h3 class="relative group">Enable Provisioning Features
    <div id="enable-provisioning-features" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#enable-provisioning-features" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>
<p><strong>Navigate to Provisioning Settings</strong></p>
<ul>
<li>In Okta Admin Console, go to <strong>Applications</strong> → <strong>Generic Database Connector</strong></li>
<li>Click the <strong>Provisioning</strong> tab</li>
<li>Navigate to <strong>Settings</strong> → <strong>To App</strong></li>
</ul>
</li>
<li>
<p><strong>Edit Provisioning Settings</strong></p>
<ul>
<li>Click <strong>Edit</strong> in the <strong>Provisioning to App</strong> section</li>
</ul>
</li>
<li>
<p><strong>Enable Provisioning Features</strong></p>
<ul>
<li>✅ <strong>Create Users</strong>: Enable to create users in the database when assigned in Okta</li>
<li>✅ <strong>Update User Attributes</strong>: Enable to sync attribute changes from Okta to database</li>
<li>✅ <strong>Deactivate Users</strong>: Enable to deactivate users in database when unassigned from Okta</li>
</ul>
</li>
<li>
<p><strong>Save Configuration</strong></p>
<ul>
<li>Click <strong>Save</strong></li>
</ul>
</li>
</ol>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Enable provisioning features including create, update, and deactivate users"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-provisioning-to-app-settings_hu_874f78b0cf560195.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-provisioning-to-app-settings_hu_439113ccbd55b27c.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-provisioning-to-app-settings_hu_5bdf434342ed0e2.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-provisioning-to-app-settings_hu_7e615f2aebbe424e.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-provisioning-to-app-settings_hu_9b05846a83db4dba.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-provisioning-to-app-settings.png"
    src="/howto/okta-generic-jdbc-connector/okta-provisioning-to-app-settings.png">


  
</figure>

<h3 class="relative group">Configure Import Schedule
    <div id="configure-import-schedule" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configure-import-schedule" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>
<p><strong>Navigate to Import Settings</strong></p>
<ul>
<li>Go to <strong>Applications</strong> → <strong>Generic Database Connector</strong></li>
<li>Click <strong>Provisioning</strong> tab</li>
<li>Navigate to <strong>Settings</strong> → <strong>To Okta</strong></li>
<li>Click <strong>Edit</strong> next to <strong>General</strong></li>
</ul>
</li>
<li>
<p><strong>Configure Import Schedule</strong></p>
<ul>
<li>Under <strong>Full Import Schedule</strong>, select the desired frequency for importing users (e.g., every 6 hours)</li>
<li>Do not configure <strong>Incremental Import Schedule</strong> as the database does not have a timestamp field to track changes</li>
</ul>
</li>
<li>
<p><strong>Configure Okta Username Format</strong></p>
<ul>
<li>Under <strong>Okta username format</strong>, select <strong>Custom</strong> from the dropdown</li>
<li>Enter: <code>appuser.ext_EMAIL</code> in the textbox</li>
<li>This ensures usernames are in email format using the <code>appuser.ext_USERNAME</code> attribute</li>
</ul>
</li>
<li>
<p><strong>Save Configuration</strong></p>
<ul>
<li>Click <strong>Save</strong></li>
</ul>
</li>
</ol>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Configure import schedule and username format for database imports"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-import-schedule-configuration_hu_2d13c1aa8bc2d4d0.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-import-schedule-configuration_hu_fed13204bc933fa9.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-import-schedule-configuration_hu_9fdb15b650f0231c.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-import-schedule-configuration_hu_ff68ae0094bc33b6.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-import-schedule-configuration_hu_e7c8b0d8a84b4e70.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-import-schedule-configuration.png"
    src="/howto/okta-generic-jdbc-connector/okta-import-schedule-configuration.png">


  
</figure>
<hr>

<h2 class="relative group">Testing the Integration
    <div id="testing-the-integration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#testing-the-integration" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>With configuration complete, it&rsquo;s time to test the full integration workflow.</p>

<h3 class="relative group">Test #1: Import Users from Database
    <div id="test-1-import-users-from-database" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-1-import-users-from-database" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>This test imports existing database users into Okta.</p>
<ol>
<li>
<p><strong>Navigate to Import Tab</strong></p>
<ul>
<li>Go to <strong>Applications</strong> → <strong>Generic Database Connector</strong></li>
<li>Click the <strong>Import</strong> tab</li>
</ul>
</li>
<li>
<p><strong>Start Import</strong></p>
<ul>
<li>Click <strong>Import Now</strong></li>
<li>Select <strong>Full Import</strong></li>
<li>Click <strong>Import</strong></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Click Import Now button and select Full Import to start user import"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-import-now-button_hu_1d6ecd7920c2b6db.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-import-now-button_hu_4dd3ab290768f37c.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-import-now-button_hu_b4044a5fc80dc13e.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-import-now-button_hu_d87e049064ae4d27.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-import-now-button_hu_7937e08a914afc13.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-import-now-button.png"
    src="/howto/okta-generic-jdbc-connector/okta-import-now-button.png">


  
</figure>
</li>
<li>
<p><strong>Review Import Results</strong></p>
<ul>
<li>Once import completes, you&rsquo;ll see an <strong>Import Success</strong> message</li>
<li>Review the list of users imported from the database</li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="View import success results with list of discovered users"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-import-success-results_hu_920259f0a6a5a625.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-import-success-results_hu_df39772fbf04dd31.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-import-success-results_hu_6d4915381ac7cd62.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-import-success-results_hu_1b56c81a11967f79.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-import-success-results_hu_c4f5741b7a4cedcc.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-import-success-results.png"
    src="/howto/okta-generic-jdbc-connector/okta-import-success-results.png">


  
</figure>
</li>
<li>
<p><strong>Confirm User Assignments</strong></p>
<ul>
<li>By default, imported users require manual confirmation (configurable in <em>Provisioning → To Okta → User Creation &amp; Matching</em>)</li>
<li>Select the users you want to import into Okta</li>
<li>Click <strong>Confirm Assignments</strong></li>
<li>Select <strong>Auto-activate users after confirmation</strong></li>
<li>Click <strong>Confirm</strong> when prompted</li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Confirm user assignments and auto-activate imported users"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-confirm-assignments_hu_1c0b53b1d40f6cad.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-confirm-assignments_hu_1aa26478834cd8ea.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-confirm-assignments_hu_c14ddab166c8f74d.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-confirm-assignments_hu_359e71f2e1f6826c.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-confirm-assignments_hu_50238c9c3251eaca.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-confirm-assignments.png"
    src="/howto/okta-generic-jdbc-connector/okta-confirm-assignments.png">


  
</figure>
</li>
<li>
<p><strong>Verify Imported Users</strong></p>
<ul>
<li>Navigate to the <strong>Assignments</strong> tab</li>
<li>Verify that imported users are now visible</li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="View imported users in the Assignments tab"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-assignments-imported-users_hu_c47f9e37320244f4.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-assignments-imported-users_hu_c8aa78ed9a23baf8.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-assignments-imported-users_hu_ac01ecfed9170e5d.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-assignments-imported-users_hu_cbaf75200dcc8a8c.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-assignments-imported-users_hu_c8b2879da3e9a39c.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-assignments-imported-users.png"
    src="/howto/okta-generic-jdbc-connector/okta-assignments-imported-users.png">


  
</figure>
</li>
<li>
<p><strong>Check Entitlements</strong></p>
<ul>
<li>Click the <strong>Governance</strong> tab</li>
<li>Click <strong>Entitlements</strong></li>
<li>Verify that the entitlements from the database are listed in Okta</li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="View all entitlements imported from database in Governance tab"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-governance-entitlements-tab_hu_e92e9cbf3d1ed8aa.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-governance-entitlements-tab_hu_2efbd56a37ec3a97.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-governance-entitlements-tab_hu_cf36d61455e904a0.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-governance-entitlements-tab_hu_e6c67c1faef069f8.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-governance-entitlements-tab_hu_937ce2418d67df98.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-governance-entitlements-tab.png"
    src="/howto/okta-generic-jdbc-connector/okta-governance-entitlements-tab.png">


  
</figure>
<ul>
<li>Notes:
<ul>
<li>At the moment only the <strong>Display Name</strong> and <strong>Value Name</strong> of the entitlement are supported. The <strong>Description</strong> is not yet included in the list</li>
<li>To define the <strong>Governance Label</strong> refer to the <a href="https://help.okta.com/oie/en-us/content/topics/identity-governance/resource-labels/resource-labels.htm"  target="_blank" rel="noreferrer">Okta documentation for Resource labels</a></li>
<li>Despite other application integrated with the Okta Governance, at the moment the Database Connector <strong>support only one entitlement type</strong> per each application instance.</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Check user entitlements</strong></p>
<ul>
<li>
<p>Click on the three dots next to the user you just imported in the <strong>Assignments</strong> tab</p>
</li>
<li>
<p>Click <strong>View access details</strong></p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Click View Access Details menu option for assigned user"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-view-access-details-menu_hu_93977f977d76fca6.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-view-access-details-menu_hu_cae0f26bfaf2056c.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-view-access-details-menu_hu_c9b7da0df4ded2c.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-view-access-details-menu_hu_de57251c8da8faab.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-view-access-details-menu_hu_64bc9f5b1544552e.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-view-access-details-menu.png"
    src="/howto/okta-generic-jdbc-connector/okta-view-access-details-menu.png">


  
</figure>
</li>
<li>
<p>Verify that the entitlements assigned to the user in the database are reflected in Okta</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="View user entitlements and access permissions synced from database"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-user-entitlements-view_hu_c9464d1e2f847157.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-user-entitlements-view_hu_1f35c72055a7dc5f.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-user-entitlements-view_hu_14fcb9211841cf57.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-user-entitlements-view_hu_3061c2ec03c1e053.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-user-entitlements-view_hu_534f49d3da82ec9c.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-user-entitlements-view.png"
    src="/howto/okta-generic-jdbc-connector/okta-user-entitlements-view.png">


  
</figure>
</li>
</ul>
</li>
</ol>

<h3 class="relative group">Test #2: Manual User Assignment and Provisioning
    <div id="test-2-manual-user-assignment-and-provisioning" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-2-manual-user-assignment-and-provisioning" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Test creating a new user in the database from Okta.</p>
<p><strong>Steps:</strong></p>
<ol>
<li>
<p><strong>Assign User to Application</strong></p>
<ul>
<li>Log on to <strong>Okta Admin Console</strong></li>
<li>Navigate to <strong>Applications</strong> → <strong>Generic Database Connector</strong></li>
<li>Click the <strong>Assignments</strong> tab</li>
<li>Click <strong>Assign</strong> → <strong>Assign to People</strong></li>
</ul>
</li>
<li>
<p><strong>Select User</strong></p>
<ul>
<li>Search for a user (e.g., <code>testuser@example.com</code>)</li>
<li>Click <strong>Assign</strong> next to the user</li>
</ul>
</li>
<li>
<p><strong>Review User Details</strong></p>
<ul>
<li>The application auto-populates custom attribute values based on your mappings</li>
<li>Review and adjust values as needed - Empty fields can be manually entered</li>
<li>Click <strong>Assign and Continue</strong></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Okta assign user to app form showing custom attribute values and continue button"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-assign-user-to-app-form_hu_45b66bed5ca50f0a.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-assign-user-to-app-form_hu_743d97d4ad5f6896.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-assign-user-to-app-form_hu_9bca606c425f493b.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-assign-user-to-app-form_hu_70c58024449df899.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-assign-user-to-app-form_hu_7a76b5bd05a4fedc.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-assign-user-to-app-form.png"
    src="/howto/okta-generic-jdbc-connector/okta-assign-user-to-app-form.png">


  
</figure>
</li>
<li>
<p><strong>Assign Entitlements</strong></p>
<ul>
<li>In the <strong>Select Assignment</strong> section, select <strong>Custom Values</strong> from the <strong>Entitlement assignment method</strong>  dropdown</li>
<li>Under <strong>Entitlements</strong>, select desired entitlements (e.g., &ldquo;VPN Access&rdquo;, &ldquo;GitHub Admin&rdquo;)</li>
<li>Click <strong>Save</strong></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Okta assign entitlements to user"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-assign-entitlements-to-user_hu_c506ea79e185e1bd.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-assign-entitlements-to-user_hu_c82727a69a4af7be.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-assign-entitlements-to-user_hu_84b7837a997c9ad.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-assign-entitlements-to-user_hu_3b77e3519ede714.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-assign-entitlements-to-user_hu_582662e190c105c.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-assign-entitlements-to-user.png"
    src="/howto/okta-generic-jdbc-connector/okta-assign-entitlements-to-user.png">


  
</figure>
</li>
<li>
<p><strong>Verify in Okta</strong></p>
<ul>
<li>The user should now appear under the <strong>Assignments</strong> tab</li>
<li>Click the menu button (three vertical dots) next to the user</li>
<li>Select <strong>View access details</strong> to see assigned entitlements</li>
</ul>
</li>
<li>
<p><strong>Verify in Database</strong></p>
<ul>
<li>
<p>Check that the user was created in the database:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose <span class="nb">exec</span> db mariadb -u oktademo -poktademo oktademo -e <span class="s2">&#34;SELECT USER_ID,USERNAME,FIRSTNAME,LASTNAME,EMAIL FROM USERS WHERE EMAIL=&#39;testuser@example.com&#39;;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># SAMPLE OUTPUT</span>
</span></span><span class="line"><span class="cl"><span class="c1">#  +----------------------+-----------+-----------+----------+----------------------+</span>
</span></span><span class="line"><span class="cl"><span class="c1"># | USER_ID              | USERNAME  | FIRSTNAME | LASTNAME | EMAIL                |</span>
</span></span><span class="line"><span class="cl"><span class="c1"># +----------------------+-----------+-----------+----------+----------------------+</span>
</span></span><span class="line"><span class="cl"><span class="c1"># | testuser@example.com | test.user | Test      | User     | testuser@example.com |</span>
</span></span><span class="line"><span class="cl"><span class="c1"># +----------------------+-----------+-----------+----------+----------------------+</span></span></span></code></pre></div></div>
<p>You can also verify with DBGate UI, by opening the <code>USERS</code> table.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Verify new user created in DBGate USERS table showing USER_ID, USERNAME, FIRSTNAME, LASTNAME, and EMAIL columns"
    srcset="
      /howto/okta-generic-jdbc-connector/dbgate-users-table-verification_hu_d5a1d708c2afbca7.webp  330w,
      /howto/okta-generic-jdbc-connector/dbgate-users-table-verification_hu_a92b04f45f95de58.webp  660w,
      /howto/okta-generic-jdbc-connector/dbgate-users-table-verification_hu_1e8dec2e0ee2fbe1.webp  960w,
      /howto/okta-generic-jdbc-connector/dbgate-users-table-verification_hu_5ea6302811c64676.webp 1280w,
      /howto/okta-generic-jdbc-connector/dbgate-users-table-verification_hu_a665e635f6e0fdae.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/dbgate-users-table-verification.png"
    src="/howto/okta-generic-jdbc-connector/dbgate-users-table-verification.png">


  
</figure>
</li>
</ul>
</li>
<li>
<p><strong>Verify Entitlements in Database</strong></p>
<ul>
<li>
<p>Check that entitlements were assigned:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose <span class="nb">exec</span> db mariadb -u oktademo -poktademo oktademo -e <span class="s2">&#34;CALL GET_USER_ENTITLEMENT(&#39;testuser@example.com&#39;);&#34;</span></span></span></code></pre></div></div>
<p>You can also verify with DBGate UI, by opening the <code>USERENTITLEMENTS</code> table or the <code>v_userentitlements</code> view.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Verify user entitlements in DBGate USERENTITLEMENTS table showing assigned entitlements with dates"
    srcset="
      /howto/okta-generic-jdbc-connector/dbgate-userentitlements-table-verification_hu_99246189553f4a20.webp  330w,
      /howto/okta-generic-jdbc-connector/dbgate-userentitlements-table-verification_hu_acf5e2d9c243b367.webp  660w,
      /howto/okta-generic-jdbc-connector/dbgate-userentitlements-table-verification_hu_fdb8e0f73c7ad029.webp  960w,
      /howto/okta-generic-jdbc-connector/dbgate-userentitlements-table-verification_hu_9d6af66326684f96.webp 1280w,
      /howto/okta-generic-jdbc-connector/dbgate-userentitlements-table-verification_hu_8ed7a5109c7ca706.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/dbgate-userentitlements-table-verification.png"
    src="/howto/okta-generic-jdbc-connector/dbgate-userentitlements-table-verification.png">


  
</figure>
</li>
</ul>
</li>
</ol>

<h3 class="relative group">Test #3: User Attribute Update
    <div id="test-3-user-attribute-update" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-3-user-attribute-update" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Test that attribute changes in Okta are synchronized to the database.</p>
<p><strong>Steps:</strong></p>
<ol>
<li>
<p><strong>Update User in Okta</strong></p>
<ul>
<li>Navigate to <strong>Directory</strong> → <strong>People</strong></li>
<li>Find and select a user (e.g., <code>testuser@example.com</code>)</li>
<li>Click <strong>Profile</strong> → <strong>Edit</strong></li>
<li>Change an attribute (e.g., <code>title</code>, <code>department</code>)</li>
<li>Click <strong>Save</strong></li>
</ul>
</li>
<li>
<p><strong>Verify in Database</strong></p>
<ul>
<li>
<p>Check that changes were synced:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose <span class="nb">exec</span> db mariadb -u oktademo -poktademo oktademo <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="s2">&#34;SELECT USER_ID, TITLE, DEPARTMENT, EMAIL FROM USERS WHERE EMAIL=&#39;testuser@example.com&#39;;&#34;</span></span></span></code></pre></div></div>
<p>You can also verify with DBGate UI, by opening the <code>USERS</code> table.</p>
</li>
</ul>
</li>
</ol>

<h3 class="relative group">Test #4: User Deactivation
    <div id="test-4-user-deactivation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-4-user-deactivation" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Test that unassigning a user from the application deactivates them in the database.</p>
<p><strong>Steps:</strong></p>
<ol>
<li>
<p><strong>Unassign User</strong></p>
<ul>
<li>Navigate to <strong>Applications</strong> → <strong>Generic Database Connector</strong> → <strong>Assignments</strong></li>
<li>Find the user and click the menu button</li>
<li>Select <strong>Unassign</strong></li>
<li>Confirm the action</li>
</ul>
</li>
<li>
<p><strong>Verify Deactivation in Database</strong></p>
<ul>
<li>
<p>Check that the user&rsquo;s <code>IS_ACTIVE</code> flag is set to <code>0</code>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose <span class="nb">exec</span> db mariadb -u oktademo -poktademo oktademo <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="s2">&#34;SELECT USER_ID, EMAIL, IS_ACTIVE FROM USERS WHERE EMAIL=&#39;testuser@example.com&#39;;&#34;</span></span></span></code></pre></div></div>
<p>You can also verify with DBGate UI, by opening the <code>USERS</code> table or the <code>v_inactive_users</code> view.</p>
</li>
</ul>
</li>
</ol>

<h3 class="relative group">Test #5: Check Okta and Local Logs
    <div id="test-5-check-okta-and-local-logs" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-5-check-okta-and-local-logs" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>To better understand the process and the link between all the components, you can also check the Okta and local logs.</p>

<h4 class="relative group">Okta Logs
    <div id="okta-logs" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#okta-logs" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>You can check the Okta System Logs to see the events related to user provisioning and entitlement management. Look for events such as:</p>
<ol>
<li><strong>User&rsquo;s entitlements updated successfully</strong> (<code>resource.user_entitlements.update</code>): This event indicates that a user&rsquo;s entitlements were updated in Okta, which should trigger the provisioning flow to sync changes to the database.</li>
<li><strong>Push new user to external application</strong> (<code>application.provision.user.push</code>): This event indicates that Okta is attempting to provision a new user to the database via the OPP Agent.</li>
<li><strong>Successfully pushed new user account to app&quot;</strong> (<code>app.user_management.push_new_user_success</code>): This event confirms that the user account was successfully created in the database, and the entitlements were assigned.</li>
<li><strong>Sync user in external application</strong> (<code>application.provision.user.sync</code>): This event indicates that Okta is attempting to sync user changes to the database, which can be triggered by attribute updates or entitlement changes.</li>
</ol>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Okta logs"
    srcset="
      /howto/okta-generic-jdbc-connector/okta-logs_hu_3727cb830876c62e.webp  330w,
      /howto/okta-generic-jdbc-connector/okta-logs_hu_ee60b95aba0cd4eb.webp  660w,
      /howto/okta-generic-jdbc-connector/okta-logs_hu_59a5b6e1463834b0.webp  960w,
      /howto/okta-generic-jdbc-connector/okta-logs_hu_fde0a506956e7c66.webp 1280w,
      /howto/okta-generic-jdbc-connector/okta-logs_hu_9ef272287c0df6b0.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-generic-jdbc-connector/okta-logs.png"
    src="/howto/okta-generic-jdbc-connector/okta-logs.png">


  
</figure>
<p>Even if they aren&rsquo;t verbose, these logs can help you understand when provisioning actions are triggered and if they succeed or fail. And you can use them to get the timestamp of an event and correlate it with the OPP Agent and SCIM server logs.</p>

<h4 class="relative group">OPP Agent Logs
    <div id="opp-agent-logs" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#opp-agent-logs" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>You will find the OPP Agent logs mounted in the local folder <code>./data/okta-opp/logs/*.log</code></p>

<h4 class="relative group">SCIM Server Logs
    <div id="scim-server-logs" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#scim-server-logs" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>You will find the SCIM server logs mounted in the local folder <code>./data/okta-scim/logs/*.log</code></p>
<hr>

<h2 class="relative group">Beyond Basic Provisioning: Identity Governance
    <div id="beyond-basic-provisioning-identity-governance" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#beyond-basic-provisioning-identity-governance" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Once you have the Generic Database Connector operational, you can explore advanced identity governance use cases:</p>
<ul>
<li>
<p><strong>Entitlements Policies</strong>: Define policies in Okta to govern how entitlements are assigned based on user attributes (e.g., department, location). Documentation: <a href="https://help.okta.com/oie/en-us/content/topics/governance/policies/entitlement-policy-create.htm"  target="_blank" rel="noreferrer">Okta Help - Create an Entitlement Policy</a>.</p>
</li>
<li>
<p><strong>Access Requests</strong>: Use Okta&rsquo;s Access Request feature to allow users to request entitlements, with approval workflows and automated provisioning. Documentation: <a href="https://help.okta.com/oie/en-us/content/topics/governance/access-requests.htm"  target="_blank" rel="noreferrer">Okta Help - Access Requests</a>.</p>
</li>
<li>
<p><strong>Access Certification Campaigns</strong>: Implement one time or periodic access reviews for entitlements to ensure compliance and recertification. Documentation: <a href="https://help.okta.com/oie/en-us/content/topics/governance/access-certification.htm"  target="_blank" rel="noreferrer">Okta Help - Access Certification</a>.</p>
</li>
</ul>
<p>These governance features transform the Generic Database Connector from a simple provisioning tool into a comprehensive identity governance solution.</p>
<hr>

<h2 class="relative group">Production Considerations
    <div id="production-considerations" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#production-considerations" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Deployment Architecture Options
    <div id="deployment-architecture-options" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#deployment-architecture-options" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>When deploying to production, you have flexibility in how to architect your environment:</p>

<h4 class="relative group">Single Server Deployment
    <div id="single-server-deployment" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#single-server-deployment" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li><strong>Install both OPP Agent and SCIM Server on the same host</strong></li>
<li>Simpler infrastructure and management</li>
<li>Lower operational costs</li>
<li>Adequate security for most use cases</li>
<li>The OPP Agent communicates with SCIM Server via localhost</li>
<li>This is the <strong>most common production deployment model</strong></li>
</ul>

<h4 class="relative group">Multi-Server Deployment
    <div id="multi-server-deployment" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#multi-server-deployment" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li>Install OPP Agent and SCIM Server on separate hosts</li>
<li>Enhanced security through physical/virtual separation</li>
<li>Ability to scale components independently</li>
<li>Useful for high-availability configurations</li>
<li>May be required for specific compliance requirements</li>
</ul>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855856313081 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855856313081 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855856313081 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855856313081 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855856313081" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>Lab Architecture Note</strong>: This Docker lab uses separate containers to demonstrate how the components interact. This simulates a multi-server deployment but is primarily for educational purposes. In production, co-locating both on a single server is fully supported and commonly preferred.
  </div>
</div>

<h3 class="relative group">Using Different Databases
    <div id="using-different-databases" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#using-different-databases" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>To use PostgreSQL instead of MariaDB:</p>
<ol>
<li>
<p>Update <code>docker-compose.yml</code>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">db</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">postgres:16</span><span class="w"> </span><span class="c"># Use official PostgreSQL image</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Update environment variables accordingly</span></span></span></code></pre></div></div>
</li>
<li>
<p>Change sql mountpoint to load appropriate initialization scripts for PostgreSQL</p>
</li>
<li>
<p>Place appropriate JDBC driver in <code>./docker/okta-scim/packages/</code>:</p>
</li>
</ol>

<h3 class="relative group">Other JDBC Drivers
    <div id="other-jdbc-drivers" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#other-jdbc-drivers" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>For other databases (Oracle, SQL Server, etc.):</p>
<ol>
<li>Download appropriate JDBC driver</li>
<li>Place <code>.jar</code> file in <code>./docker/okta-scim/packages/</code></li>
<li>Rebuild the container: <code>make rebuild</code></li>
<li>Configure SCIM Server with database-specific connection string</li>
</ol>
<p><strong>Note</strong>: MySQL Connector/J 9.6.0 is automatically downloaded from Maven Central during build. All <code>.jar</code> files in <code>./docker/okta-scim/packages/</code> are copied to <code>/opt/OktaOnPremScimServer/userlib/</code> during container build.</p>

<h3 class="relative group">Connecting to Multiple Databases
    <div id="connecting-to-multiple-databases" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#connecting-to-multiple-databases" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>A single OPP Agent and SCIM Server deployment can manage <strong>up to 8 databases simultaneously</strong>:</p>
<ul>
<li>Each database requires a separate Generic Database Connector application instance in Okta</li>
<li>All databases can be different types (e.g., MySQL, PostgreSQL, Oracle, SQL Server)</li>
<li>Useful for managing users across multiple applications, environments (prod/staging), or organizational units</li>
<li>All JDBC drivers must be present in <code>./docker/okta-scim/packages/</code> before building the container or in <code>/opt/OktaOnPremScimServer/userlib/</code> if you are not using this Docker setup.</li>
</ul>
<p>For detailed configuration instructions, see <a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc/blob/main/doc/Okta_Provisioning_Configuration.md"  target="_blank" rel="noreferrer">doc/Okta_Provisioning_Configuration.md</a>.</p>

<h3 class="relative group">Production Best Practices
    <div id="production-best-practices" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#production-best-practices" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>When moving from lab to production:</p>
<p><strong>Security:</strong></p>
<ul>
<li>Use strong, unique passwords (never use defaults like <code>oktademo</code>)</li>
<li>Implement proper SSL/TLS certificates (not self-signed)</li>
<li>Create dedicated service accounts with minimal permissions</li>
<li>Use firewall rules to restrict database access</li>
<li>Regularly rotate bearer tokens and credentials</li>
</ul>
<p><strong>High Availability (when it will be available):</strong></p>
<ul>
<li>Deploy at least two OPP Agents for redundancy and failover</li>
<li>Enable automatic agent updates (requires multiple agents)</li>
<li>Implement database replication/clustering</li>
<li>Configure health monitoring and alerting</li>
</ul>
<p><strong>Performance:</strong></p>
<ul>
<li>Tune SCIM Server connection pool settings</li>
<li>Optimize stored procedures and add appropriate indexes</li>
<li>Configure OPP Agent threading parameters (<code>pollingThreadCount</code>, <code>maxConnectionsPerHost</code>)</li>
<li>Monitor query performance and connection pool usage</li>
</ul>
<p><strong>Monitoring:</strong></p>
<ul>
<li>Enable appropriate logging levels (i.e., INFO for production, DEBUG for troubleshooting)</li>
<li>Set up centralized log aggregation</li>
<li>Monitor Okta System Logs for provisioning events</li>
<li>Create alerts for authentication failures and errors</li>
<li>Track provisioning success/failure rates</li>
</ul>

<h3 class="relative group">Troubleshooting Common Issues
    <div id="troubleshooting-common-issues" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#troubleshooting-common-issues" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Here are the most common issues you might encounter:</p>

<h4 class="relative group">ValidationException: executeCall not allowed / execute not allowed
    <div id="validationexception-executecall-not-allowed--execute-not-allowed" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#validationexception-executecall-not-allowed--execute-not-allowed" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>If you see this error:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">Error code: 400, error: . Errors received from SCIM server by the connector :
</span></span><span class="line"><span class="cl">{&#34;schemas&#34;:[&#34;urn:ietf:params:scim:api:messages:2.0:Error&#34;],&#34;scimType&#34;:&#34;INVALID_SYNTAX&#34;,
</span></span><span class="line"><span class="cl">&#34;detail&#34;:&#34;statement=CALL ACTIVATE_USER(?), errors=[ValidationException: executeCall not allowed.,
</span></span><span class="line"><span class="cl">ValidationException: execute not allowed.]&#34;,&#34;status&#34;:400}</span></span></code></pre></div></div>
<p>The operation is configured as &ldquo;<strong>SQL Statement</strong>&rdquo; instead of &ldquo;<strong>Execute Stored Procedure</strong>&rdquo;.</p>
<ol>
<li>Go to Okta Admin Console → Applications → Generic Database Connector</li>
<li>Navigate to the Provisioning tab → To App / To Okta → Edit</li>
<li>For each operation that calls a stored procedure, ensure <strong>Operation Type</strong> is set to <strong>&ldquo;Execute Stored Procedure&rdquo;</strong> (NOT &ldquo;SQL Statement&rdquo;)</li>
</ol>

<h4 class="relative group">Stored Procedure Not Found
    <div id="stored-procedure-not-found" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#stored-procedure-not-found" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li>Verify procedures are installed: <code>docker compose exec db mariadb -u oktademo -poktademo oktademo -e &quot;SHOW PROCEDURE STATUS WHERE Db='oktademo';&quot;</code></li>
<li>Reinitialize database if needed (see README.md)</li>
</ul>

<h4 class="relative group">Parameter Mismatch
    <div id="parameter-mismatch" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#parameter-mismatch" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li>Ensure parameter count matches the stored procedure definition</li>
<li>Check parameter types (DATABASE_FIELD, CURSOR, etc.)</li>
<li>Review <code>sql/stored_proc.sql</code> for exact signatures</li>
</ul>

<h4 class="relative group">Connection Timeout
    <div id="connection-timeout" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#connection-timeout" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li>Verify SCIM server is running: <code>docker compose ps okta-scim</code></li>
<li>Check database connectivity: <code>docker compose exec okta-scim mysql -h db -u oktademo -poktademo oktademo -e &quot;SELECT 1;&quot;</code></li>
</ul>

<h4 class="relative group">Entitlement Operations Failing
    <div id="entitlement-operations-failing" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#entitlement-operations-failing" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li>Verify ENT_ID exists: <code>SELECT * FROM ENTITLEMENTS;</code></li>
<li>Check foreign key constraints</li>
<li>Review USERENTITLEMENTS table structure</li>
</ul>

<h3 class="relative group">Advanced Troubleshooting
    <div id="advanced-troubleshooting" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#advanced-troubleshooting" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The <a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc#-troubleshooting"  target="_blank" rel="noreferrer">GitHub repository README</a> contains comprehensive troubleshooting documentation including:</p>
<ul>
<li>Build errors with SSL certificate issues</li>
<li>Database query logging (enable/disable/monitor)</li>
<li>Agent and SCIM server log analysis</li>
<li>Certificate warnings and VPN configurations</li>
<li>Performance monitoring and optimization</li>
</ul>
<p>For detailed troubleshooting steps, debug mode configuration, and performance tuning, refer to the <a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc#-troubleshooting"  target="_blank" rel="noreferrer">complete troubleshooting guide</a> in the repository.</p>
<p>For deep technical insights into the SCIM Server&rsquo;s internal architecture and API endpoints, see the <strong><a href="/posts/howto/okta-scim-server-deep-dive/" >SCIM Server Technical Deep Dive</a></strong> article.</p>
<hr>

<h2 class="relative group">Conclusion
    <div id="conclusion" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#conclusion" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>This comprehensive guide has walked you through deploying a complete Okta database provisioning solution, from initial Docker setup to advanced testing workflows. You now have a fully functional environment that demonstrates:</p>
<ul>
<li><strong>Secure Bridge Architecture</strong>: How the OPP Agent and SCIM Server connect Okta cloud to on-premises databases without inbound firewall rules</li>
<li><strong>User Lifecycle Management</strong>: Automated create, update, activate, and deactivate operations</li>
<li><strong>Entitlement Management</strong>: Sophisticated role and permission assignment through database-backed entitlements</li>
<li><strong>Stored Procedure Abstraction</strong>: Clean, secure, and maintainable provisioning logic</li>
<li><strong>Real-time Synchronization</strong>: Bidirectional data flow between Okta and your database</li>
</ul>
<p>Whether you&rsquo;re evaluating Okta for a database provisioning use case, building a proof of concept, or learning the integration workflow, this lab environment provides a solid foundation. The Docker-based approach accelerates setup and experimentation, while the configuration steps apply equally to manual installations on production systems.</p>
<p>Remember that while Docker simplifies the learning process, production deployments should follow official Okta documentation and best practices for supported operating systems, security hardening, and high availability.</p>

<h3 class="relative group">Next Steps
    <div id="next-steps" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#next-steps" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><strong>Explore Advanced Features</strong>: Test access requests, entitlement policies, and certification campaigns</li>
<li><strong>Customize for Your Use Case</strong>: Adapt the stored procedures and schema to match your application&rsquo;s data model</li>
<li><strong>Scale the Environment</strong>: Add multiple databases or integrate with other on-premises applications</li>
<li><strong>Plan Production Deployment</strong>: Review the architecture options and production considerations for your organization</li>
</ul>

<h3 class="relative group">Questions or feedback?
    <div id="questions-or-feedback" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#questions-or-feedback" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li>Leave a <strong><a href="/howto/okta-generic-jdbc-connector/#comments" >comment to this article</a></strong></li>
<li>Contribute to the <a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc"  target="_blank" rel="noreferrer">GitHub repository</a></li>
<li>Follow me on <a href="https://www.linkedin.com/in/fabiograsso/"  target="_blank" rel="noreferrer">LinkedIn</a> for updates on Okta and identity management topics</li>
<li>Explore the additional documentation:</li>
</ul>

<h3 class="relative group">Technical Documentation
    <div id="technical-documentation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#technical-documentation" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><strong><a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc/blob/main/doc/Okta_Provisioning_Configuration.md"  target="_blank" rel="noreferrer">Okta Provisioning Configuration Guide</a></strong> - Step-by-step Okta Admin Console setup instructions</li>
<li><strong><a href="https://github.com/fabiograsso/okta-lab-onprem-jdbc/blob/main/doc/Okta_SCIM_Server.md"  target="_blank" rel="noreferrer">Okta SCIM Server Technical Documentation</a></strong> - Advanced technical reference for the SCIM Server&rsquo;s internal architecture, API endpoints, and troubleshooting (<em>reverse-engineered, educational purposes only</em>)</li>
<li><strong><a href="/posts/howto/okta-scim-server-deep-dive/" >Okta On-Prem SCIM Server: Technical Deep Dive and Architecture</a></strong> - Comprehensive exploration of SCIM Server internals, REST endpoints, authentication mechanisms, and multi-tenancy support</li>
</ul>

<h4 class="relative group">Official Documentation
    <div id="official-documentation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#official-documentation" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li><strong><a href="https://help.okta.com/oie/en-us/content/topics/provisioning/opc/connectors/on-prem-connector-generic-db.htm"  target="_blank" rel="noreferrer">On-premises Connector for Generic Databases</a></strong></li>
<li><strong><a href="https://help.okta.com/oie/en-us/content/topics/provisioning/opp/opp-install-agent.htm"  target="_blank" rel="noreferrer">Install the Okta Provisioning Agent</a></strong></li>
<li><strong><a href="https://help.okta.com/oie/en-us/content/topics/provisioning/opp/on-prem-scim-install.htm"  target="_blank" rel="noreferrer">Install the Okta On-prem SCIM Server</a></strong></li>
</ul>

<h4 class="relative group">SCIM Protocol
    <div id="scim-protocol" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#scim-protocol" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li><a href="https://datatracker.ietf.org/doc/html/rfc7644"  target="_blank" rel="noreferrer">SCIM 2.0 RFC 7644</a></li>
<li><a href="https://developer.okta.com/docs/concepts/scim/"  target="_blank" rel="noreferrer">Okta SCIM Documentation</a></li>
</ul>

<h4 class="relative group">Okta Community
    <div id="okta-community" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#okta-community" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li><a href="https://devforum.okta.com/"  target="_blank" rel="noreferrer">Okta Developer Forums</a></li>
<li><a href="https://support.okta.com/help/s/community?language=en_US"  target="_blank" rel="noreferrer">Okta Community</a></li>
</ul>
<hr>

<h2 class="relative group">Disclaimer
    <div id="disclaimer" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#disclaimer" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>This is a demonstration laboratory environment designed for testing, learning, and demonstration purposes. It is <strong>not officially supported by Okta for production use</strong>.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855856567459 {
    background: #FFFAE6;
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855856567459 .panel-icon {
    color: rgb(224,108,0);
  }
  html.dark #panel-1778753855856567459 {
    background: rgb(51,46,27);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855856567459 .panel-icon {
    color: rgb(251,200,40);
  }
</style>

<div id="panel-1778753855856567459" class="flex px-4 py-3 rounded-md shadow panel-warning ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>Docker</strong> is not officially supported by Okta for running the OPP Agent and SCIM Server in production environments. Always consult <a href="https://help.okta.com/en-us/content/topics/provisioning/opc/connectors/on-prem-connector-generic-db.htm"  target="_blank" rel="noreferrer">official Okta documentation</a> and support for production deployments.
  </div>
</div>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855856887526 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855856887526 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855856887526 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855856887526 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855856887526" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><strong>JDBC Driver Licensing:</strong><br>
The MySQL Connector/J driver is automatically downloaded during Docker build from Maven Central (Oracle&rsquo;s official distribution). Licensed under GPL v2 with the Universal FOSS Exception. The driver is NOT included in the Git repository or any published Docker images—only the Dockerfile build instructions are shared.
  </div>
</div>
]]></content:encoded>
      <category>Okta</category>
      <category>Docker</category>
      <category>SCIM</category>
      <category>Provisioning</category>
      <category>Database</category>
      <category>JDBC</category>
      <category>Identity</category>
      <category>Governance</category>
      <category>IAM</category>
      <category>MySQL</category>
      <category>MariaDB</category>
    </item>
    <item>
      <title>Integrating Okta with IBM i (AS/400) for MFA and Lifecycle Management</title>
      <link>https://iam.fabiograsso.net/howto/okta-as400-ibmi/</link>
      <pubDate>Fri, 30 Jan 2026 10:00:00 +0000</pubDate>
      <guid>https://iam.fabiograsso.net/howto/okta-as400-ibmi/</guid>
      <description>A comprehensive guide to modernizing IBM i (AS/400) security by integrating Okta. This post covers MFA for terminal access using Precisely and explores two options for Lifecycle Management (LCM): the Okta OPP agent with custom scripts and the Aquera SCIM gateway.</description>
      <content:encoded>&lt;![CDATA[
<h2 class="relative group">Securing Your Business-Critical Platforms
    <div id="securing-your-business-critical-platforms" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#securing-your-business-critical-platforms" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Recently, while working with a prospect, a common but critical challenge came up: how to integrate their IBM i (AS/400) platform with Okta. Their top priority was to enforce modern Multi-Factor Authentication (MFA) to protect all access points, especially the 5250 &ldquo;green screen&rdquo; terminals that are central to their operations. This inquiry inspired me to do a deep-dive and outline the available strategies, which I&rsquo;ll share in this post.</p>
<p>For decades, IBM i systems have been the silent workhorses powering core business operations for companies worldwide. While these platforms are renowned for their reliability and security, the broader security landscape has evolved dramatically. Today, protecting the invaluable data on these systems requires a modern, identity-first security approach.</p>
<p>Integrating a centralized Identity Provider (IdP) like Okta is crucial. It allows you to enforce consistent security policies, streamline user access, and gain visibility across your entire technology stack—from the newest cloud applications to your foundational IBM i platforms. This article explores how you can extend Okta&rsquo;s powerful MFA and Lifecycle Management (LCM) capabilities to your IBM i environment.</p>

<h2 class="relative group">Protecting the Terminal with MFA
    <div id="protecting-the-terminal-with-mfa" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#protecting-the-terminal-with-mfa" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Protecting access to modern web-based applications with Okta is straightforward using standard protocols like SAML and OIDC. However, securing terminal access to IBM i systems, which rely on protocols like 5250, FTP, and Telnet, presents a unique challenge. These protocols lack the browser-based interaction needed for many modern MFA flows.</p>
<p>While web-based terminal emulators like <strong>Flynet Viewer™</strong> or <strong>z/Scope Anywhere</strong> can be protected at the web application layer, securing native <em>&ldquo;green screen&rdquo;</em> terminal access requires a different approach.</p>

<h3 class="relative group">The Precisely Assure Security Solution
    <div id="the-precisely-assure-security-solution" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-precisely-assure-security-solution" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>This is where a third-party solution like <strong><a href="https://www.precisely.com/fr/product/precisely-assure/assure-security/"  target="_blank" rel="noreferrer">Precisely&rsquo;s Assure Security</a></strong> comes into play. It acts as a bridge between the IBM i sign-on process and Okta. The integration works by intercepting the user login attempt on the IBM i system and using the <strong>Okta RADIUS Server Agent</strong> to trigger an MFA challenge.</p>
<p>The flow is as follows:</p>
<ol>
<li>A user attempts to log in to the IBM i system via a 5250 terminal.</li>
<li>Precisely Assure Security, installed on the IBM i, intercepts the login.</li>
<li>It sends a RADIUS authentication request to the Okta RADIUS Agent.</li>
<li>The Okta RADIUS Agent communicates with the Okta cloud service to evaluate sign-on policies.</li>
<li>Okta pushes an MFA challenge (e.g., Okta Verify Push, OTP) to the user&rsquo;s device.</li>
<li>The user approves the challenge.</li>
<li>Okta sends an &ldquo;Access-Accept&rdquo; response back through the RADIUS agent to the Precisely solution.</li>
<li>The user is granted access to the IBM i terminal.</li>
</ol>
<pre class="not-prose mermaid">
sequenceDiagram
    actor User
    participant Terminal as 5250 Terminal
    participant IBMi as IBM i / Precisely
    participant RadiusAgent as Okta RADIUS Agent
    participant Okta as Okta Cloud

    User->>Terminal: 1. Enter Credentials
    Terminal->>IBMi: 2. Submit Login Request
    IBMi->>IBMi: 3. Precisely Intercepts Login
    IBMi->>RadiusAgent: 4. RADIUS Access-Request
    RadiusAgent->>Okta: 5. Forward Auth Request
    Okta-->>User: 6. Push MFA Challenge
    User-->>Okta: 7. Approve MFA
    Okta-->>RadiusAgent: 8. RADIUS Access-Accept
    RadiusAgent-->>IBMi: 9. Forward Access-Accept
    IBMi-->>Terminal: 10. Grant Access
</pre>


<h3 class="relative group">How Precisely Intercepts the Login Process
    <div id="how-precisely-intercepts-the-login-process" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-precisely-intercepts-the-login-process" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>A key strength of the Precisely solution is its ability to integrate without requiring any changes to your existing IBM i applications or the operating system&rsquo;s core sign-on programs.</p>
<ul>
<li>
<p><strong>Non-Invasive Interception</strong>: Precisely Assure Security uses IBM i&rsquo;s native &ldquo;Exit Point&rdquo; architecture. Exit points are essentially hooks provided by the operating system that allow a third-party program to be called before or after a specific system event—in this case, the sign-on event. When a user attempts to log in, the exit point program intercepts the request and redirects it to the Assure Security engine for MFA validation before the operating system processes the password. This is seamless to the end-user and requires no modification of legacy code.</p>
</li>
<li>
<p><strong>Rules-Based Step-Up MFA</strong>: Security shouldn&rsquo;t be all-or-nothing. Precisely&rsquo;s official documentation highlights its ability to &ldquo;invoke rules-based multi-factor authentication only for users or specific situations that require it&rdquo;. This granular control ensures that you can strengthen security where it matters most without adding unnecessary friction to all user logins.
This means you can implement intelligent, risk-based MFA. For example, you can configure rules to:</p>
<ul>
<li><strong>Always</strong> require MFA for powerful &ldquo;privileged user&rdquo; profiles.</li>
<li><strong>Only</strong> trigger an MFA challenge when a standard user attempts to access a highly sensitive application or execute a specific, powerful command.</li>
<li><strong>Bypass MFA</strong> for low-risk users or during certain times of the day.</li>
</ul>
</li>
</ul>

<h3 class="relative group">Configuration Example
    <div id="configuration-example" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuration-example" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Configuring the Precisely solution involves registering the Okta RADIUS server within the Assure MFA administration menu on the IBM i. This screen requires key details to establish a secure connection.</p>
<p>The key fields for configuration are:</p>
<ul>
<li><strong>Priority</strong>: Determines the order of servers for high availability.</li>
<li><strong>Server</strong>: The hostname or IP address of your Okta RADIUS Agent.</li>
<li><strong>Port</strong>: The UDP port for RADIUS communication, typically <code>1812</code>.</li>
<li><strong>Description</strong>: A friendly name for the server entry.</li>
<li><strong>Held</strong>: Must be set to <code>N</code> (No) for the server to be active.</li>
<li><strong>Server Secret</strong>: The shared secret key configured in the Okta RADIUS App.</li>
<li><strong>Dead Time</strong>: A timeout value before considering a server non-responsive.</li>
</ul>
<p>Following are some screenshots illustrating the configuration process:</p>
<p>








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="AS400 Configuration for Okta MFA using RADIUS (Precisely)"
    srcset="
      /howto/okta-as400-ibmi/okta-as400-1_hu_688cd224e96d3f54.webp  330w,
      /howto/okta-as400-ibmi/okta-as400-1_hu_e3dbf448f1c6228a.webp  660w,
      /howto/okta-as400-ibmi/okta-as400-1_hu_2b3a5726a6b900d6.webp  960w,
      /howto/okta-as400-ibmi/okta-as400-1_hu_aeb838396307d4bf.webp 1280w,
      /howto/okta-as400-ibmi/okta-as400-1_hu_4899fe76c3e84922.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-as400-ibmi/okta-as400-1.png"
    src="/howto/okta-as400-ibmi/okta-as400-1.png">


  
</figure>










<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="AS400 Configuration for Okta MFA using RADIUS (Precisely)"
    srcset="
      /howto/okta-as400-ibmi/okta-as400-2_hu_bbc87d9ed520eff6.webp  330w,
      /howto/okta-as400-ibmi/okta-as400-2_hu_5b40853f5b955883.webp  660w,
      /howto/okta-as400-ibmi/okta-as400-2_hu_c8d6c0b37cd1240.webp  960w,
      /howto/okta-as400-ibmi/okta-as400-2_hu_284ad125df319d05.webp 1280w,
      /howto/okta-as400-ibmi/okta-as400-2_hu_fbfef897da44b341.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-as400-ibmi/okta-as400-2.png"
    src="/howto/okta-as400-ibmi/okta-as400-2.png">


  
</figure>










<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="AS400 Configuration for Okta MFA using RADIUS (Precisely)"
    srcset="
      /howto/okta-as400-ibmi/okta-as400-3_hu_8ac7911a5cb8e275.webp  330w,
      /howto/okta-as400-ibmi/okta-as400-3_hu_a9a82ea7973c52d1.webp  660w,
      /howto/okta-as400-ibmi/okta-as400-3_hu_fd9e014c046d9944.webp  960w,
      /howto/okta-as400-ibmi/okta-as400-3_hu_8bbde10941b2337.webp 1280w,
      /howto/okta-as400-ibmi/okta-as400-3_hu_ab3232afb1578732.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-as400-ibmi/okta-as400-3.png"
    src="/howto/okta-as400-ibmi/okta-as400-3.png">


  
</figure>










<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="AS400 Configuration for Okta MFA using RADIUS (Precisely)"
    srcset="
      /howto/okta-as400-ibmi/okta-as400-4_hu_15ee53137a6afd6e.webp  330w,
      /howto/okta-as400-ibmi/okta-as400-4_hu_403907ac1ccd747e.webp  660w,
      /howto/okta-as400-ibmi/okta-as400-4_hu_2091ea7933d90072.webp  960w,
      /howto/okta-as400-ibmi/okta-as400-4_hu_9db7cee4228137c1.webp 1280w,
      /howto/okta-as400-ibmi/okta-as400-4_hu_659a621a43a6495b.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-as400-ibmi/okta-as400-4.png"
    src="/howto/okta-as400-ibmi/okta-as400-4.png">


  
</figure>










<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="AS400 Configuration for Okta MFA using RADIUS (Precisely)"
    srcset="
      /howto/okta-as400-ibmi/okta-as400-5_hu_a2c2eae0f6a5e393.webp  330w,
      /howto/okta-as400-ibmi/okta-as400-5_hu_99a069ebcf2e97c0.webp  660w,
      /howto/okta-as400-ibmi/okta-as400-5_hu_4a2536395bf3273e.webp  960w,
      /howto/okta-as400-ibmi/okta-as400-5_hu_aabf7aa5b590e031.webp 1280w,
      /howto/okta-as400-ibmi/okta-as400-5_hu_514f0f1a6e08488a.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-as400-ibmi/okta-as400-5.png"
    src="/howto/okta-as400-ibmi/okta-as400-5.png">


  
</figure>










<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="AS400 Configuration for Okta MFA using RADIUS (Precisely)"
    srcset="
      /howto/okta-as400-ibmi/okta-as400-6_hu_f836fc2be7440d62.webp  330w,
      /howto/okta-as400-ibmi/okta-as400-6_hu_cbf28b7f5fec3273.webp  660w,
      /howto/okta-as400-ibmi/okta-as400-6_hu_9ae69086c30dbbc2.webp  960w,
      /howto/okta-as400-ibmi/okta-as400-6_hu_8ff3b2c69f515807.webp 1280w,
      /howto/okta-as400-ibmi/okta-as400-6_hu_7d576dd37a6b4ec8.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-as400-ibmi/okta-as400-6.png"
    src="/howto/okta-as400-ibmi/okta-as400-6.png">


  
</figure>
</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855860037390 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855860037390 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855860037390 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855860037390 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855860037390" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><p><strong>A Note on Licensing</strong></p>
<p>While we are not discussing specific pricing, it&rsquo;s useful to understand the licensing model for such solutions. Typically, licensing for security software on IBM i is tied to the underlying hardware configuration, such as the number of processor cores or Logical Partitions (LPARs) where the software is active. For detailed information, it is always best to consult directly with the solution provider.</p>

  </div>
</div>

<h3 class="relative group">Limitations of the RADIUS Protocol
    <div id="limitations-of-the-radius-protocol" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#limitations-of-the-radius-protocol" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>While RADIUS is a powerful and universal protocol, it has limitations in the context of modern authentication:</p>
<ul>
<li><strong>No Browser-Based Flows</strong>: Since terminal access is not browser-based, you cannot leverage phishing-resistant authenticators like FIDO2/WebAuthn or experience frictionless authentication like <strong><a href="https://www.okta.com/products/fastpass/"  target="_blank" rel="noreferrer">Okta FastPass</a></strong>.</li>
<li><strong>Limited User Experience</strong>: The MFA interaction is typically limited to push notifications or entering a one-time passcode. Richer interactions, like number challenges in Okta Verify, are not supported.</li>
</ul>
<p>Despite these limitations, using RADIUS is a highly effective and widely adopted method for adding a critical layer of security to non-web systems.</p>

<h3 class="relative group">Alternatives
    <div id="alternatives" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#alternatives" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Precisely Assure Security is not the only solution available for integrating Okta MFA with IBM i systems. While during my test with Precisely I found the setup straightforward and well-documented, other vendors can be considered. For example:</p>
<ul>
<li><strong><a href="https://razlee.com/isecurity-multi-factor-authentication/"  target="_blank" rel="noreferrer">Raz-Lee iSecurity Multi Factor Authentication</a></strong></li>
<li><strong><a href="https://www.midlandinfosys.com/ibm-i-mfa-multi-factor-authentication"  target="_blank" rel="noreferrer">Midland IBM i Security Access Controls</a></strong></li>
</ul>
<p>While I have not personally tested these alternatives, they may offer similar capabilities for integrating Okta MFA via RADIUS or other methods. When evaluating options, consider factors such as ease of integration, support for your specific IBM i version, and the ability to implement rules-based MFA.</p>
<hr>

<h2 class="relative group">Lifecycle Management (LCM)
    <div id="lifecycle-management-lcm" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#lifecycle-management-lcm" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Beyond authentication, managing the <strong>JML (Joiner, Mover, Leaver) user lifecycle</strong> on IBM i systems is critical for security and operational efficiency. Automating provisioning and de-provisioning ensures that access is granted promptly and, more importantly, revoked immediately when a user leaves the organization.</p>
<p>Okta&rsquo;s standard for automation is the <strong>System for Cross-domain Identity Management (SCIM)</strong> protocol. However, IBM i does not natively support SCIM. To bridge this gap, there are two primary approaches.</p>

<h3 class="relative group">Option 1: Okta On-Premises Provisioning (OPP) Agent with Custom Scripts
    <div id="option-1-okta-on-premises-provisioning-opp-agent-with-custom-scripts" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#option-1-okta-on-premises-provisioning-opp-agent-with-custom-scripts" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>For organizations willing to handle this completely autonomously, the <strong><a href="https://help.okta.com/oie/en-us/content/topics/provisioning/opp/opp-main.htm/"  target="_blank" rel="noreferrer">Okta On-Premises Provisioning (OPP) Agent</a></strong> offers a flexible, custom-tailored solution.</p>
<p><strong>How it works:</strong></p>
<ol>
<li>You install the lightweight OPP agent on a Linux or Windows server in your network.</li>
<li>You build a custom SCIM connector, which consists of a set of PowerShell scripts that the agent executes.</li>
<li>These scripts are responsible for translating Okta&rsquo;s SCIM commands (like <code>CreateUser</code>, <code>UpdateUser</code>, <code>DeactivateUser</code>) into native IBM i commands. This could involve using scripts (such as PowerShell, Bash, Python, etc.) to make SSH connections to the IBM i and run CL (Control Language) commands, or calling specific APIs if available.</li>
</ol>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Okta On-Premises Provisioning (OPP) Agent Sample Flow"
    srcset="
      /howto/okta-as400-ibmi/okta-opp_hu_f5effcd88252324d.webp  330w,
      /howto/okta-as400-ibmi/okta-opp_hu_f2f663c1ed27eeb8.webp  660w,
      /howto/okta-as400-ibmi/okta-opp_hu_2db952ff1c2282c7.webp  960w,
      /howto/okta-as400-ibmi/okta-opp_hu_9c9336ff23879ca8.webp 1280w,
      /howto/okta-as400-ibmi/okta-opp_hu_9514cb63afd1510.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-as400-ibmi/okta-opp.png"
    src="/howto/okta-as400-ibmi/okta-opp.png">


  
</figure>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855860319997 {
    background: #FFFAE6;
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855860319997 .panel-icon {
    color: rgb(224,108,0);
  }
  html.dark #panel-1778753855860319997 {
    background: rgb(51,46,27);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855860319997 .panel-icon {
    color: rgb(251,200,40);
  }
</style>

<div id="panel-1778753855860319997" class="flex px-4 py-3 rounded-md shadow panel-warning ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>This approach gives you complete control over the integration logic but requires development and maintenance effort.</p>
<p><em>Okta only supports on-premises provisioning where Okta Professional Services or a Certified Partner performed the implementation.</em></p>

  </div>
</div>

<h3 class="relative group">Option 2: A SaaS-Based SCIM Gateway like Aquera
    <div id="option-2-a-saas-based-scim-gateway-like-aquera" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#option-2-a-saas-based-scim-gateway-like-aquera" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>For a faster, out-of-the-box solution, you can use a third-party SCIM gateway like <strong><a href="https://www.okta.com/integrations/ibm-os400-on-as400-ibm-i-on-power-systems-by-aquera/"  target="_blank" rel="noreferrer">Aquera</a></strong>.</p>
<p><strong>How it works:</strong>
Aquera is a SaaS platform that specializes in connecting IdPs like Okta to applications and systems that don&rsquo;t support modern protocols. It acts as a managed SCIM translator:</p>
<ol>
<li>Okta communicates with the Aquera platform via a standard SCIM connection.</li>
<li>Aquera translates Okta&rsquo;s SCIM commands into the native APIs and commands that the IBM i system understands.</li>
<li>The result is a seamless, pre-built integration that enables full lifecycle management without the need for custom development.</li>
</ol>
<p>This approach significantly reduces implementation time and effort, making it ideal for organizations that prefer a managed solution.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Okta SCIM Gateway by Aquera"
    srcset="
      /howto/okta-as400-ibmi/okta-aquera_hu_6c74ce9777801e9c.webp  330w,
      /howto/okta-as400-ibmi/okta-aquera_hu_237aaa7bf2086d64.webp  660w,
      /howto/okta-as400-ibmi/okta-aquera_hu_5076c2fd011dccc1.webp  960w,
      /howto/okta-as400-ibmi/okta-aquera_hu_8d33ae1a63578311.webp 1280w,
      /howto/okta-as400-ibmi/okta-aquera_hu_62b6b1cc95be007b.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-as400-ibmi/okta-aquera.png"
    src="/howto/okta-as400-ibmi/okta-aquera.png">


  
</figure>

<h4 class="relative group">Entitlements and Governance
    <div id="entitlements-and-governance" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#entitlements-and-governance" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Regardless of the approach you choose, a robust LCM integration extends to managing entitlements. By mapping Okta groups to specific roles or profiles on the IBM i, you can automate access to necessary libraries, objects, and functions.</p>
<p>This integration is a cornerstone of modern identity governance. With a solution like <strong>Okta Identity Governance (OIG)</strong>, you can run access certification campaigns where reviewers can approve or revoke access to IBM i resources directly from the Okta interface, creating a complete and auditable governance trail.</p>
<p>Aquera support automatically the entitlements management as part of their SCIM gateway offering. While with OPP, you need to build the logic to handle entitlements in your custom scripts.</p>

<h3 class="relative group">Option 3: Provisioning via the Okta LDAP Agent
    <div id="option-3-provisioning-via-the-okta-ldap-agent" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#option-3-provisioning-via-the-okta-ldap-agent" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>For organizations that want to leverage existing LDAP infrastructure, there is a third, <em>more theoretical</em> path for provisioning: using the <strong><a href="https://help.okta.com/oie/en-us/content/topics/directory/ldap-agent-manage-integration.htm"  target="_blank" rel="noreferrer">Okta LDAP Agent</a></strong>. This approach is possible because IBM i includes a built-in Directory Server that can be configured to expose its native user database (<code>*USRPRF</code>) as an LDAP directory.</p>
<p>However, this method comes with significant caveats and complexities that make it less common in practice.</p>

<h4 class="relative group">How it Works
    <div id="how-it-works" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-it-works" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>The conceptual flow is as follows:</p>
<ol>
<li><strong>Okta Trigger</strong>: A user is assigned the IBM i group in Okta.</li>
<li><strong>LDAP Agent</strong>: The Okta LDAP Agent, installed on a server in your network, receives the provisioning command from the Okta cloud service.</li>
<li><strong>LDAP Provisioning</strong>: The agent sends standard LDAP commands (e.g., <code>add</code>, <code>modify</code>) to the IBM i Directory Server endpoint.</li>
<li><strong>IBM i Backend</strong>: The IBM i system, through its directory server, interprets the LDAP command and creates a corresponding user profile on the system.</li>
</ol>

<h4 class="relative group">The Challenge: Rich Profiles vs. Basic LDAP
    <div id="the-challenge-rich-profiles-vs-basic-ldap" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-challenge-rich-profiles-vs-basic-ldap" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>You might think that since Okta allows for custom LDAP attribute mapping, you could simply extend the schema to manage all the necessary IBM i settings. However, the limitations are not about mapping attributes, but about the fundamental nature of the operations required to manage a true IBM i User Profile.</p>
<p>The Okta LDAP Agent is designed to manage standard LDAP attributes like <code>uid</code>, <code>cn</code>, and <code>userPassword</code>. While it can successfully create a basic user on the IBM i, a native User Profile (<code>*USRPRF</code>) requires a rich set of specific security parameters that have no equivalent in a standard LDAP schema.</p>
<p>These critical attributes, which the LDAP agent cannot manage out-of-the-box, include:</p>
<ul>
<li><strong>Special Authorities</strong>: Permissions like <code>*ALLOBJ</code> (all object access) or <code>*SECADM</code> (security administrator)</li>
<li><strong>Initial Program/Menu</strong>: The first screen a user sees after logging in</li>
<li><strong>Library List</strong>: The specific libraries and data a user can access</li>
<li><strong>Job Description</strong>: Defines how batch jobs are run under the user’s profile</li>
<li><strong>Object Ownership</strong> and countless other security-relevant settings</li>
</ul>
<p>To bridge this gap, you would need to supplement the LDAP provisioning with custom <em>Control Language (CL)</em> scripts on the IBM i host. These scripts would need to be triggered after the user is created via LDAP to apply the necessary security settings, turning this into a complex, hybrid solution that requires significant custom development and maintenance, much like the custom scripting required for the OPP agent in Option 1.</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Limitation</th>
          <th style="text-align: left">Explanation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><strong>Command Execution vs. Data Writing</strong></td>
          <td style="text-align: left">The core issue is that creating a secure IBM i user is a <strong>command execution</strong> (<code>CRTUSRPRF</code> - Create User Profile) with dozens of parameters, not a simple data-writing operation. LDAP&rsquo;s <code>add</code> operation is a data protocol. The IBM i Directory Server&rsquo;s translation layer is not designed to handle the complex, procedural logic of the <code>CRTUSRPRF</code> command, which might involve creating objects or checking system states.</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Complex Lifecycle Logic</strong></td>
          <td style="text-align: left">Lifecycle management isn&rsquo;t just about creation. When a user&rsquo;s role changes, you need to execute a <code>CHGUSRPRF</code> (Change User Profile) command, often with specific parameters. You cannot simply &ldquo;map&rdquo; an Okta attribute update to trigger the correct IBM i command on the backend. An update to an LDAP attribute does not translate into the execution of a privileged command.</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>Lack of Transactional Integrity</strong></td>
          <td style="text-align: left">The native <code>CRTUSRPRF</code> command is <strong>atomic</strong>—it either succeeds completely or fails and rolls back, leaving the system clean. An LDAP-based approach relies on multiple, non-atomic attribute writes. If the <code>uid</code> is written but a subsequent write for a critical security attribute fails, you could be left with a partially configured, insecure &ldquo;zombie&rdquo; user profile, which is a significant security risk and administrative burden.</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>No Official Support</strong></td>
          <td style="text-align: left">This is not a standard, documented, or supported integration pattern from either Okta or IBM. You would be operating in uncharted territory, effectively reverse-engineering the behavior of the IBM i Directory Server&rsquo;s backend. This makes it unacceptably risky for a production enterprise environment as there would be no support path when issues inevitably arise.</td>
      </tr>
  </tbody>
</table>
<p>In essence, while the LDAP agent can create a basic user &ldquo;shell,&rdquo; it cannot safely manage the rich security context of a true IBM i User Profile. The extensive custom scripting and backend logic required to overcome these limitations ultimately make this approach as complex as using the Okta OPP Agent (Option 1), but with significantly higher risk and no official support.</p>
<hr>

<h2 class="relative group">Conclusions
    <div id="conclusions" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#conclusions" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Your IBM i systems are too critical to be left out of your modern identity security strategy. While they may not speak the same language as your cloud applications, proven solutions exist to bridge the gap.</p>
<p>By leveraging <strong>Precisely Assure Security</strong> with the <strong>Okta RADIUS Agent</strong>, you can extend strong Multi-Factor Authentication to every corner of your IBM i environment. For lifecycle management, you have the flexibility to either build a custom solution with the <strong>Okta OPP Agent</strong> or accelerate your deployment with a pre-built SCIM gateway like <strong>Aquera</strong>.</p>
<p>Integrating these foundational platforms with Okta not only modernizes their security but also provides a unified, consistent, and auditable identity fabric across your entire organization.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855860566625 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855860566625 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855860566625 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855860566625 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855860566625" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><p><strong>Disclaimer</strong></p>
<p>I work with Okta as a Solutions Engineer. The third-party solutions mentioned in this article (Precisely, Aquera, Raz-Lee, Midland) are not affiliated with me or Okta. They&rsquo;re presented as potential solutions to evaluate based on your organization&rsquo;s specific needs, technical requirements, and business context. Always conduct your own due diligence and testing before making integration decisions.</p>

  </div>
</div>
<hr>
<p>What&rsquo;s your experience with IBM i security and identity management? Have you integrated your legacy systems with modern IAM platforms? <a href="/howto/okta-as400-ibmi/#comments" >Share your thoughts, questions, or feedback in the comments below</a>! 👇</p>
]]></content:encoded>
      <category>Okta</category>
      <category>IBMi</category>
      <category>AS400</category>
      <category>MFA</category>
      <category>Provisioning</category>
      <category>LCM</category>
      <category>RADIUS</category>
      <category>OPP</category>
      <category>SCIM</category>
      <category>Precisely</category>
      <category>Aquera</category>
      <category>Legacy Modernization</category>
    </item>
    <item>
      <title>GLPI Integration with Okta</title>
      <link>https://iam.fabiograsso.net/howto/okta-glpi-11/</link>
      <pubDate>Sat, 15 Nov 2025 15:46:00 +0200</pubDate>
      <guid>https://iam.fabiograsso.net/howto/okta-glpi-11/</guid>
      <description>How to integrate GLPI 11, an open-source IT service management platform, with Okta for SSO. It covers running a GLPI test environment via Docker, LDAP and SAML configuration walkthroughs, and notes on OAuth/OIDC with commercial plugins. The guide highlights user import, authentication options, demo readiness, and security limitations for non-production use.</description>
      <content:encoded>&lt;![CDATA[
<h2 class="relative group">Intro
    <div id="intro" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#intro" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p><a href="https://glpi-project.org/" title="https://glpi-project.org/" target="_blank" rel="noreferrer">GLPI</a> is an open-source service management software made in France and is used by a lot of companies in EMEA.</p>
<p>I worked with a customer for the integration with Okta. Here are some notes and the instructions for running a test environment with docker.</p>
<p>Note: since it’s very easy to run GLPI with docker-compose and configure it with LDAP and/or SAML, it can be a good solution for a demo environment when you have to demonstrate the typical user experience with an LDAP (or SAML) integration.</p>

<h2 class="relative group">Run a test environment with Docker Compose
    <div id="run-a-test-environment-with-docker-compose" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#run-a-test-environment-with-docker-compose" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>There is an <a href="https://github.com/glpi-project/docker-images"  target="_blank" rel="noreferrer">official docker image</a> that permits running GLPI very quickly in docker or docker-compose.</p>
<p>I used it as a base, and builded a custom Docker Compose file to run it along with MariaDB, Mailpit (for email testing) and DBGate (a web-based database management tool).</p>
<p>The source code is available on the following GitHub repository: <a href="https://github.com/fabiograsso/okta-lab-glpi11"  target="_blank" rel="noreferrer">fabiograsso/okta-lab-glpi11</a>.</p>
<p>To run it, just clone the repo, edit the .env file, and execute <code>make start</code>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/fabiograsso/okta-lab-glpi11
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> okta-lab-glpi11
</span></span><span class="line"><span class="cl">cp .env.example .env
</span></span><span class="line"><span class="cl"><span class="c1"># (edit .env if needed)</span>
</span></span><span class="line"><span class="cl">make start</span></span></code></pre></div></div>
<p>It exposes GLPI on port 80 in localhost, so you can open it using <a href="http://localhost/" title="http://localhost" target="_blank" rel="noreferrer">http://localhost</a></p>
<p>Once started, here are the default users for login in GLPI:</p>
<table>
  <thead>
      <tr>
          <th>Login/Password</th>
          <th>Role</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>glpi/glpi</td>
          <td>admin account</td>
      </tr>
      <tr>
          <td>tech/tech</td>
          <td>technical account</td>
      </tr>
      <tr>
          <td>normal/normal</td>
          <td>&ldquo;normal&rdquo; account</td>
      </tr>
      <tr>
          <td>post-only/postonly</td>
          <td>post-only account</td>
      </tr>
  </tbody>
</table>
<p>You will find two new folders:</p>
<p><code>./data/mysql</code> contains the database, in order to make it persistent when you restart the docker image
<code>./data/glpi</code> contains the GLPI web application</p>





  



  



















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855864693020 {
    background: #FFFAE6;
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855864693020 .panel-icon {
    color: rgb(224,108,0);
  }
  html.dark #panel-1778753855864693020 {
    background: rgb(51,46,27);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855864693020 .panel-icon {
    color: rgb(251,200,40);
  }
</style>

<div id="panel-1778753855864693020" class="flex px-4 py-3 rounded-md shadow panel-warning ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>
  <div class="panel-text">Note: this docker-compose is for internal testing only. There is no security configured, and must not be used in a production environment or in a public-facing server without hardening the security.
  </div>
</div>

<h2 class="relative group">Authentication and SSO with Okta
    <div id="authentication-and-sso-with-okta" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#authentication-and-sso-with-okta" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>To federate it with Okta, there are three options:</p>
<ol>
<li><strong>LDAP Interface</strong></li>
<li><strong>SAML</strong> using an open-source plugin called ‘phpsaml’</li>
<li><strong>OAuth/OIDC</strong> using an official plugin provided by GLPI Network</li>
</ol>

<h3 class="relative group">LDAP Interface
    <div id="ldap-interface" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ldap-interface" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>
<p>Go to Home → Setup → Authentication → LDAP directories → Add









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/ldap-interface0_hu_7a2f36d699776b23.webp  330w,
      /howto/okta-glpi-11/ldap-interface0_hu_88ffcbb9f33023b7.webp  660w,
      /howto/okta-glpi-11/ldap-interface0_hu_536c52c4edefc12.webp  960w,
      /howto/okta-glpi-11/ldap-interface0_hu_59d77e42552d8255.webp 1280w,
      /howto/okta-glpi-11/ldap-interface0_hu_c1503abb02a4de09.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/ldap-interface0.png"
    src="/howto/okta-glpi-11/ldap-interface0.png">


  
</figure>
</p>
</li>
<li>
<p>Use the following configuration for Okta LDAP Interface:</p>
<ul>
<li>Server: <code>your_okta_domain.ldap.okta.com</code></li>
<li>Port: <code>389</code> (or <code>636</code> for LDAPS)</li>
<li>Connection Filter: <code>(objectClass=person)</code></li>
<li>BaseDN: <code>dc=your_okta_domain,dc=okta,dc=com</code> (replace with your Okta LDAP Base DN)</li>
<li>Use bind: <code>Yes</code></li>
<li>Root DN: <code>cn=your_bind_user,dc=your_okta_domain,dc=okta,dc=com</code> (replace with your Okta LDAP Bind DN)</li>
<li>Password: <code>your_bind_user_password</code></li>
<li>Login field: <code>uid</code>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/ldap-interface1_hu_d1178026186c5146.webp  330w,
      /howto/okta-glpi-11/ldap-interface1_hu_573a7aea3a864b72.webp  660w,
      /howto/okta-glpi-11/ldap-interface1_hu_70129fb36ca76d23.webp  960w,
      /howto/okta-glpi-11/ldap-interface1_hu_e8bb077239cda318.webp 1280w,
      /howto/okta-glpi-11/ldap-interface1_hu_a155a70c984c8200.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/ldap-interface1.png"
    src="/howto/okta-glpi-11/ldap-interface1.png">


  
</figure>
</li>
</ul>
</li>
<li>
<p>In the <code>Users</code> tab you can define which user attributes to sync. Additional attributes can be added - based on you needs.









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/ldap-interface2_hu_b34be1fb985613e3.webp  330w,
      /howto/okta-glpi-11/ldap-interface2_hu_64cbb69b8afb97dd.webp  660w,
      /howto/okta-glpi-11/ldap-interface2_hu_29ee5f525376e0bf.webp  960w,
      /howto/okta-glpi-11/ldap-interface2_hu_7b6c0aa4209c4644.webp 1280w,
      /howto/okta-glpi-11/ldap-interface2_hu_e8089f8b10aad0a0.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/ldap-interface2.png"
    src="/howto/okta-glpi-11/ldap-interface2.png">


  
</figure>
</p>
</li>
<li>
<p>For the <code>Groups</code> tab the configuration is very simple:</p>
<ul>
<li>Search type: &ldquo;In Groups&rdquo;</li>
<li>Filter: <code>(objectClass=groupOfUniqueNames)</code></li>
<li>Group attribute: <code>uniquemember</code></li>
<li>Use DN in search: <code>No</code>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/ldap-interface3_hu_243d54e44fba4e7a.webp  330w,
      /howto/okta-glpi-11/ldap-interface3_hu_adabf851d99acffe.webp  660w,
      /howto/okta-glpi-11/ldap-interface3_hu_e48151db41115396.webp  960w,
      /howto/okta-glpi-11/ldap-interface3_hu_81ad10954bbb9b85.webp 1280w,
      /howto/okta-glpi-11/ldap-interface3_hu_e4bb5f3e66579814.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/ldap-interface3.png"
    src="/howto/okta-glpi-11/ldap-interface3.png">


  
</figure>
</li>
</ul>
</li>
<li>
<p>In the <code>Advanced information</code> tab, you can set the following parameters:</p>
<ul>
<li>Use TLS: <code>Yes</code></li>
<li>LDAP Directory time zone: <code>GMT</code></li>
<li>Timeout: <code>30</code> - I suggest keeping the timeout as long as possible (the maximum is 30) in order to give to users the time to accept the push notification on the phone (if push MFA is used)</li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/ldap-interface4_hu_3bb210ee8cf3a0f6.webp  330w,
      /howto/okta-glpi-11/ldap-interface4_hu_ce12299c2a315ecd.webp  660w,
      /howto/okta-glpi-11/ldap-interface4_hu_ea5e002155cdcea5.webp  960w,
      /howto/okta-glpi-11/ldap-interface4_hu_1e71e5135e7135de.webp 1280w,
      /howto/okta-glpi-11/ldap-interface4_hu_eef3fabb6c631bf6.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/ldap-interface4.png"
    src="/howto/okta-glpi-11/ldap-interface4.png">


  
</figure>
</li>
<li>
<p>Once the LDAP is configured, you can import users from the Administration → Users → LDAP directory link.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/ldap-interface5_hu_fb29bb1be8a0ec7c.webp  330w,
      /howto/okta-glpi-11/ldap-interface5_hu_b04c6239b99f8405.webp  660w,
      /howto/okta-glpi-11/ldap-interface5_hu_86c8d26ad1e76948.webp  960w,
      /howto/okta-glpi-11/ldap-interface5_hu_9bb742890c103bb3.webp 1280w,
      /howto/okta-glpi-11/ldap-interface5_hu_e286581b9c1a9a3d.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/ldap-interface5.png"
    src="/howto/okta-glpi-11/ldap-interface5.png">


  
</figure>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/ldap-interface6_hu_1648ee4f28e6ee8e.webp  330w,
      /howto/okta-glpi-11/ldap-interface6_hu_a59d1d5c6728f151.webp  660w,
      /howto/okta-glpi-11/ldap-interface6_hu_9635a5809446145f.webp  960w,
      /howto/okta-glpi-11/ldap-interface6_hu_54946af545d3fb6d.webp 1280w,
      /howto/okta-glpi-11/ldap-interface6_hu_6fb309726143fa63.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/ldap-interface6.png"
    src="/howto/okta-glpi-11/ldap-interface6.png">


  
</figure>
<p>The same can also be done for groups.</p>
</li>
<li>
<p>Then, the LDAP users can log in by selecting the proper login source on the login page (or by keeping the default one if “Default Server: Yes” is configured on the LDAP setting)









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/ldap-interface7_hu_e27557063bbc8e75.webp  330w,
      /howto/okta-glpi-11/ldap-interface7_hu_7c39cbe77e3df404.webp  660w,
      /howto/okta-glpi-11/ldap-interface7_hu_e97ac6fc9cfc2bf7.webp  960w,
      /howto/okta-glpi-11/ldap-interface7_hu_84cc7a20a2a44efd.webp 1280w,
      /howto/okta-glpi-11/ldap-interface7_hu_8095c1f17b2f564c.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/ldap-interface7.png"
    src="/howto/okta-glpi-11/ldap-interface7.png">


  
</figure>
</p>
</li>
</ol>

<h3 class="relative group">SAML
    <div id="saml" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#saml" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The LDAP interface is limited in terms of features and user experience. Another option is to use SAML. There is an open-source plugin called <a href="https://github.com/DonutsNL/samlsso"  target="_blank" rel="noreferrer">samlSSO</a>. The installation is very easy:</p>
<ol>
<li>Download the zip file from <a href="https://github.com/DonutsNL/samlsso/releases"  target="_blank" rel="noreferrer"><strong>Releases · DonutsNL/samlsso</strong></a></li>
<li>Extract and copy in the folder <code>&lt;GLPI_ROOT&gt;/marketplace/samlsso</code> (if using my docker-compose, that’s the folder <code>.data/glpi/marketplace/samlsso</code>)</li>
<li>Enable and configure it from the web interface of GLPI (Home → Setup → Plugins)</li>
</ol>
<p>Alternatively, you can also install it from the <a href="https://plugins.glpi-project.org/#/plugin/samlsso"  target="_blank" rel="noreferrer">GLPI Marketplace</a>, in this case you&rsquo;ll need to <a href="https://plugins.glpi-project.org/#/signup"  target="_blank" rel="noreferrer">register for a free account</a>.</p>

<h4 class="relative group">Configuration - Okta Side
    <div id="configuration---okta-side" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuration---okta-side" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>On Okta, the configuration is very easy, just a custom SAML application.</p>
<ul>
<li>Single sign on URL: <code>http://localhost/plugins/samlsso/front/acs/1</code></li>
<li>Audience URI (SP Entity ID): <code>http://localhost/</code></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/saml1_hu_3a590904ba64d992.webp  330w,
      /howto/okta-glpi-11/saml1_hu_5c7d30f13aaed41b.webp  660w,
      /howto/okta-glpi-11/saml1_hu_817da207771e1cdd.webp  960w,
      /howto/okta-glpi-11/saml1_hu_e2f5ec135637fd54.webp 1280w,
      /howto/okta-glpi-11/saml1_hu_409c478d39f8e4f8.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/saml1.png"
    src="/howto/okta-glpi-11/saml1.png">


  
</figure>

<h4 class="relative group">Configuration - GLPI Side
    <div id="configuration---glpi-side" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuration---glpi-side" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ol>
<li>
<p>Go to Home → Setup → samlSSO → Add</p>
</li>
<li>
<p>In the <code>General</code> tab, use the following configuration:</p>
<ul>
<li>FRIENDLY NAME : <code>Okta</code></li>
<li>LOGIN ICON  : <code>fa-solid fa-key</code> (you can choose any icon you want from FontAwesome)</li>
<li>USERDOMAIN: leave empty (optionally you can set a domain to force the users on that domain to use SSO)</li>
<li>IS ACTIVE: <code>Yes</code></li>
<li>DEBUG: <code>No</code>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/saml2_hu_86108a03217afef3.webp  330w,
      /howto/okta-glpi-11/saml2_hu_cc70655995c34e63.webp  660w,
      /howto/okta-glpi-11/saml2_hu_70d91f0d8c024093.webp  960w,
      /howto/okta-glpi-11/saml2_hu_406ee286db0236bb.webp 1280w,
      /howto/okta-glpi-11/saml2_hu_a31e4c34199c25ed.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/saml2.png"
    src="/howto/okta-glpi-11/saml2.png">


  
</figure>
</li>
</ul>
</li>
<li>
<p>In the <code>Transit</code> tab just keep the default value









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/saml3_hu_def16906db6b090d.webp  330w,
      /howto/okta-glpi-11/saml3_hu_db31cb72bd584150.webp  660w,
      /howto/okta-glpi-11/saml3_hu_4c49e8bf6fc5f88d.webp  960w,
      /howto/okta-glpi-11/saml3_hu_8f48940c5d9714a0.webp 1280w,
      /howto/okta-glpi-11/saml3_hu_48a631ae3f0d2589.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/saml3.png"
    src="/howto/okta-glpi-11/saml3.png">


  
</figure>
</p>
</li>
<li>
<p>In the <code>Service Provider</code> tab, set the following parameters:</p>
<ul>
<li>NAMEID FORMAT: <code>Unspecified</code></li>
<li>SP CERTIFICATE and PRIVATE KEY: leave empty - optionally you can generate a certificate and use it use Single Logout or to encrypt/sign the SAML requests









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/saml4_hu_1f2ed02672f1bb4f.webp  330w,
      /howto/okta-glpi-11/saml4_hu_7ff60cf3feb78572.webp  660w,
      /howto/okta-glpi-11/saml4_hu_1b2d39f428a89eca.webp  960w,
      /howto/okta-glpi-11/saml4_hu_255886ff10599dd5.webp 1280w,
      /howto/okta-glpi-11/saml4_hu_7d1aff198f3e150.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/saml4.png"
    src="/howto/okta-glpi-11/saml4.png">


  
</figure>

You will find also the Entity ID and ACS URL that you have to use in Okta configuration.</li>
</ul>
<p>For a simple demo perspective and to make the configuration easier, do not enable Strict and Single logout, so there is no need to generate an SP certificate.</p>
</li>
<li>
<p>In the <code>Identity Provider</code> tab, set the following parameters:</p>
<ul>
<li>ENTITY ID: <code>http://www.okta.com/your_okta_org_id</code> (replace with your Okta Org ID)</li>
<li>SSO URL: <code>https://your_okta_domain.okta.com/app/your_app_id/sso/saml</code> (replace with your Okta SAML SSO URL)</li>
<li>IDP CERTIFICATE: copy/paste the Okta X.509 certificate here</li>
<li>SLO URL: leave empty (unless you want to configure Single Logout)









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/saml5_hu_ccf3b8573895c4aa.webp  330w,
      /howto/okta-glpi-11/saml5_hu_8fcafb15c28b9932.webp  660w,
      /howto/okta-glpi-11/saml5_hu_a5f845ee56f4178b.webp  960w,
      /howto/okta-glpi-11/saml5_hu_42e0e74020e93a6.webp 1280w,
      /howto/okta-glpi-11/saml5_hu_efcb072951cc451.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/saml5.png"
    src="/howto/okta-glpi-11/saml5.png">


  
</figure>
</li>
</ul>
</li>
<li>
<p>Finally, the <code>Security</code> tab permit to change some security options:









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/saml6_hu_998a935b1c028625.webp  330w,
      /howto/okta-glpi-11/saml6_hu_74ada600a1755c97.webp  660w,
      /howto/okta-glpi-11/saml6_hu_d7092c5abaf1ca89.webp  960w,
      /howto/okta-glpi-11/saml6_hu_1baef5890957599c.webp 1280w,
      /howto/okta-glpi-11/saml6_hu_a7a4cfd7bdd9fbc.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/saml6.png"
    src="/howto/okta-glpi-11/saml6.png">


  
</figure>
</p>
<p>For example:</p>
<ul>
<li>JIT USER CREATION : <code>Yes</code> - to create the users on the fly at first login</li>
<li>STRICT: <code>Yes</code> - to enforce SAML response validation</li>
<li>ENFORCED: <code>No</code> - Unless you want to force all users to use SSO. Otherwise, there is an option “Sign In with SSO” on the login page.</li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-11/saml7_hu_e767b9d3807cc471.webp  330w,
      /howto/okta-glpi-11/saml7_hu_65edca637acba516.webp  660w,
      /howto/okta-glpi-11/saml7_hu_270362ab447cc23b.webp  960w,
      /howto/okta-glpi-11/saml7_hu_3e924d4785543690.webp 1280w,
      /howto/okta-glpi-11/saml7_hu_6d1ae85622622f8a.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-11/saml7.png"
    src="/howto/okta-glpi-11/saml7.png">


  
</figure>
</li>
</ol>

<h3 class="relative group">OAuth / OIDC / SCIM
    <div id="oauth--oidc--scim" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#oauth--oidc--scim" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The last option for federate GLPI is to use OAuth.</p>
<p>There is a plugin <a href="https://services.glpi-network.com/documentation/1731/file/README.md" title="https://services.glpi-network.com/documentation/1731/file/README.md" target="_blank" rel="noreferrer">Oauth SSO client for GLPI</a> included in the <a href="https://services.glpi-network.com/"  target="_blank" rel="noreferrer"><strong>GLPI Network</strong></a> subscription (BASIC or higher).</p>
<p>In this case, is not included in the Open Source project and can be used only by customers with an active (paid) subscription.</p>
<p>I have not tested it, but they <a href="https://glpi-plugins.readthedocs.io/fr/latest/oauthsso/okta.html"  target="_blank" rel="noreferrer">explicitly mention Okta in their documentation</a>.</p>
<p>With the GLPI Network subscription you can also leverage on the <a href="https://glpi-plugins.readthedocs.io/en/latest/scim/index.html"  target="_blank" rel="noreferrer">SCIM connector for GLPI</a>.</p>

<h2 class="relative group">Workflows Integration with Okta
    <div id="workflows-integration-with-okta" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#workflows-integration-with-okta" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Another interesting aspect of GLPI is the possibility to integrate it with Okta Workflows, using the <a href="https://glpi-project.org/documentation/rest-api/"  target="_blank" rel="noreferrer">GLPI API</a> and the <a href="https://help.glpi-project.org/documentation/modules/configuration/webhook"  target="_blank" rel="noreferrer">GLPI WebHook</a>.</p>
<p>I will update this post soon with some examples of integration.</p>
]]></content:encoded>
      <category>Okta</category>
      <category>GLPI</category>
      <category>SSO</category>
      <category>LDAP</category>
      <category>SAML</category>
      <category>OAuth</category>
      <category>OIDC</category>
      <category>SCIM</category>
      <category>Integration</category>
      <category>Docker</category>
      <category>Workflows</category>
    </item>
    <item>
      <title>Lab for test the Okta LDAP Agent with (or without) Docker</title>
      <link>https://iam.fabiograsso.net/howto/okta-lab-ldap/</link>
      <pubDate>Fri, 07 Nov 2025 09:00:00 +0100</pubDate>
      <guid>https://iam.fabiograsso.net/howto/okta-lab-ldap/</guid>
      <description>Introduction # This guide provides a step-by-step walkthrough for deploying the OpenLDAP directory service, and integrate it with Okta, using Docker and Docker Compose.&#xA;</description>
      <content:encoded>&lt;![CDATA[
<h2 class="relative group">Introduction
    <div id="introduction" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#introduction" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>This guide provides a step-by-step walkthrough for deploying the <strong>OpenLDAP</strong> directory service, and integrate it with <strong>Okta</strong>, using <strong>Docker</strong> and <strong>Docker Compose.</strong></p>
<p>You will find also step by step instructions for install in a clean <strong>Ubuntu 24.04 LTS server</strong>, in case you don&rsquo;t want (or can&rsquo;t) use Docker.</p>
<p>We will cover everything from the initial server setup to populating the directory with data for a fictional company, &ldquo;<strong>The Galaxy</strong>&rdquo; (<code>galaxy.universe</code>), using management tools, and integrating with Okta using the LDAP Agent.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855886716945 {
    background: rgb(220,255,241);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855886716945 .panel-icon {
    color: rgb(106,154,35);
  }
  html.dark #panel-1778753855886716945 {
    background: rgb(28,51,41);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855886716945 .panel-icon {
    color: rgb(179,223,114);
  }
</style>

<div id="panel-1778753855886716945" class="flex px-4 py-3 rounded-md shadow panel-success ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M438.6 105.4C451.1 117.9 451.1 138.1 438.6 150.6L182.6 406.6C170.1 419.1 149.9 419.1 137.4 406.6L9.372 278.6C-3.124 266.1-3.124 245.9 9.372 233.4C21.87 220.9 42.13 220.9 54.63 233.4L159.1 338.7L393.4 105.4C405.9 92.88 426.1 92.88 438.6 105.4H438.6z"/></svg>
</span>
  </span>
  <div class="panel-text">This article has been tested with Okta LDAP Agent version 05.24.00 (Release 2025.07.0)
  </div>
</div>

<h3 class="relative group">Navigation Guide
    <div id="navigation-guide" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#navigation-guide" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>This guide offers multiple setup approaches to match your needs:</p>
<ul>
<li><strong><a href="/howto/okta-lab-ldap/#containerized-deployment-with-docker-compose" >Containerized Deployment with Docker Compose</a></strong> (<em>Recommended</em>) - The fastest and easiest way to get started</li>
<li><strong><a href="/howto/okta-lab-ldap/#manual-installation" >Manual Installation</a></strong> - Step-by-step native installation (based on Ubuntu 24.04 LTS) for learning and production</li>
<li><strong><a href="/howto/okta-lab-ldap/#fully-automated-one-click-installation-script" >Fully Automated One-click Installation Script</a></strong> - Automated native installation for quick deployment on Ubuntu 24.04 LTS</li>
<li><strong><a href="/howto/okta-lab-ldap/#using-management-interfaces" >Using Management Interfaces</a></strong> - Two different approach for manage the LDAP Server: a web interface and a Desktop Client</li>
<li><strong><a href="/howto/okta-lab-ldap/#managing-the-okta-ldap-integration" >Managing the Okta-LDAP Integration</a></strong> - Configure Okta integration after any setup method</li>
</ul>
<!-- Consider adding after the Docker warning panel -->





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855887124482 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855887124482 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855887124482 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855887124482 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855887124482" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><p><strong>When to use each approach:</strong></p>
<ul>
<li><strong><a href="/#containerized-deployment-with-docker-compose" >Docker Compose</a></strong>: Development, Demos, POCs</li>
<li><strong><a href="/#manual-installation" >Manual Install</a></strong>: Production environments, long-term deployments</li>
<li><strong><a href="/#fully-automated-one-click-installation-script" >Automated Script</a></strong>: Quick development, Demos, POCs, when Docker is not an option</li>
</ul>
  </div>
</div>

<h3 class="relative group">The LDIF Files
    <div id="the-ldif-files" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-ldif-files" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>We will define our entries using the LDAP Data Interchange Format (LDIF).</p>
<p>Once cloned the GitHub repository <a href="https://github.com/fabiograsso/okta-lab-ldap/"  target="_blank" rel="noreferrer"><span class="relative inline-block align-text-bottom icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg></span> fabiograsso/okta-lab-ldap</a> you will find the ready to use LDIF files in the <code>./src/ldifs-base/</code> folder. Additional files that will be used in the <a href="/howto/okta-lab-ldap/#managing-the-okta-ldap-integration" >Okta-LDAP Integration</a> are available in the <code>./src/ldifs/</code> folder.</p>
<p>Let&rsquo;s take a look at our files:</p>
<ol>
<li>
<p><strong>root.ldif</strong> - Root Domain, create the root of our LDAP server (<code>dc=galaxy,dc=universe</code>):</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-ldif" data-lang="ldif">dn: dc=galaxy,dc=universe
objectClass: top
objectClass: domain
dc: galaxy</code></pre></div>
</li>
<li>
<p><strong>ou.ldif</strong> - Organizational Units, which creates the top-level containers (OU - Organizational Units) for our users and groups:</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-ldif" data-lang="ldif">dn: ou=People,dc=galaxy,dc=universe
objectClass: organizationalUnit
ou: People

dn: ou=Groups,dc=galaxy,dc=universe
objectClass: organizationalUnit
ou: Groups</code></pre></div>
</li>
<li>
<p><strong>users.ldif</strong> - Sample Users, which defines 12 users with various attributes, for example:</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-ldif" data-lang="ldif">dn: uid=luke.skywalker@galaxy.local,ou=People,dc=galaxy,dc=universe
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
cn: Luke Skywalker
givenName: Luke
sn: Skywalker
uid: luke.skywalker@galaxy.local
mail: luke.skywalker@galaxy.local
userPassword: {SSHA}P@ssword2024!
title: Jedi Knight
departmentNumber: JEDI-COUNCIL
manager: uid=obiwan.kenobi@galaxy.local,ou=People,dc=galaxy,dc=universe
o: Jedi
postalAddress: Lars Moisture Farm, Anchorhead, Tatooine
displayName: Luke Skywalker
employeeNumber: 10021</code></pre></div>
</li>
<li>
<p><strong>groups.ldif</strong> - Sample Groups, which defines groups using the <code>groupOfUniqueNames</code> object class, and the <code>uniqueMember</code> attribute. Example:</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-ldif" data-lang="ldif">dn: cn=Droids,ou=Groups,dc=galaxy,dc=universe
objectClass: top
objectClass: groupOfUniqueNames
cn: Droids
description: Intelligent mechanical droid characters
uniqueMember: uid=c-3po@galaxy.local,ou=People,dc=galaxy,dc=universe
uniqueMember: uid=r2-d2@galaxy.local,ou=People,dc=galaxy,dc=universe</code></pre></div>
</li>
<li>
<p><strong>photos.ldif</strong> - While it is not actually supported in Okta, we can load profile pictures in the LDAP Directory. It&rsquo;s one of the most requested features in Okta Ideas, so the hope is that, sooner or later, Okta will support it, and our directory will be ready 😉
Photos are Base64 encoded JPEG files, and are defined in the <code>jpegPhoto</code> attribute of <code>inetOrgPerson</code> Object Class. For example:</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-ldif" data-lang="ldif">dn: uid=padme.amidala@galaxy.local,ou=People,dc=galaxy,dc=universe
changetype: modify
add: jpegPhoto
jpegPhoto:: /9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAY[...]</code></pre></div>
</li>
</ol>
<p>The following table summarizes the users who will be loaded in our example:</p>
<table>
  <thead>
      <tr>
          <th>UserID (@galaxy.local)</th>
          <th>Full Name</th>
          <th>Organization</th>
          <th>Groups</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>luke.skywalker</td>
          <td>Luke Skywalker</td>
          <td>Jedi</td>
          <td>Light-Side, Rebel-Alliance, Jedi-Council, Pilots</td>
      </tr>
      <tr>
          <td>leia.organa</td>
          <td>Leia Organa</td>
          <td>Resistance</td>
          <td>Light-Side, Rebel-Alliance</td>
      </tr>
      <tr>
          <td>han.solo</td>
          <td>Han Solo</td>
          <td>Resistance</td>
          <td>Light-Side, Rebel-Alliance, Pilots</td>
      </tr>
      <tr>
          <td>obiwan.kenobi</td>
          <td>Obi-Wan Kenobi</td>
          <td>Jedi</td>
          <td>Light-Side, Rebel-Alliance, Jedi-Council</td>
      </tr>
      <tr>
          <td>yoda</td>
          <td>Yoda</td>
          <td>Jedi</td>
          <td>Light-Side, Jedi-Council</td>
      </tr>
      <tr>
          <td>chewbacca</td>
          <td>Chewbacca</td>
          <td>Resistance</td>
          <td>Light-Side, Rebel-Alliance</td>
      </tr>
      <tr>
          <td>padme.amidala</td>
          <td>Padmé Amidala</td>
          <td>Resistance</td>
          <td>Light-Side, Naboo-Delegation</td>
      </tr>
      <tr>
          <td>lando.calrissian</td>
          <td>Lando Calrissian</td>
          <td>Resistance</td>
          <td>Light-Side, Rebel-Alliance</td>
      </tr>
      <tr>
          <td>qui-gon.jinn</td>
          <td>Qui-Gon Jinn</td>
          <td>Jedi</td>
          <td>Light-Side, Jedi-Council</td>
      </tr>
      <tr>
          <td>mace.windu</td>
          <td>Mace Windu</td>
          <td>Jedi</td>
          <td>Light-Side, Jedi-Council</td>
      </tr>
      <tr>
          <td>darth.vader</td>
          <td>Darth Vader</td>
          <td>Empire</td>
          <td>Dark-Side, Galactic-Empire, Pilots</td>
      </tr>
      <tr>
          <td>darth.sidious</td>
          <td>Darth Sidious</td>
          <td>Empire</td>
          <td>Dark-Side, Galactic-Empire</td>
      </tr>
      <tr>
          <td>wilhuff.tarkin</td>
          <td>Wilhuff Tarkin</td>
          <td>Empire</td>
          <td>Dark-Side, Galactic-Empire</td>
      </tr>
      <tr>
          <td>c-3po</td>
          <td>C-3PO</td>
          <td>Droid</td>
          <td>Light-Side, Droids, Rebel-Alliance</td>
      </tr>
      <tr>
          <td>r2-d2</td>
          <td>R2-D2</td>
          <td>Droid</td>
          <td>Light-Side, Droids, Rebel-Alliance</td>
      </tr>
  </tbody>
</table>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855887700607 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855887700607 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855887700607 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855887700607 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855887700607" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>All users are configured with the fake email domain <code>@galaxy.local</code>.<br>
You can easily replace it with a custom domain using:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">DOMAIN</span><span class="o">=</span><span class="s2">&#34;custom-domain.com&#34;</span>  
</span></span><span class="line"><span class="cl">find ./src/ldifs-base ./src/ldifs -type f -name <span class="s2">&#34;*.ldif&#34;</span> <span class="p">|</span> <span class="se">\ </span> 
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="nb">read</span> file<span class="p">;</span> <span class="k">do</span>  
</span></span><span class="line"><span class="cl">  sed -i <span class="s1">&#39;&#39;</span> -E <span class="s2">&#34;s/^(mail: )([a-zA-Z0-9._-]+)@galaxy\.local/\1\2@</span><span class="si">${</span><span class="nv">DOMAIN</span><span class="si">}</span><span class="s2">/g&#34;</span> <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span>  
</span></span><span class="line"><span class="cl"><span class="k">done</span>  </span></span></code></pre></div></div>
<p>Or, for example, if you have a Gmail account you can leverage on the feature of Gmail that permit to have multiple aliases, by adding a <code>+alias</code> after your name:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">GMAIL_USER</span><span class="o">=</span><span class="s2">&#34;your.name&#34;</span>  
</span></span><span class="line"><span class="cl"><span class="nv">GMAIL_DOMAIN</span><span class="o">=</span><span class="s2">&#34;gmail.com&#34;</span>  
</span></span><span class="line"><span class="cl">find ./src/ldifs-base ./src/ldifs -type f -name <span class="s2">&#34;*.ldif&#34;</span> <span class="p">|</span> <span class="se">\ </span> 
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="nb">read</span> file<span class="p">;</span> <span class="k">do</span>  
</span></span><span class="line"><span class="cl">  sed -i <span class="s1">&#39;&#39;</span> -E <span class="s2">&#34;s/^(mail: )([a-zA-Z0-9._-]+)@galaxy\.local/\1</span><span class="si">${</span><span class="nv">GMAIL_USER</span><span class="si">}</span><span class="s2">+\2@</span><span class="si">${</span><span class="nv">GMAIL_DOMAIN</span><span class="si">}</span><span class="s2">/g&#34;</span> <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span>  
</span></span><span class="line"><span class="cl"><span class="k">done</span>  </span></span></code></pre></div></div>

  </div>
</div>

<h3 class="relative group">TCP Ports
    <div id="tcp-ports" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tcp-ports" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>This guide uses ports <strong>1389</strong> (LDAP) and <strong>1636</strong> (LDAPS) instead of the standard <strong>389/636</strong> for several reasons:</p>
<ol>
<li><strong>Non-privileged access</strong>: Ports below 1024 require root privileges to bind. Using higher ports allows the Docker containers to run with reduced privileges, improving security.</li>
<li><strong>Avoid conflicts</strong>: If your system already has an LDAP service or Active Directory domain controller running, it would be using the standard ports. The alternate ports prevent conflicts.</li>
<li><strong>Development flexibility</strong>: In development environments, you can run multiple LDAP instances simultaneously on the same host using different port ranges.</li>
<li><strong>Security through obscurity</strong>: While not a primary security measure, using non-standard ports can reduce automated scanning attempts in exposed environments.</li>
</ol>
<p>If you need to use standard ports, you can modify the port mappings in <code>docker-compose.yml</code>, or in the file <code>/etc/default/slapd</code>.</p>

<h3 class="relative group">The Okta LDAP Agent
    <div id="the-okta-ldap-agent" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-okta-ldap-agent" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The <strong>Okta LDAP Agent</strong> is a lightweight software component that acts as a secure bridge between your on-premises LDAP directory and Okta&rsquo;s cloud-based identity platform. It enables organizations to integrate existing LDAP directories (such as OpenLDAP, Oracle Directory Server, or IBM Security Directory Server) with Okta without requiring direct network connectivity or exposing LDAP ports to the internet.</p>
<p><strong>Key capabilities:</strong></p>
<ul>
<li><strong>Delegated Authentication</strong>: Allows users to authenticate against your LDAP directory while accessing Okta-protected applications, maintaining your existing password policies and authentication logic</li>
<li><strong>User Provisioning</strong>: Synchronizes user profiles and groups from LDAP to Okta, or vice versa, based on your configuration</li>
<li><strong>Password Management</strong>: Supports password changes and resets initiated from Okta, propagating them back to your LDAP directory</li>
<li><strong>Secure Communication</strong>: Establishes an outbound HTTPS connection from your network to Okta, eliminating the need for inbound firewall rules</li>
<li><strong>High Availability</strong>: Supports multiple agents for redundancy and load balancing</li>
<li><strong>Automatic Updates</strong>: Agents can update themselves automatically when multiple instances are deployed</li>
</ul>
<p>The agent runs as a service on Windows or Linux servers within your network, polling your LDAP directory at regular intervals and communicating changes to Okta via secure REST API calls.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Okta LDAP Agent Architecture"
    srcset="
      /howto/okta-lab-ldap/okta-ldap-agent-architecture_hu_6bdb4183db4475e7.webp  330w,
      /howto/okta-lab-ldap/okta-ldap-agent-architecture_hu_c2b439e067923119.webp  660w,
      /howto/okta-lab-ldap/okta-ldap-agent-architecture_hu_ea96e204ed6a355a.webp  960w,
      /howto/okta-lab-ldap/okta-ldap-agent-architecture_hu_d1f4cc6c26e8a5c0.webp 1280w,
      /howto/okta-lab-ldap/okta-ldap-agent-architecture_hu_e72e49853852d6ec.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/okta-ldap-agent-architecture.png"
    src="/howto/okta-lab-ldap/okta-ldap-agent-architecture.png">


  
</figure>
<hr>

<h2 class="relative group">Containerized Deployment with Docker Compose
    <div id="containerized-deployment-with-docker-compose" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#containerized-deployment-with-docker-compose" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p><code>Docker</code> is the ideal solution for a fast, reproducible, portable, and isolated setup. This section provides a complete <code>docker-compose.yml</code> configuration to deploy the entire stack.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855888868747 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855888868747 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855888868747 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855888868747 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855888868747" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>Okta does not officially support (yet) Docker for executing the LDAP Agent. While it&rsquo;s a fantastic way to quickly set up a full environment, I don&rsquo;t suggest using it in production. In that case you can follow the <a href="/#manual-installation" >Manual Installation</a>.</p>
<p>On the other hand, Docker is the best solution for demoes, POCs, quick test.</p>

  </div>
</div>

<h3 class="relative group">Prerequisites
    <div id="prerequisites" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#prerequisites" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><strong>Docker</strong> and <strong>Docker Compose</strong> are installed.
<ul>
<li>If you are using a Ubuntu 24.04 Linux server: <code>apt-get install -y docker docker-compose</code></li>
<li>In a macOS or Windows environment, you can install <strong><a href="https://www.docker.com/products/docker-desktop/"  target="_blank" rel="noreferrer">Docker Desktop</a></strong></li>
</ul>
</li>
<li>Git clone the following repository:
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/fabiograsso/okta-lab-ldap/
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> okta-lab-ldap</span></span></code></pre></div></div>
</li>
<li>Download the <code>.deb</code> file of the Okta LDAP Agent and copy it into the <code>./docker/okta-ldap-agent/package</code> folder.
<ol>
<li>Log in to your Okta Admin Console.</li>
<li>Navigate to <strong>Directory → Directory Integrations</strong>.</li>
<li>Click <strong>Add Directory → Add LDAP Directory</strong>.</li>
<li>Follow the setup steps. On the &ldquo;Install Agent&rdquo; step, right-click the Download Agent button and copy the link.</li>
<li>Download the <code>.deb</code> file on your LDAP server:
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -o ./docker/okta-ldap-agent/package/OktaLDAPAgent.deb <span class="se">\
</span></span></span><span class="line"><span class="cl">https://xxx.okta.com/artifacts/JAVA_LDAP/05.24.00/OktaLDAPAgent-05.24.00-27823a892f7b.x86_64.deb</span></span></code></pre></div></div>
</li>
</ol>
</li>
<li>Alternatively, populate the <code>DEB_URL</code> environment variable with the download URL</li>
</ul>

<h3 class="relative group">Configuration File
    <div id="configuration-file" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuration-file" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The <code>.env</code> file contains all the configuration. You can customize it, or you can keep the default value and change only the <code>OKTA_ORG</code> value to your Okta tenant name.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">OKTA_ORG</span><span class="o">=</span>myorg.okta.com
</span></span><span class="line"><span class="cl"><span class="nv">LDAP_ORGANISATION</span><span class="o">=</span>The Galaxy
</span></span><span class="line"><span class="cl"><span class="nv">LDAP_DOMAIN</span><span class="o">=</span>galaxy.universe
</span></span><span class="line"><span class="cl"><span class="nv">LDAP_BASE_DN</span><span class="o">=</span><span class="nv">dc</span><span class="o">=</span>galaxy,dc<span class="o">=</span>universe
</span></span><span class="line"><span class="cl"><span class="nv">LDAP_ADMIN_PASSWORD</span><span class="o">=</span>adminpassword
</span></span><span class="line"><span class="cl"><span class="nv">LDAP_CONFIG_PASSWORD</span><span class="o">=</span>configpassword</span></span></code></pre></div></div>
<p>The <code>src/ldifs-base</code> folder contains the LDIF files for OU, Users, and Groups. It will load the <a href="/#the-ldif-files" >sample data described before</a>.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855889151925 {
    background: #FFFAE6;
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855889151925 .panel-icon {
    color: rgb(224,108,0);
  }
  html.dark #panel-1778753855889151925 {
    background: rgb(51,46,27);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855889151925 .panel-icon {
    color: rgb(251,200,40);
  }
</style>

<div id="panel-1778753855889151925" class="flex px-4 py-3 rounded-md shadow panel-warning ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>If you change <code>LDAP_DOMAIN</code> and <code>LDAP_BASE_DN</code>, remeber to change all the LDIF files in order to reflect the new values. The LDIF files are static and will be not changed automatically.<br>
If you don&rsquo;t have experience with LDIF files, I warmly suggest to use the default value.</p>
<p>Remember: <code>LDAP_BASE_DN</code> must be derived from <code>LDAP_DOMAIN</code>. Example: <code>LDAP_DOMAIN=galaxy.universe</code> → <code>LDAP_BASE_DN=dc=galaxy,dc=universe</code></p>

  </div>
</div>

<h3 class="relative group">Execution
    <div id="execution" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#execution" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>There is a <code>Makefile</code> that contains some shortcuts in order to make it easier to execute the needed Docker Compose commands.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855889347213 {
    background: #FFFAE6;
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855889347213 .panel-icon {
    color: rgb(224,108,0);
  }
  html.dark #panel-1778753855889347213 {
    background: rgb(51,46,27);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855889347213 .panel-icon {
    color: rgb(251,200,40);
  }
</style>

<div id="panel-1778753855889347213" class="flex px-4 py-3 rounded-md shadow panel-warning ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>Before starting, double-check again to:</p>
<ul>
<li>Have downloaded the Okta LDAP Agent deb file in the folder <code>./docker/okta-ldap-agent/package/</code> (or populated the <code>DEB_URL</code> environment variable with the download URL)</li>
<li>Have edited the <code>.env</code> file and customized the <code>OKTA_ORG</code>, <code>LDAP_ADMIN_PASSWORD</code>, and <code>LDAP_CONFIG_PASSWORD</code> variables</li>
</ul>
  </div>
</div>
<ol>
<li>
<p><strong>Build the Stack</strong></p>
<p>Since there is a custom image used for the Okta LDAP agent, the first step is to build it. From the <code>okta-lab-ldap</code> root directory, run:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make build</span></span></code></pre></div></div>
</li>
<li>
<p><strong>Start the Stack</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make start</span></span></code></pre></div></div>
</li>
<li>
<p><strong>Configure the Okta Agent</strong></p>
<p>This is a one-time manual step. You don&rsquo;t need to run it again when you restart the service.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make configure</span></span></code></pre></div></div>
<p>Follow the prompts:</p>
<ol>
<li><strong>Okta Subdomain</strong>: Enter your Okta org URL (e.g., <code>https://myorg.okta.com</code>).</li>
<li><strong>Proxy Server</strong>: Press Enter to skip.</li>
<li><strong>LDAP Server</strong>: <code>ldaps://openldap:1636</code></li>
<li><strong>LDAP Admin DN for Agent</strong>: For testing, you can use the main admin account: <code>cn=admin,dc=galaxy,dc=universe</code>. For production, see the considerations in the Appendix.</li>
<li><strong>LDAP Admin Password</strong>: Enter the password for the account above.</li>
<li><strong>Register Agent in Okta</strong>:
<ul>
<li>You will see a prompt like this: <code>To continue, navigate to the following website: [https://xxx.okta.com/activate] and enter the following code: [MSJPKGRP]</code></li>
<li>Follow the instructions and open the <code>/activate</code> URL in your Okta tenant</li>
<li>Enter the Activation code</li>
<li>Click on &ldquo;Allow&rdquo;</li>
</ul>
</li>
</ol>
<p>You can ignore the final error: <code>/opt/Okta/OktaLDAPAgent/scripts/configure_agent.sh: line 302: systemctl: command not found</code> - This is due to the fact that the Docker image doesn&rsquo;t support systemctl. The startup of the agent will be in any case handled by the docker image script.</p>
</li>
</ol>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855889598861 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855889598861 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855889598861 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855889598861 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855889598861" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>Access the Services:</p>
<ul>
<li><strong>LDAP</strong>: Available on <code>localhost:1389</code>, <code>localhost:1636</code>.</li>
<li><strong>ldap-ui</strong>: Available at <code>http://localhost:5000</code></li>
</ul>
<p>If you are running it on Docker Desktop, the easiest way is to open a browser and use the localhost URL.</p>

  </div>
</div>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855889943918 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855889943918 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855889943918 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855889943918 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855889943918" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>If your Docker is remote, use the following command to open an SSH tunnel and get access to the LDAP, LDAPS, and ldap-ui ports. This will avoid opening the ports on the firewall, and it&rsquo;s a more secure way to connect your server:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh -f -N -L 1389:localhost:1389 ubuntu@myserverIP  
</span></span><span class="line"><span class="cl">ssh -f -N -L 1636:localhost:1636 ubuntu@myserverIP  
</span></span><span class="line"><span class="cl">ssh -f -N -L 5000:localhost:5000 ubuntu@myserverIP  </span></span></code></pre></div></div>
  </div>
</div>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="LDAP running on Docker Desktop"
    srcset="
      /howto/okta-lab-ldap/docker-desktop_hu_c1086412b9b2d334.webp  330w,
      /howto/okta-lab-ldap/docker-desktop_hu_a43dc653a57dbcf5.webp  660w,
      /howto/okta-lab-ldap/docker-desktop_hu_9bef56e595ca5954.webp  960w,
      /howto/okta-lab-ldap/docker-desktop_hu_f5d22e749a843cc2.webp 1280w,
      /howto/okta-lab-ldap/docker-desktop_hu_88b84dbfd9f96016.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/docker-desktop.png"
    src="/howto/okta-lab-ldap/docker-desktop.png">


  
</figure>

<h3 class="relative group">Other commands
    <div id="other-commands" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#other-commands" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Other <code>Makefile</code> commands:</p>
<table>
  <thead>
      <tr>
          <th>Command</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>make stop</code></td>
          <td>Stop Docker-Compose</td>
      </tr>
      <tr>
          <td><code>make restart</code></td>
          <td>Restart docker-compose</td>
      </tr>
      <tr>
          <td><code>make logs</code></td>
          <td>Show the last 500 logs and start tail -f</td>
      </tr>
      <tr>
          <td><code>make start</code></td>
          <td>Start Docker-Compose (in the background)</td>
      </tr>
      <tr>
          <td><code>make start-logs</code></td>
          <td>Start Docker-Compose with logs</td>
      </tr>
      <tr>
          <td><code>make restart-logs</code></td>
          <td>Restart Docker-Compose with logs</td>
      </tr>
      <tr>
          <td><code>make build</code></td>
          <td>Rebuild all Docker images</td>
      </tr>
      <tr>
          <td><code>make configure</code></td>
          <td>Start the Okta LDAP Agent configuration</td>
      </tr>
  </tbody>
</table>
<hr>

<h2 class="relative group">Using Management Interfaces
    <div id="using-management-interfaces" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#using-management-interfaces" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>While command-line tools are powerful, graphical interfaces are more comfortable for daily administration.</p>
<p>This section will cover two approaches: the first with a light Web UI (ldap-ui) and the second with a powerful fat client.</p>

<h3 class="relative group">Web-Based Management with ldap-ui
    <div id="web-based-management-with-ldap-ui" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#web-based-management-with-ldap-ui" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>There are many web management tools for LDAP, but the choice of using <code>ldap-ui</code> is due to:</p>
<ul>
<li><code>phpLDAPadmin</code>, a classic choice, is largely unmaintained and has significant compatibility issues with the modern PHP versions included in Ubuntu 24.04.</li>
<li><code>LDAP Account Manager (LAM)</code> is a powerful and feature-rich tool, but many of its advanced features are restricted to the paid &ldquo;Pro&rdquo; version.</li>
<li><code>ldap-ui</code> provides a clean and simple interface for daily administrative tasks, is open-source, and well-maintained.</li>
</ul>

<h4 class="relative group">Access the ldap-ui interface
    <div id="access-the-ldap-ui-interface" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#access-the-ldap-ui-interface" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li>If you&rsquo;re running you Docker Compose stack locally, you can simply open <a href="http://localhost:5000"  target="_blank" rel="noreferrer">http://localhost:5000</a> in a web browser, and log in using <code>admin</code> as username and the <code>cn=admin</code> password.</li>
<li>If you&rsquo;re running it in a remote server, since there is no specific security, I warmly suggest:
<ul>
<li>Publish it using a reverse proxy (i.e., nginx), or</li>
<li>Create an SSH tunnel and then open the web portal from your PC instead of exposing port 5000 of the server
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh -f -N -L 5000:localhost:5000 ubuntu@myopenldapserver</span></span></code></pre></div></div>
Once opened the tunnel open <a href="http://localhost:5000"  target="_blank" rel="noreferrer">http://localhost:5000</a> in a web browser</li>
</ul>
</li>
</ul>
<p>More information about <code>ldap-ui</code> can be found in <a href="https://github.com/dnknth/ldap-ui"  target="_blank" rel="noreferrer"><span class="relative inline-block align-text-bottom icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg></span> dnknth/ldap-ui Github repository</a>.</p>

<h3 class="relative group">Desktop Management with Apache Directory Studio
    <div id="desktop-management-with-apache-directory-studio" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#desktop-management-with-apache-directory-studio" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Apache Directory Studio is the industry standard as a native desktop client (Eclipse-based) for heavy-duty LDAP work. It supports many types of directories, along with an LDIF editor, a Schema browser, and many advanced features.</p>
<ol>
<li>
<p><strong>Download</strong> - Visit the <a href="https://directory.apache.org/studio/downloads.html"  target="_blank" rel="noreferrer">Apache Directory Studio downloads page</a> and download the appropriate version for your operating system (e.g., macOS, Windows, Linux).</p>
</li>
<li>
<p><strong>Install</strong> - Follow the standard installation procedure for your OS.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855890582993 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855890582993 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855890582993 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855890582993 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855890582993" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/></svg>
</span>
  </span>
  <div class="panel-text"><h4 class="relative group">Install on Silicon MacOS (M1/M2/M3/M4 - ARM CPU)
    <div id="install-on-silicon-macos-m1m2m3m4---arm-cpu" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#install-on-silicon-macos-m1m2m3m4---arm-cpu" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>New MacBooks have a Silicon (ARM-based) CPU, while Apache Directory Studio is compiled for x86. In order to run it properly, you need to follow some additional steps:</p>
<ol>
<li><strong>Rosetta 2</strong>, which enables the use of apps that were built for a Mac with an Intel processor:
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">/usr/sbin/softwareupdate --install-rosetta <span class="se">\ </span> 
</span></span><span class="line"><span class="cl">                        --agree-to-license  </span></span></code></pre></div></div>
</li>
<li><strong>Homebrew</strong>, a powerful package manager for macOS:
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">/bin/bash -c <span class="s2">&#34;</span><span class="k">$(</span>curl -fsSL <span class="se">\ </span> 
</span></span><span class="line"><span class="cl">https://raw.githubusercontent.com/Homebrew/<span class="se">\ </span> 
</span></span><span class="line"><span class="cl">install/HEAD/install.sh<span class="k">)</span><span class="s2">&#34;</span>  </span></span></code></pre></div></div>
</li>
<li><strong>An x86 Java SDK</strong>:
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">arch -x86_64 brew install oracle-jdk  </span></span></code></pre></div></div>
</li>
<li>You can then install <strong>Apache Directory Studio</strong> directly with brew:
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew install apache-directory-studio  </span></span></code></pre></div></div>
</li>
</ol>
  </div>
</div>
</li>
<li>
<p><strong>Create a New Connection</strong></p>
<ol>
<li>Launch Apache Directory Studio.</li>
<li>Go to <strong>File → New&hellip; → LDAP Connection</strong>.</li>
<li><strong>Hostname</strong>: <code>localhost</code></li>
<li><strong>Port</strong>: <code>1389</code>. (or <code>1636</code> for LDAPS)</li>
<li>Click &ldquo;<strong>Check Network Parameter</strong>&rdquo; to verify connectivity.</li>
<li>On the next screen, enter the <strong>Bind DN</strong> (<code>cn=admin,dc=galaxy,dc=universe</code>) and your <strong>Admin password</strong>.</li>
<li>Click <strong>Finish</strong>.</li>
</ol>
</li>
</ol>
<p>You can now use the LDAP Browser to explore, edit, and manage the directory.</p>
<hr>

<h2 class="relative group">Managing the Okta-LDAP Integration
    <div id="managing-the-okta-ldap-integration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#managing-the-okta-ldap-integration" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Once the agent is connected, you can configure the integration from your Okta Admin Console.</p>

<h3 class="relative group">Complete the LDAP configuration in Okta
    <div id="complete-the-ldap-configuration-in-okta" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#complete-the-ldap-configuration-in-okta" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>Open the Okta Admin Console and go to <strong>Directory → Directory Integrations</strong></li>
<li>You will find a <em>&ldquo;Not yet configured&rdquo;</em> LDAP in the list. Click on the &ldquo;LDAP&rdquo; link: 








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Okta Directory Integrations"
    srcset="
      /howto/okta-lab-ldap/ldap-screen1_hu_37c665c698bd191b.webp  330w,
      /howto/okta-lab-ldap/ldap-screen1_hu_613de4d6563c03c5.webp  660w,
      /howto/okta-lab-ldap/ldap-screen1_hu_fb0072ad167d377e.webp  960w,
      /howto/okta-lab-ldap/ldap-screen1_hu_4e8487cd698427e1.webp 1280w,
      /howto/okta-lab-ldap/ldap-screen1_hu_272f573a655fb1c8.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-screen1.png"
    src="/howto/okta-lab-ldap/ldap-screen1.png">


  
</figure>
</li>
<li>Configure using the following parameters:
<ol>
<li><strong>LDAP Version</strong>: <code>OpenLDAP</code></li>
<li><strong>Objects</strong>
<ul>
<li>Unique Identifier Attribute: <code>entryuuid</code></li>
<li>DN Attribute: <code>entrydn</code></li>
</ul>
</li>
<li><strong>User</strong>
<ul>
<li>User Search Base: <code>ou=People,dc=galaxy,dc=universe</code></li>
<li>Object Class: <code>inetorgperson</code></li>
<li>User Object Filter: <code>(objectclass=inetorgperson)</code></li>
<li>Account Disabled Attribute: <code>fax</code>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855891782132 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855891782132 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855891782132 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855891782132 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855891782132" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text">To simplify things with a simple demo, we are not implementing any password/lockout policy at the LDAP Server level. Despite this, Okta needs to define an attribute to enable/disable users. So we are configuring an unused attribute (the <code>fax</code>). In a real environment, this attribute will be the one defined by the lockout and password policy.
  </div>
</div></li>
<li>Account Disabled Value: <code>TRUE</code></li>
<li>Account Enabled Value: <code>FALSE</code></li>
<li>Password Attribute: <code>userpassword</code></li>
</ul>
</li>
<li><strong>Group</strong>
<ul>
<li>Group Search Base: <code>ou=Groups,dc=galaxy,dc=universe</code></li>
<li>Group Object Class: <code>groupofuniquenames</code></li>
<li>Group Object Filter: <code>(objectclass=groupofuniquenames)</code></li>
<li>Member Attribute: <code>uniqueMember</code></li>
</ul>
</li>
<li><strong>Role</strong>
<ul>
<li>Object Class: leave blank</li>
<li>Membership Attribute: leave blank</li>
</ul>
</li>
<li><strong>Validate Configuration</strong>
<ul>
<li>Okta username format: User Id (UID)</li>
<li>Example username: <code>yoda@galaxy.local</code></li>
</ul>
</li>
<li>Click on &ldquo;<strong>Test Configuration</strong>&rdquo;. You will see the user details: 








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Okta Configuration Test"
    srcset="
      /howto/okta-lab-ldap/ldap-user-details_hu_2ba1e55d006003c0.webp  330w,
      /howto/okta-lab-ldap/ldap-user-details_hu_5f0a69eba11956e2.webp  660w,
      /howto/okta-lab-ldap/ldap-user-details_hu_3dee97b666736e80.webp  960w,
      /howto/okta-lab-ldap/ldap-user-details_hu_52a97a042f03f229.webp 1280w,
      /howto/okta-lab-ldap/ldap-user-details_hu_6042ba72f0ea6a27.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-user-details.png"
    src="/howto/okta-lab-ldap/ldap-user-details.png">


  
</figure>
</li>
<li>Click &ldquo;<strong>Next</strong>&rdquo;</li>
<li>Click &ldquo;<strong>Done</strong>&rdquo; 








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Okta LDAP Configuration Done"
    srcset="
      /howto/okta-lab-ldap/ldap-done_hu_8efb459ef368616.webp  330w,
      /howto/okta-lab-ldap/ldap-done_hu_8b24011a1fc8ba8.webp  660w,
      /howto/okta-lab-ldap/ldap-done_hu_f280ff399aabbb45.webp  960w,
      /howto/okta-lab-ldap/ldap-done_hu_f89bf738c1258069.webp 1280w,
      /howto/okta-lab-ldap/ldap-done_hu_ffb51bea257f0b83.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-done.png"
    src="/howto/okta-lab-ldap/ldap-done.png">


  
</figure>
</li>
</ol>
</li>
</ol>

<h3 class="relative group">Importing Users and Groups
    <div id="importing-users-and-groups" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#importing-users-and-groups" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>
<p>Configure Import Settings</p>
<ol>
<li>In your Okta Admin Console, go to the settings for your newly configured LDAP directory.</li>
<li>Navigate to the <strong>Provisioning → To Okta</strong> tab.</li>
<li>Configure the settings:
<ul>
<li>Schedule import: <code>every hour</code> (or choose another timeframe)</li>
<li>Okta Username Format: <code>Email</code></li>
<li>flag &ldquo;<code>Don't send new user activation emails for this domain</code>&rdquo; and &ldquo;<code>Create and update users on login</code>&rdquo;</li>
<li>Enable &ldquo;<code>Auto-confirm new users</code>&rdquo; and &ldquo;<code>Auto-activate new users</code>&rdquo;









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Import Settings"
    srcset="
      /howto/okta-lab-ldap/ldap-import-settings_hu_7dde1ce22814429d.webp  330w,
      /howto/okta-lab-ldap/ldap-import-settings_hu_765282d21c939928.webp  660w,
      /howto/okta-lab-ldap/ldap-import-settings_hu_85ef241f61ae2f20.webp  960w,
      /howto/okta-lab-ldap/ldap-import-settings_hu_950f8285ff7ba250.webp 1280w,
      /howto/okta-lab-ldap/ldap-import-settings_hu_add4a717ae065e77.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-import-settings.png"
    src="/howto/okta-lab-ldap/ldap-import-settings.png">


  
</figure>
</li>
</ul>
</li>
</ol>
</li>
<li>
<p>Run a manual Import</p>
<ol>
<li>Navigate to the <strong>Import</strong> tab.</li>
<li>Click <strong>Import Now</strong>. Okta will scan your LDAP directory and import the users and groups

        <figure>
                <img
                  class="my-0 rounded-md ma0 w-75"
                  loading="lazy"
                  decoding="async"
                  fetchpriority="auto"
                  alt="Okta Import Process"
                  width="306"
                  height="468"
                  src="/howto/okta-lab-ldap/ldap-manual-import_hu_ebeffd43a781ceb3.png"
                  srcset="/howto/okta-lab-ldap/ldap-manual-import_hu_ebeffd43a781ceb3.png 800w,/howto/okta-lab-ldap/ldap-manual-import_hu_932d452225f8a1c3.png 1280w"
                  sizes="(min-width: 768px) 50vw, 65vw"
                  data-zoom-src="/howto/okta-lab-ldap/ldap-manual-import.png"
                />
          
          
          </figure></li>
<li>You will see the LDAP Users and Groups in your Okta Directory









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Okta Users Imported"
    srcset="
      /howto/okta-lab-ldap/ldap-users-imported_hu_82dd5ca7e3b06d57.webp  330w,
      /howto/okta-lab-ldap/ldap-users-imported_hu_464517ada762c5e7.webp  660w,
      /howto/okta-lab-ldap/ldap-users-imported_hu_d87de672bdce1131.webp  960w,
      /howto/okta-lab-ldap/ldap-users-imported_hu_9def8d91162f03ac.webp 1280w,
      /howto/okta-lab-ldap/ldap-users-imported_hu_a6a06d13f34440ef.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-users-imported.png"
    src="/howto/okta-lab-ldap/ldap-users-imported.png">


  
</figure>










<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Okta Groups Imported"
    srcset="
      /howto/okta-lab-ldap/ldap-groups-imported_hu_7c76e5fcab55465c.webp  330w,
      /howto/okta-lab-ldap/ldap-groups-imported_hu_3a35b770ab6f1429.webp  660w,
      /howto/okta-lab-ldap/ldap-groups-imported_hu_9a4edc71ece549ed.webp  960w,
      /howto/okta-lab-ldap/ldap-groups-imported_hu_5a5f3e24afbeafdb.webp 1280w,
      /howto/okta-lab-ldap/ldap-groups-imported_hu_a59e48f3a5720486.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-groups-imported.png"
    src="/howto/okta-lab-ldap/ldap-groups-imported.png">


  
</figure>
</li>
</ol>
</li>
</ol>

<h3 class="relative group">Delegated Authentication
    <div id="delegated-authentication" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#delegated-authentication" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Enable Delegated Authentication and allow users to authenticate against your OpenLDAP directory.</p>
<ol>
<li>Navigate to <strong>Security → Delegated Authentication</strong>.</li>
<li>Enable <strong>LDAP Authentication</strong>.</li>
<li>Test with an existing user and their password</li>
</ol>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855892158199 {
    background: #FFFAE6;
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855892158199 .panel-icon {
    color: rgb(224,108,0);
  }
  html.dark #panel-1778753855892158199 {
    background: rgb(51,46,27);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855892158199 .panel-icon {
    color: rgb(251,200,40);
  }
</style>

<div id="panel-1778753855892158199" class="flex px-4 py-3 rounded-md shadow panel-warning ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>
  <div class="panel-text">Delegated Authentication and Sync Password are mutually exclusive. If you enable Delegated Authentication, you must disable Sync Password. See <a href="https://help.okta.com/oie/en-us/content/topics/security/security_authentication.htm"  target="_blank" rel="noreferrer">Enable delegated authentication for LDAP</a>
  </div>
</div>

<h3 class="relative group">Attribute Mapping and Custom Schema
    <div id="attribute-mapping-and-custom-schema" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#attribute-mapping-and-custom-schema" aria-label="Anchor">#</a>
    </span>
    
</h3>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855892391237 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855892391237 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855892391237 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855892391237 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855892391237" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>In the following chapters, we&rsquo;ll import various LDIF files. You have various options for doing this:</p>
<ol>
<li>
<p><strong>Docker desktop</strong> - you can open the <code>lab-ldap-openldap-1</code> container terminal and run the commands directly:</p>
<ol>
<li>Open Docker Desktop</li>
<li>Select the <code>lab-ldap-openldap-1</code> container</li>
<li>Click on <strong>Exec</strong></li>
<li>Run the commands in the container terminal</li>
</ol>









<figure>
    <img class="my-0 rounded-md" loading="lazy" alt="Exec command in Docker Desktop" src="exec-dockerdesktop.gif">


  
</figure>
</li>
<li>
<p><strong>Docker Compose CLI</strong> - directly from your host OS, you can run the following command to open a bash terminal in the container, and then run the <code>ldapmodify</code> commands inside it.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose <span class="nb">exec</span> openldap bash  </span></span></code></pre></div></div>
<p>Alternatively, you can run the commands directly from your host OS, by using</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose <span class="nb">exec</span> openldap ldapmodify ...  </span></span></code></pre></div></div>
</li>
<li>
<p><strong>Apache Directory Studio</strong> - you can import the LDIF files graphically:</p>
<ol>
<li>Open Apache Directory Studio and connect to your LDAP server</li>
<li>Right-click on your connection and select <strong>Import → LDIF Import&hellip;</strong></li>
<li>Select the LDIF file you want to import</li>
<li>Follow the prompts to complete the import process</li>
</ol>
</li>
</ol>

  </div>
</div>
<p>You can map attributes between Okta and LDAP, including custom ones.</p>
<ol>
<li>
<p>Extend the Schema
You can load the ready-to-use file <code>5.schema.ldif</code>, and run:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ldapmodify -Y EXTERNAL -H ldapi:/// -f ./src/ldifs/5.schema.ldif</span></span></code></pre></div></div>
<p>The file contains the following:</p>
<ol>
<li>
<p><strong>Extend the OpenLDAP Schema</strong> - To add a custom attribute (e.g., <code>species</code>), you must first define it in your OpenLDAP schema.</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-ldif" data-lang="ldif"># Add the new attribute type &#39;species&#39;
dn: cn=LucasfilmPerson,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: LucasfilmPerson
olcAttributeTypes: ( 1.3.6.1.4.1.55555.1.2
NAME &#39;species&#39;
DESC &#39;The species of the person&#39;
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
olcObjectClasses: ( 1.3.6.1.4.1.55555.2.2
NAME &#39;LucasfilmPerson&#39;
DESC &#39;Lucasfilm person attributes&#39;
SUP top
AUXILIARY
MAY ( species ) )</code></pre></div>
</li>
<li>
<p><strong>(Optional)</strong> Load the constraint module and create a constraint rule in order to accept only some type of data in the new attribute (in this case, for example, we will accept only one from Wookiee, Human, Droid, Gungan, Ewok, Yoda, or Clone):</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-ldif" data-lang="ldif"># Load the constraint module
dn: cn=module,cn=config
changetype: add
cn: module
objectClass: olcModuleList
olcModuleLoad: constraint.so

# Load the constraint overlay on your database 
dn: olcOverlay=constraint,olcDatabase={2}mdb,cn=config
changetype: add
objectClass: olcOverlayConfig
objectClass: olcConstraintConfig
olcOverlay: constraint
olcConstraintAttribute: species regex &#34;^(Wookiee|Human|Droid|Gungan|Ewok|Yoda|Clone)$&#34;</code></pre></div>
</li>
</ol>
</li>
<li>
<p>Update the users</p>
<p>Also here, you can load the ready-to-use file <code>6.updateusers.ldif</code>, and run:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ldapmodify -Y EXTERNAL -H ldapi:/// -f ./src/ldifs/6.updateusers.ldif</span></span></code></pre></div></div>
<p>The file contains a series of lines like:</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-ldif" data-lang="ldif">dn: uid=luke.skywalker@galaxy.local,ou=People,dc=galaxy,dc=universe
changetype: modify
add: objectClass
objectClass: LucasfilmPerson
-
add: species
species: Human

[...]</code></pre></div>
</li>
<li>
<p>Map the Attribute in Okta</p>
<ol>
<li>Go to <strong>Directory → Directory Integrations</strong> and select your LDAP directory</li>
<li>In the <strong>Provisioning → Integration</strong> tab, add <code>LucasfilmPerson</code> as an <strong>Auxiliary Object Class</strong>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Custom Attribute Mapping"
    srcset="
      /howto/okta-lab-ldap/ldap-mapping1_hu_f80fbf6f52cf1e78.webp  330w,
      /howto/okta-lab-ldap/ldap-mapping1_hu_6e816e80032e77e9.webp  660w,
      /howto/okta-lab-ldap/ldap-mapping1_hu_b5302e3dba26bf79.webp  960w,
      /howto/okta-lab-ldap/ldap-mapping1_hu_db099e7318de420c.webp 1280w,
      /howto/okta-lab-ldap/ldap-mapping1_hu_6826050e97788d09.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-mapping1.png"
    src="/howto/okta-lab-ldap/ldap-mapping1.png">


  
</figure>
</li>
<li>Test the configuration with a user (i.e. <code>yoda@galaxy.local</code>) and save</li>
<li>Go to <strong>Directory → Profile Editor → Okta → User (default)</strong></li>
<li>Click <strong>Add Attribute</strong></li>
<li>Create a new attribute named <strong>Species</strong>
<ul>
<li><strong>Data type</strong>: <code>string</code></li>
<li><strong>Display name</strong>: <code>Species</code></li>
<li><strong>Variable name</strong>: <code>species</code></li>
<li><strong>Description</strong>:</li>
<li><strong>(optional) Enum</strong>: insert the following list: <code>Wookiee, Human, Droid, Gungan, Ewok, Yoda, Clone</code>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Custom Attribute Mapping"
    srcset="
      /howto/okta-lab-ldap/ldap-mapping2_hu_c97f1d4fe687396b.webp  330w,
      /howto/okta-lab-ldap/ldap-mapping2_hu_12b2c03d0fb10a78.webp  660w,
      /howto/okta-lab-ldap/ldap-mapping2_hu_46b9cc9c32209252.webp  960w,
      /howto/okta-lab-ldap/ldap-mapping2_hu_1c24e6a4ee57c65c.webp 1280w,
      /howto/okta-lab-ldap/ldap-mapping2_hu_7ec9a9b586d61c6.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-mapping2.png"
    src="/howto/okta-lab-ldap/ldap-mapping2.png">


  
</figure>
</li>
</ul>
</li>
<li>Go back to <strong>Directory → Profile Editor → Directories</strong></li>
<li>Find your LDAP directory profile and click <strong>Add Attributes</strong></li>
<li>Search and select <code>species</code>, then <strong>Save</strong>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Custom Attribute Mapping"
    srcset="
      /howto/okta-lab-ldap/ldap-mapping3_hu_76aba71956d143b0.webp  330w,
      /howto/okta-lab-ldap/ldap-mapping3_hu_1e995e428d54a815.webp  660w,
      /howto/okta-lab-ldap/ldap-mapping3_hu_284552d2eb6a1266.webp  960w,
      /howto/okta-lab-ldap/ldap-mapping3_hu_2e8f5c1c4b57d977.webp 1280w,
      /howto/okta-lab-ldap/ldap-mapping3_hu_ec35d36fdc69afb9.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-mapping3.png"
    src="/howto/okta-lab-ldap/ldap-mapping3.png">


  
</figure>
</li>
<li>Click on <strong>Mappings</strong>.</li>
<li>Map the LDAP &lsquo;species&rsquo; attribute with Okta&rsquo;s attribute in both directions (LDAP to Okta, and Okta to LDAP) and then <strong>Save</strong>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Custom Attribute Mapping"
    srcset="
      /howto/okta-lab-ldap/ldap-mapping4_hu_cbfb6e2b49d90379.webp  330w,
      /howto/okta-lab-ldap/ldap-mapping4_hu_60636c71ca6fa7fb.webp  660w,
      /howto/okta-lab-ldap/ldap-mapping4_hu_2fb23ada3a640d70.webp  960w,
      /howto/okta-lab-ldap/ldap-mapping4_hu_2a29f3d74624b3af.webp 1280w,
      /howto/okta-lab-ldap/ldap-mapping4_hu_9ce4eee6106f7cac.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-mapping4.png"
    src="/howto/okta-lab-ldap/ldap-mapping4.png">


  
</figure>
</li>
<li>Go back to <strong>Directory → Directory Integrations</strong>, select your LDAP directory, and run an LDAP import. Verify that the field is populated for the users









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Custom Attribute Mapping"
    srcset="
      /howto/okta-lab-ldap/ldap-mapping5_hu_b8d3a0c50fbd6778.webp  330w,
      /howto/okta-lab-ldap/ldap-mapping5_hu_cf7f273c81ead5ca.webp  660w,
      /howto/okta-lab-ldap/ldap-mapping5_hu_d3529c8299e3c83b.webp  960w,
      /howto/okta-lab-ldap/ldap-mapping5_hu_5db88a564bdfb3aa.webp 1280w,
      /howto/okta-lab-ldap/ldap-mapping5_hu_d1fc92477327f017.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-mapping5.png"
    src="/howto/okta-lab-ldap/ldap-mapping5.png">


  
</figure>
</li>
</ol>
</li>
</ol>

<h3 class="relative group">Provisioning from Okta to LDAP
    <div id="provisioning-from-okta-to-ldap" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#provisioning-from-okta-to-ldap" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>You can also create and manage users in LDAP directly from Okta.</p>
<ol>
<li>
<p>Disable the Profile Sourcing</p>
<ol>
<li>Go to the <strong>Provisioning → To Okta</strong> tab for your LDAP integration.</li>
<li>Remove the flag from &ldquo;<strong>Allow LDAP to source Okta users</strong>&rdquo;









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="placeholder"
    srcset="
      /howto/okta-lab-ldap/ldap-provisioning1_hu_9fec05286e1618a5.webp  330w,
      /howto/okta-lab-ldap/ldap-provisioning1_hu_978bc8572b827342.webp  660w,
      /howto/okta-lab-ldap/ldap-provisioning1_hu_63355575289f17c9.webp  960w,
      /howto/okta-lab-ldap/ldap-provisioning1_hu_2514152cd3af38b2.webp 1280w,
      /howto/okta-lab-ldap/ldap-provisioning1_hu_963d38cc86a37851.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-provisioning1.png"
    src="/howto/okta-lab-ldap/ldap-provisioning1.png">


  
</figure>
</li>
</ol>
</li>
<li>
<p>Enable Provisioning</p>
<ol>
<li>Go to the <strong>Provisioning → To App</strong> tab for your LDAP integration.</li>
<li>Enable the features you need, such as <strong>Create Users</strong>, <strong>Update User Attributes</strong>, and <strong>Deactivate Users</strong>.</li>
<li>For &ldquo;<strong>Create Users</strong>&rdquo; use the following parameters:
<ul>
<li><strong>Activation email recipient</strong>: your email address (or an galaxy.local alias)</li>
<li><strong>RDN attribute name</strong>: <code>uid</code> ⚠️ <strong>this is very important</strong></li>
</ul>
</li>
<li>Change the mapping for the <strong>Distinguished Name</strong> <code>dn</code> attribute, by using the following expression:
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&#34;uid=&#34; + user.login + &#34;,ou=People,dc=galaxy,dc=universe&#34;</span></span></code></pre></div></div>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="placeholder"
    srcset="
      /howto/okta-lab-ldap/ldap-provisioning2_hu_1245d2277f5828db.webp  330w,
      /howto/okta-lab-ldap/ldap-provisioning2_hu_91df2ffe20b2339b.webp  660w,
      /howto/okta-lab-ldap/ldap-provisioning2_hu_9a69e77194f8a715.webp  960w,
      /howto/okta-lab-ldap/ldap-provisioning2_hu_c76840ed11ebbac7.webp 1280w,
      /howto/okta-lab-ldap/ldap-provisioning2_hu_bdbc6bccf0f7e35.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-provisioning2.png"
    src="/howto/okta-lab-ldap/ldap-provisioning2.png">


  
</figure>
</li>
<li>Change the mapping for the <strong>Common Name</strong> (<code>cn</code>) attribute, and map it to the Okta attribute &ldquo;<strong>Display Name</strong>&rdquo;









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="placeholder"
    srcset="
      /howto/okta-lab-ldap/ldap-provisioning3_hu_54942e90a238ba02.webp  330w,
      /howto/okta-lab-ldap/ldap-provisioning3_hu_ded5f87f281338ca.webp  660w,
      /howto/okta-lab-ldap/ldap-provisioning3_hu_6924cf033254dd7f.webp  960w,
      /howto/okta-lab-ldap/ldap-provisioning3_hu_786c98ff1c38e3d4.webp 1280w,
      /howto/okta-lab-ldap/ldap-provisioning3_hu_6f0ce3d77779fbd8.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-provisioning3.png"
    src="/howto/okta-lab-ldap/ldap-provisioning3.png">


  
</figure>
</li>
<li>Optionally map the other optional attributes, such as
<ul>
<li><code>managerDn</code></li>
<li><code>postalAddress</code></li>
</ul>
</li>
</ol>
</li>
<li>
<p>Create two new users:</p>
<ol>
<li><strong>Jar Jar</strong>
<ul>
<li>First name: <code>Jar Jar</code></li>
<li>Last name: <code>Binks</code></li>
<li>Username: <code>jarjar@galaxy.local</code></li>
<li>Primary email: <code>jarjar.binks@galaxy.local</code></li>
<li>Title: <code>Junior Representative</code></li>
<li>DepartmentNumber: <code>GUNGAN-GRAND-ARMY</code></li>
<li>Organization: <code>Resistance</code></li>
<li>Postal Address: <code>Otoh Gunga, Naboo</code></li>
<li>DisplayName: <code>Jar Jar</code></li>
<li>employeeNumber: <code>10030</code></li>
<li>Species: <code>Gungan</code></li>
</ul>
</li>
<li><strong>Boba Fett</strong>
<ul>
<li>First name: <code>Boba</code></li>
<li>Last name: <code>Fett</code></li>
<li>Username: <code>boba.fett@galaxy.local</code></li>
<li>Primary email: <code>boba.fett@galaxy.local</code></li>
<li>Title: <code>Bounty Hunter</code></li>
<li>DepartmentNumber: <code>BOUNTY-HUNTERS-GUILD</code></li>
<li>Organization: <code>Empire</code></li>
<li>Postal Address: <code>Slave I, Outer Rim</code></li>
<li>Display Name: <code>Boba Fett</code></li>
<li>employeeNumber: <code>8084</code></li>
<li>Species: <code>Clone</code></li>
</ul>
</li>
</ol>
</li>
<li>
<p>Create an LDAP Provisioning group</p>
<ol>
<li>In the <strong>Directory → People → Groups</strong> section, click on &ldquo;<strong>Add Group</strong>&rdquo;</li>
<li>name it &ldquo;<strong><code>LDAP Provisioning</code></strong>&rdquo;</li>
<li>Once created, click on it and go to the <strong>Directories</strong> tab</li>
<li>Click on <strong>Manage directories</strong></li>
<li>Add the LDAP Directory as a member and click <strong>Next</strong>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="LDAP Provisioning Group"
    srcset="
      /howto/okta-lab-ldap/ldap-provisioning4_hu_6b7495363e6846cf.webp  330w,
      /howto/okta-lab-ldap/ldap-provisioning4_hu_3588e3da02ab3e7.webp  660w,
      /howto/okta-lab-ldap/ldap-provisioning4_hu_a3343f8718d87ef0.webp  960w,
      /howto/okta-lab-ldap/ldap-provisioning4_hu_82f72275bf4ca7b5.webp 1280w,
      /howto/okta-lab-ldap/ldap-provisioning4_hu_d0df1c9c12c4aacb.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-provisioning4.png"
    src="/howto/okta-lab-ldap/ldap-provisioning4.png">


  
</figure>
</li>
<li>Insert <code>ou=People,dc=galaxy,dc=universe</code> as <strong>Provisioning Destination DN</strong></li>
<li>Click &ldquo;<strong>Confirm Changes</strong>&rdquo;</li>
</ol>
</li>
<li>
<p>Add the two users (<code>Jar Jar</code> and <code>Boba Fett</code>) to the &ldquo;<code>LDAP Provisioning</code>&rdquo; Group</p>
</li>
<li>
<p>You will then see the users created in the LDAP directory</p>
</li>
</ol>









<figure>
    <img class="my-0 rounded-md" loading="lazy" alt="Placeholder - New Users in LDAP" src="new-users-ldap.png">


  
</figure>

<h3 class="relative group">Password reset
    <div id="password-reset" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#password-reset" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>You can also enable Self-Service Password Reset (SSPR) for LDAP Users.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855893115911 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855893115911 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855893115911 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855893115911 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855893115911" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text">This feature works with any LDAP distribution that correctly sets the <code>pwdReset</code> attribute to <code>TRUE</code> when a password is expired (for example, OpenLDAP and IBM)
  </div>
</div>
<ol>
<li>In the Admin Console, go to <strong>Security → Delegated Authentication</strong>.</li>
<li>Click the <strong>LDAP</strong> tab.</li>
<li>In <strong>Delegated Authentication</strong>, click <strong>Edit</strong>.</li>
<li>Verify that <strong>Enable delegated authentication to LDAP</strong> is enabled.</li>
<li>Go to <strong>Security → Authenticators</strong>, click on the <strong>Actions</strong> dropdown near <strong>Password</strong>, and select <strong>Edit</strong></li>
<li>Select &ldquo;<strong>LDAP Policy</strong>&rdquo; from the list on the left</li>
<li>Scroll to the bottom and click <strong>Add Rule</strong>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="placeholder"
    srcset="
      /howto/okta-lab-ldap/ldap-SSPR1_hu_30332f658bb08f22.webp  330w,
      /howto/okta-lab-ldap/ldap-SSPR1_hu_1b78cf943260487d.webp  660w,
      /howto/okta-lab-ldap/ldap-SSPR1_hu_68b1ef803a5a4647.webp  960w,
      /howto/okta-lab-ldap/ldap-SSPR1_hu_708d16d2275280ce.webp 1280w,
      /howto/okta-lab-ldap/ldap-SSPR1_hu_ac0ce9d39273c464.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-SSPR1.png"
    src="/howto/okta-lab-ldap/ldap-SSPR1.png">


  
</figure>
</li>
<li>Create a new rule and enable <strong>Users can perform self-service options</strong>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="placeholder"
    srcset="
      /howto/okta-lab-ldap/ldap-SSPR2_hu_6ddddd2b1a6d30ba.webp  330w,
      /howto/okta-lab-ldap/ldap-SSPR2_hu_2787b0c277d8531d.webp  660w,
      /howto/okta-lab-ldap/ldap-SSPR2_hu_e1436ad177e3fdbd.webp  960w,
      /howto/okta-lab-ldap/ldap-SSPR2_hu_8222b5b7b95320a6.webp 1280w,
      /howto/okta-lab-ldap/ldap-SSPR2_hu_d2104c63ea53fe4d.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-SSPR2.png"
    src="/howto/okta-lab-ldap/ldap-SSPR2.png">


  
</figure>
</li>
</ol>

<h4 class="relative group">Password validation
    <div id="password-validation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#password-validation" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Use the <code>pwdPolicy</code> object class and <code>pwdPolicySubentry</code> attribute to implement OpenLDAP-specific password policies. You can refer to the <a href="https://www.openldap.org/doc/admin24/overlays.html#Password%20Policies"  target="_blank" rel="noreferrer">OpenLDAP manual</a> on how to configure password policies inside the LDAP server.</p>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855893347349 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855893347349 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855893347349 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855893347349 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855893347349" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><p><strong>Best Practice</strong>: It&rsquo;s always preferable to have the same password policy in Okta and the LDAP server so that Okta can prevent passwords not associated with the policy before contacting the LDAP server.</p>
<p>Regarding unlock, it is preferable to set the number of attempts before lock to a smaller number in Okta than in the LDAP server. With this method, Okta can lock the user before it is locked in the LDAP server.</p>

  </div>
</div>

<h3 class="relative group">OU Change
    <div id="ou-change" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ou-change" aria-label="Anchor">#</a>
    </span>
    
</h3>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855893535648 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855893535648 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855893535648 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855893535648 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855893535648" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><p><strong>OU moves for LDAP-provisioned users</strong></p>
<p>Recently, Okta added a feature that permits changing the OU for LDAP-provisioned users. Before, this feature was available only for Active Directory and not LDAP.</p>
<p>When an admin configures Okta to LDAP provisioning settings, they can now move users to a different Organizational Unit (OU) by changing their group assignments. See <a href="https://help.okta.com/en-us/content/topics/directory/ldap-provisioning.htm"  target="_blank" rel="noreferrer">Configure Okta to LDAP provisioning settings</a>. (From <a href="https://help.okta.com/en-us/content/topics/releasenotes/2025-07.htm"  target="_blank" rel="noreferrer">Okta&rsquo;s release notes - Version: 2025.07.0</a>)</p>

  </div>
</div>
<p>Load the file <code>8.new-ous.ldif</code> by running:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ldapadd -Y EXTERNAL -H ldapi:/// -f ./src/ldifs/8.new-ous.ldif</span></span></code></pre></div></div>
<p>The file contains instructions to create new OUs:</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-ldif" data-lang="ldif"># --- CREATE NEW ORGANIZATIONAL UNITS ---

dn: ou=Jedi,ou=People,dc=galaxy,dc=universe
objectClass: organizationalUnit
ou: Jedi

dn: ou=Resistance,ou=People,dc=galaxy,dc=universe
objectClass: organizationalUnit
ou: Resistance

dn: ou=Empire,ou=People,dc=galaxy,dc=universe
objectClass: organizationalUnit
ou: Empire

dn: ou=Droid,ou=People,dc=galaxy,dc=universe
objectClass: organizationalUnit
ou: Droid</code></pre></div>
<p>We can now change the OU using an LDIF, for example:</p>
<div class="highlight-wrapper"><pre tabindex="0"><code class="language-ldif" data-lang="ldif"># --- MOVE USERS TO NEW OUs ---

dn: uid=luke.skywalker@galaxy.local,ou=People,dc=galaxy,dc=universe
changetype: modrdn
newrdn: uid=luke.skywalker@galaxy.local
deleteoldrdn: 1
newsuperior: ou=Jedi,ou=People,dc=galaxy,dc=universe</code></pre></div>
<p>But, since we want to demonstrate Okta, let&rsquo;s see how to manage the OU changes using Okta:</p>
<ol>
<li>
<p>Go to <strong>Settings → Features</strong> and enable the Early Access feature <strong>Support user moves across OUs in LDAP</strong>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="placeholder"
    srcset="
      /howto/okta-lab-ldap/ldap-ouchange1_hu_db9efdcd4b9a9994.webp  330w,
      /howto/okta-lab-ldap/ldap-ouchange1_hu_2378c0ee68df4d27.webp  660w,
      /howto/okta-lab-ldap/ldap-ouchange1_hu_5be85da3a5500091.webp  960w,
      /howto/okta-lab-ldap/ldap-ouchange1_hu_dc6e0c18f71bd03d.webp 1280w,
      /howto/okta-lab-ldap/ldap-ouchange1_hu_2c9bc2f052116851.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-ouchange1.png"
    src="/howto/okta-lab-ldap/ldap-ouchange1.png">


  
</figure>
</p>
</li>
<li>
<p>Go back to <strong>Directory → Directory Integrations</strong>, click on <strong>LDAP</strong> and <strong>Provisioning → To App</strong></p>
</li>
<li>
<p>Enable both <strong>Update User Attributes</strong> and <strong>Update OU when the group that provisions a user to LDAP changes</strong>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="placeholder"
    srcset="
      /howto/okta-lab-ldap/ldap-ouchange2_hu_6830b7fe7154e40a.webp  330w,
      /howto/okta-lab-ldap/ldap-ouchange2_hu_e2b2a3b91b89a91d.webp  660w,
      /howto/okta-lab-ldap/ldap-ouchange2_hu_12c566ae2e34e9d0.webp  960w,
      /howto/okta-lab-ldap/ldap-ouchange2_hu_42ab82c51da36177.webp 1280w,
      /howto/okta-lab-ldap/ldap-ouchange2_hu_6d61267ccecfd489.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-ouchange2.png"
    src="/howto/okta-lab-ldap/ldap-ouchange2.png">


  
</figure>
</p>
</li>
<li>
<p>Create a new group named &ldquo;<strong>LDAP Provisioning - Resistance</strong>&rdquo;</p>
</li>
</ol>
<p>Follow the same instructions as <strong>Provisioning from Okta to LDAP</strong></p>
<p>But change the <strong>Provisioning Destination DN</strong> value to <code>ou=Resistance,ou=People,dc=galaxy,dc=universe</code></p>
<ol start="5">
<li>Pick the user <strong>Jar Jar Binks</strong>, remove him from the group &ldquo;<strong>LDAP Provisioning</strong>&rdquo; and add him to the group &ldquo;<strong>LDAP Provisioning - Resistance</strong>&rdquo;</li>
<li>You will find Jar Jar moved to the Resistance OU









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="placeholder"
    srcset="
      /howto/okta-lab-ldap/ldap-ouchange3_hu_97b28c598ab2e964.webp  330w,
      /howto/okta-lab-ldap/ldap-ouchange3_hu_dbd1033b5985438f.webp  660w,
      /howto/okta-lab-ldap/ldap-ouchange3_hu_b1edfbe50e2c4967.webp  960w,
      /howto/okta-lab-ldap/ldap-ouchange3_hu_2f115444b43587ab.webp 1280w,
      /howto/okta-lab-ldap/ldap-ouchange3_hu_503009bf6763c103.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/ldap-ouchange3.png"
    src="/howto/okta-lab-ldap/ldap-ouchange3.png">


  
</figure>
</li>
</ol>
<p>You can then continue by generating other groups, such as &ldquo;<strong>LDAP Provisioning - Empire</strong>&rdquo; and so on</p>
<p>As an alternative, we can do the same operation with an LDIF file:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ldapadd -Y EXTERNAL -H ldapi:/// -f ./src/ldifs/9.change-ou.ldif</span></span></code></pre></div></div>

<h3 class="relative group">Group Push
    <div id="group-push" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#group-push" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The <strong>Group Push</strong> feature, which synchronizes group memberships from Okta to an application, is not currently supported for OpenLDAP directories. This functionality is on Okta&rsquo;s product roadmap.</p>
<p>This article will be updated as soon as the feature will be available.</p>
<hr>

<h2 class="relative group">Manual Installation
    <div id="manual-installation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#manual-installation" aria-label="Anchor">#</a>
    </span>
    
</h2>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855893764086 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855893764086 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855893764086 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855893764086 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855893764086" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text">If you&rsquo;re using the Docker Compose version of the LDAP server+agent stack, you can skip to <a href="/#appendix-a-documentation" >Appendix A</a>
  </div>
</div>
<p>This section details the manual process of setting up an OpenLDAP server from scratch on a clean Ubuntu 24.04 installation, and assumes that the operating system is already installed (e.g., deploying an EC2 VM on AWS). It&rsquo;s an alternative if you cannot use Docker, or if you want a system that it&rsquo;s then production-ready.</p>
<p>For a Demo/PoC, an EC2 <code>t3.micro</code> instance (included in the free tier) is enough.</p>

<h3 class="relative group">OpenLDAP Server Installation and Configuration
    <div id="openldap-server-installation-and-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#openldap-server-installation-and-configuration" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>

<h4 class="relative group">System Preparation
    <div id="system-preparation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#system-preparation" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Update the system packages to latest version:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt update <span class="o">&amp;&amp;</span> sudo apt upgrade</span></span></code></pre></div></div>
</li>
<li>

<h4 class="relative group">OpenLDAP Installation
    <div id="openldap-installation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#openldap-installation" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Install the OpenLDAP server (<code>slapd</code>) and command-line utilities (<code>ldap-utils</code>):</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt update <span class="o">&amp;&amp;</span> sudo apt install -y slapd ldap-utils </span></span></code></pre></div></div>
</li>
<li>

<h4 class="relative group">Core OpenLDAP Configuration
    <div id="core-openldap-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#core-openldap-configuration" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>The <code>slapd</code> package includes an interactive configuration wizard, which is critical for defining the Directory&rsquo;s structure. Even if you can change these settings later, it is better and quicker to provide the right information during this step.</p>
<p>Execute the configuration tool:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo dpkg-reconfigure slapd</span></span></code></pre></div></div>

<h5 class="relative group">Follow the Prompts
    <div id="follow-the-prompts" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#follow-the-prompts" aria-label="Anchor">#</a>
    </span>
    
</h5>
<p>Answer the questions as follows:</p>
<ul>
<li><strong>Omit OpenLDAP server configuration?</strong> → No.</li>
<li><strong>DNS domain name?</strong> → <code>galaxy.universe</code>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855893930944 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855893930944 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855893930944 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855893930944 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855893930944" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text">This is the most important setting. It automatically generates the directory&rsquo;s Base Distinguished Name (DN). For <code>galaxy.universe</code>, the wizard creates <code>dc=galaxy,dc=universe</code>.
  </div>
</div></li>
<li><strong>Organization name?</strong> → The Galaxy</li>
<li><strong>Administrator password?</strong> → Enter a password (e.g., <code>adminpassword</code>).</li>
<li><strong>Confirm password:</strong> → Re-enter the password.</li>
<li><strong>Database backend?</strong> → MDB</li>
<li><strong>Do you want the database to be removed when slapd is purged?</strong> → No</li>
<li><strong>Move old database?</strong> → Yes.</li>
</ul>
</li>
<li>

<h4 class="relative group">Verification
    <div id="verification" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#verification" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li>

<h5 class="relative group">Check Service Status
    <div id="check-service-status" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#check-service-status" aria-label="Anchor">#</a>
    </span>
    
</h5>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl status slapd</span></span></code></pre></div></div>
<p>The service should be <code>active (running)</code>.</p>
</li>
<li>

<h5 class="relative group">Inspect the Directory
    <div id="inspect-the-directory" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#inspect-the-directory" aria-label="Anchor">#</a>
    </span>
    
</h5>
<p>Use <code>slapcat</code>, which will show the entire content of the LDAP database:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo slapcat</span></span></code></pre></div></div>
<p>You should see the base structure for <code>dc=galaxy,dc=universe</code>, and the admin user (<code>cn=admin,dc=galaxy,dc=universe</code>).</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="OpenLDAP systemctl status output"
    srcset="
      /howto/okta-lab-ldap/openldap-systemctl-status_hu_d5baed735eabe531.webp  330w,
      /howto/okta-lab-ldap/openldap-systemctl-status_hu_743de8d738d57467.webp  660w,
      /howto/okta-lab-ldap/openldap-systemctl-status_hu_c2d75b3e95d7e6fe.webp  960w,
      /howto/okta-lab-ldap/openldap-systemctl-status_hu_8bbc2e6a6f7db560.webp 1280w,
      /howto/okta-lab-ldap/openldap-systemctl-status_hu_5f8873e539200060.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-ldap/openldap-systemctl-status.png"
    src="/howto/okta-lab-ldap/openldap-systemctl-status.png">


  
</figure>
</li>
</ul>
</li>
<li>

<h4 class="relative group">cn=config
    <div id="cnconfig" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#cnconfig" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>The next step is to update the password of the <code>cn=config</code> user. Remember to change line 5 and choose a strong password:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo ldapmodify -Y EXTERNAL -H ldapi:/// <span class="s">&lt;&lt;EOF
</span></span></span><span class="line"><span class="cl"><span class="s">dn: olcDatabase={0}config,cn=config
</span></span></span><span class="line"><span class="cl"><span class="s">changetype: modify
</span></span></span><span class="line"><span class="cl"><span class="s">replace: olcRootPW
</span></span></span><span class="line"><span class="cl"><span class="s">olcRootPW: $(slappasswd -s &#34;configpassword&#34;)
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span></span></span></code></pre></div></div>
</li>
<li>

<h4 class="relative group">olcAccess
    <div id="olcaccess" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#olcaccess" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Now we&rsquo;ll apply some changes to the default config and security:</p>
<ol>
<li>
<p>in OpenLDAP configuration, the DN <code>gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth</code> represents the local root user (typically UID/GID 0) authenticating via a secure Unix socket for administrative tasks, while <code>cn=config</code> and <code>cn=admin,dc=galaxy,dc=universe</code> are built-in or custom admin DNs with full privileges over the config and MDB databases, respectively. The changes replace existing ACLs to strictly grant these users &ldquo;manage&rdquo; access (full read/write/control) to all attributes, denying everything to others for enhanced security, ensuring only trusted admins can modify server settings or data.</p>
</li>
<li>
<p>Creates indexes on the attributes &ldquo;mail&rdquo;, &ldquo;surname&rdquo;, and &ldquo;givenname&rdquo;. This optimizes searches involving email addresses or names, reducing lookup times in large directories.</p>
</li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo ldapmodify -Y EXTERNAL -H ldapi:/// <span class="s">&lt;&lt;EOF
</span></span></span><span class="line"><span class="cl"><span class="s">dn: olcDatabase={0}config,cn=config
</span></span></span><span class="line"><span class="cl"><span class="s">changetype: modify
</span></span></span><span class="line"><span class="cl"><span class="s">replace: olcAccess
</span></span></span><span class="line"><span class="cl"><span class="s">olcAccess: {0}to * 
</span></span></span><span class="line"><span class="cl"><span class="s">by dn.base=&#34;gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth&#34; manage
</span></span></span><span class="line"><span class="cl"><span class="s">by dn.base=&#34;cn=config&#34; manage
</span></span></span><span class="line"><span class="cl"><span class="s">by * none
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">dn: olcDatabase={1}mdb,cn=config
</span></span></span><span class="line"><span class="cl"><span class="s">changetype: modify
</span></span></span><span class="line"><span class="cl"><span class="s">replace: olcAccess
</span></span></span><span class="line"><span class="cl"><span class="s">olcAccess: {0}to * 
</span></span></span><span class="line"><span class="cl"><span class="s">by dn.base=&#34;gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth&#34; manage
</span></span></span><span class="line"><span class="cl"><span class="s">by dn.base=&#34;cn=admin,dc=galaxy,dc=universe&#34; manage
</span></span></span><span class="line"><span class="cl"><span class="s">by * none
</span></span></span><span class="line"><span class="cl"><span class="s">-
</span></span></span><span class="line"><span class="cl"><span class="s">add: olcDbIndex
</span></span></span><span class="line"><span class="cl"><span class="s">olcDbIndex: mail,surname,givenname eq,pres,sub
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span></span></span></code></pre></div></div>
</li>
<li>

<h4 class="relative group">Securing OpenLDAP with LDAPS
    <div id="securing-openldap-with-ldaps" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#securing-openldap-with-ldaps" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>To encrypt communication with your LDAP server, you can enable LDAPS (LDAP over TLS) on port 1636.</p>
<ol>
<li>
<p>Generate a Self-Signed Certificate:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo openssl req -x509 -nodes -days <span class="m">3650</span> -newkey rsa:2048 <span class="se">\
</span></span></span><span class="line"><span class="cl">-keyout /etc/ldap/sasl2/cert.key -out /etc/ldap/sasl2/cert.crt</span></span></code></pre></div></div>
</li>
<li>
<p>Configure <code>slapd</code> for LDAPS: Execute the following LDIF to load the certificate paths into the LDAP configuration.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo chown -R openldap:openldap /etc/ldap/sasl2
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sudo ldapmodify -Y EXTERNAL -H ldapi:/// <span class="s">&lt;&lt;EOF
</span></span></span><span class="line"><span class="cl"><span class="s">dn: cn=config
</span></span></span><span class="line"><span class="cl"><span class="s">changetype: modify
</span></span></span><span class="line"><span class="cl"><span class="s">replace: olcTLSCACertificateFile
</span></span></span><span class="line"><span class="cl"><span class="s">olcTLSCACertificateFile: /etc/ldap/sasl2/cert.crt
</span></span></span><span class="line"><span class="cl"><span class="s">-
</span></span></span><span class="line"><span class="cl"><span class="s">replace: olcTLSCertificateKeyFile
</span></span></span><span class="line"><span class="cl"><span class="s">olcTLSCertificateKeyFile: /etc/ldap/sasl2/cert.key
</span></span></span><span class="line"><span class="cl"><span class="s">-
</span></span></span><span class="line"><span class="cl"><span class="s">replace: olcTLSCertificateFile
</span></span></span><span class="line"><span class="cl"><span class="s">olcTLSCertificateFile: /etc/ldap/sasl2/cert.crt
</span></span></span><span class="line"><span class="cl"><span class="s">-
</span></span></span><span class="line"><span class="cl"><span class="s">replace: olcTLSVerifyClient
</span></span></span><span class="line"><span class="cl"><span class="s">olcTLSVerifyClient: never
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span></span></span></code></pre></div></div>
</li>
<li>
<p>Enable LDAPS Port: Edit the <code>slapd</code> service file to include the LDAPS URL.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo nano /etc/default/slapd</span></span></code></pre></div></div>
<p>Modify the SLAPD_SERVICES line:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">SLAPD_SERVICES</span><span class="o">=</span><span class="s2">&#34;ldap://:1389/ ldapi:/// ldaps://:1636/&#34;</span></span></span></code></pre></div></div>
<p>Restart the service:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl restart slapd</span></span></code></pre></div></div>
</li>
</ol>
</li>
</ol>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855894082223 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855894082223 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855894082223 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855894082223 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855894082223" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>Use the following command to open an SSH tunnel and get access to the LDAP or LDAPS port. This will avoid opening the ports on the AWS firewall:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh -f -N -L 1389:localhost:1389 ubuntu@myserverIP  
</span></span><span class="line"><span class="cl">ssh -f -N -L 1636:localhost:1636 ubuntu@myserverIP  </span></span></code></pre></div></div>
  </div>
</div>

<h3 class="relative group">Populating the OpenLDAP Directory
    <div id="populating-the-openldap-directory" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#populating-the-openldap-directory" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>With the server running, we will now load it with an organizational structure and user data.</p>
<p>Run the following command to download the LDIF files:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir src/ldifs
</span></span><span class="line"><span class="cl">curl -o ./src/ldifs/1.ou.ldif https://raw.githubusercontent.com/fabiograsso/okta-lab-ldap/refs/heads/main/src/ldifs-base/1.ou.ldif
</span></span><span class="line"><span class="cl">curl -o ./src/ldifs/2.users.ldif https://raw.githubusercontent.com/fabiograsso/okta-lab-ldap/refs/heads/main/src/ldifs-base/2.users.ldif
</span></span><span class="line"><span class="cl">curl -o ./src/ldifs/3.groups.ldif https://raw.githubusercontent.com/fabiograsso/okta-lab-ldap/refs/heads/main/src/ldifs-base/3.groups.ldif
</span></span><span class="line"><span class="cl">curl -o ./src/ldifs/4.photos.ldif https://raw.githubusercontent.com/fabiograsso/okta-lab-ldap/refs/heads/main/src/ldifs-base/4.photos.ldif
</span></span><span class="line"><span class="cl">curl -o ./src/ldifs/5.schema.ldif https://raw.githubusercontent.com/fabiograsso/okta-lab-ldap/refs/heads/main/src/ldifs/5.schema-nodocker.ldif
</span></span><span class="line"><span class="cl">curl -o ./src/ldifs/6.updateusers.ldif https://raw.githubusercontent.com/fabiograsso/okta-lab-ldap/refs/heads/main/src/ldifs/6.updateusers.ldif
</span></span><span class="line"><span class="cl">curl -o ./src/ldifs/7.addusers.ldif https://raw.githubusercontent.com/fabiograsso/okta-lab-ldap/refs/heads/main/src/ldifs/7.addusers.ldif
</span></span><span class="line"><span class="cl">curl -o ./src/ldifs/8.new-ous.ldif https://raw.githubusercontent.com/fabiograsso/okta-lab-ldap/refs/heads/main/src/ldifs/8.new-ous.ldif
</span></span><span class="line"><span class="cl">curl -o ./src/ldifs/9.change-ou.ldif https://raw.githubusercontent.com/fabiograsso/okta-lab-ldap/refs/heads/main/src/ldifs/9.change-ou.ldif</span></span></code></pre></div></div>

<h4 class="relative group">Loading the Data
    <div id="loading-the-data" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#loading-the-data" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Use the <code>ldapadd</code> command to import the LDIF files into the LDAP Database:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo ldapadd -Y EXTERNAL -H ldapi:/// -f ./src/ldifs/1.ou.ldif
</span></span><span class="line"><span class="cl">sudo ldapadd -Y EXTERNAL -H ldapi:/// -f ./src/ldifs/2.users.ldif
</span></span><span class="line"><span class="cl">sudo ldapadd -Y EXTERNAL -H ldapi:/// -f ./src/ldifs/3.groups.ldif
</span></span><span class="line"><span class="cl">sudo ldapadd -Y EXTERNAL -H ldapi:/// -f ./src/ldifs/4.photos.ldif</span></span></code></pre></div></div>

<h4 class="relative group">Verification
    <div id="verification-1" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#verification-1" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Use <code>ldapsearch</code> to query the directory and confirm that the data was loaded correctly:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Search for a specific user</span>
</span></span><span class="line"><span class="cl">sudo ldapsearch -Y EXTERNAL -H ldapi:/// -b <span class="s2">&#34;ou=People,dc=galaxy,dc=universe&#34;</span> <span class="s2">&#34;(uid=yoda)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Search for a specific group</span>
</span></span><span class="line"><span class="cl">sudo ldapsearch -Y EXTERNAL -H ldapi:/// -b <span class="s2">&#34;ou=Groups,dc=galaxy,dc=universe&#34;</span> <span class="s2">&#34;(cn=Jedi-Council)&#34;</span></span></span></code></pre></div></div>

<h4 class="relative group">Install ldap-ui Management Interface
    <div id="install-ldap-ui-management-interface" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#install-ldap-ui-management-interface" aria-label="Anchor">#</a>
    </span>
    
</h4>

<h5 class="relative group">1. Install Dependencies
    <div id="1-install-dependencies" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#1-install-dependencies" aria-label="Anchor">#</a>
    </span>
    
</h5>
<p>Install Python, <code>pip</code>, <code>venv</code>, and the necessary LDAP development headers and tools:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt-get install -y ldap-utils tox lcov valgrind libldap2-dev libsasl2-dev <span class="se">\
</span></span></span><span class="line"><span class="cl">                        python3-dev python3-pip python3-venv python3-ldap</span></span></code></pre></div></div>

<h5 class="relative group">2. Create an Application User
    <div id="2-create-an-application-user" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#2-create-an-application-user" aria-label="Anchor">#</a>
    </span>
    
</h5>
<p>It&rsquo;s best practice to run the web service under a dedicated, non-root user:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo useradd -r -s /bin/false ldap-ui</span></span></code></pre></div></div>

<h5 class="relative group">3. Install <code>ldap-ui</code>
    <div id="3-install-ldap-ui" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#3-install-ldap-ui" aria-label="Anchor">#</a>
    </span>
    
</h5>
<p>We will install the application in <code>/opt/ldap-ui</code> and create a Python virtual environment to isolate its dependencies:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mkdir /opt/ldap-ui
</span></span><span class="line"><span class="cl">sudo chown ldap-ui:ldap-ui /opt/ldap-ui
</span></span><span class="line"><span class="cl">sudo -u ldap-ui python3 -m venv /opt/ldap-ui/.venv3
</span></span><span class="line"><span class="cl">sudo -u ldap-ui /opt/ldap-ui/.venv3/bin/pip install ldap-ui</span></span></code></pre></div></div>

<h5 class="relative group">4. Create a systemd Service
    <div id="4-create-a-systemd-service" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#4-create-a-systemd-service" aria-label="Anchor">#</a>
    </span>
    
</h5>
<p>To ensure the service starts automatically and runs in the background, create a systemd service file:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo nano /etc/systemd/system/ldap-ui.service</span></span></code></pre></div></div>
<p>Paste the following content into the file. This service runs the process as the <code>ldap-ui</code> user:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">ldap-ui - Lightweight LDAP Web UI</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network.target auditd.service slapd.service</span>
</span></span><span class="line"><span class="cl"><span class="na">BindsTo</span><span class="o">=</span><span class="s">slapd.service</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Environment variables for ldap-ui</span>
</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">&#34;BASE_DN=dc=galaxy,dc=universe&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">&#34;LDAP_URL=ldap://127.0.0.1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">&#34;LOGIN_ATTR=uid&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">&#34;BIND_PATTERN=cn=%%s,dc=galaxy,dc=universe&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/opt/ldap-ui</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/opt/ldap-ui/.venv3/bin/ldap-ui</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">simple</span>
</span></span><span class="line"><span class="cl"><span class="na">User</span><span class="o">=</span><span class="s">ldap-ui</span>
</span></span><span class="line"><span class="cl"><span class="na">Group</span><span class="o">=</span><span class="s">ldap-ui</span>
</span></span><span class="line"><span class="cl"><span class="na">Restart</span><span class="o">=</span><span class="s">always</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Security Hardening</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectHome</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectSystem</span><span class="o">=</span><span class="s">strict</span>
</span></span><span class="line"><span class="cl"><span class="na">SystemCallFilter</span><span class="o">=</span><span class="s">@system-service</span>
</span></span><span class="line"><span class="cl"><span class="na">NoNewPrivileges</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span></span></span></code></pre></div></div>

<h5 class="relative group">5. Start and Enable the Service
    <div id="5-start-and-enable-the-service" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#5-start-and-enable-the-service" aria-label="Anchor">#</a>
    </span>
    
</h5>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl daemon-reload
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now ldap-ui.service
</span></span><span class="line"><span class="cl">sudo systemctl status ldap-ui.service</span></span></code></pre></div></div>
<p>The <code>ldap-ui</code> service is now running and listening on port 5000 (<code>http://&lt;your_server_ip&gt;:5000</code>).</p>

<h5 class="relative group">6. Access the ldap-ui interface
    <div id="6-access-the-ldap-ui-interface" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#6-access-the-ldap-ui-interface" aria-label="Anchor">#</a>
    </span>
    
</h5>
<p>Since there is no specific security, I warmly suggest:</p>
<ul>
<li>
<p>Publish it using a reverse proxy (i.e., nginx), or</p>
</li>
<li>
<p>Create an SSH tunnel and then open the web portal from your PC instead of exposing port 5000 of the server</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh -f -N -L 5000:localhost:5000 ubuntu@myopenldapserver</span></span></code></pre></div></div>
</li>
</ul>
<p>You can log in using &ldquo;admin&rdquo; as username and the <code>cn=admin</code> password.</p>
<p>More information about <code>ldap-ui</code> can be found in <a href="https://github.com/dnknth/ldap-ui"  target="_blank" rel="noreferrer"><span class="relative inline-block align-text-bottom icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg></span>  dnknth/ldap-ui Github repository</a>.</p>

<h3 class="relative group">Integrating with Okta via the LDAP Agent
    <div id="integrating-with-okta-via-the-ldap-agent" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#integrating-with-okta-via-the-ldap-agent" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Once the directory is up and running, the next step is to install and configure the Okta LDAP Agent.</p>
<p>In a real environment, the Agent is normally installed on a dedicated server, different from the LDAP server. In this case, since we are just creating a demo/test environment, we will install everything on the same server.</p>

<h4 class="relative group">Installing the Okta LDAP Agent
    <div id="installing-the-okta-ldap-agent" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#installing-the-okta-ldap-agent" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ol>
<li>Log in to your Okta Admin Console.</li>
<li>Navigate to <strong>Directory → Directory Integrations</strong>.</li>
<li>Click <strong>Add Directory → Add LDAP Directory</strong>.</li>
<li>Follow the setup steps. On the &ldquo;Install Agent&rdquo; step, right-click the Download Agent button and copy the link.</li>
<li>Download the <code>.deb</code> file on your LDAP server (e.g., using <code>wget https://....</code>).</li>
<li>Install the Agent using dpkg:</li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo dpkg -i OktaLDAPAgent-*.deb</span></span></code></pre></div></div>

<h4 class="relative group">Add the TLS Certificate
    <div id="add-the-tls-certificate" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#add-the-tls-certificate" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Since the certificate is self-signed, we need to import it into the Okta Agent&rsquo;s Java environment in order to make it trusted:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">/opt/Okta/OktaLDAPAgent/jre/bin/keytool -importcert <span class="se">\
</span></span></span><span class="line"><span class="cl">-alias openldap <span class="se">\
</span></span></span><span class="line"><span class="cl">-file /etc/ldap/sasl2/cert.crt <span class="se">\
</span></span></span><span class="line"><span class="cl">-cacerts <span class="se">\
</span></span></span><span class="line"><span class="cl">-storepass changeit <span class="se">\
</span></span></span><span class="line"><span class="cl">-noprompt</span></span></code></pre></div></div>

<h4 class="relative group">Configuring the Agent
    <div id="configuring-the-agent" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuring-the-agent" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>The agent requires an interactive setup to register with your Okta org.</p>
<ol>
<li>
<p>Run the Configuration Script:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo /opt/Okta/OktaLDAPAgent/scripts/configure_agent.sh</span></span></code></pre></div></div>
</li>
<li>
<p>Follow the Prompts. The script will guide you through the setup process:</p>
<ol>
<li><strong>Okta Subdomain</strong>: Enter your Okta org URL (e.g., <code>https://myorg.okta.com</code>).</li>
<li><strong>Proxy Server</strong>: Press Enter to skip.</li>
<li><strong>LDAP Server</strong>: <code>ldaps://localhost:1636</code></li>
<li><strong>LDAP Admin DN for Agent</strong>: For testing, you can use the main admin account: <code>cn=admin,dc=galaxy,dc=universe</code>. For production, see the considerations in the next section.</li>
<li><strong>LDAP Admin Password</strong>: Enter the password for the account above.</li>
<li><strong>Register Agent in Okta</strong>:
<ul>
<li>You will see a prompt like this:</li>
</ul>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">To continue, navigate to the following website:
</span></span><span class="line"><span class="cl">[https://xxx.okta.com/activate]
</span></span><span class="line"><span class="cl">and enter the following code: [MSJPKGRP]</span></span></code></pre></div></div>
<ul>
<li>Follow the instructions and open the <code>/activate</code> URL in your Okta tenant</li>
<li>Enter the Activation code</li>
<li>Click on &ldquo;Allow&rdquo;</li>
</ul>
</li>
</ol>
</li>
<li>
<p>Once the configuration is complete, the script will start the agent service. You can verify its status:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl status OktaLDAPAgent</span></span></code></pre></div></div>
</li>
</ol>
<p>Once the OpenLDAP server is installed, and the agent is configured, continue with <strong><a href="/howto/okta-lab-ldap/#using-management-interfaces" >Using Management Interfaces</a></strong> and <strong><a href="/howto/okta-lab-ldap/#managing-the-okta-ldap-integration" >Managing the Okta-LDAP Integration</a></strong>.</p>
<hr>

<h2 class="relative group">Fully Automated One-click Installation Script
    <div id="fully-automated-one-click-installation-script" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#fully-automated-one-click-installation-script" aria-label="Anchor">#</a>
    </span>
    
</h2>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855894539579 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855894539579 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855894539579 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855894539579 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855894539579" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text">If you&rsquo;re using the Docker Compose version of the LDAP server+agent stack, you can skip to <a href="/#appendix-a-documentation" >Appendix A</a>
  </div>
</div>
<p>This script automates the entire native installation process described in the <a href="/howto/okta-lab-ldap/#manual-setup" >Manual Installation section</a>.</p>
<p>To use it, start from a brand new <strong>Ubuntu 24.04 LTS</strong> server and execute the following commands.</p>
<ol>
<li>
<p>Clone the repository</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt-get install -y git <span class="c1"># if git is not yet installed</span>
</span></span><span class="line"><span class="cl">git clone https://github.com/fabiograsso/okta-lab-ldap/
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> okta-lab-ldap</span></span></code></pre></div></div>
<p>The script will download the LDIF files. If you prefer to load your own data, you can change the source file and use your own LDIF files.</p>
</li>
<li>
<p>Edit the <code>.env</code> file to change the <code>OKTA_ORG</code>, <code>LDAP_ADMIN_PASSWORD</code>, and <code>LDAP_CONFIG_PASSWORD</code> variables.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cp .env.sample .env
</span></span><span class="line"><span class="cl">nano .env   <span class="c1"># or vi .env</span></span></span></code></pre></div></div>
</li>
<li>
<p>Download the <code>.deb</code> file of the Okta LDAP Agent and copy it into the <code>okta-agent</code> folder.</p>
<ol>
<li>Log in to your Okta Admin Console.</li>
<li>Navigate to <strong>Directory → Directory Integrations</strong>.</li>
<li>Click <strong>Add Directory → Add LDAP Directory</strong>.</li>
<li>Follow the setup steps. On the &ldquo;Install Agent&rdquo; step, right-click the <strong>Download Agent</strong> button and copy the link.</li>
<li>Download the <code>.deb</code> file in the folder <code>okta-agent</code> of your LDAP server (e.g., using <code>wget https://....</code>).</li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -o ./okta-agent/OktaLDAPAgent.deb <span class="se">\
</span></span></span><span class="line"><span class="cl">https://xxx.okta.com/artifacts/JAVA_LDAP/05.24.00/OktaLDAPAgent-05.24.00-27823a892f7b.x86_64.deb</span></span></code></pre></div></div>
</li>
</ol>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855894714208 {
    background: #FFFAE6;
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855894714208 .panel-icon {
    color: rgb(224,108,0);
  }
  html.dark #panel-1778753855894714208 {
    background: rgb(51,46,27);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855894714208 .panel-icon {
    color: rgb(251,200,40);
  }
</style>

<div id="panel-1778753855894714208" class="flex px-4 py-3 rounded-md shadow panel-warning ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>Before running the final command, double-check to:</p>
<ul>
<li>Have downloaded the Okta LDAP Agent deb file in the folder <code>okta-agent</code></li>
<li>Have edited the <code>.env</code> file and customized the <code>OKTA_ORG</code>, <code>LDAP_ADMIN_PASSWORD</code>, and <code>LDAP_CONFIG_PASSWORD</code> variables</li>
<li>You&rsquo;re using a brand new/clean Ubuntu installation. The script is built to run ONLY in a clean environment!</li>
</ul>
  </div>
</div>
<ol start="4">
<li>
<p>Then, execute the following commands:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo bash ./scripts/oneclickinstall.sh</span></span></code></pre></div></div>
<p>Or, if you want to skip the confirmation, you can run it in silent mode:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo bash ./scripts/oneclickinstall.sh -s</span></span></code></pre></div></div>
</li>
<li>
<p>When finished, you can run the LDAP Agent setup:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo /opt/Okta/OktaLDAPAgent/scripts/configure_agent.sh</span></span></code></pre></div></div>
<p>Follow the same prompts as described in the Manual Installation section.</p>
</li>
<li>
<p>Optionally, you can delete the GitHub cloned folder:</p>
<pre><code> ```bash
 cd ..
 sudo rm -rf okta-lab-ldap
 ```
</code></pre>
</li>
</ol>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855894873726 {
    background: rgb(233,242,254);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855894873726 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855894873726 {
    background: rgb(28,43,66);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855894873726 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855894873726" class="flex px-4 py-3 rounded-md shadow panel-info ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>
  <div class="panel-text"><ul>
<li><strong>LDAP</strong>: Available on <code>1389</code> and <code>1636</code>.</li>
<li><strong>ldap-ui</strong>: Available at <code>5000</code>.</li>
</ul>

  </div>
</div>





  





















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855895119434 {
    background: rgb(231,249,255);
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855895119434 .panel-icon {
    color: rgb(53,125,232);
  }
  html.dark #panel-1778753855895119434 {
    background: rgb(30,49,55);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855895119434 .panel-icon {
    color: rgb(70,136,236);
  }
</style>

<div id="panel-1778753855895119434" class="flex px-4 py-3 rounded-md shadow panel-idea ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>Use the following command to open an SSH tunnel and get access to the LDAP, LDAPS, and ldap-ui ports. This will avoid opening the ports on the AWS firewall:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh -f -N -L 1389:localhost:1389 ubuntu@myserverIP  
</span></span><span class="line"><span class="cl">ssh -f -N -L 1636:localhost:1636 ubuntu@myserverIP  
</span></span><span class="line"><span class="cl">ssh -f -N -L 5000:localhost:5000 ubuntu@myserverIP  </span></span></code></pre></div></div>
  </div>
</div>
<p>Once the OpenLDAP server is installed, and the agent is configured, continue with <strong><a href="/howto/okta-lab-ldap/#using-management-interfaces" >Using Management Interfaces</a></strong> and <strong><a href="/howto/okta-lab-ldap/#managing-the-okta-ldap-integration" >Managing the Okta-LDAP Integration</a></strong>.</p>
<hr>

<h2 class="relative group">Appendix A: Documentation
    <div id="appendix-a-documentation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#appendix-a-documentation" aria-label="Anchor">#</a>
    </span>
    
</h2>
<ul>
<li><a href="https://github.com/fabiograsso/okta-lab-ldap"  target="_blank" rel="noreferrer"><span class="relative inline-block align-text-bottom icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg></span> fabiograsso/okta-lab-ldap repository</a></li>
<li><a href="https://www.openldap.org/doc/admin24/"  target="_blank" rel="noreferrer">OpenLDAP Administrator&rsquo;s Guide</a></li>
<li><a href="https://github.com/dnknth/ldap-ui"  target="_blank" rel="noreferrer"><span class="relative inline-block align-text-bottom icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg></span> ldap-ui GitHub Repository</a></li>
<li><a href="https://directory.apache.org/studio/users-guide.html"  target="_blank" rel="noreferrer">Apache Directory Studio Documentation</a></li>
<li><a href="https://help.okta.com/en-us/content/topics/directory/ldap-agent.htm"  target="_blank" rel="noreferrer">Okta LDAP Agent Documentation</a></li>
<li><a href="https://ubuntu.com/server/docs"  target="_blank" rel="noreferrer">Ubuntu Server Guide</a></li>
</ul>
<hr>

<h2 class="relative group">Appendix B: Notes on Docker and Docker-compose
    <div id="appendix-b-notes-on-docker-and-docker-compose" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#appendix-b-notes-on-docker-and-docker-compose" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Update LDAP Agent version
    <div id="update-ldap-agent-version" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#update-ldap-agent-version" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>To update the LDAP agent, just delete the old <code>.deb</code> file and replace it with the new one. Then rebuild the image with <code>make build</code></p>
<hr>

<h2 class="relative group">Appendix C: Production Considerations for the Okta LDAP Agent
    <div id="appendix-c-production-considerations-for-the-okta-ldap-agent" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#appendix-c-production-considerations-for-the-okta-ldap-agent" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>When deploying in a production environment, consider the following best practices:</p>
<ul>
<li>
<p><strong>Use a Dedicated Service Account</strong> - Instead of using the master <code>cn=admin</code> account, it is highly recommended to create a dedicated service account for the Okta agent. This account needs read and write permissions to support provisioning users from Okta to LDAP. You would grant specific permissions (ACLs) to this account to limit its access to only what is necessary.</p>
</li>
<li>
<p><strong>High Availability</strong> - For redundancy and failover, always deploy at least two Okta LDAP agents on separate servers, both pointing to the same LDAP directory. Okta will automatically round-robin requests between them and fail over if one agent becomes unavailable.</p>
</li>
<li>
<p><strong>Auto-Update</strong> - The Okta LDAP agent can update itself automatically. This feature requires at least two agents to be installed to ensure zero downtime during the update process. See <em><a href="https://help.okta.com/en-us/content/topics/directory/ldap-agent-auto-update.htm"  target="_blank" rel="noreferrer">Automatically update Okta LDAP agents</a></em>.</p>
</li>
<li>
<p><strong>Performance Tuning</strong> - The agent&rsquo;s performance can be tuned by editing its configuration file at <code>/opt/Okta/OktaLDAPAgent/conf/OktaLDAPAgent.conf</code>. In particular, the <code>pollingThreadCount</code> property controls the number of threads used for polling. Increasing this value can improve performance on very busy directories (remember to change <code>maxConnectionsPerHost</code> accordingly).
For more details and other parameters, you can refer to the Documentation <a href="https://help.okta.com/en-us/content/topics/directory/ldap-agent-config-parameters.htm"  target="_blank" rel="noreferrer">LDAP configuration parameters</a> and <a href="https://help.okta.com/en-us/content/topics/directory/ldap-agent-threads.htm"  target="_blank" rel="noreferrer">Change the number of Okta LDAP agent threads</a>.</p>
</li>
<li>
<p><strong>Logging and Verbosity</strong> - Agent logs are located at <code>/opt/Okta/OktaLDAPAgent/logs/agent.log</code>. To debug issues, you can increase the logging verbosity to <code>DEBUG</code> by editing the <code>/opt/Okta/OktaLDAPAgent/conf/logback.xml</code> file. See <a href="https://help.okta.com/en-us/content/topics/directory/ldap-agent-logs.htm"  target="_blank" rel="noreferrer">Locate the Okta LDAP agent log</a>.</p>
</li>
</ul>
<hr>

<h2 class="relative group">Conclusion
    <div id="conclusion" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#conclusion" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>This comprehensive guide has walked you through deploying OpenLDAP on Ubuntu 24.04 LTS with complete Okta integration. Whether you chose the manual installation for learning purposes, the one-click script for rapid deployment, or the Docker approach for development environments, you now have a fully functional LDAP directory integrated with Okta.</p>
<p>The integration provides powerful capabilities including delegated authentication, user provisioning, custom attribute mapping, and organizational unit management. This setup serves as an excellent foundation for understanding enterprise directory services and their integration with modern identity providers.</p>









<figure>
    <img class="my-0 rounded-md" loading="lazy" alt="Placeholder - Complete Integration Architecture" src="complete-architecture.png">


  
</figure>
<p>Remember that while this guide provides a solid foundation for testing and development, production deployments require additional considerations around security, high availability, monitoring, and compliance with your organization&rsquo;s policies.</p>
<p>Questions or feedback? Feel free to reach out or contribute to the <a href="https://github.com/fabiograsso/okta-lab-ldap"  target="_blank" rel="noreferrer"><span class="relative inline-block align-text-bottom icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg></span>  fabiograsso/okta-lab-ldap repository</a>.</p>
]]></content:encoded>
      <category>openldap</category>
      <category>okta</category>
      <category>ubuntu</category>
      <category>ldap</category>
      <category>integration</category>
      <category>directory</category>
      <category>authentication</category>
      <category>docker</category>
    </item>
    <item>
      <title>Lab for test the Okta MCP Server with (or without) Docker</title>
      <link>https://iam.fabiograsso.net/howto/okta-lab-mcp/</link>
      <pubDate>Sun, 05 Oct 2025 09:00:00 +0100</pubDate>
      <guid>https://iam.fabiograsso.net/howto/okta-lab-mcp/</guid>
      <description>Set up an Okta MCP Server lab for AI-assisted administration using Docker Compose or native install, with practical examples for Claude, Gemini, and VS Code workflows.</description>
      <content:encoded>&lt;![CDATA[<div class="admonition relative overflow-hidden rounded-lg border-l-4 my-3 px-4 py-3 shadow-sm" data-type="warning">
      <div class="flex items-center gap-2 font-semibold text-inherit">
        <div class="flex shrink-0 h-5 w-5 items-center justify-center text-lg"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span></div>
        <div class="grow">
          Warning
        </div>
      </div><div class="admonition-content mt-3 text-base leading-relaxed text-inherit"><p>This article was written in October 2025. In February 2026 Okta added Docker support to the official GitHub repository, so you can now use the official Docker image instead of building your own.
I will keep this article as it can be useful for learning how to set up the MCP Server in different environments, and for testing the HTTP Gateway, which is not available in the official image.</p></div></div>
<h2 class="relative group">Introduction
    <div id="introduction" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#introduction" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Okta recently released a beta version of their Model Context Protocol (MCP) Server, which enables AI assistants and Large Language Models (LLMs) to interact with Okta APIs using natural language commands. The MCP Server acts as a bridge between AI agents and Okta&rsquo;s management APIs, allowing you to perform administrative tasks through conversational interfaces.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Claude Desktop example showing natural language Okta management"
    srcset="
      /howto/okta-lab-mcp/claude-example_hu_450fb9d5c9a5d915.webp  330w,
      /howto/okta-lab-mcp/claude-example_hu_244c580c69243ae8.webp  660w,
      /howto/okta-lab-mcp/claude-example_hu_b0f6e480d84c92db.webp  960w,
      /howto/okta-lab-mcp/claude-example_hu_b96fbd8a3bcd0ea.webp 1280w,
      /howto/okta-lab-mcp/claude-example_hu_1fcb0e61d0c10b0b.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-mcp/claude-example.png"
    src="/howto/okta-lab-mcp/claude-example.png">


  
</figure>
<p>In this article, I&rsquo;ll show you how to quickly set up the Okta MCP Server for testing and development using two different approaches:</p>
<ol>
<li><strong>Docker/Docker Compose</strong> for container-based deployment with HTTP gateway</li>
<li><strong>Manual native installation</strong> on Ubuntu 24.04 LTS for direct system integration</li>
</ol>
<p>The Dockerfile and Docker-compose are available in my <strong><a href="https://github.com/fabiograsso/okta-lab-mcp"  target="_blank" rel="noreferrer">okta-lab-mcp</a> GitHub repository</strong>, which aims to simplify the MCP Server setup process for demo and test environments.</p>
<blockquote><p><strong>Note</strong>: This article covers the first version of my lab setup - I will update it in the following days/weeks with more examples and use cases.</p>
</blockquote>
<h3 class="relative group">What is the Model Context Protocol (MCP)?
    <div id="what-is-the-model-context-protocol-mcp" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-is-the-model-context-protocol-mcp" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The <a href="https://modelcontextprotocol.io/introduction"  target="_blank" rel="noreferrer">Model Context Protocol (MCP)</a> is an open protocol introduced by Anthropic that standardizes how Large Language Models communicate with external tools, resources, and remote services. It enables AI assistants to:</p>
<ul>
<li><strong>Access external data sources</strong> and APIs in real-time</li>
<li><strong>Execute actions</strong> on behalf of users through natural language</li>
<li><strong>Maintain context</strong> across multiple interactions</li>
<li><strong>Integrate seamlessly</strong> with existing tools and workflows</li>
</ul>
<pre class="not-prose mermaid">
flowchart LR
 subgraph AI["AI applications"]
        Chat["Chat interface <br><small>(Claude Desktop, LibreChat)</small>"]
        IDE["IDEs and code editors <br><small>(Claude Code, Goose)</small>"]
        Other["Other AI applications <br><small>(5ire, Superinterface)</small>"]
  end
 subgraph Data["Data sources and tools"]
        Files["Data and file systems <br><small>(PostgreSQL, SQLite, GDrive)</small>"]
        DevTools["Development tools <br><small>(Git, Sentry, etc.)</small>"]
        ProdTools["Productivity tools <br><small>(Slack, Google Maps, etc.)</small>"]
  end
    Chat L_Chat_MCP_0@<--> MCP["MCP <br><small>Standardized protocol</small>"]
    IDE L_IDE_MCP_0@<--> MCP
    MCP L_MCP_Files_0@<--> Files & DevTools & ProdTools
    Other L_Other_MCP_0@<--> MCP

    style Chat stroke-width:2px
    style IDE stroke-width:2px
    style Other stroke-width:2px
    style Files stroke-width:2px
    style DevTools stroke-width:2px
    style ProdTools stroke-width:2px
    style MCP fill:#e6f3ff,stroke:#6699cc,stroke-width:2px
    style AI fill:#FFE0B2,color:#000000
    style Data fill:#C8E6C9

    L_Chat_MCP_0@{ animation: slow } 
    L_IDE_MCP_0@{ animation: slow } 
    L_MCP_Files_0@{ animation: slow } 
    L_MCP_DevTools_0@{ animation: slow } 
    L_MCP_ProdTools_0@{ animation: slow } 
    L_Other_MCP_0@{ animation: slow } 
</pre>


<h3 class="relative group">What is the Okta MCP Server?
    <div id="what-is-the-okta-mcp-server" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-is-the-okta-mcp-server" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The Okta MCP Server integrates with LLMs and AI agents, allowing you to perform various Okta management operations using natural language. For instance, you could simply ask Claude Desktop, VS Code Copilot, or Gemini to perform Okta management operations:</p>
<ul>
<li><em>&ldquo;Create a new user and add them to the Engineering group&rdquo;</em></li>
<li><em>&ldquo;Show me all failed login attempts from the last 24 hours&rdquo;</em></li>
<li><em>&ldquo;List all applications that haven&rsquo;t been used in the past month&rdquo;</em></li>
<li><em>&ldquo;Deactivate users who haven&rsquo;t logged in for 90 days&rdquo;</em></li>
</ul>
<p>The server provides comprehensive tool support including:</p>
<ul>
<li><strong>User Management</strong>: Create, update, deactivate, and manage user profiles</li>
<li><strong>Group Operations</strong>: Manage groups and memberships</li>
<li><strong>Application Management</strong>: Configure and manage SSO applications</li>
<li><strong>Policy Administration</strong>: Handle security policies and rules</li>
<li><strong>Audit and Logging</strong>: Access system logs and authentication data</li>
</ul>
<pre class="not-prose mermaid">
---
config:
  layout: dagre
---
flowchart LR
    agent["AI Agent"] L_agent_server_0@-- MCP Protocol --> server["Okta MCP Server"]
    server L_server_apis_0@-- API Calls --> apis["Okta APIs"]
     agent:::Sky
     server:::Rose
     apis:::Peach
    classDef Sky stroke-width:1px, stroke-dasharray:none, stroke:#374D7C, fill:#E2EBFF, color:#374D7C
    classDef Rose stroke-width:1px, stroke-dasharray:none, stroke:#FF5978, fill:#FFDFE5, color:#8E2236
    classDef Peach stroke-width:1px, stroke-dasharray:none, stroke:#FBB35A, fill:#FFEFDB, color:#8F632D
    L_agent_server_0@{ animation: slow } 
    L_server_apis_0@{ animation: slow }
</pre>


<h2 class="relative group">Official Resources
    <div id="official-resources" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#official-resources" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Before diving into the installation methods, I recommend checking out the following resources:</p>
<ol>
<li><strong><a href="https://github.com/okta/okta-mcp-server"  target="_blank" rel="noreferrer">Okta&rsquo;s official MCP Server repository</a></strong> - The source code and official documentation</li>
<li><strong><a href="https://developer.okta.com/blog/2025/09/22/okta-mcp-server"  target="_blank" rel="noreferrer">Introducing the Okta MCP Server</a></strong> - Okta&rsquo;s blog post with configuration details</li>
<li><strong><a href="https://modelcontextprotocol.io/"  target="_blank" rel="noreferrer">Model Context Protocol Documentation</a></strong> - Learn about the MCP standard</li>
</ol>
<hr>

<h2 class="relative group">Okta Setup
    <div id="okta-setup" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#okta-setup" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The MCP Server supports two authentication methods:</p>
<ol>
<li>
<p><strong>Device Authorization Grant (Interactive)</strong></p>
<ol>
<li>Create a <strong>new App Integration</strong> in your Okta org</li>
<li>Select <strong>OIDC - OpenID Connect</strong> and <strong>Native Application</strong></li>
<li>Under <strong>Grant type</strong>, ensure <strong>Device Authorization</strong> is checked</li>
<li>Go to <strong>Okta API Scopes</strong> tab and grant permissions (e.g., <code>okta.users.read</code>, <code>okta.groups.manage</code>)</li>
<li>Save and copy the <strong>Client ID</strong></li>
</ol>
</li>
<li>
<p><strong>Private Key JWT (Browserless) - Recommended</strong></p>
<ol>
<li><strong>Create App</strong>: Select <strong>API Services</strong> and save</li>
<li><strong>Configure Client Authentication</strong>:
<ul>
<li>Disable <strong>Require Demonstrating Proof of Possession (DPoP)</strong></li>
<li>Select <strong>Public key / Private key</strong> authentication</li>
</ul>
</li>
<li><strong>Add Public Key</strong>: Generate in Okta or upload your own</li>
<li><strong>Grant API Scopes</strong>: Add required permissions (e.g., <code>okta.users.read</code>, <code>okta.groups.manage</code>)</li>
<li><strong>Assign Admin Roles</strong>: Select an appropriate admin role:
<ul>
<li><strong>Super Administrator</strong>: Full access (recommended for testing)</li>
<li><strong>Read-Only Administrator</strong>: View-only access</li>
<li><strong>Custom Admin Role</strong>: Granular permissions for production use</li>
</ul>
</li>
</ol>
</li>
</ol>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >My lab is designed to use the <strong>Private Key JWT (Browserless) method</strong> to run without requiring user interaction for registration. For detailed instructions, refer to <a href="https://developer.okta.com/blog/2025/09/22/okta-mcp-server"  target="_blank" rel="noreferrer">Okta&rsquo;s blog post &ldquo;Introducing the Okta MCP Server&rdquo;</a>.</span>
</div>


<h3 class="relative group">Okta Scopes and Permissions
    <div id="okta-scopes-and-permissions" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#okta-scopes-and-permissions" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The capabilities of the MCP Server depend on the <strong>scopes</strong> granted to your Okta OIDC application. Scopes determine which operations the server can perform in your Okta tenant, and are defined on both the Okta OIDC application and the <code>OKTA_SCOPES</code> environment variable.</p>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><p><strong>Important</strong>: Scopes alone are not sufficient when using Private Key JWT authentication. You must also <strong>assign an admin role</strong> to the OIDC application. Without an admin role assignment, the server will receive a valid token with the requested scopes, but API calls will fail or return empty results due to missing permissions.</p>
<p><strong>Without proper admin role assignment</strong>, you&rsquo;ll encounter issues like:</p>
<ul>
<li>User queries returning empty results despite valid tokens</li>
<li>API calls failing with insufficient permissions errors</li>
<li>Scopes appearing valid but operations being blocked</li>
</ul>
</span>
</div>


<h4 class="relative group">Common Scopes
    <div id="common-scopes" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#common-scopes" aria-label="Anchor">#</a>
    </span>
    
</h4>
<table>
  <thead>
      <tr>
          <th>Scope</th>
          <th>Operations</th>
          <th>Use Case</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>okta.users.read</code></td>
          <td>View user profiles, list users</td>
          <td>User queries</td>
      </tr>
      <tr>
          <td><code>okta.users.manage</code></td>
          <td>Create, update, deactivate users</td>
          <td>User lifecycle management</td>
      </tr>
      <tr>
          <td><code>okta.groups.read</code></td>
          <td>View groups and memberships</td>
          <td>Group queries</td>
      </tr>
      <tr>
          <td><code>okta.groups.manage</code></td>
          <td>Create groups, manage memberships</td>
          <td>Group administration</td>
      </tr>
      <tr>
          <td><code>okta.apps.read</code></td>
          <td>List applications and assignments</td>
          <td>Application auditing</td>
      </tr>
      <tr>
          <td><code>okta.apps.manage</code></td>
          <td>Configure applications and assignments</td>
          <td>Application management</td>
      </tr>
      <tr>
          <td><code>okta.logs.read</code></td>
          <td>Access system and authentication logs</td>
          <td>Security monitoring and compliance</td>
      </tr>
      <tr>
          <td><code>okta.policies.read</code></td>
          <td>View security policies</td>
          <td>Policy auditing</td>
      </tr>
      <tr>
          <td><code>okta.policies.manage</code></td>
          <td>Configure security policies</td>
          <td>Policy administration</td>
      </tr>
  </tbody>
</table>

<h4 class="relative group">Recommended Scope Combinations
    <div id="recommended-scope-combinations" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#recommended-scope-combinations" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li>For Testing and Development:
<code>okta.users.read okta.users.manage okta.groups.read okta.groups.manage okta.apps.read okta.logs.read</code></li>
<li>For Production (Read-Only):
<code>okta.users.read okta.groups.read okta.apps.read okta.logs.read okta.policies.read</code></li>
<li>For Full Administration:
<code>okta.users.manage okta.groups.manage okta.apps.manage okta.logs.read okta.policies.manage</code></li>
</ul>

<h4 class="relative group">Additional Resources
    <div id="additional-resources" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#additional-resources" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li><a href="https://developer.okta.com/docs/api/oauth2/#okta-admin-management"  target="_blank" rel="noreferrer">Okta OAuth 2.0 Scopes</a></li>
<li><a href="https://developer.okta.com/docs/guides/implement-oauth-for-okta-serviceapp/main/"  target="_blank" rel="noreferrer">Implement OAuth for Okta with a service app</a></li>
<li><a href="https://developer.okta.com/docs/guides/implement-oauth-for-okta-serviceapp/main/#assign-admin-roles-to-the-oauth-2-0-service-app"  target="_blank" rel="noreferrer">Assign admin roles to the OAuth 2.0 service app</a></li>
<li><a href="https://developer.okta.com/blog/2025/09/22/okta-mcp-server"  target="_blank" rel="noreferrer">Okta Blog -  Introducing the Okta MCP Server</a></li>
</ul>

<h2 class="relative group">Lab Environment Overview
    <div id="lab-environment-overview" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#lab-environment-overview" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>My <strong><a href="https://github.com/fabiograsso/okta-lab-mcp"  target="_blank" rel="noreferrer">okta-lab-mcp</a></strong> project provides a comprehensive Docker-based lab environment with the Okta MCP Server, an HTTP Gateway and gemini-cli for testing. You can find also sample configurations files for other MCP Client such as VS Code and Claude Desktop.</p>

<h3 class="relative group">Docker Components
    <div id="docker-components" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#docker-components" aria-label="Anchor">#</a>
    </span>
    
</h3>
<table>
  <thead>
      <tr>
          <th>Component</th>
          <th>Purpose</th>
          <th>Access Method</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Okta MCP Server</strong></td>
          <td>Core server for Okta API operations</td>
          <td>stdio (direct)</td>
      </tr>
      <tr>
          <td><strong>MCP + HTTP Gateway</strong></td>
          <td>Network access for local and remote clients</td>
          <td>HTTP on port 8000</td>
      </tr>
      <tr>
          <td><strong>Gemini CLI</strong></td>
          <td>Google&rsquo;s AI-powered CLI for testing</td>
          <td>Via gateway</td>
      </tr>
      <tr>
          <td><strong>Sample Configurations</strong></td>
          <td>Ready-to-use configs for Gemini, VS Code, Claude Desktop</td>
          <td>Multiple options</td>
      </tr>
  </tbody>
</table>

<h3 class="relative group">Why Use the HTTP Gateway?
    <div id="why-use-the-http-gateway" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-use-the-http-gateway" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The <a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/transports"  target="_blank" rel="noreferrer">MCP protocol defines two standard transport mechanisms for client-server communication</a>:</p>
<ul>
<li><strong>stdio</strong>, communication over standard in and standard out</li>
<li><strong>Streamable HTTP</strong></li>
</ul>
<p>The first release of the Okta MCP Server supports only <strong>stdio</strong>, which works perfectly for direct, local client-server communication. However, stdio has limitations:</p>
<ul>
<li><strong>Single Connection</strong>: Only one client can connect at a time</li>
<li><strong>Local Only</strong>: No network access capability</li>
<li><strong>Docker Compose Incompatibility</strong>: Cannot directly interact with stdio in detached containers</li>
</ul>
<p>The <strong>HTTP Gateway</strong> solves these limitations by creating a bridge that enables:</p>
<ul>
<li><strong>Remote Access</strong>: Connect from anywhere via HTTP</li>
<li><strong>Multiple Clients</strong>: Support concurrent connections</li>
<li><strong>Docker Compose Support</strong>: Perfect for containerized environments</li>
</ul>
<pre class="not-prose mermaid">
flowchart LR
    agent["AI Agent"] L_agent_gateway_0@-- HTTP --> gateway["Gateway"]
    gateway L_gateway_server_0@-- stdio --> server["Okta MCP Server"]
    server L_server_apis_0@-- API Calls --> apis["Okta APIs"]

     agent:::Sky
     gateway:::Aqua
     gateway:::Ash
     server:::Rose
     apis:::Peach
    classDef Sky stroke-width:1px, stroke-dasharray:none, stroke:#374D7C, fill:#E2EBFF, color:#374D7C
    classDef Rose stroke-width:1px, stroke-dasharray:none, stroke:#FF5978, fill:#FFDFE5, color:#8E2236
    classDef Peach stroke-width:1px, stroke-dasharray:none, stroke:#FBB35A, fill:#FFEFDB, color:#8F632D
    classDef Aqua stroke-width:1px, stroke-dasharray:none, stroke:#46EDC8, fill:#DEFFF8, color:#378E7A
    classDef Ash stroke-width:1px, stroke-dasharray:none, stroke:#999999, fill:#EEEEEE, color:#000000

    L_agent_gateway_0@{ animation: slow } 
    L_gateway_server_0@{ animation: slow } 
    L_server_apis_0@{ animation: slow } 
</pre>


<h3 class="relative group">When to Use Gateway vs Direct stdio
    <div id="when-to-use-gateway-vs-direct-stdio" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#when-to-use-gateway-vs-direct-stdio" aria-label="Anchor">#</a>
    </span>
    
</h3>
<table>
  <thead>
      <tr>
          <th>Use Case</th>
          <th>Recommended Approach</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Docker Compose setup</td>
          <td>HTTP Gateway (required)</td>
      </tr>
      <tr>
          <td>Local MCP Client (i.e. VS Code or Claude Desktop)</td>
          <td>Direct stdio (Docker run without Compose)<br>or HTTP Gateway (runinng in Compose)</td>
      </tr>
      <tr>
          <td>Remote Access</td>
          <td>HTTP Gateway + SSH tunnel</td>
      </tr>
      <tr>
          <td>Web application integration</td>
          <td>HTTP Gateway</td>
      </tr>
      <tr>
          <td>API testing with Postman</td>
          <td>HTTP Gateway + SSH tunnel</td>
      </tr>
  </tbody>
</table>
<hr>

<h2 class="relative group">Docker/Docker Compose Setup
    <div id="dockerdocker-compose-setup" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#dockerdocker-compose-setup" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Using Docker is the simplest way to get started with the MCP Server without having to set up the development environment manually.</p>

<h3 class="relative group">Prerequisites
    <div id="prerequisites" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#prerequisites" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><strong><a href="https://docs.docker.com/engine/install/"  target="_blank" rel="noreferrer">Docker Engine</a> + <a href="https://docs.docker.com/compose/install/"  target="_blank" rel="noreferrer">Docker Compose</a></strong> or <strong><a href="https://docs.docker.com/desktop/"  target="_blank" rel="noreferrer">Docker Desktop</a></strong></li>
<li><strong>Git</strong></li>
<li><strong>TCP Port 8000</strong> available (configurable)</li>
<li><strong>Gemini API Key</strong> (Optional, to use the Gemini CLI)
<ul>
<li>Free tier: 100 requests/day</li>
<li>Generate at <a href="https://aistudio.google.com/apikey"  target="_blank" rel="noreferrer">aistudio.google.com/apikey</a></li>
</ul>
</li>
<li><strong>Okta tenant</strong> with appropriate permissions</li>
</ul>

<h3 class="relative group">Installation Steps
    <div id="installation-steps" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#installation-steps" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li><strong>Clone the repository with submodules:</strong></li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone --recurse-submodules https://github.com/fabiograsso/okta-lab-mcp.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> okta-lab-mcp</span></span></code></pre></div></div>
<ol start="2">
<li><strong>Initial setup:</strong></li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make setup</span></span></code></pre></div></div>
<ol start="3">
<li><strong>Configure environment variables:</strong></li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Create .env from template</span>
</span></span><span class="line"><span class="cl">cp example.env .env
</span></span><span class="line"><span class="cl"><span class="c1"># Edit with your Okta credentials</span>
</span></span><span class="line"><span class="cl">vi .env   <span class="c1"># or `nano .env`</span></span></span></code></pre></div></div>
<p>Required variables in <code>.env</code>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">OKTA_ORG_URL</span><span class="o">=</span>https://my-tenant.okta.com
</span></span><span class="line"><span class="cl"><span class="nv">OKTA_CLIENT_ID</span><span class="o">=</span>my-client-id
</span></span><span class="line"><span class="cl"><span class="nv">OKTA_KEY_ID</span><span class="o">=</span>my-key-id
</span></span><span class="line"><span class="cl"><span class="nv">OKTA_PRIVATE_KEY</span><span class="o">=</span>-----BEGIN PRIVATE KEY-----<span class="se">\n</span>my-pem-private-key...<span class="se">\n</span>-----END PRIVATE KEY-----
</span></span><span class="line"><span class="cl"><span class="nv">OKTA_SCOPES</span><span class="o">=</span>okta.users.read okta.groups.read okta.apps.read
</span></span><span class="line"><span class="cl"><span class="nv">GEMINI_API_KEY</span><span class="o">=</span>your-gemini-api-key
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># OPTIONAL</span>
</span></span><span class="line"><span class="cl"><span class="c1"># OKTA_LOG_LEVEL=DEBUG</span>
</span></span><span class="line"><span class="cl"><span class="c1"># OKTA_TIMEOUT=30</span>
</span></span><span class="line"><span class="cl"><span class="c1"># OKTA_MAX_RETRIES=3</span>
</span></span><span class="line"><span class="cl"><span class="c1"># OKTA_RATE_LIMIT=600</span>
</span></span><span class="line"><span class="cl"><span class="c1"># GATEWAY_LOG_LEVEL=debug</span>
</span></span><span class="line"><span class="cl"><span class="c1"># GEMINI_CLI_VERSION=sandbox:0.7.0</span></span></span></code></pre></div></div>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><p>The private key must be formatted as a single line with <code>\n</code> for newlines:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Original format</span>
</span></span><span class="line"><span class="cl">-----BEGIN PRIVATE KEY-----
</span></span><span class="line"><span class="cl">MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
</span></span><span class="line"><span class="cl">more_key_content_here...
</span></span><span class="line"><span class="cl">-----END PRIVATE KEY-----
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Formatted for environment variable</span>
</span></span><span class="line"><span class="cl">-----BEGIN PRIVATE KEY-----<span class="se">\n</span>..<span class="se">\n</span>..<span class="se">\n</span>-----END PRIVATE KEY-----</span></span></code></pre></div></div>
<p>You can convert it using:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">awk <span class="s1">&#39;NF {sub(/\r/, &#34;&#34;); printf &#34;%s\\n&#34;,$0;}&#39;</span> private_key.pem</span></span></code></pre></div></div>
</span>
</div>

<ol start="4">
<li><strong>Build and start services:</strong></li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Build Docker images</span>
</span></span><span class="line"><span class="cl">make build
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start all services</span>
</span></span><span class="line"><span class="cl">make start
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Verify health</span>
</span></span><span class="line"><span class="cl">make health</span></span></code></pre></div></div>

<h3 class="relative group">Docker Deployment Options
    <div id="docker-deployment-options" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#docker-deployment-options" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The lab provides multiple deployment options:</p>
<ol>
<li>
<p><strong>Full Stack (Recommended for Testing)</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Start both MCP Server + Gateway and Gemini CLI</span>
</span></span><span class="line"><span class="cl">make start</span></span></code></pre></div></div>
</li>
<li>
<p><strong>Gateway Only</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Start just the HTTP gateway for remote access</span>
</span></span><span class="line"><span class="cl">make start-gateway</span></span></code></pre></div></div>
</li>
<li>
<p><strong>Standalone MCP Server</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># For direct stdio access (VS Code, Claude Desktop)</span>
</span></span><span class="line"><span class="cl">docker run -i --rm <span class="se">\
</span></span></span><span class="line"><span class="cl">--name okta-mcp-server
</span></span><span class="line"><span class="cl">-e <span class="nv">OKTA_ORG_URL</span><span class="o">=</span><span class="s2">&#34;https://my-tenant.okta.com&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">-e <span class="nv">OKTA_CLIENT_ID</span><span class="o">=</span><span class="s2">&#34;your_client_id&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">-e <span class="nv">OKTA_KEY_ID</span><span class="o">=</span><span class="s2">&#34;your_key_id&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">-e <span class="nv">OKTA_PRIVATE_KEY</span><span class="o">=</span><span class="s2">&#34;-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">-e <span class="nv">OKTA_SCOPES</span><span class="o">=</span><span class="s2">&#34;okta.users.read okta.groups.read&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">okta-mcp-server</span></span></code></pre></div></div>
</li>
</ol>

<h3 class="relative group">Management Commands
    <div id="management-commands" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#management-commands" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The project includes a comprehensive Makefile for easy management:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make <span class="nb">help</span>           <span class="c1"># Display all available commands</span>
</span></span><span class="line"><span class="cl">make setup          <span class="c1"># Initial setup</span>
</span></span><span class="line"><span class="cl">make build          <span class="c1"># Build Docker images</span>
</span></span><span class="line"><span class="cl">make start          <span class="c1"># Start all services</span>
</span></span><span class="line"><span class="cl">make stop           <span class="c1"># Stop all services</span>
</span></span><span class="line"><span class="cl">make status         <span class="c1"># Check service status</span>
</span></span><span class="line"><span class="cl">make health         <span class="c1"># Health check</span>
</span></span><span class="line"><span class="cl">make logs-all       <span class="c1"># View all service logs</span>
</span></span><span class="line"><span class="cl">make clean          <span class="c1"># Clean Docker resources</span></span></span></code></pre></div></div>

<h2 class="relative group">Client Configuration Examples
    <div id="client-configuration-examples" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#client-configuration-examples" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The lab includes ready-to-use configurations for popular MCP clients:</p>

<h3 class="relative group">VS Code Configuration
    <div id="vs-code-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#vs-code-configuration" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Edit the file (or create it if not exist) <code>/.vscode/mcp.json</code>:</p>
<p><strong>Example with Gateway access:</strong>
(Requires the Docker-compose stack up and running)</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="nt">&#34;mcp&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;servers&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;okta-gateway&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;http&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;url&#34;</span><span class="p">:</span> <span class="s2">&#34;http://localhost:8000/mcp&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p><strong>Example with Docker and direct stdio</strong>:
(Start a temporary Docker container using the <code>okta-mcp-server</code> image - Remember to change the environment variable in the <code>args</code> part of the JSON)</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="nt">&#34;mcp&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;servers&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;okta-docker&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;stdio&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;docker&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;args&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;run&#34;</span><span class="p">,</span> <span class="s2">&#34;-i&#34;</span><span class="p">,</span> <span class="s2">&#34;--rm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;--name&#34;</span><span class="p">,</span> <span class="s2">&#34;okta-mcp-vscode&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_ORG_URL=https://xxxx.okta.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_CLIENT_ID=xxxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_SCOPES=okta.users.read okta.groups.read&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nxxxxxxxx\n-----END PRIVATE KEY-----&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_KEY_ID=xxxxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;okta-mcp-server&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<blockquote><p>You can choose what to use, or try both. I suggest in any case to keep only one active at time.</p>
</blockquote>
<h3 class="relative group">Claude Desktop Configuration
    <div id="claude-desktop-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#claude-desktop-configuration" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Create or edit <code>~/Library/Application Support/Claude/claude_desktop_config.json</code> (macOS) or <code>%APPDATA%\Claude\claude_desktop_config.json</code> (Windows):</p>
<p><strong>Example with Gateway access:</strong>
(Requires the Docker-compose stack up and running)</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;mcpServers&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;okta-gateway&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;http&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;url&#34;</span><span class="p">:</span> <span class="s2">&#34;http://localhost:8000/mcp&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p><strong>Example with Docker and direct stdio</strong>:
(Start a temporary Docker container using the <code>okta-mcp-server</code> image - Remember to change the environment variable in the <code>args</code> part of the JSON)</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;mcpServers&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;okta-docker&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;docker&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;args&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;run&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-i&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;--rm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;--name&#34;</span><span class="p">,</span> <span class="s2">&#34;okta-mcp-claude&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_ORG_URL=https://xxxx.okta.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_CLIENT_ID=xxxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_SCOPES=okta.users.read okta.groups.read okta.logs.read okta.apps.read okta.userTypes.read&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nxxxxxxxx\n-----END PRIVATE KEY-----&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_KEY_ID=xxxxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_LOG_LEVEL=DEBUG&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;okta-mcp-server:latest&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<blockquote><p>You can choose what to use, or try both. I suggest in any case to keep only one active at time.</p>
</blockquote>
<h3 class="relative group">Gemini CLI Configuration
    <div id="gemini-cli-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#gemini-cli-configuration" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>We have already a Gemini CLI running inside the Docker compose stack. If you prefer to run a local instance of the Gemini CLI here are two sample configuration that you can use with a standalone installation (see <a href="/howto/okta-lab-mcp/#testing-and-examples" >Testing and Examples</a>):</p>
<p><strong>Example with Gateway access:</strong>
(Requires the Docker-compose stack up and running)</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;mcpServers&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;okta-gateway&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;httpUrl&#34;</span><span class="p">:</span> <span class="s2">&#34;http://localhost:8000/mcp&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p><strong>Example with Docker and direct stdio</strong>:
(Start a temporary Docker container using the <code>okta-mcp-server</code> image - Remember to change the environment variable in the <code>args</code> part of the JSON)</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;mcpServers&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;okta-docker&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;docker&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;args&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;run&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-i&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;--rm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;--name&#34;</span><span class="p">,</span> <span class="s2">&#34;okta-mcp-gemini&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_ORG_URL=https://xxxx.okta.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_CLIENT_ID=xxxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_SCOPES=okta.users.read okta.groups.read okta.logs.read okta.apps.read okta.userTypes.read&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nxxxxxxxx\n-----END PRIVATE KEY-----&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_KEY_ID=xxxxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="s2">&#34;OKTA_LOG_LEVEL=DEBUG&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;-v&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;okta-mcp-server:latest&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<blockquote><p>You can choose what to use, or try both. I suggest in any case to keep only one active at time.</p>
</blockquote><hr>

<h2 class="relative group">Testing and Examples
    <div id="testing-and-examples" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#testing-and-examples" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Gemini CLI
    <div id="gemini-cli" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#gemini-cli" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>You have different ways to start gemini-cli:</p>
<ol>
<li>
<p><strong>Docker Desktop</strong>: Open the <code>gemini-cli</code> container, go to the Exec tab and run <code>gemini</code>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Gemini cli running in Docker Desktop"
    srcset="
      /howto/okta-lab-mcp/gemini-cli-docker-desktop_hu_26dfe9ed331b9f62.webp  330w,
      /howto/okta-lab-mcp/gemini-cli-docker-desktop_hu_8c888c8a4c91dfc7.webp  660w,
      /howto/okta-lab-mcp/gemini-cli-docker-desktop_hu_f99d419d4850e43d.webp  960w,
      /howto/okta-lab-mcp/gemini-cli-docker-desktop_hu_9d10c500203b6fcb.webp 1280w,
      /howto/okta-lab-mcp/gemini-cli-docker-desktop_hu_54847c8135cad99e.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-mcp/gemini-cli-docker-desktop.png"
    src="/howto/okta-lab-mcp/gemini-cli-docker-desktop.png">


  
</figure>
</p>
</li>
<li>
<p><strong>CLI Inside Docker Compose:</strong>
If you&rsquo;re running the Docker Compose stack, you can open the Gemini CLI directly on the terminal using the command:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make gemini-cli
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Or</span>
</span></span><span class="line"><span class="cl">docker compose <span class="nb">exec</span> gemini-cli gemini</span></span></code></pre></div></div>
</li>
<li>
<p><strong>Standalone Docker</strong>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Copy the configuration in  ./data/gemini/settings.json</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">docker run -it --rm <span class="se">\
</span></span></span><span class="line"><span class="cl">--name gemini-cli
</span></span><span class="line"><span class="cl">-v ./data/gemini:/home/node/.gemini:rw <span class="se">\
</span></span></span><span class="line"><span class="cl">-e <span class="nv">GEMINI_API_KEY</span><span class="o">=</span><span class="s2">&#34;your_api_key&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.8.1</span></span></code></pre></div></div>
</li>
<li>
<p><strong>Local run or installation:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Copy the correct configuration in `~/.gemini/settings.json`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run using npx (no installation required)</span>
</span></span><span class="line"><span class="cl">npx @google/gemini-cli
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Or install</span>
</span></span><span class="line"><span class="cl"><span class="c1"># with NPM</span>
</span></span><span class="line"><span class="cl">npm install -g @google/gemini-cli
</span></span><span class="line"><span class="cl"><span class="c1"># with brew</span>
</span></span><span class="line"><span class="cl">brew install gemini-cli
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Then run</span>
</span></span><span class="line"><span class="cl">gemini</span></span></code></pre></div></div>
</li>
</ol>

<h4 class="relative group">Available Commands
    <div id="available-commands" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#available-commands" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Once you&rsquo;ve started gemini-cli, you can run <code>/mcp</code> to see available MCP servers and supported commands.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="MCP server list in Gemini CLI"
    srcset="
      /howto/okta-lab-mcp/gemini-mcp-list_hu_2bdb0a8dab2e59b5.webp  330w,
      /howto/okta-lab-mcp/gemini-mcp-list_hu_e0a1791f76c77275.webp  660w,
      /howto/okta-lab-mcp/gemini-mcp-list_hu_b457f347bde15474.webp  960w,
      /howto/okta-lab-mcp/gemini-mcp-list_hu_5d2fc573e38fff77.webp 1280w,
      /howto/okta-lab-mcp/gemini-mcp-list_hu_574ac648f9690891.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-mcp/gemini-mcp-list.png"
    src="/howto/okta-lab-mcp/gemini-mcp-list.png">


  
</figure>
<p>Then test queries like:</p>
<ul>
<li><code>How many users are in my Okta tenant?</code></li>
<li><code>What are the latest logins of user Fabio Grasso?</code></li>
<li><code>Add user Fabio Grasso to the group Office 365 License</code></li>
<li><code>Check the logs for login errors and provide me the list of users with more than 5 failed login attempts</code></li>
<li><code>Check the logs, count all logins per user and give me the ranking of the 10 top users by login count</code></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Sample Gemini response showing user statistics"
    srcset="
      /howto/okta-lab-mcp/gemini-sample-response_hu_f93b5462082c55d2.webp  330w,
      /howto/okta-lab-mcp/gemini-sample-response_hu_615424276d0b39a0.webp  660w,
      /howto/okta-lab-mcp/gemini-sample-response_hu_93d1f98413beaa12.webp  960w,
      /howto/okta-lab-mcp/gemini-sample-response_hu_ed4414597cf9d08e.webp 1280w,
      /howto/okta-lab-mcp/gemini-sample-response_hu_1f756c360cd93c1f.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-lab-mcp/gemini-sample-response.png"
    src="/howto/okta-lab-mcp/gemini-sample-response.png">


  
</figure>

<h3 class="relative group">Claude Desktop Integration
    <div id="claude-desktop-integration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#claude-desktop-integration" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>[TODO: Will be added in future updates with configuration examples and demos]</p>

<h3 class="relative group">VS Code Integration
    <div id="vs-code-integration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#vs-code-integration" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>[TODO: Will be added in future updates with screenshots and examples]</p>

<h2 class="relative group">Remote Access and Security
    <div id="remote-access-and-security" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#remote-access-and-security" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">SSH Tunnel
    <div id="ssh-tunnel" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ssh-tunnel" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>If you&rsquo;re not using Docker Desktop, but a remote server, I suggest to use an SSH tunnell for secure remote access to the HTTP Gateway, without opening ports to the internet:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># On your local machine, create an SSH tunnel</span>
</span></span><span class="line"><span class="cl">ssh -L 8000:localhost:8000 username@server-ip
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Now access the gateway at http://localhost:8000 on your local machine</span></span></span></code></pre></div></div>
<p>This is the <strong>recommended approach</strong> for remote access as it keeps the gateway port closed to the internet while still allowing you to access it securely.</p>

<h3 class="relative group">Security Considerations
    <div id="security-considerations" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#security-considerations" aria-label="Anchor">#</a>
    </span>
    
</h3>

<h4 class="relative group">⚠️ Lab Environment Warning
    <div id="-lab-environment-warning" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#-lab-environment-warning" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>This setup is designed for <strong>testing and development only</strong>:</p>
<ul>
<li><strong>Plaintext Keyring</strong>: Uses basic credential storage (<code>keyrings.alt.file.PlaintextKeyring</code>)</li>
<li><strong>HTTP Transport</strong>: No TLS/SSL encryption by default</li>
<li><strong>No Authentication</strong>: Gateway has no access control</li>
<li><strong>Environment Variables</strong>: Sensitive data stored in plain text</li>
<li><strong>File Permissions</strong>: Logs and config files may contain sensitive information</li>
</ul>

<h4 class="relative group">Production Recommendations
    <div id="production-recommendations" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#production-recommendations" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>For production use, implement proper security measures:</p>
<ul>
<li>Use proper secret management solutions</li>
<li>Enable HTTPS with valid certificates for the gateway</li>
<li>Add authentication to the gateway (OAuth, API keys, etc.)</li>
<li>Implement rate limiting and request throttling</li>
<li>Use dedicated service accounts with minimal required permissions</li>
<li>Enable comprehensive audit logging</li>
<li>Encrypt data at rest and in transit</li>
<li>Use proper file permissions (600 for .env files)</li>
<li>Regularly rotate credentials and keys</li>
</ul>

<h2 class="relative group">Troubleshooting
    <div id="troubleshooting" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#troubleshooting" aria-label="Anchor">#</a>
    </span>
    
</h2>
<ol>
<li>
<p><strong>Authentication Failures</strong></p>
<ul>
<li>Verify all environment variables in <code>.env</code> file</li>
<li>Check <code>OKTA_ORG_URL</code> format (must include https://)</li>
<li>Ensure <code>OKTA_CLIENT_ID</code>, <code>OKTA_KEY_ID</code> are correct</li>
<li>Verify private key formatting (single line with <code>\n</code> separators)</li>
<li>Check that Okta application has required API scopes granted</li>
<li>Ensure the correct Administrator role is assigned to the application</li>
</ul>
</li>
<li>
<p><strong>MCP Client Connection Issues</strong></p>
<ul>
<li>Restart your MCP client after configuration changes</li>
<li>Verify server path in client configuration</li>
<li>Test the gateway with <code>curl http://localhost:8000/healthz</code></li>
</ul>
</li>
<li>
<p><strong>Enable Debug Mode</strong></p>
<ul>
<li>For more detailed logging, enable debug mode and restart:
<ul>
<li><code>OKTA_LOG_LEVEL=DEBUG</code></li>
<li><code>GATEWAY_LOG_LEVEL=debug</code></li>
</ul>
</li>
</ul>
</li>
</ol>

<h2 class="relative group">Practical Use Cases
    <div id="practical-use-cases" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#practical-use-cases" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Once configured, you can use natural language to perform Okta operations, like:</p>
<ul>
<li><strong>User Management</strong>
<ul>
<li><em>&ldquo;Create a new user John Smith with email <code>john.smith@company.com</code> and add him to the Engineering group&rdquo;</em></li>
<li><em>&ldquo;Show me all users who haven&rsquo;t logged in for more than 90 days&rdquo;</em></li>
<li><em>&ldquo;Deactivate all contractor accounts that are expired&rdquo;</em></li>
</ul>
</li>
<li><strong>Security Operations</strong>
<ul>
<li><em>&ldquo;Show me all failed login attempts from suspicious IP addresses in the past 24 hours&rdquo;</em></li>
<li><em>&ldquo;List all applications that don&rsquo;t have MFA enforced&rdquo;</em></li>
<li><em>&ldquo;Find users with admin privileges who aren&rsquo;t in the Administrators group&rdquo;</em></li>
</ul>
</li>
<li><strong>Compliance and Reporting</strong>
<ul>
<li><em>&ldquo;Generate a report of all users with access to the Finance application&rdquo;</em></li>
<li><em>&ldquo;Show me all policy exceptions for the past month&rdquo;</em></li>
<li><em>&ldquo;List applications that haven&rsquo;t been used in the past 90 days&rdquo;</em></li>
</ul>
</li>
</ul>
<hr>

<h2 class="relative group">Alternative: Native Installation
    <div id="alternative-native-installation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#alternative-native-installation" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>If you prefer not to use Docker or need a persistent installation, you can install all components natively. The following examples are for <strong>Ubuntu 24.04 LTS</strong>, but you can easily adapt them to other systems.</p>

<h3 class="relative group">Prerequisites
    <div id="prerequisites-1" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#prerequisites-1" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li>Ubuntu 24.04 LTS</li>
<li>sudo privileges</li>
<li><strong>Okta tenant</strong> with appropriate permissions (configured as described in the <a href="/howto/okta-lab-mcp/#okta-authentication-setup" >Okta Authentication Setup</a> section)</li>
</ul>

<h3 class="relative group">System Preparation
    <div id="system-preparation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#system-preparation" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>First, update the system and install required dependencies:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Update system</span>
</span></span><span class="line"><span class="cl">sudo apt-get update
</span></span><span class="line"><span class="cl">sudo apt-get upgrade -y
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install essential packages</span>
</span></span><span class="line"><span class="cl">sudo apt-get install -y <span class="se">\
</span></span></span><span class="line"><span class="cl">    ca-certificates <span class="se">\
</span></span></span><span class="line"><span class="cl">    curl <span class="se">\
</span></span></span><span class="line"><span class="cl">    gnupg <span class="se">\
</span></span></span><span class="line"><span class="cl">    lsb-release <span class="se">\
</span></span></span><span class="line"><span class="cl">    apt-transport-https <span class="se">\
</span></span></span><span class="line"><span class="cl">    software-properties-common <span class="se">\
</span></span></span><span class="line"><span class="cl">    git <span class="se">\
</span></span></span><span class="line"><span class="cl">    make <span class="se">\
</span></span></span><span class="line"><span class="cl">    jq <span class="se">\
</span></span></span><span class="line"><span class="cl">    unzip <span class="se">\
</span></span></span><span class="line"><span class="cl">    wget <span class="se">\
</span></span></span><span class="line"><span class="cl">    build-essential <span class="se">\
</span></span></span><span class="line"><span class="cl">    libssl-dev <span class="se">\
</span></span></span><span class="line"><span class="cl">    zlib1g-dev <span class="se">\
</span></span></span><span class="line"><span class="cl">    libbz2-dev <span class="se">\
</span></span></span><span class="line"><span class="cl">    libreadline-dev <span class="se">\
</span></span></span><span class="line"><span class="cl">    libsqlite3-dev <span class="se">\
</span></span></span><span class="line"><span class="cl">    libncursesw5-dev <span class="se">\
</span></span></span><span class="line"><span class="cl">    xz-utils <span class="se">\
</span></span></span><span class="line"><span class="cl">    tk-dev <span class="se">\
</span></span></span><span class="line"><span class="cl">    libxml2-dev <span class="se">\
</span></span></span><span class="line"><span class="cl">    libxmlsec1-dev <span class="se">\
</span></span></span><span class="line"><span class="cl">    libffi-dev <span class="se">\
</span></span></span><span class="line"><span class="cl">    liblzma-dev</span></span></code></pre></div></div>

<h4 class="relative group">Install Python 3.13
    <div id="install-python-313" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#install-python-313" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Python 3.13 is required for the Okta MCP Server:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Add deadsnakes PPA for Python 3.13</span>
</span></span><span class="line"><span class="cl">sudo add-apt-repository -y ppa:deadsnakes/ppa
</span></span><span class="line"><span class="cl">sudo apt-get update
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install Python 3.13 and development tools</span>
</span></span><span class="line"><span class="cl">sudo apt-get install -y python3.13 python3.13-venv python3.13-dev
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install pip for Python 3.13</span>
</span></span><span class="line"><span class="cl">curl -sS https://bootstrap.pypa.io/get-pip.py <span class="p">|</span> sudo python3.13
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install uv package manager</span>
</span></span><span class="line"><span class="cl">sudo python3.13 -m pip install uv
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Verify installation</span>
</span></span><span class="line"><span class="cl">python3.13 --version
</span></span><span class="line"><span class="cl">python3.13 -m uv --version</span></span></code></pre></div></div>

<h4 class="relative group">Install Node.js and NPM
    <div id="install-nodejs-and-npm" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#install-nodejs-and-npm" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Node.js is required for the HTTP Gateway and Gemini CLI:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Install NodeSource repository for Node.js 20</span>
</span></span><span class="line"><span class="cl">curl -fsSL https://deb.nodesource.com/setup_20.x <span class="p">|</span> sudo -E bash -
</span></span><span class="line"><span class="cl">sudo apt-get install -y nodejs
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Verify installation</span>
</span></span><span class="line"><span class="cl">node --version
</span></span><span class="line"><span class="cl">npm --version</span></span></code></pre></div></div>

<h3 class="relative group">Installation of the main components
    <div id="installation-of-the-main-components" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#installation-of-the-main-components" aria-label="Anchor">#</a>
    </span>
    
</h3>

<h4 class="relative group">Install the HTTP Gateway and Gemini-cli
    <div id="install-the-http-gateway-and-gemini-cli" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#install-the-http-gateway-and-gemini-cli" aria-label="Anchor">#</a>
    </span>
    
</h4>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Install supergateway globally</span>
</span></span><span class="line"><span class="cl">sudo npm install -g supergateway
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install Gemini CLI globally</span>
</span></span><span class="line"><span class="cl">sudo npm install -g @google/gemini-cli
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Verify installations</span>
</span></span><span class="line"><span class="cl">supergateway --version
</span></span><span class="line"><span class="cl">gemini --version</span></span></code></pre></div></div>

<h4 class="relative group">Clone and Install Okta MCP Server
    <div id="clone-and-install-okta-mcp-server" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#clone-and-install-okta-mcp-server" aria-label="Anchor">#</a>
    </span>
    
</h4>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Set installation directory</span>
</span></span><span class="line"><span class="cl"><span class="nv">BASE_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/okta-mcp-server&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Clone the official Okta MCP Server repository</span>
</span></span><span class="line"><span class="cl">git clone https://github.com/okta/okta-mcp-server.git <span class="s2">&#34;</span><span class="nv">$BASE_DIR</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> <span class="s2">&#34;</span><span class="nv">$BASE_DIR</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install Python dependencies using uv</span>
</span></span><span class="line"><span class="cl">python3.13 -m uv sync
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create directories for logs and configuration</span>
</span></span><span class="line"><span class="cl">mkdir -p <span class="s2">&#34;</span><span class="nv">$BASE_DIR</span><span class="s2">/logs&#34;</span>
</span></span><span class="line"><span class="cl">mkdir -p <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config/okta-mcp&#34;</span></span></span></code></pre></div></div>

<h4 class="relative group">Configure Environment Variables
    <div id="configure-environment-variables" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configure-environment-variables" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Create a <code>.env</code> file with your Okta configuration:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Create .env file</span>
</span></span><span class="line"><span class="cl">cat &gt; <span class="s2">&#34;</span><span class="nv">$BASE_DIR</span><span class="s2">/.env&#34;</span> <span class="s">&lt;&lt; &#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s"># Okta Configuration
</span></span></span><span class="line"><span class="cl"><span class="s"># Okta Configuration
</span></span></span><span class="line"><span class="cl"><span class="s">OKTA_ORG_URL=https://your-org.okta.com
</span></span></span><span class="line"><span class="cl"><span class="s">OKTA_CLIENT_ID=your_client_id
</span></span></span><span class="line"><span class="cl"><span class="s">OKTA_SCOPES=okta.users.read okta.groups.read okta.apps.read okta.logs.read
</span></span></span><span class="line"><span class="cl"><span class="s">OKTA_KEY_ID=your_key_id
</span></span></span><span class="line"><span class="cl"><span class="s"># Private Key (formatted as single line with \n)
</span></span></span><span class="line"><span class="cl"><span class="s">OKTA_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----
</span></span></span><span class="line"><span class="cl"><span class="s"># OKTA_LOG_LEVEL=DEBUG
</span></span></span><span class="line"><span class="cl"><span class="s"># OKTA_TIMEOUT=30
</span></span></span><span class="line"><span class="cl"><span class="s"># OKTA_MAX_RETRIES=3
</span></span></span><span class="line"><span class="cl"><span class="s"># OKTA_RATE_LIMIT=600
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s"># Gemini Configuration
</span></span></span><span class="line"><span class="cl"><span class="s"># GEMINI_CLI_VERSION=sandbox:0.7.0
</span></span></span><span class="line"><span class="cl"><span class="s">GEMINI_API_KEY=your_gemini_api_key
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Edit the file with your actual values</span>
</span></span><span class="line"><span class="cl">nano <span class="s2">&#34;</span><span class="nv">$BASE_DIR</span><span class="s2">/.env&#34;</span></span></span></code></pre></div></div>
<p><strong>Important</strong>: For the private key, convert it to a single line with <code>\n</code> separators:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Convert your private key file to the correct format</span>
</span></span><span class="line"><span class="cl">awk <span class="s1">&#39;NF {sub(/\r/, &#34;&#34;); printf &#34;%s\\n&#34;,$0;}&#39;</span> your_private_key.pem</span></span></code></pre></div></div>

<h4 class="relative group">Create Systemd Service for HTTP Gateway
    <div id="create-systemd-service-for-http-gateway" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#create-systemd-service-for-http-gateway" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Create a systemd service to run the MCP Server with HTTP Gateway:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Create systemd service file</span>
</span></span><span class="line"><span class="cl">sudo tee /etc/systemd/system/okta-mcp-gateway.service &gt; /dev/null <span class="s">&lt;&lt; EOF
</span></span></span><span class="line"><span class="cl"><span class="s">[Unit]
</span></span></span><span class="line"><span class="cl"><span class="s">Description=Okta MCP HTTP Gateway
</span></span></span><span class="line"><span class="cl"><span class="s">After=network.target
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">[Service]
</span></span></span><span class="line"><span class="cl"><span class="s">Type=simple
</span></span></span><span class="line"><span class="cl"><span class="s">User=$USER
</span></span></span><span class="line"><span class="cl"><span class="s">WorkingDirectory=$BASE_DIR
</span></span></span><span class="line"><span class="cl"><span class="s">Environment=&#34;PATH=/usr/local/bin:/usr/bin:/bin&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">EnvironmentFile=$BASE_DIR/.env
</span></span></span><span class="line"><span class="cl"><span class="s">ExecStart=/usr/bin/npx supergateway --stdio &#34;python3.13 -m uv run okta-mcp-server&#34; --outputTransport streamableHttp --stateful --port \${GATEWAY_PORT}
</span></span></span><span class="line"><span class="cl"><span class="s">Restart=always
</span></span></span><span class="line"><span class="cl"><span class="s">RestartSec=10
</span></span></span><span class="line"><span class="cl"><span class="s">StandardOutput=append:$BASE_DIR/logs/gateway.log
</span></span></span><span class="line"><span class="cl"><span class="s">StandardError=append:$BASE_DIR/logs/gateway-error.log
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">[Install]
</span></span></span><span class="line"><span class="cl"><span class="s">WantedBy=multi-user.target
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Reload systemd daemon</span>
</span></span><span class="line"><span class="cl">sudo systemctl daemon-reload
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Enable the service to start on boot</span>
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> okta-mcp-gateway.service
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start the service</span>
</span></span><span class="line"><span class="cl">sudo systemctl start okta-mcp-gateway.service
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Check status</span>
</span></span><span class="line"><span class="cl">systemctl status okta-mcp-gateway.service</span></span></code></pre></div></div>

<h4 class="relative group">Configure Gemini CLI
    <div id="configure-gemini-cli" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configure-gemini-cli" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Create a configuration file for Gemini CLI to use the MCP server:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Create Gemini configuration directory</span>
</span></span><span class="line"><span class="cl">mkdir -p <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.gemini&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create settings.json with MCP server configuration</span>
</span></span><span class="line"><span class="cl">cat &gt; <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.gemini/settings.json&#34;</span> <span class="s">&lt;&lt; EOF
</span></span></span><span class="line"><span class="cl"><span class="s">{
</span></span></span><span class="line"><span class="cl"><span class="s">  &#34;mcpServers&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s">    &#34;okta-mcp-local&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s">      &#34;type&#34;: &#34;stdio&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s">      &#34;command&#34;: &#34;python3.13&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s">      &#34;args&#34;: [&#34;-m&#34;, &#34;uv&#34;, &#34;run&#34;, &#34;okta-mcp-server&#34;],
</span></span></span><span class="line"><span class="cl"><span class="s">      &#34;cwd&#34;: &#34;$BASE_DIR&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s">      &#34;env&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s">        &#34;OKTA_ORG_URL&#34;: &#34;$(grep OKTA_ORG_URL $BASE_DIR/.env | cut -d &#39;=&#39; -f2)&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s">        &#34;OKTA_CLIENT_ID&#34;: &#34;$(grep OKTA_CLIENT_ID $BASE_DIR/.env | cut -d &#39;=&#39; -f2)&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s">        &#34;OKTA_KEY_ID&#34;: &#34;$(grep OKTA_KEY_ID $BASE_DIR/.env | cut -d &#39;=&#39; -f2)&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s">        &#34;OKTA_PRIVATE_KEY&#34;: &#34;$(grep OKTA_PRIVATE_KEY $BASE_DIR/.env | cut -d &#39;=&#39; -f2-)&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s">        &#34;OKTA_SCOPES&#34;: &#34;$(grep OKTA_SCOPES $BASE_DIR/.env | cut -d &#39;=&#39; -f2)&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">      }
</span></span></span><span class="line"><span class="cl"><span class="s">    },
</span></span></span><span class="line"><span class="cl"><span class="s">    &#34;okta-mcp-gateway&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s">      &#34;type&#34;: &#34;http&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s">      &#34;url&#34;: &#34;http://localhost:8000/mcp&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">    }
</span></span></span><span class="line"><span class="cl"><span class="s">  },
</span></span></span><span class="line"><span class="cl"><span class="s">  &#34;defaultServer&#34;: &#34;okta-mcp-gateway&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">}
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span></span></span></code></pre></div></div>
<blockquote><p>The sample code implement both ways to connect to the MCP Server: via HTTP gateway or via stdio. You can choose what to use, or try both.
I suggest in any case to keep only one active at time.</p>
</blockquote>
<h4 class="relative group">Service Management Commands
    <div id="service-management-commands" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#service-management-commands" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>Use these commands to manage the Okta MCP Gateway service:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Start the service</span>
</span></span><span class="line"><span class="cl">sudo systemctl start okta-mcp-gateway
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Stop the service</span>
</span></span><span class="line"><span class="cl">sudo systemctl stop okta-mcp-gateway
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Restart the service</span>
</span></span><span class="line"><span class="cl">sudo systemctl restart okta-mcp-gateway
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Check service status</span>
</span></span><span class="line"><span class="cl">systemctl status okta-mcp-gateway
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># View real-time logs</span>
</span></span><span class="line"><span class="cl">journalctl -u okta-mcp-gateway -f
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># View last 50 log lines</span>
</span></span><span class="line"><span class="cl">journalctl -u okta-mcp-gateway -n <span class="m">50</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Enable service to start on boot</span>
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> okta-mcp-gateway
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Disable auto-start</span>
</span></span><span class="line"><span class="cl">sudo systemctl disable okta-mcp-gateway</span></span></code></pre></div></div>

<h3 class="relative group">Testing the Installation
    <div id="testing-the-installation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#testing-the-installation" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Test that everything is working correctly:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Test the HTTP gateway</span>
</span></span><span class="line"><span class="cl">curl http://localhost:8000
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Test Gemini CLI with default gateway server</span>
</span></span><span class="line"><span class="cl">gemini
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Inside Gemini, run:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># /mcp - to see available MCP servers</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Then try: &#34;How many users are in my Okta tenant?&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># View gateway logs</span>
</span></span><span class="line"><span class="cl">tail -f ~/okta-mcp-server/logs/gateway.log</span></span></code></pre></div></div>

<h3 class="relative group">Installation Paths
    <div id="installation-paths" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#installation-paths" aria-label="Anchor">#</a>
    </span>
    
</h3>
<table>
  <thead>
      <tr>
          <th>Component</th>
          <th>Path</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>MCP Server</td>
          <td><code>~/okta-mcp-server/</code></td>
      </tr>
      <tr>
          <td>Configuration</td>
          <td><code>~/okta-mcp-server/.env</code></td>
      </tr>
      <tr>
          <td>Logs</td>
          <td><code>~/okta-mcp-server/logs/</code></td>
      </tr>
      <tr>
          <td>Gemini Config</td>
          <td><code>~/.gemini/settings.json</code></td>
      </tr>
      <tr>
          <td>VS Code Config</td>
          <td><code>~/okta-mcp-server/.vscode/mcp.json</code></td>
      </tr>
      <tr>
          <td>Systemd Service</td>
          <td><code>/etc/systemd/system/okta-mcp-gateway.service</code></td>
      </tr>
  </tbody>
</table>

<h3 class="relative group">Troubleshooting (Native Installation)
    <div id="troubleshooting-native-installation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#troubleshooting-native-installation" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>
<p><strong>MCP Client Connection Issues</strong></p>
<ul>
<li>Check that <code>uv</code> is installed and accessible</li>
<li>Test with <code>curl http://localhost:8000</code></li>
<li>Check if service is running: <code>systemctl is-active okta-mcp-gateway</code></li>
<li>Verify firewall rules: <code>sudo ufw status</code></li>
<li>Check logs: <code>tail -f ~/okta-mcp-server/logs/gateway.log</code></li>
</ul>
</li>
<li>
<p><strong>Python or Node.js Version Issues</strong></p>
<ul>
<li>Verify Python 3.13 is installed: <code>python3.13 --version</code></li>
<li>Verify Node.js is installed: <code>node --version</code></li>
<li>Ensure <code>uv</code> is installed: <code>python3.13 -m uv --version</code></li>
<li>Check that <code>supergateway</code> is installed: <code>which supergateway</code></li>
</ul>
</li>
</ol>

<h3 class="relative group">Pro and Cons
    <div id="pro-and-cons" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#pro-and-cons" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p><strong>✅ Advantages of Native Installation:</strong></p>
<ul>
<li>Direct stdio access for local clients</li>
<li>Better performance (no container overhead)</li>
<li>System service integration with systemd</li>
<li>Auto-start on boot</li>
<li>Native file system access</li>
<li>Better for long-term dedicated servers</li>
</ul>
<p><strong>❌ Disadvantages of Native Installation:</strong></p>
<ul>
<li>System-wide changes required</li>
<li>Harder to clean up completely</li>
<li>Platform-specific (The instructions provided here are Ubuntu only, but you can adapt them to other platform)</li>
<li>Manual updates required</li>
<li>Requires more system administration knowledge</li>
</ul>

<h2 class="relative group">Conclusion
    <div id="conclusion" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#conclusion" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The Okta Lab MCP setup provides a powerful way to integrate AI assistants with Okta&rsquo;s management APIs. Whether you choose the Docker-based approach for quick testing or the native installation for persistent development, you&rsquo;ll have a robust environment for exploring AI-driven Okta administration.</p>
<p>The Model Context Protocol opens up exciting possibilities for automating identity management tasks through natural language, making complex administrative operations accessible to both technical and non-technical users.</p>

<h3 class="relative group">Next Steps
    <div id="next-steps" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#next-steps" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li>Explore the <a href="https://github.com/okta/okta-mcp-server#-supported-tools"  target="_blank" rel="noreferrer">official Okta MCP Server tools</a> and Okta&rsquo;s blog post <a href="https://developer.okta.com/blog/2025/09/22/okta-mcp-server"  target="_blank" rel="noreferrer">Introducing the Okta MCP Server</a></li>
<li>Check the <a href="https://github.com/fabiograsso/okta-lab-mcp"  target="_blank" rel="noreferrer">okta-lab-mcp repository</a> for Docker-based setup and additional tools</li>
<li>Join the <a href="https://modelcontextprotocol.io/community"  target="_blank" rel="noreferrer">MCP Community</a></li>
<li>Check out other <a href="https://modelcontextprotocol.io/servers"  target="_blank" rel="noreferrer">MCP Servers</a> for additional integrations</li>
<li>Consider building custom MCP tools for your organization&rsquo;s specific needs</li>
</ul>
<hr>
<p>Questions or feedback? Feel free to <a href="/howto/okta-lab-mcp/#comments" >comment on this blog</a> or open an issue on the <a href="https://github.com/fabiograsso/okta-lab-mcp"  target="_blank" rel="noreferrer">okta-lab-mcp repository</a>.</p>
]]></content:encoded>
      <category>okta</category>
      <category>mcp</category>
      <category>api</category>
      <category>claude</category>
      <category>ai</category>
      <category>automation</category>
      <category>gemini</category>
      <category>vscode</category>
      <category>llm</category>
    </item>
    <item>
      <title>Okta RADIUS Agent &#43; Test Client &#43; OpenVPN AS with Docker-compose</title>
      <link>https://iam.fabiograsso.net/howto/okta-radius-docker-compose/</link>
      <pubDate>Sun, 24 Aug 2025 06:25:00 +0000</pubDate>
      <guid>https://iam.fabiograsso.net/howto/okta-radius-docker-compose/</guid>
      <description>Complete Docker-compose stack for testing Okta RADIUS Agent with OpenVPN AS, including automated MFA test scripts and configuration examples. The guide covers setup, configuration, client IP reporting, supported factors, and security best practices.</description>
      <content:encoded>&lt;![CDATA[
<h2 class="relative group">Introduction
    <div id="introduction" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#introduction" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>In today’s identity and access management landscape, modern VPNs and network systems increasingly support advanced authentication protocols such as <a href="https://www.okta.com/identity-101/what-is-saml/"  target="_blank" rel="noreferrer">SAML</a> and <a href="https://developer.okta.com/docs/concepts/oidc/"  target="_blank" rel="noreferrer">OIDC (OpenID Connect)</a>. These protocols offer enhanced security and significantly improved user experiences, including features like Okta’s <a href="https://www.okta.com/product/fastpass/"  target="_blank" rel="noreferrer">FastPass</a>, which enables passwordless and frictionless authentication.</p>
<p>Despite this evolution, many organizations still rely on legacy systems where the RADIUS protocol remains widely used. RADIUS is a robust and proven standard for network authentication, particularly well-suited to integrating Multi-Factor Authentication (MFA) with VPN access. Okta provides dedicated support for RADIUS through its <a href="https://www.okta.com/integrations/radius/"  target="_blank" rel="noreferrer">Okta RADIUS agent</a>, offering a seamless way to add MFA protections to these existing infrastructures.</p>
<p>This lab environment is an ideal solution for anyone looking to quickly demo or build a proof of concept (PoC) for RADIUS-based MFA integration with Okta. It encapsulates the Okta RADIUS agent, a test client, and an optional OpenVPN Access Server in a Docker Compose setup, enabling fast and easy deployment without complex manual configuration.</p>
<p><strong>Note:</strong> While Docker is not officially supported by Okta for running the RADIUS agent in production environments, it serves as an excellent tool for demonstration, testing, and PoC purposes. This approach dramatically simplifies setup and accelerates experimentation with Okta RADIUS integrations.</p>

<h2 class="relative group">Quick instructions
    <div id="quick-instructions" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#quick-instructions" aria-label="Anchor">#</a>
    </span>
    
</h2>
<ol>
<li>Clone the following git repository: <a href="https://github.com/fabiograsso/okta-lab-radius" title="https://github.com/fabiograsso/okta-lab-radius" target="_blank" rel="noreferrer">https://github.com/fabiograsso/okta-lab-radius</a>.</li>
<li>Before starting, copy the Okta RADIUS Agent setup file (<code>OktaRadiusAgentSetup-x.xx.x.deb</code>) to the <code>./docker/okta-radius-agent/package</code> folder.</li>
<li>Edit the <code>.env</code> file, run <code>make config</code> , and then <code>make start</code></li>
</ol>

<h2 class="relative group">Prerequisites
    <div id="prerequisites" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#prerequisites" aria-label="Anchor">#</a>
    </span>
    
</h2>
<ul>
<li><a href="https://docs.docker.com/engine/install/"  target="_blank" rel="noreferrer">Docker</a> and <a href="https://docs.docker.com/compose/install/"  target="_blank" rel="noreferrer">Docker Compose</a> are installed.
<ul>
<li>If you are using a Ubuntu 24.04 Linux server:
<code>apt-get install -y docker docker-compose</code></li>
<li>You can also use <a href="https://docs.docker.com/desktop/"  target="_blank" rel="noreferrer">Docker Desktop</a>.</li>
</ul>
</li>
<li>Git clone the following project:
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt-get install -y git <span class="c1"># if git is not yet installed</span>
</span></span><span class="line"><span class="cl">git clone https://github.com/fabiograsso/okta-lab-radius</span></span></code></pre></div></div>
Or, if you don’t want to use git:
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -o lab-radius.tar.gz https://github.com/fabiograsso/okta-lab-radius/main.tar.gz 
</span></span><span class="line"><span class="cl">tar -xvf  lab-radius.tar.gz<span class="sb">`</span></span></span></code></pre></div></div>
</li>
<li>OKTA RADIUS Agent Installation Package:
<ul>
<li>Download the <code>.deb</code> file of the RADIUS Agent and copy it into the <code>./docker/okta-radius-agent/package</code> folder.</li>
<li>Alternatively, insert the download URL in the <code>DEB_URL</code> variable in the <code>.env</code> file.</li>
</ul>
</li>
</ul>

<h2 class="relative group">Okta RADIUS App Configuration
    <div id="okta-radius-app-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#okta-radius-app-configuration" aria-label="Anchor">#</a>
    </span>
    
</h2>
<ol>
<li>Click Browse App Catalog.</li>
<li>Search for RADIUS App, select it, and then click Add Integration.</li>
<li>Enter a unique app label and click Next.</li>
<li>In the <strong>Sign On</strong> tab, do the following:
<ul>
<li>Select the <strong>Authentication</strong> checkbox.</li>
<li>Enter a <strong>UDP Port</strong> (for example, 1812). The UDP port values of the app and the client gateway must match.</li>
<li>Enter the <strong>Secret Key</strong> to use to encrypt the user password. The secret key for the app and the client gateway must match. The maximum length of the secret key is 16 characters.</li>
<li>Select an appropriate username format from the <strong>Application username format</strong> dropdown.</li>
<li>To enable authentication with Active Directory (AD) UPN or AD Sam account name, do the following:
<ol>
<li>Select the <strong>Sign On</strong> tab.</li>
<li>Scroll to the <strong>Advanced RADIUS Settings</strong> section.</li>
<li>Click <strong>Edit</strong> .</li>
<li>In the <strong>Authentication</strong> section, select <strong>Enable UPN or SAM Account Name Login</strong> .
Users assigned to this app must have their username set to the AD user principal name before being assigned to the RADIUS app.
For the SAM account name to be used successfully, it must have the same prefix as the UPN.</li>
<li>Click <strong>Save</strong> .</li>
<li>Scroll to the <strong>Settings</strong> section of the <strong>Sign On</strong> tab.</li>
<li>Click <strong>Edit</strong> .</li>
<li>Select <strong>Email</strong> from the <strong>Application username format</strong> dropdown to import users are imported with their full <code>username@domain.com</code> value.</li>
<li>Click <strong>Save</strong> .</li>
</ol>
</li>
</ul>
</li>
<li>Add to groups
<ol>
<li>On the app page, click the <strong>Assignments</strong> tab.</li>
<li>Click <strong>Assign to Groups</strong> .</li>
<li>Find the group that you want to assign the app to and click <strong>Assign</strong> .</li>
<li>Repeat for any additional groups.</li>
<li>Click <strong>Done</strong> .</li>
</ol>
</li>
</ol>
<p>Reference doc: <a href="https://help.okta.com/oie/en-us/content/topics/integrations/okta_radius_app-gen-add-app.htm"  target="_blank" rel="noreferrer"><strong>Add the RADIUS app | Okta Identity Engine</strong></a></p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-radius-docker-compose/okta-radius-flow_hu_4cb7e7d2d7986b49.webp  330w,
      /howto/okta-radius-docker-compose/okta-radius-flow_hu_630ca3840776a99.webp  660w,
      /howto/okta-radius-docker-compose/okta-radius-flow_hu_c937f481b7567302.webp  960w,
      /howto/okta-radius-docker-compose/okta-radius-flow_hu_cd92bd0dbe11c8.webp 1280w,
      /howto/okta-radius-docker-compose/okta-radius-flow_hu_d64183ccc274230a.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-radius-docker-compose/okta-radius-flow.jpg"
    src="/howto/okta-radius-docker-compose/okta-radius-flow.jpg">


  
</figure>

<h2 class="relative group">Run the Docker Compose
    <div id="run-the-docker-compose" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#run-the-docker-compose" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Configuration
    <div id="configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuration" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Rename the <code>.env.sample</code> file to <code>.env</code>, and change the parameters based on your configuration:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Okta tenant</span>
</span></span><span class="line"><span class="cl"><span class="nv">OKTA_ORG</span><span class="o">=</span>xxxxx.okta.com 
</span></span><span class="line"><span class="cl"><span class="c1"># RADIUS Secret and Port, as defined in the Okta RADIUS App configuration</span>
</span></span><span class="line"><span class="cl"><span class="nv">RADIUS_SECRET</span><span class="o">=</span>mysecret123
</span></span><span class="line"><span class="cl"><span class="nv">RADIUS_PORT</span><span class="o">=</span><span class="m">1812</span> 
</span></span><span class="line"><span class="cl"><span class="c1"># Password for the &#39;openvpn&#39; admin user </span>
</span></span><span class="line"><span class="cl"><span class="nv">OPENVPN_PASSWORD</span><span class="o">=</span>admin
</span></span><span class="line"><span class="cl"><span class="c1">#Optional for the test script</span>
</span></span><span class="line"><span class="cl"><span class="c1">#TEST_USERNAME=radius.user </span>
</span></span><span class="line"><span class="cl"><span class="c1">#TEST_PASSWORD=Radius.pass </span>
</span></span><span class="line"><span class="cl"><span class="c1">#TEST_IP=127.0.0.1</span></span></span></code></pre></div></div>

<h3 class="relative group">How to start
    <div id="how-to-start" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-to-start" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Only for the first time, you will need to run <code>make configure</code>, in order to start the Okta RADIUS Agent Configuration.</p>
<p>Then follow the onscreen instructions. At the end, you will see something like:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Please visit the URL: &lt;https://xxxx.okta.com/oauth2/auth?code=9hkkimp1&gt;
</span></span><span class="line"><span class="cl">before Wed Aug 06 14:10:27 UTC 2025 to authenticate and continue agent registration.</span></span></code></pre></div></div>
<p>Open the provided URL and confirm the registration.</p>
<p>Then, run <code>make start</code>, to start the compose stack.</p>
<p>For the following run, you can just run <code>make start</code>, as the configuration is already done.</p>

<h3 class="relative group">Commands Recap
    <div id="commands-recap" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#commands-recap" aria-label="Anchor">#</a>
    </span>
    
</h3>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Start the stack</span>
</span></span><span class="line"><span class="cl">make start
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run the RADIUS Agent configuration</span>
</span></span><span class="line"><span class="cl">make config
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run the RADIUS client to test</span>
</span></span><span class="line"><span class="cl">make radius-test
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># View the full command list</span>
</span></span><span class="line"><span class="cl">make help</span></span></code></pre></div></div>

<h2 class="relative group">Test Script
    <div id="test-script" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-script" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>This bundle includes a Bash script utility for testing RADIUS authentication, designed specifically to handle interactive multi-factor authentication (MFA) challenges. It initiates an authentication attempt using the <code>radclient</code> tool. The script can be configured via command-line arguments, environment variables, or interactive prompts if no other values are provided.</p>
<p>Its main feature is the ability to parse <code>Access-Challenge</code> responses from the RADIUS server. When an MFA challenge is received, the script prompts the user for the required input (such as an OTP or a factor selection) and submits it back to the server. This process repeats until a final <code>Access-Accept</code> or <code>Access-Reject</code> is received. Finally, it analyzes the server&rsquo;s reply message to provide a clear, success or failure status.</p>
<p>In order to test the RADIUS Authentication, you have three options:</p>
<ol>
<li>
<p>Run <code>make radius-test</code> and digit the username and password when prompted</p>
</li>
<li>
<p>Pass the <em>username</em> and <em>password</em> as arguments to the command:
<code>make radius-test USERNAME=myuser@test.email PASSWORD=mypassword</code></p>
</li>
<li>
<p>Set username and password in the .env file:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">TEST_USERNAME</span><span class="o">=</span>myuser@test.email
</span></span><span class="line"><span class="cl"><span class="nv">TEST_PASSWORD</span><span class="o">=</span>mypassword</span></span></code></pre></div></div>
<p>Then run <code>make radius-test</code></p>
</li>
</ol>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >The script <a href="https://github.com/fabiograsso/okta-lab-radius/blob/main/docker/okta-radius-agent/test-radius.sh" title="https://github.com/fabiograsso/okta-lab-radius/blob/main/docker/okta-radius-agent/test-radius.sh" target="_blank" rel="noreferrer">test-radius.sh</a> can also be used outside this Docker compose stack, just remember to install <code>radclient</code> (with <code>apt-get install freeradius-utils</code> on Ubuntu/Debian)</span>
</div>


<h3 class="relative group">Flowchart
    <div id="flowchart" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#flowchart" aria-label="Anchor">#</a>
    </span>
    
</h3>
<pre class="not-prose mermaid">
---
config:
  layout: dagre
---
flowchart TD
    A["🙎‍♂️ Start Test Script"] --> B["🪪 Get Credentials"]
    B --> G["Send RADIUS Access-Request"]
    I{"RADIUS Response?"} -- "Access-Accept" --> J["✅ Authentication Successful"]
    I -- "Access-Reject" --> K["❌ Authentication Failed"]
    I -- "Access-Challenge" --> L["🧐 Parse MFA Challenge"]
    L --> M["🔐 Display Challenge Message to User"]
    O["User Provides MFA Input"] --> P["Send Access-Request with State + MFA Response"]
    G --> I
    J --> T["🏁 End"]
    K --> T
    M --> O
    P --> I
     G:::Peach
     G:::Ash
     L:::Ash
     L:::Okta
     M:::Ash
     M:::Okta
     O:::Ash
     P:::Okta
    classDef Peach stroke-width:1px, stroke-dasharray:none, stroke:#FBB35A, fill:#FFEFDB, color:#8F632D
    classDef Ash stroke-width:1px, stroke-dasharray:none, stroke:#999999, fill:#EEEEEE, color:#000000
    classDef Okta stroke-width:1px, stroke-dasharray:none, fill:#fff3cd, stroke:#856404, color:#856404
    style J fill:#d4edda,stroke:#155724,color:#155724
    style K fill:#f8d7da,stroke:#721c24,color:#721c24
</pre>


<h3 class="relative group">Sequence Diagram
    <div id="sequence-diagram" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sequence-diagram" aria-label="Anchor">#</a>
    </span>
    
</h3>
<pre class="not-prose mermaid">
sequenceDiagram
  actor User as User
  participant TestScript as TestScript
  participant radclient as radclient
  participant RADIUS_Agent as RADIUS_Agent
  participant Okta as Okta
  autonumber
  User ->> TestScript: Start Script
  TestScript ->> User: Prompt for Credentials
  User ->> TestScript: Enter Username/Password
  TestScript ->> radclient: Send Access-Request (credentials)
  radclient ->> RADIUS_Agent: Forward Request
  RADIUS_Agent ->> Okta: Authenticate Credentials
  Okta -->> RADIUS_Agent: Authentication Response
  RADIUS_Agent -->> radclient: Return RADIUS Response (Accept/Reject/Challenge)
  radclient -->> TestScript: Parse Response
  alt Access-Accept
    TestScript ->> User: ✅ Display Success
  else Access-Reject
    TestScript ->> User: ❌ Display Failure/Error
  else Access-Challenge
    TestScript ->> User: 🔐 Prompt for MFA input
    User ->> TestScript: Provide MFA response
    TestScript ->> radclient: Send Response (with State)
    radclient ->> RADIUS_Agent: Forward MFA response
    RADIUS_Agent ->> Okta: Validate MFA
    Okta -->> RADIUS_Agent: MFA Result
    RADIUS_Agent -->> radclient: Return RADIUS Response
    radclient -->> TestScript: Parse Response
    TestScript ->> User: Loop until Access-Accept/Reject
  end

</pre>


<h2 class="relative group">Notes
    <div id="notes" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#notes" aria-label="Anchor">#</a>
    </span>
    
</h2>
<ul>
<li>
<p>Persistent configuration will be stored in the <code>data</code> folder.</p>
</li>
<li>
<p>The docker-compose exposes UDP port <code>1812</code>. To use a different port, change the <code>RADIUS_PORT</code> in the <code>.env</code> file.</p>
</li>
<li>
<p>I tried to parse all the possible outputs of the <code>radclient</code> command, but it’s possible that I missed some use case. You can run the <code>radclient</code> manually by executing:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;User-Name=testuser,User-Password=testpass&#34;</span> <span class="p">|</span> docker-compose <span class="nb">exec</span> -T radius-client radclient -x radius-agent:1812 auth test123</span></span></code></pre></div></div>
</li>
<li>
<p>Based on the settings of the RADIUS app in Okta, you can:</p>
<ul>
<li>Skip the password verification and do only the MFA challenge</li>
<li>Default to the push if the user has Okta Verify enrolled</li>
<li>Pass the preferred MFA method after the password, separated by a comma. Example <code>mypassword,push</code>
The available methods are: <code>PUSH</code>, <code>SMS</code>, <code>CALL</code>, or <code>EMAIL</code></li>
<li>Pass the OTP after the password, separated by a comma. Example <code>mypassword,123456</code></li>
</ul>
</li>
<li>
<p><code>radclient</code> automatically generates <code>Message-Authenticator</code> when needed, so you can enable the feature in the Okta configuration</p>
</li>
<li>
<p>To simplify, I’ve used PAP authentication. This is also the supported method for OpenVPN AS.</p>
</li>
</ul>

<h2 class="relative group">OpenVPN AS
    <div id="openvpn-as" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#openvpn-as" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The Docker Compose includes a pre-configured OpenVPN AS Server. The free license includes 2 concurrent connections, which is more than enough for a test/demo environment.</p>
<p>You can open the OpenVPN management console at <a href="https://localhost:943/admin/settings"  target="_blank" rel="noreferrer">https://localhost:943/admin/settings</a></p>
<p>You will find the RADIUS configuration already prepared and active:</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="RADIUS Configuration in OpenVPN"
    srcset="
      /howto/okta-radius-docker-compose/openvpn1_hu_3953da63e183266d.webp  330w,
      /howto/okta-radius-docker-compose/openvpn1_hu_fa9f369524ff3c34.webp  660w,
      /howto/okta-radius-docker-compose/openvpn1_hu_8a4dc43e4675df9d.webp  960w,
      /howto/okta-radius-docker-compose/openvpn1_hu_86443eae02fdbc58.webp 1280w,
      /howto/okta-radius-docker-compose/openvpn1_hu_71cdcc674e92a087.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-radius-docker-compose/openvpn1.png"
    src="/howto/okta-radius-docker-compose/openvpn1.png">


  
</figure>
<p>You can then test to log in with a user at <a href="https://localhost/login"  target="_blank" rel="noreferrer">https://localhost/login</a></p>
<p>After inserting the username/password, it will require the MFA challenge:</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="MFA Challenge in OpenVPN"
    srcset="
      /howto/okta-radius-docker-compose/openvpn2_hu_efaa704192f1125b.webp  330w,
      /howto/okta-radius-docker-compose/openvpn2_hu_7701f05b13b37c67.webp  660w,
      /howto/okta-radius-docker-compose/openvpn2_hu_51fb78aeed07e622.webp  960w,
      /howto/okta-radius-docker-compose/openvpn2_hu_388131dcbd9432b9.webp 1280w,
      /howto/okta-radius-docker-compose/openvpn2_hu_fb1be83d4b27827.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-radius-docker-compose/openvpn2.png"
    src="/howto/okta-radius-docker-compose/openvpn2.png">


  
</figure>
<style type='text/css'>
img[alt="MFA Challenge in OpenVPN"] { width: 300px; }
</style>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >OpenVPN AS doesn’t support multiline MFA prompts. Remember to enable the flag “Single-line MFA prompt” in the Okta RADIUS app configuration.









<figure>
    <img class="my-0 rounded-md" loading="lazy" alt="" src="openvpn3-oktasettings.png">


  
</figure></span>
</div>


<h2 class="relative group">Client IP Consideration
    <div id="client-ip-consideration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#client-ip-consideration" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>When integrating a VPN or network access device with Okta via the RADIUS protocol, a critical aspect to consider is the visibility of the end-user&rsquo;s original IP address. Correctly configuring the client IP ensures that Okta security policies, such as <strong>Network Zones</strong> and <strong>behavior detection</strong> , function as intended.</p>

<h3 class="relative group">Why is the Client IP Important?
    <div id="why-is-the-client-ip-important" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-is-the-client-ip-important" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Passing the true client IP address to Okta is essential for:</p>
<ul>
<li><strong>Applying Location-Based Policies:</strong> Enforce or relax security measures based on the user&rsquo;s geographical location by using Okta&rsquo;s Network Zones.</li>
<li><strong>Behavior and Threat Detection:</strong> Allow Okta to accurately identify suspicious activity, such as logins from impossible-to-travel-to locations or unfamiliar devices.</li>
<li><strong>Enhanced Security Auditing:</strong> Provide clearer and more accurate logs in the Okta System Log for security analysis and incident response.</li>
</ul>

<h3 class="relative group">How Okta Handles the Client IP
    <div id="how-okta-handles-the-client-ip" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-okta-handles-the-client-ip" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>To solve this, many RADIUS-enabled devices can send the end-user&rsquo;s IP address within a specific RADIUS attribute. The Okta RADIUS Server Agent is designed to look for the client IP in the following standard RADIUS attributes, such as: <code>Calling-Station-Id</code> or <code>Framed-IP-Address</code></p>
<p>If your device, like OpenVPN AS, can be configured to send one of these attributes containing the client&rsquo;s public IP address, Okta will use it to evaluate security policies.</p>

<h3 class="relative group">Configuration Steps
    <div id="configuration-steps" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuration-steps" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>While this document does not cover the specific configuration within specific VPN services, the general steps are as follows:</p>
<ol>
<li><strong>Configure VPN:</strong> You must configure your OpenVPN Access Server to include the IP attribute in the RADIUS packets it sends to the Okta RADIUS Agent. Consult the OpenVPN AS documentation for instructions on how to add RADIUS attributes to authentication requests.</li>
<li><strong>Configure the Okta RADIUS App:</strong> In your Okta admin console, navigate to the RADIUS application you&rsquo;ve configured.
<ul>
<li>Go to the <strong>Sign On</strong> tab.</li>
<li>In the &ldquo;<strong>Advanced RADIUS Settings</strong> &quot; section, locate the <strong>Report client IP</strong> setting.</li>
<li>From the dropdown menu, select the attribute that your VPN Server is configured to send (i.e. <code>Calling-Station-Id</code> or <code>Framed-IP-Address</code>).</li>
<li>For OpenVPN (and for our test script), you can use <code>31 Calling-Station-Id</code>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-radius-docker-compose/radius-report-client-ip_hu_d932bea94ba5be39.webp  330w,
      /howto/okta-radius-docker-compose/radius-report-client-ip_hu_6c7e2b5d8a989f9a.webp  660w,
      /howto/okta-radius-docker-compose/radius-report-client-ip_hu_e36c0ef8633871c9.webp  960w,
      /howto/okta-radius-docker-compose/radius-report-client-ip_hu_1141ef0951db69f2.webp 1280w,
      /howto/okta-radius-docker-compose/radius-report-client-ip_hu_44cf329f97374ddf.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-radius-docker-compose/radius-report-client-ip.png"
    src="/howto/okta-radius-docker-compose/radius-report-client-ip.png">


  
</figure>
</li>
</ul>
</li>
</ol>
<p>For more technical details, you can refer to the official Okta documentation: <a href="https://help.okta.com/oie/en-us/content/topics/integrations/okta_radius_app-gen-client-ip.htm"  target="_blank" rel="noreferrer"><strong>Client IP reporting | Okta Identity Engine</strong></a>.</p>

<h2 class="relative group">Limitations and Supported Factors
    <div id="limitations-and-supported-factors" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#limitations-and-supported-factors" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>When deploying the Okta RADIUS Server Agent, it&rsquo;s important to be aware of its capabilities and constraints to ensure a successful and secure integration.</p>

<h3 class="relative group">Limitations
    <div id="limitations" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#limitations" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The Okta RADIUS Agent has the following limitations:</p>
<ul>
<li><strong>Authentication Protocol:</strong> The agent exclusively supports the Password Authentication Protocol (PAP). Other protocols, such as CHAP or EAP, are not supported.</li>
<li><strong>WiFi Infrastructure:</strong> The agent is not designed to support WiFi infrastructure integrations.</li>
<li><strong>Single Okta Verify Enrollment:</strong> To avoid unexpected behavior, users should only have a single Okta Verify device enrolled.</li>
</ul>

<h3 class="relative group">Supported Factors
    <div id="supported-factors" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#supported-factors" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The Okta RADIUS integration doesn’t support all the Factors supported by Okta. Especially, factors that require browser interaction (i.e., FastPass or FIDO2) are not supported. Also, Number Challenge is not supported.</p>
<ul>
<li><strong>Factor Enrollment:</strong> Okta recommends that users enroll no more than eight MFA factors at a time. If too many factors are enrolled, the challenge message displayed in the RADIUS prompt can become too large, leading to a poor user experience.</li>
<li><strong>Passwordless Mode (2FA Only):</strong> You can configure the RADIUS application to bypass primary password authentication and proceed directly to the second factor challenge. This is also known as &ldquo;passwordless mode&rdquo; and is configured by clearing the <strong>Okta performs primary authentication</strong> checkbox in the application settings.</li>
</ul>
<p>For detailed information and a list of supported factors, you can refer to the following documentation: <a href="https://help.okta.com/oie/en-us/content/topics/integrations/okta_radius_app.htm"  target="_blank" rel="noreferrer"><strong>RADIUS applications in Okta | Okta Identity Engine</strong></a></p>

<h2 class="relative group">Production Considerations
    <div id="production-considerations" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#production-considerations" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>This is just a demo/example project for test and experiment with the Okta RADIUS Agent. In production remember to implement some best-practices:</p>
<ul>
<li><strong>Network Security</strong>
<ul>
<li>Use firewall rules to restrict access to the RADIUS port (default 1812)</li>
<li>Consider placing the RADIUS agent in a DMZ or secured network segment</li>
<li>Use strong, unique secret keys for each RADIUS client</li>
<li>When supported, prefer more secure protocols such as EAP-TTLS</li>
</ul>
</li>
<li><strong>Client IP Reporting</strong> Configure proper client IP reporting to enable Okta&rsquo;s location-based policies. See <a href="/howto/okta-radius-docker-compose/#client-ip-consideration" >Client IP Consideration</a></li>
<li><strong>HA and Performance</strong>
<ul>
<li>Install at leas two RADIUS Agent, in order to guarantee high availability and eventually load balancing. Define if use a load balancer or an active/passive aproach, depending also on what is supported with your RADIUS Client: <a href="https://help.okta.com/oie/en-us/content/topics/integrations/radius-best-pract-deploy-arch.htm"  target="_blank" rel="noreferrer">RADIUS deployment architectures</a>.</li>
<li>If you&rsquo;re using a Load Balancer, consider also <a href="https://help.okta.com/oie/en-us/content/topics/integrations/radius-best-pract-sess-per.htm"  target="_blank" rel="noreferrer">RADIUS session persistence best practices</a></li>
<li>Fine tune the RADIUS Agent properties, especially <code>ragent.num_request_threads</code> and <code>ragent.num_max_http_connection</code>:
<ul>
<li><a href="https://help.okta.com/oie/en-us/content/topics/integrations/agent_install_linux-conf-properties.htm"  target="_blank" rel="noreferrer">Configure properties</a></li>
<li><a href="https://help.okta.com/oie/en-us/content/topics/integrations/radius-best-pract-thruput.htm"  target="_blank" rel="noreferrer">RADIUS throughput and scaling benchmarks</a></li>
</ul>
</li>
</ul>
</li>
<li><strong>Monitoring and Auditing</strong>
<ul>
<li>Review Okta System Logs regularly for authentication events</li>
<li>Monitor RADIUS agent logs for failed authentication attempts</li>
<li>Set up alerts for repeated authentication failures</li>
</ul>
</li>
</ul>

<h2 class="relative group">Appendix
    <div id="appendix" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#appendix" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Documentation
    <div id="documentation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#documentation" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ul>
<li><a href="https://help.okta.com/oie/en-us/content/topics/integrations/ha-main.htm"  target="_blank" rel="noreferrer">Okta  RADIUS Integrations | Okta Identity Engine</a></li>
<li><a href="https://help.okta.com/oie/en-us/content/topics/integrations/getting-started.htm"  target="_blank" rel="noreferrer">Getting started with Okta RADIUS Integrations | Okta Identity Engine</a></li>
<li><a href="https://help.okta.com/oie/en-us/content/topics/integrations/okta_radius_app.htm"  target="_blank" rel="noreferrer">RADIUS applications in Okta | Okta Identity Engine</a></li>
<li><a href="https://help.okta.com/oie/en-us/content/topics/integrations/integrations.htm"  target="_blank" rel="noreferrer">RADIUS integrations | Okta Identity Engine</a></li>
<li><a href="https://wiki.freeradius.org/config/Radclient"  target="_blank" rel="noreferrer">Radclient</a></li>
<li><a href="https://support.okta.com/help/s/article/RADIUS-MFA-is-bypassing-other-options-and-immediately-sending-the-user-a-Verify-Push?language=en_US" title="https://support.okta.com/help/s/article/RADIUS-MFA-is-bypassing-other-options-and-immediately-sending-the-user-a-Verify-Push?language=en_US" target="_blank" rel="noreferrer">RADIUS Multi-Factor Authentication to Send Automatic Push Notification</a></li>
<li>Security considerations: <a href="https://support.okta.com/help/s/article/securing-radius-authentication-against-malicious-logins?language=en_US" title="https://support.okta.com/help/s/article/securing-radius-authentication-against-malicious-logins?language=en_US" target="_blank" rel="noreferrer">Securing RADIUS Authentication Against Malicious Logins</a></li>
<li>An alternative test client for Windows: <a href="https://support.okta.com/help/s/article/how-to-test-if-okta-radius-agent-radius-application-is-working-properly-with-ntradping?language=en_US" title="https://support.okta.com/help/s/article/how-to-test-if-okta-radius-agent-radius-application-is-working-properly-with-ntradping?language=en_US" target="_blank" rel="noreferrer">How to Test if Okta RADIUS Agent / RADIUS Application is Working Properly with NTRadPing</a></li>
<li><a href="https://www.okta.com/resources/ebook/integration-patterns-for-legacy-applications/"  target="_blank" rel="noreferrer">Integration Patterns for Legacy Applications</a></li>
</ul>

<h3 class="relative group">Sample Output
    <div id="sample-output" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sample-output" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Example of output from <code>radclient</code>. These outputs are parsed by the <a href="/howto/okta-radius-docker-compose/#test-script" >test script</a>.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># Case 1 - Push
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Sent Access-Request Id 206 from 0.0.0.0:53925 to 172.19.0.4:1812 length 111
</span></span><span class="line"><span class="cl">	User-Name = &#34;myuser@okta.com&#34;
</span></span><span class="line"><span class="cl">	User-Password = &#34;mypassword,push&#34;
</span></span><span class="line"><span class="cl">	Cleartext-Password = &#34;mypassword,push&#34;
</span></span><span class="line"><span class="cl">Sent Access-Request Id 206 from 0.0.0.0:53925 to 172.19.0.4:1812 length 111
</span></span><span class="line"><span class="cl">	User-Name = &#34;myuser@okta.com&#34;
</span></span><span class="line"><span class="cl">	User-Password = &#34;mypassword,push&#34;
</span></span><span class="line"><span class="cl">	Cleartext-Password = &#34;mypassword,push&#34;
</span></span><span class="line"><span class="cl">Received Access-Accept Id 206 from 172.19.0.4:1812 to 172.19.0.2:53925 length 70
</span></span><span class="line"><span class="cl">	Message-Authenticator = 0x4486d15514efd4f5dba5d26b62d0c2fa
</span></span><span class="line"><span class="cl">	Reply-Message = &#34;Welcome myuser@okta.com!&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Case 2 - SMS
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">(0) -: Expected Access-Accept got Access-Challenge
</span></span><span class="line"><span class="cl">Sent Access-Request Id 216 from 0.0.0.0:53608 to 172.19.0.4:1812 length 111
</span></span><span class="line"><span class="cl">	User-Name = &#34;myuser@okta.com&#34;
</span></span><span class="line"><span class="cl">	User-Password = &#34;mypassword,sms&#34;
</span></span><span class="line"><span class="cl">	Cleartext-Password = &#34;mypassword,sms&#34;
</span></span><span class="line"><span class="cl">Received Access-Challenge Id 216 from 172.19.0.4:1812 to 172.19.0.2:53608 length 211
</span></span><span class="line"><span class="cl">	Message-Authenticator = 0x22006e1a7dd6c98b45556c3650730a31
</span></span><span class="line"><span class="cl">	Reply-Message = &#34;Enter the code sent to your phone.\nEnter &#39;0&#39; to abort.\n&#34;
</span></span><span class="line"><span class="cl">	State = 0x794f7151376c42327646686e376b61636650395a2f43654972507959764a52365278484334645773534c47444f445156304838366475736d32467a72707558664939366553372f7764544b696a7150456364343631582b5a787278644b2b444a654d5066724f384f6d35633d
</span></span><span class="line"><span class="cl">	Session-Timeout = 60
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Case 3 - OTP
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Sent Access-Request Id 227 from 0.0.0.0:36997 to 172.19.0.4:1812 length 111
</span></span><span class="line"><span class="cl">	User-Name = &#34;myuser@okta.com&#34;
</span></span><span class="line"><span class="cl">	User-Password = &#34;mypassword,191813&#34;
</span></span><span class="line"><span class="cl">	Cleartext-Password = &#34;mypassword,191813&#34;
</span></span><span class="line"><span class="cl">Received Access-Accept Id 227 from 172.19.0.4:1812 to 172.19.0.2:36997 length 70
</span></span><span class="line"><span class="cl">	Message-Authenticator = 0xc0f9ae1aa6f5f62ecf689925cc23a47c
</span></span><span class="line"><span class="cl">	Reply-Message = &#34;Welcome myuser@okta.com!&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Case 4 - OTP with wrong code
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">(0) -: Expected Access-Accept got Access-Reject
</span></span><span class="line"><span class="cl">Sent Access-Request Id 222 from 0.0.0.0:46127 to 172.19.0.4:1812 length 111
</span></span><span class="line"><span class="cl">	User-Name = &#34;myuser@okta.com&#34;
</span></span><span class="line"><span class="cl">	User-Password = &#34;mypassword,191813&#34;
</span></span><span class="line"><span class="cl">	Cleartext-Password = &#34;mypassword,191813&#34;
</span></span><span class="line"><span class="cl">Received Access-Reject Id 222 from 172.19.0.4:1812 to 172.19.0.2:46127 length 134
</span></span><span class="line"><span class="cl">	Message-Authenticator = 0x516023275a7dbcff037bff24e90c44cf
</span></span><span class="line"><span class="cl">	Reply-Message = &#34;Authentication failed for user myuser@okta.com, reason --- Access denied. Invalid creds?&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Case 5 - Push Refused
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">(0) -: Expected Access-Accept got Access-Reject
</span></span><span class="line"><span class="cl">Sent Access-Request Id 173 from 0.0.0.0:52349 to 172.19.0.4:1812 length 111
</span></span><span class="line"><span class="cl">	User-Name = &#34;myuser@okta.com&#34;
</span></span><span class="line"><span class="cl">	User-Password = &#34;mypassword&#34;
</span></span><span class="line"><span class="cl">	Cleartext-Password = &#34;mypassword&#34;
</span></span><span class="line"><span class="cl">Sent Access-Request Id 173 from 0.0.0.0:52349 to 172.19.0.4:1812 length 111
</span></span><span class="line"><span class="cl">	User-Name = &#34;myuser@okta.com&#34;
</span></span><span class="line"><span class="cl">	User-Password = &#34;mypassword&#34;
</span></span><span class="line"><span class="cl">	Cleartext-Password = &#34;mypassword&#34;
</span></span><span class="line"><span class="cl">Sent Access-Request Id 173 from 0.0.0.0:52349 to 172.19.0.4:1812 length 111
</span></span><span class="line"><span class="cl">	User-Name = &#34;myuser@okta.com&#34;
</span></span><span class="line"><span class="cl">	User-Password = &#34;mypassword&#34;
</span></span><span class="line"><span class="cl">	Cleartext-Password = &#34;mypassword&#34;
</span></span><span class="line"><span class="cl">Received Access-Reject Id 173 from 172.19.0.4:1812 to 172.19.0.2:52349 length 134
</span></span><span class="line"><span class="cl">	Message-Authenticator = 0x2a3aed152db05367588b5210746a38c9
</span></span><span class="line"><span class="cl">	Reply-Message = &#34;Authentication failed for user myuser@okta.com, reason --- Access denied. Invalid creds?&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Case 6 - Push timeout
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Sent Access-Request Id 74 from 0.0.0.0:49880 to 172.19.0.4:1812 length 111
</span></span><span class="line"><span class="cl">	User-Name = &#34;myuser@okta.com&#34;
</span></span><span class="line"><span class="cl">	User-Password = &#34;mypassword&#34;
</span></span><span class="line"><span class="cl">	Cleartext-Password = &#34;mypassword&#34;
</span></span><span class="line"><span class="cl">Sent Access-Request Id 74 from 0.0.0.0:49880 to 172.19.0.4:1812 length 111
</span></span><span class="line"><span class="cl">	User-Name = &#34;myuser@okta.com&#34;
</span></span><span class="line"><span class="cl">	User-Password = &#34;mypassword&#34;
</span></span><span class="line"><span class="cl">	Cleartext-Password = &#34;mypassword&#34;
</span></span><span class="line"><span class="cl">Sent Access-Request Id 74 from 0.0.0.0:49880 to 172.19.0.4:1812 length 111
</span></span><span class="line"><span class="cl">	User-Name = &#34;myuser@okta.com&#34;
</span></span><span class="line"><span class="cl">	User-Password = &#34;mypassword&#34;
</span></span><span class="line"><span class="cl">	Cleartext-Password = &#34;mypassword&#34;
</span></span><span class="line"><span class="cl">(0) No reply from server for ID 74 socket 5</span></span></code></pre></div></div>
]]></content:encoded>
      <category>Okta</category>
      <category>RADIUS</category>
      <category>Docker</category>
      <category>MFA</category>
      <category>OpenVPN</category>
      <category>Authentication</category>
      <category>PAP</category>
      <category>Network</category>
      <category>VPN</category>
    </item>
    <item>
      <title>Citrix step-up MFA with Okta</title>
      <link>https://iam.fabiograsso.net/howto/okta-citrix-stepup-mfa/</link>
      <pubDate>Fri, 27 Jun 2025 00:00:00 +0000</pubDate>
      <guid>https://iam.fabiograsso.net/howto/okta-citrix-stepup-mfa/</guid>
      <description>Learn how to implement step-up MFA with Okta in Citrix environments. This article explores three practical solutions: Okta group-based policies, dual-StoreFront architecture, and Citrix ADC with nFactor authentication. Discover the best approach for your organization.</description>
      <content:encoded>&lt;![CDATA[<p>I received a request from a customer. They use Citrix XenApp, with some applications used for operations under <strong>PCI DSS</strong>. These applications are already separated and accessible only to some users.</p>
<p>They don’t use MFA for access to Citrix &ldquo;normal applications&rdquo; but, due to the PCI DSS regulation, they must implement the MFA for all PCI DSS applications.</p>
<p>Their initial request was to use the Okta MFA Credential Provider for Windows<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, with the idea to install it only on the PCI DSS Citrix server. However, I will explain in this article why it&rsquo;s not possible and what native Okta and Citrix options we have.</p>
<p>A special thanks to my colleague <strong>Ivan Rodriguez</strong> (former Citrix SE) for the help on this topic.</p>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><strong>TL;DR</strong> → the solution for a near-step-up MFA is described in <a href="/#3-solution-with-citrix-adc-netscaler-single-portal-and-contextual-mfa-for-applications" >Chapter 3. Solution with Citrix ADC (NetScaler): Single Portal and Contextual MFA for Applications</a>.</span>
</div>

<hr>

<h2 class="relative group">Note about Okta MFA Credential Provider for Windows
    <div id="note-about-okta-mfa-credential-provider-for-windows" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#note-about-okta-mfa-credential-provider-for-windows" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The <strong><a href="https://help.okta.com/oie/en-us/content/topics/security/proc-mfa-win-creds-rdp.htm"  target="_blank" rel="noreferrer">Okta MFA Credential Provider for Windows</a></strong> is not compatible with Citrix sessions based on the <strong>ICA/HDX</strong> protocol.</p>
<p>This agent is specifically designed to intercept the Windows login process that occurs via <strong>Remote Desktop Protocol (RDP)</strong>. It functions as a <em>Credential Provider</em> that activates on the Windows server&rsquo;s interactive login screen.</p>
<p>The Citrix ICA/HDX protocol, on the other hand, uses a completely different authentication mechanism. When a user authenticates to Citrix StoreFront, they receive a token. This token is then passed directly to the Citrix server to start the session, completely bypassing the operating system&rsquo;s interactive login screen. Since the Okta agent hooks into a screen that is never presented during a Citrix session launch, it has no opportunity to intervene and request MFA.</p>
<p>&#x2139;&#xfe0f; Documentation: <a href="https://help.okta.com/en-us/content/topics/security/proc-mfa-win-creds-rdp.htm" title="https://help.okta.com/en-us/content/topics/security/proc-mfa-win-creds-rdp.htm" target="_blank" rel="noreferrer">Okta - MFA Credential Provider for Windows</a>, which exclusively mentions its use with RDP.</p>
<p>The supported solution to have MFA is via <strong>StoreFront</strong> or <strong>ADC (NetScaler)</strong>.</p>
<hr>

<h2 class="relative group">Solution #1: Okta Groups and Authentication Policies
    <div id="solution-1-okta-groups-and-authentication-policies" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#solution-1-okta-groups-and-authentication-policies" aria-label="Anchor">#</a>
    </span>
    
</h2>
<div class="lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl">
  Simpler to implement
</div>

<p>Okta checks at login time if the user belongs to a group that requires access to sensitive data (e.g., &ldquo;PCI-Users&rdquo;). If the user is in the group, they are immediately prompted for MFA. If not, they access without MFA. In this model, however, <strong>PCI users will always receive an MFA prompt (even if they access the Citrix portal for non-PCI applications)</strong>.</p>

<h3 class="relative group">Diagram
    <div id="diagram" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#diagram" aria-label="Anchor">#</a>
    </span>
    
</h3>
<div style="background-color:white; padding: 20px">
<pre class="not-prose mermaid">
---
config:
  layout: elk
---
flowchart TD
    U["👤 User"] --> SF["🏪 Citrix StoreFront"]
    SF --> Okta["🔐 Okta IdP"]
    Okta --> Decision{"🛡️ Authentication Policies"}
    Decision -- "User in PCI-Users group" --> MFA["🔑 MFA Challenge"]
    Decision -- "User not in PCI-Users group" --> LIST["📱 All Apps (PCI + non-PCI)"]
    MFA --> LIST
    LIST --> LAUNCH["🚀 Launch App"]
     U:::userNode
     SF:::authNode
     Okta:::authNode
     Decision:::decisionNode
     MFA:::authNode
     LIST:::appNode
     LAUNCH:::appNode
    classDef userNode fill:#e1f5fe
    classDef authNode fill:#fff3e0
    classDef appNode fill:#e8f5e8
    classDef decisionNode fill:#fce4ec


</pre>

</div>
<hr>

<h2 class="relative group">Solution #2: Dual-StoreFront Architecture
    <div id="solution-2-dual-storefront-architecture" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#solution-2-dual-storefront-architecture" aria-label="Anchor">#</a>
    </span>
    
</h2>
<div class="lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl">
  Without Citrix ADC (NetScaler)
</div>

<p>Citrix StoreFront alone <strong>does not</strong> natively support &ldquo;<strong>step-up MFA</strong>&rdquo; logic for individual applications<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. Authentication policies are applied at the entire &ldquo;Store&rdquo; level, not per application. Once the user is authenticated to the Store, they have access to all published resources without further MFA prompts from StoreFront.</p>
<p><strong>Proposed Workaround:</strong> To meet the security requirement for PCI DSS applications without a Citrix ADC, the architectural approach is to create two distinct and isolated access paths:</p>
<ol>
<li><strong>Standard Store (No MFA):</strong> A dedicated portal for non-PCI applications. Authentication will be handled via a specific SAML application<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> in Okta with a policy that does <em>not</em> require MFA.</li>
<li><strong>PCI Store (with MFA):</strong> A second, completely separate portal that will exclusively publish PCI DSS applications. Authentication will be managed by a second SAML application<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> in Okta, whose authentication policy will <em>require</em> MFA.</li>
</ol>
<p>This solution implies that users will have two separate URLs and dashboards: one for standard activities and one for high-security activities. To mitigate this impact and offer a unified access point, you can configure the <strong>Okta user dashboard</strong> to include &ldquo;bookmarks&rdquo; for the Citrix applications. This way, the user will always start from a single point (their Okta dashboard) to access both types of resources.</p>

<h3 class="relative group">Diagram
    <div id="diagram-1" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#diagram-1" aria-label="Anchor">#</a>
    </span>
    
</h3>
<div style="background-color:white; padding: 20px">
<pre class="not-prose mermaid">
---
config:
  layout: elk
---
flowchart TD
    U["👤 User"] --> SF_STD["🏪 Standard StoreFront"] & SF_PCI["🏪 PCI StoreFront"]
    SF_STD --> Okta_STD["🔐 Okta IdP - App #1 - No MFA"]
    Okta_STD --> LIST_STD["📱 Non-PCI Apps"]
    LIST_STD --> LAUNCH_STD["🚀 Launch Standard App"]
    SF_PCI --> Okta_PCI["🔐 Okta IdP - App #2 - MFA Required"]
    Okta_PCI --> MFA["🔑 MFA Challenge"]
    MFA --> LIST_PCI["🔒 PCI Apps"]
    LIST_PCI --> LAUNCH_PCI["🚀 Launch PCI App"]
     U:::userNode
     SF_STD:::standardPath
     SF_PCI:::pciPath
     Okta_STD:::standardPath
     LIST_STD:::standardPath
     LAUNCH_STD:::standardPath
     Okta_PCI:::pciPath
     MFA:::pciPath
     LIST_PCI:::pciPath
     LAUNCH_PCI:::pciPath
    classDef userNode fill:#e1f5fe
    classDef standardPath fill:#e8f5e8
    classDef pciPath fill:#ffebee

</pre>

</div>
<hr>

<h2 class="relative group">Solution #3 - Single Portal and Contextual MFA for Applications
    <div id="solution-3---single-portal-and-contextual-mfa-for-applications" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#solution-3---single-portal-and-contextual-mfa-for-applications" aria-label="Anchor">#</a>
    </span>
    
</h2>
<div class="lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl">
  With Citrix ADC (NetScaler)
</div>

<p>The introduction of a <strong>Citrix ADC</strong> (formerly known as <em>NetScaler</em>) into the architecture allows for achieving the goal of a single portal securely, using its <strong>nFactor Authentication</strong><sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> functionality.</p>
<p>However, it&rsquo;s important to understand how the architecture works and what the user experience will be. There are two main models.</p>
<p>It is possible to configure the ADC to require MFA only when a specific application is launched. In this scenario, two <strong>Citrix Gateway Virtual Servers</strong> are configured on the ADC, but a <strong>single StoreFront portal</strong> is used:</p>
<ul>
<li><strong>Standard Gateway (VS1)</strong> → configured with basic authentication only.</li>
<li><strong>PCI Gateway (VS2)</strong> → configured with nFactor authentication that enforces MFA.</li>
<li><strong>StoreFront:</strong> Uses a feature called <strong>Optimal Gateway Routing (OGR)</strong>. This rule routes launch requests for PCI applications to the &ldquo;<em>PCI Gateway</em> &quot; and all others to the &ldquo;<em>Standard Gateway</em> .&rdquo;</li>
</ul>
<ol>
<li>The user connects to the portal and authenticates against the Standard Gateway (no MFA).</li>
<li>StoreFront displays <strong>all applications</strong> (PCI and non-PCI).</li>
<li>If the user launches a <strong>non-PCI</strong> app, OGR routes it to the Standard Gateway, and the app launches immediately.</li>
<li>If the user launches a <strong>PCI app</strong>, OGR routes the request to the <strong>PCI Gateway</strong>. At this point, the <strong>ADC enforces MFA</strong> via Okta.</li>
<li>Once MFA succeeds, the PCI app launches.</li>
</ol>
<p>This creates a real <strong>step-up MFA</strong> experience: users authenticate without MFA initially, and are only challenged for MFA when launching a sensitive PCI application.</p>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >In this configuration, you can also decide to use Okta only for the MFA, without use it for the basic authentication.
You can then configure the Standard Gateway (VS1) to use LDAP or AD. And the PCI Gateway (VS2) will be connected to Okta.</span>
</div>


<h3 class="relative group">Diagram
    <div id="diagram-2" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#diagram-2" aria-label="Anchor">#</a>
    </span>
    
</h3>
<div style="background-color:white; padding: 20px">
<pre class="not-prose mermaid">
---
config:
  layout: elk
---
flowchart TD
    U["👤 User"] --> ADC["🌐 Citrix ADC"]
    ADC --> VS1["⚡ Virtual Server #1 - Standard Gateway"]
    VS1 --> Auth["🔐 Basic Auth - (Okta/LDAP/AD)"]
    Auth --> LIST["🏪 StoreFront - All Apps (PCI + non-PCI)"]
    LIST --> OGR{"🎯 OGR Routing - Decision"}
    OGR -- Standard App --> LAUNCH_STD["🚀 Launch Standard App"]
    OGR -- PCI App --> VS2["🔒 Virtual Server #2 - PCI Gateway"]
    VS2 --> MFA["🔑 MFA Challenge"]
    MFA --> LAUNCH_PCI["🚀 Launch PCI App"]
     U:::userNode
     ADC:::standardFlow
     VS1:::standardFlow
     Auth:::standardFlow
     LIST:::standardFlow
     OGR:::mfaNode
     LAUNCH_STD:::standardFlow
     VS2:::pciFlow
     LAUNCH_PCI:::pciFlow
     MFA:::pciFlow
    classDef userNode fill:#e1f5fe
    classDef standardFlow fill:#e8f5e8
    classDef pciFlow fill:#ffebee
    classDef mfaNode fill:#f3e5f5
</pre>

</div>
<p>For the &ldquo;PCI Gateway&rdquo; that requires MFA, integration with Okta can be done in two ways:</p>
<ul>
<li><strong>RADIUS<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>:</strong> The ADC sends an authentication request to an on-premises Okta RADIUS Agent, which contacts Okta for MFA validation. There are some limitations about which MFA can be used<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> (i.e., FIDO2 and FastPass are not compatible with RADIUS).</li>
<li><strong>SAML<sup id="fnref2:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>:</strong> This is a more modern alternative that allows for a better user experience, including the ability to use Okta FastPass for passwordless access.
The ADC redirects the user&rsquo;s browser directly to Okta. This architecture mandatorily requires the implementation of the <strong>Citrix Federated Authentication Service (FAS)</strong><sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> to ensure Single Sign-On (SSO) to applications.</li>
</ul>
<hr>

<h2 class="relative group">Final Note
    <div id="final-note" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#final-note" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>I hope this information is useful. Due to the complexity of the configuration, I warmly suggest to involve Okta PS (Professional Services) or a certified Okta Partner, who can provide the necessary expertise and support for your production environment.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://help.okta.com/en-us/content/topics/security/proc-mfa-win-creds-rdp.htm" title="https://help.okta.com/en-us/content/topics/security/proc-mfa-win-creds-rdp.htm" target="_blank" rel="noreferrer">Okta - MFA Credential Provider for Windows</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://docs.citrix.com/en-us/storefront/current-release/stores/authentication.html" title="https://docs.citrix.com/en-us/storefront/current-release/stores/authentication.html" target="_blank" rel="noreferrer">Citrix - Configure authentication</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://saml-doc.okta.com/SAML_Docs/How-to-Configure-SAML-2.0-for-NetScaler-Gateway.html"  target="_blank" rel="noreferrer">Okta - How to Configure SAML 2.0 for Citrix Gateway (formerly NetScaler Gateway)</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://community.citrix.com/tech-zone/learn/tech-briefs/citrix-nfactor-mfa/" title="https://community.citrix.com/tech-zone/learn/tech-briefs/citrix-nfactor-mfa/" target="_blank" rel="noreferrer">Citrix - nFactor Authentication</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://help.okta.com/oie/en-us/content/topics/integrations/citrix-netscaler-radius-int.htm"  target="_blank" rel="noreferrer">Okta - Configure Citrix Netscaler gateway (with RADIUS)</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://help.okta.com/oie/en-us/content/topics/integrations/okta_radius_app.htm"  target="_blank" rel="noreferrer">Okta - RADIUS applications and MFA limitations</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p><a href="https://community.citrix.com/tech-zone/design/reference-architectures/federated-authentication-service/"  target="_blank" rel="noreferrer">Citrix - Reference Architecture: Federated Authentication Service</a>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
      <category>Okta</category>
      <category>MFA</category>
      <category>Citrix</category>
    </item>
    <item>
      <title>AWS Utilities EC2 with Workflows and auto-update DNS</title>
      <link>https://iam.fabiograsso.net/howto/aws-ec2-workflows/</link>
      <pubDate>Mon, 16 Jun 2025 00:00:00 +0000</pubDate>
      <guid>https://iam.fabiograsso.net/howto/aws-ec2-workflows/</guid>
      <description>Automate AWS EC2 power management and DNS updates using Okta Workflows, AWS Lambda, and CloudWatch. Start and stop VMs from the Okta dashboard, enforce scheduled shutdowns, and dynamically update DNS records. The guide covers setup steps, security considerations, and cost management in AWS demo environments.</description>
      <content:encoded>&lt;![CDATA[
<h2 class="relative group">Introduction
    <div id="introduction" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#introduction" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The following is a collections of scripts and Workflows for automating the startup/shutdown of EC2 VMs in AWS.</p>
<p>I use it in my demo environment, but it&rsquo;s a good example of integration between AWS and Okta Workflows.</p>
<p>I’m using a mix of <strong>Lambda</strong> scripts, <strong>CloudWatch</strong> Automation, <strong>Route53</strong> DNS Zone, and Okta <strong>Workflows</strong> , and at the end I have:</p>
<ul>
<li>Startup/shutdown button in my Okta dashboard









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/console_hu_20195c7434212b81.webp  330w,
      /howto/aws-ec2-workflows/console_hu_8610ada923f48bec.webp  660w,
      /howto/aws-ec2-workflows/console_hu_bba403cb18fe77f7.webp  960w,
      /howto/aws-ec2-workflows/console_hu_ac51e7152c080fc6.webp 1280w,
      /howto/aws-ec2-workflows/console_hu_5ba4e913d248fd61.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/console.png"
    src="/howto/aws-ec2-workflows/console.png">


  
</figure>
</li>
<li>Auto-shutdown of the VMs every evening (in case I forgot it)</li>
<li>Auto-update the Public DNS so that I don’t care about the dynamic Public IP address</li>
</ul>

<h3 class="relative group">High-level diagram
    <div id="high-level-diagram" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#high-level-diagram" aria-label="Anchor">#</a>
    </span>
    
</h3>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/diagram_hu_909e6aa6c7d9635c.webp  330w,
      /howto/aws-ec2-workflows/diagram_hu_1eec96e1979d6233.webp  660w,
      /howto/aws-ec2-workflows/diagram_hu_9695fbab14e07c2b.webp  960w,
      /howto/aws-ec2-workflows/diagram_hu_1bb86252a1d60b88.webp 1280w,
      /howto/aws-ec2-workflows/diagram_hu_b0d09721cb90d4a2.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/diagram.png"
    src="/howto/aws-ec2-workflows/diagram.png">


  
</figure>
<p>Let&rsquo;s see in detail how to put this in place.</p>
<hr>

<h2 class="relative group">AWS IAM Role
    <div id="aws-iam-role" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#aws-iam-role" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The first step is creating an IAM role to run the automation. For practicality, I made a single role for both EC2 and Route53 management, but you can split them if you prefer.</p>
<ol>
<li>
<p>Open the <a href="https://us-east-1.console.aws.amazon.com/iam/home" title="https://us-east-1.console.aws.amazon.com/iam/home" target="_blank" rel="noreferrer">AWS IAM Console</a></p>
</li>
<li>
<p>Create a new Policy (<em>Policies → Create policy</em> )</p>
</li>
<li>
<p>On the policy editor, click on JSON</p>
</li>
<li>
<p>Copy the following code:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;logs:CreateLogGroup&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;logs:CreateLogStream&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;logs:PutLogEvents&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Resource&#34;</span><span class="p">:</span> <span class="s2">&#34;*&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;route53:ChangeResourceRecordSets&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;route53:ListHostedZones&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;route53:ListResourceRecordSets&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Resource&#34;</span><span class="p">:</span> <span class="s2">&#34;*&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;ec2:DescribeInstances&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;ec2:StartInstances&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;ec2:StopInstances&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Resource&#34;</span><span class="p">:</span> <span class="s2">&#34;*&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</li>
<li>
<p>Save the policy, giving a name like “<em>LambdaAutomationsPolicy</em> ”</p>
</li>
<li>
<p>Create a new IAM Role of “<em>Custom trust policy</em> ” type</p>
</li>
<li>
<p>Select “<em>Lambda</em> ” as Service or use case</p>
</li>
<li>
<p>In the Add permissions step, select the previously created policy</p>
</li>
<li>
<p>Give it a name (i.e., “<em>LambdaAutomationRole</em> ”) and save</p>
</li>
</ol>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><code>&quot;Resource&quot;: &quot;*&quot;</code> gives access to all the EC2 or Route53 resources. This is fine in a demo/dev environment. For use in production, it’s warmly recommended to use a least privilege approach: create different roles for EC2 and Route53, and limit the scope only to the resources that need to be accessed by the Lambda script.</span>
</div>


<h2 class="relative group">AWS Lambda functions
    <div id="aws-lambda-functions" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#aws-lambda-functions" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Power On/Off
    <div id="power-onoff" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#power-onoff" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>
<p>Create a new Lambda function</p>
</li>
<li>
<p>Name: “<em>PowerCycle</em> ”</p>
</li>
<li>
<p>Runtime: “<em>Python 3</em> ”</p>
</li>
<li>
<p>Click on “<em>Change default execution role</em> ” and then “<em>Use an existing role</em> ”</p>
</li>
<li>
<p>Search for the role previously created (<em>LambdaAutomationRole</em> )</p>
</li>
<li>
<p>Leave all the other options as default</p>
</li>
<li>
<p>Click on “<em>Create function</em> ”</p>
</li>
<li>
<p>In the Code source section, copy the following script:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">logging</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">boto3</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">re</span> <span class="c1"># Import the regular expression module</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">botocore.exceptions</span> <span class="kn">import</span> <span class="n">ClientError</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialize logger for better logging to CloudWatch</span>
</span></span><span class="line"><span class="cl"><span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span> <span class="c1"># Set to logging.DEBUG for more detailed logs</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">lambda_handler</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Log the entire incoming event for debugging</span>
</span></span><span class="line"><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Received event: </span><span class="si">{</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">event</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># --- Input Validation for Region ---</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="s1">&#39;region&#39;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">event</span> <span class="ow">or</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">event</span><span class="p">[</span><span class="s1">&#39;region&#39;</span><span class="p">],</span> <span class="nb">str</span><span class="p">)</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">event</span><span class="p">[</span><span class="s1">&#39;region&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">&#34;Validation Error: Missing or invalid &#39;region&#39; in event.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span><span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;Missing or invalid &#39;region&#39; in event.&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">region</span> <span class="o">=</span> <span class="n">event</span><span class="p">[</span><span class="s1">&#39;region&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># --- Input Validation for Operation ---</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="s1">&#39;operation&#39;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">event</span> <span class="ow">or</span> <span class="n">event</span><span class="p">[</span><span class="s1">&#39;operation&#39;</span><span class="p">]</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">[</span><span class="s1">&#39;start&#39;</span><span class="p">,</span> <span class="s1">&#39;stop&#39;</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">&#34;Validation Error: Missing or invalid &#39;operation&#39; in event. Must be &#39;start&#39; or &#39;stop&#39;.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span><span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;Missing or invalid &#39;operation&#39; in event. Must be &#39;start&#39; or &#39;stop&#39;.&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">operation</span> <span class="o">=</span> <span class="n">event</span><span class="p">[</span><span class="s1">&#39;operation&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># --- Input Validation and Processing for Instances ---</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="s1">&#39;instances&#39;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">event</span> <span class="ow">or</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">event</span><span class="p">[</span><span class="s1">&#39;instances&#39;</span><span class="p">],</span> <span class="nb">str</span><span class="p">)</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">event</span><span class="p">[</span><span class="s1">&#39;instances&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">&#34;Validation Error: Missing or invalid &#39;instances&#39; in event. Must be &#39;all&#39; or a comma-separated list of EC2 instance IDs.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span><span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;Missing or invalid &#39;instances&#39; in event. Must be &#39;all&#39; or a comma-separated list of EC2 instance IDs.&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">instances_input</span> <span class="o">=</span> <span class="n">event</span><span class="p">[</span><span class="s1">&#39;instances&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">instances_to_process</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="n">failed_initial_validation_instances</span> <span class="o">=</span> <span class="p">[]</span> <span class="c1"># To capture instances that failed format validation or &#39;all&#39; logic</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Initialize Boto3 EC2 client outside the instance loop but after region validation</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">ec2</span> <span class="o">=</span> <span class="n">boto3</span><span class="o">.</span><span class="n">client</span><span class="p">(</span><span class="s1">&#39;ec2&#39;</span><span class="p">,</span> <span class="n">region_name</span><span class="o">=</span><span class="n">region</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Failed to initialize EC2 client for region </span><span class="si">{</span><span class="n">region</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">500</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span><span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;Failed to initialize EC2 client for region </span><span class="si">{</span><span class="n">region</span><span class="si">}</span><span class="s2">.&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Handle &#34;all&#34; instances logic</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">instances_input</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s1">&#39;all&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Operation &#39;</span><span class="si">{</span><span class="n">operation</span><span class="si">}</span><span class="s2">&#39; requested for ALL EC2 instances in region &#39;</span><span class="si">{</span><span class="n">region</span><span class="si">}</span><span class="s2">&#39;.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Describe all instances to filter based on current state</span>
</span></span><span class="line"><span class="cl">            <span class="n">describe_paginator</span> <span class="o">=</span> <span class="n">ec2</span><span class="o">.</span><span class="n">get_paginator</span><span class="p">(</span><span class="s1">&#39;describe_instances&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">all_reservations</span> <span class="o">=</span> <span class="n">describe_paginator</span><span class="o">.</span><span class="n">paginate</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">page</span> <span class="ow">in</span> <span class="n">all_reservations</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">for</span> <span class="n">reservation</span> <span class="ow">in</span> <span class="n">page</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;Reservations&#39;</span><span class="p">,</span> <span class="p">[]):</span>
</span></span><span class="line"><span class="cl">                    <span class="k">for</span> <span class="n">instance</span> <span class="ow">in</span> <span class="n">reservation</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;Instances&#39;</span><span class="p">,</span> <span class="p">[]):</span>
</span></span><span class="line"><span class="cl">                        <span class="n">instance_id</span> <span class="o">=</span> <span class="n">instance</span><span class="p">[</span><span class="s1">&#39;InstanceId&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">                        <span class="n">instance_state</span> <span class="o">=</span> <span class="n">instance</span><span class="p">[</span><span class="s1">&#39;State&#39;</span><span class="p">][</span><span class="s1">&#39;Name&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                        <span class="k">if</span> <span class="n">operation</span> <span class="o">==</span> <span class="s1">&#39;start&#39;</span> <span class="ow">and</span> <span class="n">instance_state</span> <span class="ow">in</span> <span class="p">[</span><span class="s1">&#39;stopped&#39;</span><span class="p">,</span> <span class="s1">&#39;stopping&#39;</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">                            <span class="n">instances_to_process</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">instance_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                        <span class="k">elif</span> <span class="n">operation</span> <span class="o">==</span> <span class="s1">&#39;stop&#39;</span> <span class="ow">and</span> <span class="n">instance_state</span> <span class="ow">in</span> <span class="p">[</span><span class="s1">&#39;running&#39;</span><span class="p">,</span> <span class="s1">&#39;pending&#39;</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">                            <span class="n">instances_to_process</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">instance_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                            <span class="c1"># Log instances that are not in a suitable state for the operation</span>
</span></span><span class="line"><span class="cl">                            <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Skipping instance </span><span class="si">{</span><span class="n">instance_id</span><span class="si">}</span><span class="s2"> for &#39;</span><span class="si">{</span><span class="n">operation</span><span class="si">}</span><span class="s2">&#39; operation due to current state: &#39;</span><span class="si">{</span><span class="n">instance_state</span><span class="si">}</span><span class="s2">&#39;.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">instances_to_process</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;No instances found in a suitable state (&#39;stopped&#39; for start, &#39;running&#39; for stop) for &#39;</span><span class="si">{</span><span class="n">operation</span><span class="si">}</span><span class="s2">&#39; operation in region &#39;</span><span class="si">{</span><span class="n">region</span><span class="si">}</span><span class="s2">&#39;.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># Return 200 OK, as the operation completed successfully by finding no targets.</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;No instances found in suitable state for &#39;</span><span class="si">{</span><span class="n">operation</span><span class="si">}</span><span class="s2">&#39; operation in region &#39;</span><span class="si">{</span><span class="n">region</span><span class="si">}</span><span class="s2">&#39;.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;operation&#34;</span><span class="p">:</span> <span class="n">operation</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;successful_instances&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;failed_instances&#34;</span><span class="p">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">                    <span class="p">})</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="n">ClientError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">error_code</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="n">response</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Error&#34;</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Code&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">error_message</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="n">response</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Error&#34;</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;AWS API Error describing all instances in </span><span class="si">{</span><span class="n">region</span><span class="si">}</span><span class="s2">: [</span><span class="si">{</span><span class="n">error_code</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">error_message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">500</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span><span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;Failed to list all instances in </span><span class="si">{</span><span class="n">region</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">error_message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">logger</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unexpected error describing all instances in </span><span class="si">{</span><span class="n">region</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">500</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span><span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;Unexpected error listing all instances in </span><span class="si">{</span><span class="n">region</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Process comma-separated list with instance ID format validation</span>
</span></span><span class="line"><span class="cl">        <span class="n">instance_ids_raw</span> <span class="o">=</span> <span class="n">instances_input</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;,&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Regex for standard EC2 instance ID format: &#39;i-&#39; followed by 17 lowercase hexadecimal characters</span>
</span></span><span class="line"><span class="cl">        <span class="n">instance_id_regex</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^i-[0-9a-f]</span><span class="si">{17}</span><span class="s2">$&#34;</span><span class="p">)</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">instance_id_str</span> <span class="ow">in</span> <span class="n">instance_ids_raw</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">cleaned_id</span> <span class="o">=</span> <span class="n">instance_id_str</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">cleaned_id</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span> <span class="c1"># Skip empty strings</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">instance_id_regex</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">cleaned_id</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">                <span class="n">instances_to_process</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">cleaned_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># Add to failed list right away if format is invalid</span>
</span></span><span class="line"><span class="cl">                <span class="n">failed_initial_validation_instances</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;instance_id&#34;</span><span class="p">:</span> <span class="n">cleaned_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;reason&#34;</span><span class="p">:</span> <span class="s2">&#34;Invalid EC2 instance ID format (must be i-xxxxxxxxxxxxxxxxx)&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="cl">                <span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Skipping instance ID &#39;</span><span class="si">{</span><span class="n">cleaned_id</span><span class="si">}</span><span class="s2">&#39; due to invalid format. Must match &#39;i- followed by 17 hex characters&#39;.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># If no valid instances were provided (either empty list or all invalid format)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">instances_to_process</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">failed_initial_validation_instances</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">&#34;Validation Error: No valid EC2 instance IDs provided for processing.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span><span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;No valid EC2 instance IDs provided for processing.&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="ow">not</span> <span class="n">instances_to_process</span> <span class="ow">and</span> <span class="n">failed_initial_validation_instances</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># If instances_to_process is empty but failed_initial_validation_instances is not,</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># it means all provided IDs were invalid format.</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Validation Error: All provided EC2 instance IDs had an invalid format: </span><span class="si">{</span><span class="n">failed_initial_validation_instances</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;All provided EC2 instance IDs had an invalid format.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;invalid_instances&#34;</span><span class="p">:</span> <span class="n">failed_initial_validation_instances</span> <span class="c1"># Include details of invalid instances</span>
</span></span><span class="line"><span class="cl">            <span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># --- End Input Validation and Processing for Instances ---</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Attempting to </span><span class="si">{</span><span class="n">operation</span><span class="si">}</span><span class="s2"> the following instances: </span><span class="si">{</span><span class="n">instances_to_process</span><span class="si">}</span><span class="s2"> in region: </span><span class="si">{</span><span class="n">region</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Initialize lists to store results of the EC2 operations</span>
</span></span><span class="line"><span class="cl">    <span class="n">successful_operations</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="n">failed_operations</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Pre-populate failed_operations with instances that failed initial format validation</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># This ensures they are part of the final error report.</span>
</span></span><span class="line"><span class="cl">    <span class="n">failed_operations</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">failed_initial_validation_instances</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">instance_id</span> <span class="ow">in</span> <span class="n">instances_to_process</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">operation</span> <span class="o">==</span> <span class="s1">&#39;start&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">ec2</span><span class="o">.</span><span class="n">start_instances</span><span class="p">(</span><span class="n">InstanceIds</span><span class="o">=</span><span class="p">[</span><span class="n">instance_id</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">                <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Successfully initiated &#39;start&#39; for instance: </span><span class="si">{</span><span class="n">instance_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">elif</span> <span class="n">operation</span> <span class="o">==</span> <span class="s1">&#39;stop&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">ec2</span><span class="o">.</span><span class="n">stop_instances</span><span class="p">(</span><span class="n">InstanceIds</span><span class="o">=</span><span class="p">[</span><span class="n">instance_id</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">                <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Successfully initiated &#39;stop&#39; for instance: </span><span class="si">{</span><span class="n">instance_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1"># Append to successful_operations only after the API call succeeds</span>
</span></span><span class="line"><span class="cl">            <span class="n">successful_operations</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">instance_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="n">ClientError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Granular Exception Handling for AWS API errors during start/stop</span>
</span></span><span class="line"><span class="cl">            <span class="n">error_code</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="n">response</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Error&#34;</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Code&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">error_message</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="n">response</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Error&#34;</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;AWS API Error during &#39;</span><span class="si">{</span><span class="n">operation</span><span class="si">}</span><span class="s2">&#39; of instance </span><span class="si">{</span><span class="n">instance_id</span><span class="si">}</span><span class="s2">: [</span><span class="si">{</span><span class="n">error_code</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">error_message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">failed_operations</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;instance_id&#34;</span><span class="p">:</span> <span class="n">instance_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;reason&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;AWS API Error: [</span><span class="si">{</span><span class="n">error_code</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">error_message</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Catch any other unexpected Python exceptions</span>
</span></span><span class="line"><span class="cl">            <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unexpected error during &#39;</span><span class="si">{</span><span class="n">operation</span><span class="si">}</span><span class="s2">&#39; of instance </span><span class="si">{</span><span class="n">instance_id</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">failed_operations</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;instance_id&#34;</span><span class="p">:</span> <span class="n">instance_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;reason&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;Unexpected error: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Determine overall status and structure response</span>
</span></span><span class="line"><span class="cl">    <span class="n">overall_message</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">status_code</span> <span class="o">=</span> <span class="mi">200</span> <span class="c1"># Default to success unless all failed</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">successful_operations</span> <span class="ow">and</span> <span class="n">failed_operations</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># All attempted operations (including initial format failures) failed</span>
</span></span><span class="line"><span class="cl">        <span class="n">status_code</span> <span class="o">=</span> <span class="mi">500</span> <span class="c1"># Indicates a problem with the overall execution or input</span>
</span></span><span class="line"><span class="cl">        <span class="n">overall_message</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Failed to </span><span class="si">{</span><span class="n">operation</span><span class="si">}</span><span class="s2"> any of the specified instances. Check &#39;failed_instances&#39; for details.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">successful_operations</span> <span class="ow">and</span> <span class="n">failed_operations</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Partial success (some worked, some failed)</span>
</span></span><span class="line"><span class="cl">        <span class="n">status_code</span> <span class="o">=</span> <span class="mi">200</span> <span class="c1"># Still considered a success for the Lambda invocation, but with warnings</span>
</span></span><span class="line"><span class="cl">        <span class="n">overall_message</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Successfully initiated </span><span class="si">{</span><span class="n">operation</span><span class="si">}</span><span class="s2"> for some instances, others failed. Check results for details.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># All successful (including cases where &#39;all&#39; was specified but no suitable instances were found)</span>
</span></span><span class="line"><span class="cl">        <span class="n">overall_message</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Successfully initiated </span><span class="si">{</span><span class="n">operation</span><span class="si">}</span><span class="s2"> for all specified instances.&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Final response body</span>
</span></span><span class="line"><span class="cl">    <span class="n">response_body</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="n">overall_message</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;operation&#34;</span><span class="p">:</span> <span class="n">operation</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;successful_instances&#34;</span><span class="p">:</span> <span class="n">successful_operations</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;failed_instances&#34;</span><span class="p">:</span> <span class="n">failed_operations</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;region&#34;</span><span class="p">:</span> <span class="n">region</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Function execution completed. Status: </span><span class="si">{</span><span class="n">status_code</span><span class="si">}</span><span class="s2">, Message: </span><span class="si">{</span><span class="n">overall_message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="n">status_code</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">response_body</span><span class="p">)</span> <span class="c1"># Body must be a JSON string</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span></span></span></code></pre></div></div>
</li>
<li>
<p>In the “<em>Configuration</em>” tab, click on “<em>Edit</em>” on the General Configuration and change the timeout to 30 seconds</p>
</li>
<li>
<p>Let’s test our code, click on the tab “<em>Test</em>”
a. Create a new event
b. Insert a name like “<em>Test Start VM</em>”
c. In the JSON area, insert this code (remember to change the region and instances based on your environment):</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;region&#34;</span><span class="p">:</span><span class="s2">&#34;us-east-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;operation&#34;</span><span class="p">:</span><span class="s2">&#34;start&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;instances&#34;</span><span class="p">:</span> <span class="s2">&#34;i-xxxxxxxxxxxxxxxxxx&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</li>
<li>
<p>Click on “<em>Deploy</em>” on the left in order to publish and enable the code</p>
</li>
<li>
<p>Click on “<em>Save</em>”</p>
</li>
</ol>

<h4 class="relative group">How it works
    <div id="how-it-works" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-it-works" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>The script receives as input a JSON with some data:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;region&#34;</span><span class="p">:</span><span class="s2">&#34;us-east-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;operation&#34;</span><span class="p">:</span><span class="s2">&#34;start&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;instances&#34;</span><span class="p">:</span> <span class="s2">&#34;i-xxxxxxxxxxxxxxxxxx,i-yyyyyyyyyyyyyyyyy&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>The parameters are:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>region</strong></td>
          <td>AWS EC2 region</td>
      </tr>
      <tr>
          <td><strong>operation</strong></td>
          <td><em>start</em> or <em>stop</em></td>
      </tr>
      <tr>
          <td><strong>instances</strong></td>
          <td>comma-separated list of the instances on which to do the operation.</td>
      </tr>
  </tbody>
</table>
<p>
  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><p>You can find the instance IDs in the <a href="https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#Instances:v=3"  target="_blank" rel="noreferrer">AWS EC2 console</a>.</p>









<figure>
    <img class="my-0 rounded-md" loading="lazy" alt="" src="aws-console.png">


  
</figure></span>
</div>

<br/>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><p>If the instance value is “<em>all</em>” it will execute the operation on all the EC2 VMs of the selected region. Example:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;region&#34;</span><span class="p">:</span><span class="s2">&#34;us-east-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;operation&#34;</span><span class="p">:</span><span class="s2">&#34;start&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;instances&#34;</span><span class="p">:</span> <span class="s2">&#34;all&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div></span>
</div>
</p>

<h3 class="relative group">DNS Update
    <div id="dns-update" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#dns-update" aria-label="Anchor">#</a>
    </span>
    
</h3>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >You can skip this part if you want to implement only the start/stop automation without the DNS auto-update.</span>
</div>

<ol>
<li>
<p>Create a new Lambda function</p>
</li>
<li>
<p>Name: “<em>UpdateDNS</em> ”</p>
</li>
<li>
<p>Runtime: “<em>Python 3</em> ”</p>
</li>
<li>
<p>Click on “<em>Change default execution role</em> ” and then “<em>Use an existing role</em> ”</p>
</li>
<li>
<p>Search for the role previously created (<em>LambdaAutomationRole</em> )</p>
</li>
<li>
<p>Leave all the other options as default</p>
</li>
<li>
<p>Click on “<em>Create function</em> ”</p>
</li>
<li>
<p>In the Code source section, copy the following script and change the <code>HOSTED_ZONE_ID</code> variable with your Route53 zone ID:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">boto3</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">logging</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">botocore.exceptions</span> <span class="kn">import</span> <span class="n">ClientError</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialize logger</span>
</span></span><span class="line"><span class="cl"><span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span> <span class="c1"># Set to logging.DEBUG for more verbose logs</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># --- GLOBAL STATIC CONFIGURATION VARIABLES ---</span>
</span></span><span class="line"><span class="cl"><span class="n">HOSTED_ZONE_ID</span> <span class="o">=</span> <span class="s2">&#34;Z00000000X0XXXXXXXXX&#34;</span> <span class="c1"># Your Route 53 Hosted Zone ID</span>
</span></span><span class="line"><span class="cl"><span class="n">DNS_RECORD_TTL</span> <span class="o">=</span> <span class="mi">30</span>                     <span class="c1"># Time-to-Live for the DNS record (in seconds)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># --- END GLOBAL STATIC CONFIGURATION VARIABLES ---</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">lambda_handler</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Received event: </span><span class="si">{</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">event</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Extract instance-id and region from the EC2 State-change event</span>
</span></span><span class="line"><span class="cl">    <span class="n">detail</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;detail&#39;</span><span class="p">,</span> <span class="p">{})</span>
</span></span><span class="line"><span class="cl">    <span class="n">instance_id</span> <span class="o">=</span> <span class="n">detail</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;instance-id&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">region</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;region&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># --- Input Validation for Event ---</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">instance_id</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">region</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">error_body</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;Missing &#39;instance-id&#39; or &#39;region&#39; in the EC2 state-change event detail.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;event&#34;</span><span class="p">:</span> <span class="n">event</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Validation Error: </span><span class="si">{</span><span class="n">error_body</span><span class="p">[</span><span class="s1">&#39;message&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2"> Event: </span><span class="si">{</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">event</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">error_body</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Processing EC2 instance &#39;</span><span class="si">{</span><span class="n">instance_id</span><span class="si">}</span><span class="s2">&#39; in region &#39;</span><span class="si">{</span><span class="n">region</span><span class="si">}</span><span class="s2">&#39; for DNS update.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Initialize Boto3 clients</span>
</span></span><span class="line"><span class="cl">        <span class="n">ec2</span> <span class="o">=</span> <span class="n">boto3</span><span class="o">.</span><span class="n">client</span><span class="p">(</span><span class="s1">&#39;ec2&#39;</span><span class="p">,</span> <span class="n">region_name</span><span class="o">=</span><span class="n">region</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">update_results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Describe the instance to get the public IP and tags</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">described_instances_response</span> <span class="o">=</span> <span class="n">ec2</span><span class="o">.</span><span class="n">describe_instances</span><span class="p">(</span><span class="n">InstanceIds</span><span class="o">=</span><span class="p">[</span><span class="n">instance_id</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">            <span class="n">reservations</span> <span class="o">=</span> <span class="n">described_instances_response</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;Reservations&#39;</span><span class="p">,</span> <span class="p">[])</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="n">ClientError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">error_code</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="n">response</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Error&#34;</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Code&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">error_message</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="n">response</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Error&#34;</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;AWS API Error describing instance </span><span class="si">{</span><span class="n">instance_id</span><span class="si">}</span><span class="s2">: [</span><span class="si">{</span><span class="n">error_code</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">error_message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">404</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span><span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span> <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;Failed to describe instance </span><span class="si">{</span><span class="n">instance_id</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">error_message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">},</span> <span class="n">default</span><span class="o">=</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">logger</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unexpected error describing instance </span><span class="si">{</span><span class="n">instance_id</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">500</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span><span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span> <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;Unexpected error describing instance </span><span class="si">{</span><span class="n">instance_id</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">},</span> <span class="n">default</span><span class="o">=</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">found_instance_details</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">reservation</span> <span class="ow">in</span> <span class="n">reservations</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">instance</span> <span class="ow">in</span> <span class="n">reservation</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;Instances&#39;</span><span class="p">,</span> <span class="p">[]):</span>
</span></span><span class="line"><span class="cl">                <span class="n">found_instance_details</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="c1"># Get the PublicDNS tag value</span>
</span></span><span class="line"><span class="cl">                <span class="n">dns_name</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">                <span class="n">ec2_name</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="s1">&#39;Tags&#39;</span> <span class="ow">in</span> <span class="n">instance</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="k">for</span> <span class="n">tag</span> <span class="ow">in</span> <span class="n">instance</span><span class="p">[</span><span class="s1">&#39;Tags&#39;</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">                        <span class="k">if</span> <span class="n">tag</span><span class="p">[</span><span class="s1">&#39;Key&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;PublicDNS&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                            <span class="n">dns_name</span> <span class="o">=</span> <span class="n">tag</span><span class="p">[</span><span class="s1">&#39;Value&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">                        <span class="k">if</span> <span class="n">tag</span><span class="p">[</span><span class="s1">&#39;Key&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;Name&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                            <span class="n">ec2_name</span> <span class="o">=</span> <span class="n">tag</span><span class="p">[</span><span class="s1">&#39;Value&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="ow">not</span> <span class="n">dns_name</span><span class="p">:</span> <span class="c1"># This condition handles both &#39;tag not present&#39; and &#39;tag is empty string&#39;</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="n">ec2_name</span><span class="p">:</span> <span class="c1"># Check if the Name tag was found and has a value</span>
</span></span><span class="line"><span class="cl">                            <span class="n">dns_name</span> <span class="o">=</span> <span class="n">ec2_name</span>
</span></span><span class="line"><span class="cl">                            <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;&#39;PublicDNS&#39; tag was missing or empty for instance &#39;</span><span class="si">{</span><span class="n">instance</span><span class="p">[</span><span class="s1">&#39;InstanceId&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#39;. Falling back to &#39;Name&#39; tag: &#39;</span><span class="si">{</span><span class="n">ec2_name</span><span class="si">}</span><span class="s2">&#39;.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="k">else</span><span class="p">:</span> <span class="c1"># Log specific warnings if neither tag provided a usable name</span>
</span></span><span class="line"><span class="cl">                        <span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Neither &#39;PublicDNS&#39; nor &#39;Name&#39; tags found for instance &#39;</span><span class="si">{</span><span class="n">instance</span><span class="p">[</span><span class="s1">&#39;InstanceId&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#39;.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">                                <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;Cannot determine DNS name for instance &#39;</span><span class="si">{</span><span class="n">instance</span><span class="p">[</span><span class="s1">&#39;InstanceId&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#39;: No tags found on the instance.&#34;</span>
</span></span><span class="line"><span class="cl">                            <span class="p">})</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="c1"># Get the public IP address</span>
</span></span><span class="line"><span class="cl">                <span class="n">ip_address</span> <span class="o">=</span> <span class="n">instance</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;PublicIpAddress&#39;</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="n">dns_name</span> <span class="ow">and</span> <span class="n">ip_address</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Found instance &#39;</span><span class="si">{</span><span class="n">instance</span><span class="p">[</span><span class="s1">&#39;InstanceId&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#39;, DNS name: &#39;</span><span class="si">{</span><span class="n">dns_name</span><span class="si">}</span><span class="s2">&#39;, Public IP: &#39;</span><span class="si">{</span><span class="n">ip_address</span><span class="si">}</span><span class="s2">&#39;. Attempting DNS update.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="c1"># Perform the DNS update</span>
</span></span><span class="line"><span class="cl">                    <span class="n">update_result</span> <span class="o">=</span> <span class="n">do_dns_update</span><span class="p">(</span><span class="n">HOSTED_ZONE_ID</span><span class="p">,</span> <span class="n">dns_name</span><span class="p">,</span> <span class="n">ip_address</span><span class="p">,</span> <span class="n">DNS_RECORD_TTL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="n">update_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">update_result</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="c1"># Domain name (from tag or fallback) or public IP is missing</span>
</span></span><span class="line"><span class="cl">                    <span class="n">reason_msg</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="ow">not</span> <span class="n">dns_name</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                        <span class="n">reason_msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;Missing or empty &#39;PublicDNS&#39; and no usable &#39;Name&#39; tag.&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="ow">not</span> <span class="n">ip_address</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                        <span class="k">if</span> <span class="n">reason_msg</span><span class="p">:</span> <span class="n">reason_msg</span> <span class="o">+=</span> <span class="s2">&#34; And &#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="n">reason_msg</span> <span class="o">+=</span> <span class="s2">&#34;missing &#39;PublicIpAddress&#39;.&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                    <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Skipping instance &#39;</span><span class="si">{</span><span class="n">instance</span><span class="p">[</span><span class="s1">&#39;InstanceId&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#39;: </span><span class="si">{</span><span class="n">reason_msg</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="n">update_results</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;instance_id&#34;</span><span class="p">:</span> <span class="n">instance</span><span class="p">[</span><span class="s1">&#39;InstanceId&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;skipped&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;reason&#34;</span><span class="p">:</span> <span class="n">msg</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;skipped&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">found_instance_details</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">error_body</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;Instance with ID &#39;</span><span class="si">{</span><span class="n">instance_id</span><span class="si">}</span><span class="s2">&#39; not found or could not be described.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;instance_id&#34;</span><span class="p">:</span> <span class="n">instance_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;region&#34;</span><span class="p">:</span> <span class="n">region</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Execution Error: </span><span class="si">{</span><span class="n">error_body</span><span class="p">[</span><span class="s1">&#39;message&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">404</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">error_body</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Determine overall status for the response</span>
</span></span><span class="line"><span class="cl">        <span class="n">overall_status_message</span> <span class="o">=</span> <span class="s2">&#34;DNS update process completed successfully.&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">final_status_code</span> <span class="o">=</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;status&#39;</span><span class="p">)</span> <span class="o">==</span> <span class="s1">&#39;error&#39;</span> <span class="k">for</span> <span class="n">result</span> <span class="ow">in</span> <span class="n">update_results</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="n">overall_status_message</span> <span class="o">=</span> <span class="s2">&#34;Some DNS updates failed. Check &#39;updates&#39; for details.&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">update_results</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">             <span class="n">overall_status_message</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;No DNS updates were attempted for instance </span><span class="si">{</span><span class="n">instance_id</span><span class="si">}</span><span class="s2">.&#34;</span>
</span></span><span class="line"><span class="cl">             <span class="n">final_status_code</span> <span class="o">=</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">response_body</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;success&#34;</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;status&#39;</span><span class="p">)</span> <span class="o">==</span> <span class="s1">&#39;error&#39;</span> <span class="k">for</span> <span class="n">result</span> <span class="ow">in</span> <span class="n">update_results</span><span class="p">)</span> <span class="k">else</span> <span class="s2">&#34;partial_success_with_errors&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="n">overall_status_message</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;instance_id&#34;</span><span class="p">:</span> <span class="n">instance_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;region&#34;</span><span class="p">:</span> <span class="n">region</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;updates&#34;</span><span class="p">:</span> <span class="n">update_results</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Function execution completed with status: </span><span class="si">{</span><span class="n">response_body</span><span class="p">[</span><span class="s1">&#39;status&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="n">final_status_code</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;body&#34;</span><span class="p">:</span>  <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">response_body</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">error_body</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;An unexpected error occurred during execution: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;instance_id&#34;</span><span class="p">:</span> <span class="n">instance_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;region&#34;</span><span class="p">:</span> <span class="n">region</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;event&#34;</span><span class="p">:</span> <span class="n">event</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unhandled critical exception in lambda_handler: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">500</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">error_body</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">### Helper Functions ###</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">do_dns_update</span><span class="p">(</span><span class="n">hosted_zone_id</span><span class="p">,</span> <span class="n">dns_name</span><span class="p">,</span> <span class="n">ip_address</span><span class="p">,</span> <span class="n">ttl</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Upserts (creates or updates) a DNS record in Route 53 for the given domain name.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Attempting UPSERT for DNS record: </span><span class="si">{</span><span class="n">dns_name</span><span class="si">}</span><span class="s2"> (Type: A, TTL: </span><span class="si">{</span><span class="n">ttl</span><span class="si">}</span><span class="s2">) to IP: </span><span class="si">{</span><span class="n">ip_address</span><span class="si">}</span><span class="s2"> in Hosted Zone: </span><span class="si">{</span><span class="n">hosted_zone_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">route53</span> <span class="o">=</span> <span class="n">boto3</span><span class="o">.</span><span class="n">client</span><span class="p">(</span><span class="s1">&#39;route53&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">route53</span><span class="o">.</span><span class="n">change_resource_record_sets</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">HostedZoneId</span><span class="o">=</span><span class="n">hosted_zone_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">ChangeBatch</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;Comment&#39;</span><span class="p">:</span> <span class="s1">&#39;Updated by Lambda upon EC2 state change&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s1">&#39;Changes&#39;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                    <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="s1">&#39;Action&#39;</span><span class="p">:</span> <span class="s1">&#39;UPSERT&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="s1">&#39;ResourceRecordSet&#39;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="s1">&#39;Name&#39;</span><span class="p">:</span> <span class="n">dns_name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="s1">&#39;Type&#39;</span><span class="p">:</span> <span class="s1">&#39;A&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="s1">&#39;TTL&#39;</span><span class="p">:</span> <span class="n">ttl</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="s1">&#39;ResourceRecords&#39;</span><span class="p">:</span> <span class="p">[{</span><span class="s1">&#39;Value&#39;</span><span class="p">:</span> <span class="n">ip_address</span><span class="p">}]</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">change_info</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;ChangeInfo&#34;</span><span class="p">,</span> <span class="p">{})</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Successfully UPSERTed DNS record for </span><span class="si">{</span><span class="n">dns_name</span><span class="si">}</span><span class="s2"> -&gt; </span><span class="si">{</span><span class="n">ip_address</span><span class="si">}</span><span class="s2">. Change ID: </span><span class="si">{</span><span class="n">change_info</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;Id&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Route53 Change Info: </span><span class="si">{</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">change_info</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="nb">str</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;dns_name&#34;</span><span class="p">:</span> <span class="n">dns_name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;ip_address&#34;</span><span class="p">:</span> <span class="n">ip_address</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;UPSERT&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;change_info&#34;</span><span class="p">:</span> <span class="n">change_info</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;success&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="n">ClientError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">error_code</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="n">response</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Error&#34;</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Code&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">error_message</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="n">response</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Error&#34;</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Message&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">error_msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;AWS API Error updating DNS record for </span><span class="si">{</span><span class="n">dns_name</span><span class="si">}</span><span class="s2"> to </span><span class="si">{</span><span class="n">ip_address</span><span class="si">}</span><span class="s2">: [</span><span class="si">{</span><span class="n">error_code</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">error_message</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">error_msg</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;dns_name&#34;</span><span class="p">:</span> <span class="n">dns_name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;ip_address&#34;</span><span class="p">:</span> <span class="n">ip_address</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;UPSERT&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;error_code&#34;</span><span class="p">:</span> <span class="n">error_code</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;error_message&#34;</span><span class="p">:</span> <span class="n">error_message</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">error_msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Unexpected error updating DNS record for </span><span class="si">{</span><span class="n">dns_name</span><span class="si">}</span><span class="s2"> to </span><span class="si">{</span><span class="n">ip_address</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">error_msg</span><span class="p">,</span> <span class="n">exc_info</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;dns_name&#34;</span><span class="p">:</span> <span class="n">dns_name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;ip_address&#34;</span><span class="p">:</span> <span class="n">ip_address</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;UPSERT&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;error_message&#34;</span><span class="p">:</span> <span class="n">error_msg</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span></span></span></code></pre></div></div>
</li>
<li>
<p>In the “<em>Configuration</em> ” tab, click on “<em>Edit</em> ” on the <em>General Configuration</em> and change the timeout to 30 seconds</p>
</li>
<li>
<p>Let’s test our code, click on the tab “<em>Test</em> ”</p>
<p>a. Create a new event
b. Insert a name like “<em>Test DNS Update</em> ”
c. In the JSON area, insert this code (remember to change the region and instances based on your environment):</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;region&#34;</span><span class="p">:</span> <span class="s2">&#34;us-east-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;detail&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;instance-id&#34;</span><span class="p">:</span> <span class="s2">&#34;i-xxxxxxxxxxxxxxxxxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;state&#34;</span><span class="p">:</span> <span class="s2">&#34;running&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
</li>
<li>
<p>Click on “<em>Deploy</em>” on the left in order to publish and enable the code</p>
</li>
<li>
<p>Click on Save</p>
</li>
</ol>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M48 32H197.5C214.5 32 230.7 38.74 242.7 50.75L418.7 226.7C443.7 251.7 443.7 292.3 418.7 317.3L285.3 450.7C260.3 475.7 219.7 475.7 194.7 450.7L18.75 274.7C6.743 262.7 0 246.5 0 229.5V80C0 53.49 21.49 32 48 32L48 32zM112 176C129.7 176 144 161.7 144 144C144 126.3 129.7 112 112 112C94.33 112 80 126.3 80 144C80 161.7 94.33 176 112 176z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><p>Remember to change the <em>HOSTED_ZONE_ID</em> variable with your Route53 zone ID in the code!</p>
<p>You can find it in the Route53 dashboard: 








<figure>
    <img class="my-0 rounded-md" loading="lazy" alt="" src="aws-zone.png">


  
</figure>
</p>
</span>
</div>


<h4 class="relative group">How it works
    <div id="how-it-works-1" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-it-works-1" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>The script will receive an event from CloudWatch, which contains something like:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;7d708c67-bcb4-5397-9ad8-783d6aadc17f&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;detail-type&#34;</span><span class="p">:</span> <span class="s2">&#34;EC2 Instance State-change Notification&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;aws.ec2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;account&#34;</span><span class="p">:</span> <span class="s2">&#34;123456789012&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;time&#34;</span><span class="p">:</span> <span class="s2">&#34;2025-01-06T12:34:56Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;region&#34;</span><span class="p">:</span> <span class="s2">&#34;us-east-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;resources&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;arn:aws:ec2:us-east-1:123456789012:instance/i-xxxxxxxxxxxxxxxxxx&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;detail&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;instance-id&#34;</span><span class="p">:</span> <span class="s2">&#34;i-xxxxxxxxxxxxxxxxxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;state&#34;</span><span class="p">:</span> <span class="s2">&#34;running&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>It extracts the <em>instance ID</em> and uses it to obtain information about the EC2 VM (tags and public IP).</p>
<p>At this point, if the hostname has the tag ‘PublicDNS’, it will use it. If not, it will use the VM name.</p>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Using this method permits pointing directly to a DNS record, which will update automatically, eliminating the need to obtain the dynamic public IP address every time the EC2 instance is powered on. It’s also a way to avoid using an <em>Elastic IP Address</em> , and consequently save on AWS costs.tag</span>
</div>

<p>Sample with <em>PublicDNS</em> tag: 








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/aws-dns-tag_hu_3bf1494f0b14d890.webp  330w,
      /howto/aws-ec2-workflows/aws-dns-tag_hu_43c91a697f103272.webp  660w,
      /howto/aws-ec2-workflows/aws-dns-tag_hu_f06b6029986d10c4.webp  960w,
      /howto/aws-ec2-workflows/aws-dns-tag_hu_d770bad51acd28df.webp 1280w,
      /howto/aws-ec2-workflows/aws-dns-tag_hu_74a1a3063b208035.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/aws-dns-tag.png"
    src="/howto/aws-ec2-workflows/aws-dns-tag.png">


  
</figure>
</p>
<p>Sample with EC2 VM Name: 








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/aws-dns-name_hu_5ce73799dcf6e469.webp  330w,
      /howto/aws-ec2-workflows/aws-dns-name_hu_9af562176836aa1.webp  660w,
      /howto/aws-ec2-workflows/aws-dns-name_hu_1905d353d19737dd.webp  960w,
      /howto/aws-ec2-workflows/aws-dns-name_hu_7d30134b43451074.webp 1280w,
      /howto/aws-ec2-workflows/aws-dns-name_hu_18910493ace26b66.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/aws-dns-name.png"
    src="/howto/aws-ec2-workflows/aws-dns-name.png">


  
</figure>
</p>
<p>Example of a successful log:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">INIT_START Runtime Version: python:3.13.v45	Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:1c3b07a0dd0601e71b0292c41581cb633e076ad4eb3b0408ebe3d708418bd1b8
</span></span><span class="line"><span class="cl">START RequestId: f9781c8c-4d8e-484e-9504-abaefc5d87fc Version: $LATEST
</span></span><span class="line"><span class="cl">[INFO]	2025-06-13T08:22:22.557Z	f9781c8c-4d8e-484e-9504-abaefc5d87fc	Received event: 
</span></span><span class="line"><span class="cl">{
</span></span><span class="line"><span class="cl">    &#34;version&#34;: &#34;0&#34;,
</span></span><span class="line"><span class="cl">    &#34;id&#34;: &#34;7d708c67-bcb4-5397-9ad8-783d6aadc17f&#34;,
</span></span><span class="line"><span class="cl">    &#34;detail-type&#34;: &#34;EC2 Instance State-change Notification&#34;,
</span></span><span class="line"><span class="cl">    &#34;source&#34;: &#34;aws.ec2&#34;,
</span></span><span class="line"><span class="cl">    &#34;account&#34;: &#34;123456789012&#34;,
</span></span><span class="line"><span class="cl">    &#34;time&#34;: &#34;2025-01-06T12:34:56Z&#34;,
</span></span><span class="line"><span class="cl">    &#34;region&#34;: &#34;us-east-1&#34;,
</span></span><span class="line"><span class="cl">    &#34;resources&#34;: [
</span></span><span class="line"><span class="cl">        &#34;arn:aws:ec2:us-east-1:123456789012:instance/i-xxxxxxxxxxxxxxxxxx&#34;
</span></span><span class="line"><span class="cl">    ],
</span></span><span class="line"><span class="cl">    &#34;detail&#34;: {
</span></span><span class="line"><span class="cl">        &#34;instance-id&#34;: &#34;i-xxxxxxxxxxxxxxxxxx&#34;,
</span></span><span class="line"><span class="cl">        &#34;state&#34;: &#34;running&#34;
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[INFO]	2025-06-13T08:22:22.558Z	f9781c8c-4d8e-484e-9504-abaefc5d87fc	Processing EC2 instance &#39;i-xxxxxxxxxxxxxxxxxx&#39; in region &#39;eu-south-1&#39; for DNS update.
</span></span><span class="line"><span class="cl">[INFO]	2025-06-13T08:22:22.751Z	f9781c8c-4d8e-484e-9504-abaefc5d87fc	Found credentials in environment variables.
</span></span><span class="line"><span class="cl">[INFO]	2025-06-13T08:22:26.168Z	f9781c8c-4d8e-484e-9504-abaefc5d87fc	Found instance &#39;i-xxxxxxxxxxxxxxxxxx&#39;, DNS name: &#39;xyz&#39;, Public IP: &#39;1.2.3.4&#39;. Attempting DNS update.
</span></span><span class="line"><span class="cl">[INFO]	2025-06-13T08:22:26.168Z	f9781c8c-4d8e-484e-9504-abaefc5d87fc	Attempting UPSERT for DNS record: xyz (Type: A, TTL: 30) to IP: 1.2.3.4 in Hosted Zone: Z00000000X0XXXXXXXXX
</span></span><span class="line"><span class="cl">[INFO]	2025-06-13T08:22:26.964Z	f9781c8c-4d8e-484e-9504-abaefc5d87fc	Successfully UPSERTed DNS record for xyz -&gt; 1.2.3.4. Change ID: /change/C07906251F9R4KH5FQN6Q
</span></span><span class="line"><span class="cl">[INFO]	2025-06-13T08:22:26.966Z	f9781c8c-4d8e-484e-9504-abaefc5d87fc	Function execution completed with status: success
</span></span><span class="line"><span class="cl">END RequestId: f9781c8c-4d8e-484e-9504-abaefc5d87fc
</span></span><span class="line"><span class="cl">REPORT RequestId: f9781c8c-4d8e-484e-9504-abaefc5d87fc	Duration: 4451.47 ms	Billed Duration: 4452 ms	Memory Size: 128 MB	Max Memory Used: 95 MB	Init Duration: 387.96 ms</span></span></code></pre></div></div>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >With this basic script, the text collected will not be validated. This means that if there isn’t a PublicDNS tag and the Name is not a valid DNS record name, the script will end with an error.</span>
</div>


<h2 class="relative group">AWS CloudWatch Automation
    <div id="aws-cloudwatch-automation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#aws-cloudwatch-automation" aria-label="Anchor">#</a>
    </span>
    
</h2>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >You can skip this part if you want to implement only the start/stop automation without the DNS auto-update.</span>
</div>

<p>In this step, we will define the CloudWatch automation that calls the UpdateDNS lambda every time a VM is started.</p>
<ol>
<li>
<p>Open the CloudWatch dashboard, and click on Events &gt; Rules</p>
</li>
<li>
<p>Create a new rule</p>
</li>
<li>
<p>Name: EC2StartDNSUpdate









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/cloudwatch1_hu_92ba8b4546965f28.webp  330w,
      /howto/aws-ec2-workflows/cloudwatch1_hu_e10b71a1f0a80f69.webp  660w,
      /howto/aws-ec2-workflows/cloudwatch1_hu_c9d23d5e6dc68136.webp  960w,
      /howto/aws-ec2-workflows/cloudwatch1_hu_810436344f9a111e.webp 1280w,
      /howto/aws-ec2-workflows/cloudwatch1_hu_46247a8841ee582d.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/cloudwatch1.png"
    src="/howto/aws-ec2-workflows/cloudwatch1.png">


  
</figure>
</p>
</li>
<li>
<p>In the “Build event pattern” section, select “Other” as a source, then copy the following JSON in the Event pattern area:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;aws.ec2&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;detail-type&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;EC2 Instance State-change Notification&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;detail&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;state&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;running&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/cloudwatch2_hu_44575a804e4f8310.webp  330w,
      /howto/aws-ec2-workflows/cloudwatch2_hu_78caee68bc81da1b.webp  660w,
      /howto/aws-ec2-workflows/cloudwatch2_hu_7f31e07235db6991.webp  960w,
      /howto/aws-ec2-workflows/cloudwatch2_hu_f3555caba2ce57a1.webp 1280w,
      /howto/aws-ec2-workflows/cloudwatch2_hu_f2997d7cd071a25b.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/cloudwatch2.png"
    src="/howto/aws-ec2-workflows/cloudwatch2.png">


  
</figure>
</li>
<li>
<p>In the “Select target” section, select “AWS service” as type. Then “Lambda function” as target. In the function area, select the Lambda function “UpdateDNS” that we created just before.</p>
</li>
<li>
<p>To conclude, flag the “Use execution role” and assign the role “LambdaAutomationsRole” that we created at the beginning. This grants CloudWatch permission to invoke the Lambda.









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/cloudwatch3_hu_66729f5e6156ae00.webp  330w,
      /howto/aws-ec2-workflows/cloudwatch3_hu_8faa4c00808ed5.webp  660w,
      /howto/aws-ec2-workflows/cloudwatch3_hu_9324d3bec38e1e21.webp  960w,
      /howto/aws-ec2-workflows/cloudwatch3_hu_7195df7ac980f211.webp 1280w,
      /howto/aws-ec2-workflows/cloudwatch3_hu_f4f4172b08e376ef.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/cloudwatch3.png"
    src="/howto/aws-ec2-workflows/cloudwatch3.png">


  
</figure>
</p>
</li>
<li>
<p>Skip the tag configuration and click on “Create rule” to conclude</p>
</li>
</ol>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Lambda scripts and CloudWatch events are based on the region. If you’re using multiple regions, you have to copy the UpdateIP script and the CloudWatch configuration in every region.</span>
</div>


<h2 class="relative group">Okta Workflows
    <div id="okta-workflows" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#okta-workflows" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Allow AWS Lambda access from Okta Workflows
    <div id="allow-aws-lambda-access-from-okta-workflows" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#allow-aws-lambda-access-from-okta-workflows" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>
<p>Navigate to the IAM Console in AWS and Create a new IAM User:
a. In the IAM dashboard, select Users from the navigation pane and then click Add users.
b. Provide a user name (e.g., <code>okta-workflows-user</code>).
c. For the AWS credential type, select Access key - Programmatic access.</p>
</li>
<li>
<p>Set Permissions for the User:
a. Select Attach existing policies directly.
b. In the filter box, search for the policy you created earlier, <code>LambdaAutomationsPolicy</code>.
c. Check the box next to the policy.</p>
</li>
<li>
<p>Add Tags (Optional): You can add tags for organizational purposes if you wish.</p>
</li>
<li>
<p>Review and Create User:
a. Review the user details to ensure the correct policy is attached.
b. Click <em>Create user</em> .</p>
</li>
<li>
<p>Save Your Credentials: on the final screen, you will see the Access key ID and the Secret access key.</p>
<p><span class="relative inline-block align-text-bottom icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg></span> This is the only time the Secret Access Key will be shown. You must click *Show and copy * both the <em>Access Key ID</em> and the <em>Secret Access Key</em> to a secure location. You can also download them as a .csv file.</p>
</li>
<li>
<p>In the Workflows dashboard, click on “Connections” and then “+ New Connection”</p>
</li>
<li>
<p>Give a mnemonic name (i.e., “<em>AWS Lambda</em> ”), select the region, and copy the Access Key and Secret generated in Step 5.</p>
<p>[TO DO SCREENSHOT]</p>
</li>
</ol>

<h3 class="relative group">Flowpac
    <div id="flowpac" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#flowpac" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>You can download and import the following sample flowpack:
<a href="https://github.com/fabiograsso/okta-flowpack/blob/main/AWS%20Utilities%20-%20EC2%20PowerCycle%20with%20auto-update%20DNS/flowpack.folder"  target="_blank" rel="noreferrer">https://github.com/fabiograsso/okta-flowpack/blob/main/AWS%20Utilities%20-%20EC2%20PowerCycle%20with%20auto-update%20DNS/flowpack.folder</a></p>
<p>Which contains 5 sample Workflow:</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/workflows-list_hu_6e646275d8815a77.webp  330w,
      /howto/aws-ec2-workflows/workflows-list_hu_d45bdee5dfdd2795.webp  660w,
      /howto/aws-ec2-workflows/workflows-list_hu_8aa15378d0a9c289.webp  960w,
      /howto/aws-ec2-workflows/workflows-list_hu_5092bbf8c4d8f4c0.webp 1280w,
      /howto/aws-ec2-workflows/workflows-list_hu_b87156c8584e7f15.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/workflows-list.png"
    src="/howto/aws-ec2-workflows/workflows-list.png">


  
</figure>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><p>Remember to change the Lambda Invoke box with your Lambda connector, and to change the instance ID/region with yours:</p>
<p>








<figure>
    <img class="my-0 rounded-md" loading="lazy" alt="" src="workflows-conn1.png">


  
</figure>
 








<figure>
    <img class="my-0 rounded-md" loading="lazy" alt="" src="workflows-conn2.png">


  
</figure>
</p>
</span>
</div>


<h3 class="relative group">Sample Workflow 1 (API Endpoint)
    <div id="sample-workflow-1-api-endpoint" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sample-workflow-1-api-endpoint" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The following sample can be utilized to dynamically start/stop VMs by calling a public HTTP endpoint. It can be used in a script or called from a <a href="/howto/aws-ec2-workflows/#Bookmarks" title="#Bookmarks">bookmark in the Okta dashboard</a>.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/wf1-flux_hu_9811d97a0da6eedc.webp  330w,
      /howto/aws-ec2-workflows/wf1-flux_hu_f13dd946bf3a6a43.webp  660w,
      /howto/aws-ec2-workflows/wf1-flux_hu_6dd86f3be6a11dbf.webp  960w,
      /howto/aws-ec2-workflows/wf1-flux_hu_f32ed25b7959f18f.webp 1280w,
      /howto/aws-ec2-workflows/wf1-flux_hu_eac37e8deccdc842.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/wf1-flux.png"
    src="/howto/aws-ec2-workflows/wf1-flux.png">


  
</figure>
<ol>
<li>The first part will take operation (start/stop), instances, and region from the query string and use them to call the URL of the API Endpoint.









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/wf1a_hu_d25dd4cf61b8b50b.webp  330w,
      /howto/aws-ec2-workflows/wf1a_hu_98982124f208ba37.webp  660w,
      /howto/aws-ec2-workflows/wf1a_hu_e529de6d08e28f32.webp  960w,
      /howto/aws-ec2-workflows/wf1a_hu_38843b7f4ee1d54a.webp 1280w,
      /howto/aws-ec2-workflows/wf1a_hu_8da3ec8c243ae0d6.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/wf1a.png"
    src="/howto/aws-ec2-workflows/wf1a.png">


  
</figure>
</li>
<li>Then we compose the JSON payload by taking the information received from the query string, and we invoke the Lambda script</li>
<li>The last part is just a verification of the status code of the Lambda script, and a simple HTML page to show the output in a more fancy way









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/wf1b_hu_a3f83188a96f5119.webp  330w,
      /howto/aws-ec2-workflows/wf1b_hu_5fa9cb44eded4a17.webp  660w,
      /howto/aws-ec2-workflows/wf1b_hu_6d6fc768faf49f89.webp  960w,
      /howto/aws-ec2-workflows/wf1b_hu_55300b56b645438c.webp 1280w,
      /howto/aws-ec2-workflows/wf1b_hu_aae9415d38f289c4.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/wf1b.png"
    src="/howto/aws-ec2-workflows/wf1b.png">


  
</figure>
</li>
</ol>
<p>I’m using simple security via client token. This may be fine in a demo/non-production environment. If it has to be used in production, I warmly suggest moving to OAuth-based security or using only the delegated workflows feature.</p>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >I’m using simple security via client token. This may be fine in a demo/non-production environment. If it has to be used in production, I warmly suggest moving to OAuth-based security or using only the delegated workflows feature.</span>
</div>


  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >It’s also possible to generate an HTTPS Endpoint directly in AWS Lambda (<a href="https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html"  target="_blank" rel="noreferrer"><strong>Invoking Lambda function URLs - AWS Lambda</strong></a>). However, I prefer to use Workflows because they are tools we know better, and they permit showcasing both Workflows themselves and other possible integrations, i.e., Delegation, and eventually Access Requests.Lambda scripts and CloudWatch events are based on the region. If you’re using multiple regions, you have to copy the UpdateIP script and the CloudWatch configuration in every region</span>
</div>


<h4 class="relative group">How to call the Workflow
    <div id="how-to-call-the-workflow" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-to-call-the-workflow" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>In the <em>API endpoint settings,</em> you can see the Invoke URL and the Client token</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/wf1-inv_hu_5d08948da0671522.webp  330w,
      /howto/aws-ec2-workflows/wf1-inv_hu_1c1e9955ef565ad6.webp  660w,
      /howto/aws-ec2-workflows/wf1-inv_hu_22b2d62315be9abf.webp  960w,
      /howto/aws-ec2-workflows/wf1-inv_hu_6ed67c7e3e00bb64.webp 1280w,
      /howto/aws-ec2-workflows/wf1-inv_hu_57cb2cdfd268c76a.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/wf1-inv.png"
    src="/howto/aws-ec2-workflows/wf1-inv.png">


  
</figure>
<p>You can then invoke the Workflow by opening the following URL:
<code>&lt;InvokeURL&gt;/?operation=&lt;start|stop&gt;&amp;region=&lt;region&gt;&amp;instances=&lt;all|instancesList&gt;&amp;clientToken=&lt;token&gt;</code></p>
<p>Example:
<code>https://mytenant.workflows.okta.com/api/flo/13c02bb9d5ff94c2caf299c579dded95/invoke?operation=stop&amp;region=us-east-1&amp;instances=i-xxxxxxxxxxxxxxxxxx,i-yyyyyyyyyyyyyyyyyyy&amp;clientToken=xxxx</code></p>
<p>HTML output of the workflow (when invoked in a browser):









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/wf1-out_hu_cc9279824a2c02f7.webp  330w,
      /howto/aws-ec2-workflows/wf1-out_hu_90779424f5cc1fb4.webp  660w,
      /howto/aws-ec2-workflows/wf1-out_hu_b234a5e7be3de49b.webp  960w,
      /howto/aws-ec2-workflows/wf1-out_hu_dac50f9ff27a4e17.webp 1280w,
      /howto/aws-ec2-workflows/wf1-out_hu_df6965d275892058.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/wf1-out.png"
    src="/howto/aws-ec2-workflows/wf1-out.png">


  
</figure>
</p>

<h3 class="relative group">Sample Workflow 2 (Scheduled Shutdown)
    <div id="sample-workflow-2-scheduled-shutdown" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sample-workflow-2-scheduled-shutdown" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The second workflow is a scheduled shutdown of all the EC2 servers in a specific AWS region at 21:00 (Paris time). I use it to avoid forgetting to shut down when I end my demos (which consequently increases the AWS invoice).</p>
<p>The invoke process is similar to sample 1, with the operation, region, and instances as static variables. You can then customize it by changing the <em>Object construct</em> block:









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/wf2-obj_hu_44f90b2282b28f92.webp  330w,
      /howto/aws-ec2-workflows/wf2-obj_hu_f0b955411225cfae.webp  660w,
      /howto/aws-ec2-workflows/wf2-obj_hu_88af066f22960a7c.webp  960w,
      /howto/aws-ec2-workflows/wf2-obj_hu_f477e2800613fcab.webp 1280w,
      /howto/aws-ec2-workflows/wf2-obj_hu_ba9a987cd04a7601.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/wf2-obj.png"
    src="/howto/aws-ec2-workflows/wf2-obj.png">


  
</figure>
</p>

<h3 class="relative group">Sample Workflow 3 (Scheduled Startup)
    <div id="sample-workflow-3-scheduled-startup" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sample-workflow-3-scheduled-startup" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Very similar to sample 2, in this case, I schedule a weekly startup of an EC2 instance.</p>
<p>In my case, I use it to start my OAG server half an hour before the scheduled shutdown. Since there are issues when OAG remains powered off for a long time, this assures me that there is a power cycle at least once per week.</p>

<h3 class="relative group">Sample Workflow 4-5 (Delegated)
    <div id="sample-workflow-4-5-delegated" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sample-workflow-4-5-delegated" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The latest samples are delegated workflows that will turn on/off all the servers in a specific region. In this case, you can showcase during a demo how to delegate a Workflow to someone without giving them access to the EC2 console. For example, a customer can give the helpdesk the right to Powercycle an EC2 server directly from the Okta console.</p>
<p>Remember to change the region in the Object construct block.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/wf5-deleg_hu_83e2a07a4b9ec630.webp  330w,
      /howto/aws-ec2-workflows/wf5-deleg_hu_dbdcb4c0f0e3f7a7.webp  660w,
      /howto/aws-ec2-workflows/wf5-deleg_hu_dbf0372a2195727c.webp  960w,
      /howto/aws-ec2-workflows/wf5-deleg_hu_772ced319f6d7481.webp 1280w,
      /howto/aws-ec2-workflows/wf5-deleg_hu_631b4d1db1aa4aff.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/wf5-deleg.png"
    src="/howto/aws-ec2-workflows/wf5-deleg.png">


  
</figure>

<h2 class="relative group">Okta Bookmarks
    <div id="okta-bookmarks" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#okta-bookmarks" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Leveraging the workflow shown in <em><a href="/howto/aws-ec2-workflows/#sample-workflow-1-api-endpoint" >Sample Workflow 1 (API Endpoint)</a></em> , it’s easy to create some bookmarks in the Okta dashboard.</p>
<p>For example, in my case, I created:</p>
<ol>
<li>Link to start my AD DC and domain members
<code>https://mytenant.workflows.okta.com/api/flo/13c02bb9d5ff94c2caf299c579dded95/invoke?operation=start&amp;region=us-east-1&amp;instances=i-xxxxxxxxxxxxxxxxxx,i-yyyyyyyyyyyyyyyyyyy,i-0999f40dd3c643fac,i-0b267b2e8b7db8317,i-xxxxxxxxxxxxxxxxxx&amp;clientToken=xxxxx</code></li>
<li>Link to start the AD DC only</li>
<li>Link to start OAG</li>
<li>Link to shut down everything
<code>https://mytenant.workflows.okta.com/api/flo/13c02bb9d5ff94c2caf299c579dded95/invoke?operation=stop&amp;region=us-east-1&amp;instances=all&amp;clientToken=xxxx</code></li>
</ol>
<p>When I need to start my demo, I can just click on the dashboard icon without opening the AWS EC2 console (or running a command in the AWS CLI). This is also a nice way to show customers the possible usage of Workflows, not strictly related to Identity.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/console_hu_20195c7434212b81.webp  330w,
      /howto/aws-ec2-workflows/console_hu_8610ada923f48bec.webp  660w,
      /howto/aws-ec2-workflows/console_hu_bba403cb18fe77f7.webp  960w,
      /howto/aws-ec2-workflows/console_hu_ac51e7152c080fc6.webp 1280w,
      /howto/aws-ec2-workflows/console_hu_5ba4e913d248fd61.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/console.png"
    src="/howto/aws-ec2-workflows/console.png">


  
</figure>

<h3 class="relative group">Voice control
    <div id="voice-control" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#voice-control" aria-label="Anchor">#</a>
    </span>
    
</h3>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Since it’s a simple HTTP request, you can also use it with a voice control such as Siri, Alexa, Google, etc.</span>
</div>


<h4 class="relative group">iOS Example (Shortcut + Siri)
    <div id="ios-example-shortcut--siri" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ios-example-shortcut--siri" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>For example, on my iPhone, I created some <a href="https://support.apple.com/guide/shortcuts/welcome/ios" title="https://support.apple.com/guide/shortcuts/welcome/ios" target="_blank" rel="noreferrer">Shortcuts</a> with the link to the start/stop Workflows. Then I use <a href="https://support.apple.com/guide/shortcuts/run-shortcuts-with-siri-apd07c25bb38/ios" title="https://support.apple.com/guide/shortcuts/run-shortcuts-with-siri-apd07c25bb38/ios" target="_blank" rel="noreferrer">Siri to execute the shortcut</a>:</p>
<p>:speaking head: *Hey Siri, execute “Start Domain Controller”! *
or
:speaking head: Hey Siri, run shortcut *“Start Domain Controller”! *</p>
<p>In theory, you can just say, “<em>Hey Siri, Start Domain Controller!</em> ” But I found that it doesn&rsquo;t work most of the time, so it&rsquo;s better to explicitly say “<em>execute”</em> or “<em>run shortcut</em> .”</p>
<p>








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/siri1_hu_17ce609fa934eaa4.webp  330w,
      /howto/aws-ec2-workflows/siri1_hu_2087bc9d221c909c.webp  660w,
      /howto/aws-ec2-workflows/siri1_hu_92bf3ee21cbd76f1.webp  960w,
      /howto/aws-ec2-workflows/siri1_hu_de3ae2c4c20a47db.webp 1280w,
      /howto/aws-ec2-workflows/siri1_hu_5353e8d953fe8674.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/siri1.png"
    src="/howto/aws-ec2-workflows/siri1.png">


  
</figure>










<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/aws-ec2-workflows/siri2_hu_6826ffb564e8b5a0.webp  330w,
      /howto/aws-ec2-workflows/siri2_hu_88381b39153c332e.webp  660w,
      /howto/aws-ec2-workflows/siri2_hu_3854d43791943e3d.webp  960w,
      /howto/aws-ec2-workflows/siri2_hu_7869aca9f5ce5754.webp 1280w,
      /howto/aws-ec2-workflows/siri2_hu_c1c45e8a02c51f39.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/aws-ec2-workflows/siri2.png"
    src="/howto/aws-ec2-workflows/siri2.png">


  
</figure>
</p>

<h2 class="relative group">AWS Costs
    <div id="aws-costs" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#aws-costs" aria-label="Anchor">#</a>
    </span>
    
</h2>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M48 32H197.5C214.5 32 230.7 38.74 242.7 50.75L418.7 226.7C443.7 251.7 443.7 292.3 418.7 317.3L285.3 450.7C260.3 475.7 219.7 475.7 194.7 450.7L18.75 274.7C6.743 262.7 0 246.5 0 229.5V80C0 53.49 21.49 32 48 32L48 32zM112 176C129.7 176 144 161.7 144 144C144 126.3 129.7 112 112 112C94.33 112 80 126.3 80 144C80 161.7 94.33 176 112 176z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><strong>TL;DR</strong> The low usage of these Lambda functions and CloudWatch events falls well within the <strong>AWS free tier</strong> . In a demo environment, there will therefore be no cost to use them.</span>
</div>

<p>**AWS Lambda pricing ** is based on two main components: the number of requests for your functions and the duration of their execution.</p>
<ul>
<li><em>Requests</em> : You are charged for the total number of requests made to your functions.</li>
<li><em>Duration</em> : This is calculated from the time your code begins executing until it terminates, rounded up to the nearest millisecond. The price depends on the amount of memory you allocate to the function.</li>
</ul>
<p>The <strong>Lambda free tier</strong> is quite generous, as it includes:</p>
<ul>
<li>1 million free requests per month</li>
<li>400,000 GB-seconds of compute time per month</li>
</ul>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >For the <code>PowerCycle</code> and <code>UpdateDNS</code> functions described, which are executed infrequently (manually or on a schedule), it is highly probable that their usage will remain well within the free tier, resulting in no cost.</span>
</div>

<p><strong>CloudWatch pricing</strong> has a more detailed structure with several components. For our use case, the relevant components are <em>Logs</em> and <em>Events</em> .</p>
<ul>
<li><em>CloudWatch Logs:</em> You are charged for the amount of log data ingested, stored, and analyzed. The Lambda functions we created will send log events to CloudWatch</li>
<li><em>CloudWatch Events:</em> The automation uses a CloudWatch Rule to trigger the <code>UpdateDNS</code> Lambda when a VM starts</li>
</ul>
<p>The <strong>CloudWatch free tier</strong> is also generous, and includes:</p>
<ul>
<li>5 GB of Log data ingestion and 5 GB of Log archival per month</li>
<li>1 million custom events per month</li>
</ul>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Given the low volume of events (only when an EC2 instance starts) and the small amount of log data these functions would generate, your CloudWatch usage would certainly be covered by the free tier.</span>
</div>


<h2 class="relative group">To Do
    <div id="to-do" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#to-do" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Some ideas for improvements:</p>
<ul>
<li>DNS Lambda:
<ul>
<li>Delete the DNS record on power off</li>
<li>IPv6 version</li>
<li>When falling back to Name tag, verify that it’s a valid DNS record. If not, exit with an error</li>
</ul>
</li>
<li>Start/stop Lambda:
<ul>
<li>Add a <code>region=all</code> case, and run a for cycle for all the regions</li>
<li>Add an <code>operation=info</code> case, that will output just the info of the instances using <a href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/describe_instances.html" title="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/describe_instances.html" target="_blank" rel="noreferrer">describe_instances</a></li>
<li>Use the  <code>operation=info</code> in order to auto-update a Network zone with the IPs used by the AWS EC2 Instances</li>
</ul>
</li>
<li>Workflows:
<ul>
<li>Add an email/Slack notification</li>
</ul>
</li>
<li>Others:
<ul>
<li>Create a Slack bot</li>
<li>Component for demo.okta.com</li>
</ul>
</li>
</ul>
]]></content:encoded>
      <category>AWS</category>
      <category>EC2</category>
      <category>Okta</category>
      <category>Workflows</category>
      <category>Lambda</category>
      <category>Route53</category>
      <category>CloudWatch</category>
      <category>Automation</category>
      <category>IAM</category>
      <category>Infrastructure</category>
    </item>
    <item>
      <title>GLPI Integration with Okta</title>
      <link>https://iam.fabiograsso.net/howto/okta-glpi-10/</link>
      <pubDate>Wed, 15 Nov 2023 15:46:00 +0200</pubDate>
      <guid>https://iam.fabiograsso.net/howto/okta-glpi-10/</guid>
      <description>How to integrate GLPI, an open-source IT service management platform, with Okta for SSO. It covers running a GLPI test environment via Docker, LDAP and SAML configuration walkthroughs, and notes on OAuth/OIDC with commercial plugins. The guide highlights user import, authentication options, demo readiness, and security limitations for non-production use.</description>
      <content:encoded>&lt;![CDATA[




  



  



















  
  <style>
     
     
     .panel-text p {
      margin-top: 0;
    }
    .panel-icon {
      vertical-align: top;
      display: inline;
      padding-top: 0.5em;
    }

  </style>












<style>
  html:not(.dark) #panel-1778753855932496760 {
    background: #FFFAE6;
    color: rgb(41,42,46);
  }
  html:not(.dark) #panel-1778753855932496760 .panel-icon {
    color: rgb(224,108,0);
  }
  html.dark #panel-1778753855932496760 {
    background: rgb(51,46,27);
    color: rgb(191,193,196);
  }
  html.dark #panel-1778753855932496760 .panel-icon {
    color: rgb(251,200,40);
  }
</style>

<div id="panel-1778753855932496760" class="flex px-4 py-3 rounded-md shadow panel-warning ">  
  <span class="text-primary-400 pe-3 flex items-center panel-icon">
     
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>
  <div class="panel-text"><p>The following guide is for the GLPI 10.x version. I&rsquo;ve wrote a new one for GLPI 11.x, as there are some changes in the SAML plugin and other improvements.</p>
<p>The new article is available here: <a href="/content/posts/howto/okta-glpi-11/" >GLPI 11.x Integration with Okta</a></p>

  </div>
</div>

<h2 class="relative group">Intro
    <div id="intro" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#intro" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p><a href="https://glpi-project.org/" title="https://glpi-project.org/" target="_blank" rel="noreferrer">GLPI</a> is an open-source service management software made in France and is used by a lot of companies in EMEA.</p>
<p>I worked with a customer for the integration with Okta. Here are some notes and the instructions for running a test environment with docker.</p>
<p>Note: since it’s very easy to run GLPI with docker-compose and configure it with LDAP and/or SAML, it can be a good solution for a demo environment when you have to demonstrate the typical user experience with an LDAP (or SAML) integration.</p>

<h2 class="relative group">Run a test environment with Docker Compose
    <div id="run-a-test-environment-with-docker-compose" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#run-a-test-environment-with-docker-compose" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>There is an <a href="https://hub.docker.com/r/diouxx/glpi" title="https://hub.docker.com/r/diouxx/glpi" target="_blank" rel="noreferrer">existing docker image</a> that permits running GLPI very quickly in docker or docker-compose. Unfortunately, it is not built for ARM and will crash with Mac M1/M2.</p>
<p>I downloaded it and made some changes in order to run a custom build with ARM.</p>
<p>Here is the zip file: glpi-docker-compose.zip [TODO - Download Link]</p>
<p>To run it, just extract the zip and execute:</p>
<p><code>make start</code></p>
<p>It exposes GLPI on port 80 in localhost, so you can open it using <a href="http://localhost/" title="http://localhost" target="_blank" rel="noreferrer">http://localhost</a></p>
<p>For the database configuration, you can use the following parameters:</p>
<p>To run it, just extract the zip and execute:</p>
<p><code>make start</code></p>
<p>It exposes GLPI on port 80 in localhost, so you can open it using <a href="http://localhost/" title="http://localhost" target="_blank" rel="noreferrer">http://localhost</a></p>
<p>For the database configuration, you can use the following parameters:</p>
<p>Database server: <code>mariadb</code>
Username: <code>glpi_user</code>
Password: <code>glpi</code>
Existing Database: <code>glpi</code></p>
<p>Once started, here are the default users for login in GLPI:</p>
<table>
  <thead>
      <tr>
          <th>Login/Password</th>
          <th>Role</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>glpi/glpi</td>
          <td>admin account</td>
      </tr>
      <tr>
          <td>tech/tech</td>
          <td>technical account</td>
      </tr>
      <tr>
          <td>normal/normal</td>
          <td>&ldquo;normal&rdquo; account</td>
      </tr>
      <tr>
          <td>post-only/postonly</td>
          <td>post-only account</td>
      </tr>
  </tbody>
</table>
<p>You will find two new folders:</p>
<p><code>mysql</code> contains the database, in order to make it persistent when you restart the docker image
<code>www</code> contains the GLPI web application</p>
<p>&#x26a0;&#xfe0f; Note: this docker-compose is for internal testing only. There is no security configured, and must not be used in a production environment or in a public-facing server without hardening the security.</p>

<h2 class="relative group">Integration with Okta - SSO
    <div id="integration-with-okta---sso" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#integration-with-okta---sso" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>To federate it with Okta, there are three options:</p>
<ol>
<li><strong>LDAP Interface</strong></li>
<li><strong>SAML</strong> using an open-source plugin called ‘phpsaml’</li>
<li><strong>OAuth/OIDC</strong> using an official plugin provided by GLPI Network</li>
</ol>

<h3 class="relative group">LDAP Interface
    <div id="ldap-interface" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ldap-interface" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>During my test, it was impossible to configure it using port 636. So, I switched on using port 389 with TLS. This is my configuration:</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-10/ldap-interface1_hu_15242c646b1edc8e.webp  330w,
      /howto/okta-glpi-10/ldap-interface1_hu_50b7f76c1f1fccef.webp  660w,
      /howto/okta-glpi-10/ldap-interface1_hu_da3ed47efb0e4248.webp  960w,
      /howto/okta-glpi-10/ldap-interface1_hu_83e0c27085a37849.webp 1280w,
      /howto/okta-glpi-10/ldap-interface1_hu_93e9c32fd2546f26.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-10/ldap-interface1.png"
    src="/howto/okta-glpi-10/ldap-interface1.png">


  
</figure>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-10/ldap-interface2_hu_b34be1fb985613e3.webp  330w,
      /howto/okta-glpi-10/ldap-interface2_hu_64cbb69b8afb97dd.webp  660w,
      /howto/okta-glpi-10/ldap-interface2_hu_29ee5f525376e0bf.webp  960w,
      /howto/okta-glpi-10/ldap-interface2_hu_7b6c0aa4209c4644.webp 1280w,
      /howto/okta-glpi-10/ldap-interface2_hu_e8089f8b10aad0a0.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-10/ldap-interface2.png"
    src="/howto/okta-glpi-10/ldap-interface2.png">


  
</figure>
<p>(additional attributes can be added - based on customer needs)</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-10/ldap-interface3_hu_243d54e44fba4e7a.webp  330w,
      /howto/okta-glpi-10/ldap-interface3_hu_adabf851d99acffe.webp  660w,
      /howto/okta-glpi-10/ldap-interface3_hu_e48151db41115396.webp  960w,
      /howto/okta-glpi-10/ldap-interface3_hu_81ad10954bbb9b85.webp 1280w,
      /howto/okta-glpi-10/ldap-interface3_hu_e4bb5f3e66579814.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-10/ldap-interface3.png"
    src="/howto/okta-glpi-10/ldap-interface3.png">


  
</figure>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-10/ldap-interface4_hu_3bb210ee8cf3a0f6.webp  330w,
      /howto/okta-glpi-10/ldap-interface4_hu_ce12299c2a315ecd.webp  660w,
      /howto/okta-glpi-10/ldap-interface4_hu_ea5e002155cdcea5.webp  960w,
      /howto/okta-glpi-10/ldap-interface4_hu_1e71e5135e7135de.webp 1280w,
      /howto/okta-glpi-10/ldap-interface4_hu_eef3fabb6c631bf6.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-10/ldap-interface4.png"
    src="/howto/okta-glpi-10/ldap-interface4.png">


  
</figure>
<p>Note: I suggest keeping the timeout as long as possible (the maximum is 30) in order to give to users the time to accept the push notification on the phone (if push MFA is used)</p>
<p>Once the LDAP is configured, you can import users from the Administration / Users &gt; LDAP directory link.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-10/ldap-interface5_hu_fbfc119669b805e7.webp  330w,
      /howto/okta-glpi-10/ldap-interface5_hu_b7228927e80c6b81.webp  660w,
      /howto/okta-glpi-10/ldap-interface5_hu_ed2630b8f057af7c.webp  960w,
      /howto/okta-glpi-10/ldap-interface5_hu_344e4a0ad636fc5a.webp 1280w,
      /howto/okta-glpi-10/ldap-interface5_hu_1a9616515326c2a4.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-10/ldap-interface5.png"
    src="/howto/okta-glpi-10/ldap-interface5.png">


  
</figure>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-10/ldap-interface6_hu_71980f3ec2bd8900.webp  330w,
      /howto/okta-glpi-10/ldap-interface6_hu_6eb69534952ce293.webp  660w,
      /howto/okta-glpi-10/ldap-interface6_hu_4dd4843c7b2836a4.webp  960w,
      /howto/okta-glpi-10/ldap-interface6_hu_440315a9f252e8ca.webp 1280w,
      /howto/okta-glpi-10/ldap-interface6_hu_1063ac7cdb8ba0ad.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-10/ldap-interface6.png"
    src="/howto/okta-glpi-10/ldap-interface6.png">


  
</figure>
<p>The same can also be done for groups.</p>
<p>Then, the LDAP users can log in by selecting the proper login source on the login page (or by keeping the default one if “Default Server: Yes” is configured on the LDAP setting)</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-10/ldap-interface7_hu_e27557063bbc8e75.webp  330w,
      /howto/okta-glpi-10/ldap-interface7_hu_7c39cbe77e3df404.webp  660w,
      /howto/okta-glpi-10/ldap-interface7_hu_e97ac6fc9cfc2bf7.webp  960w,
      /howto/okta-glpi-10/ldap-interface7_hu_84cc7a20a2a44efd.webp 1280w,
      /howto/okta-glpi-10/ldap-interface7_hu_8095c1f17b2f564c.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-10/ldap-interface7.png"
    src="/howto/okta-glpi-10/ldap-interface7.png">


  
</figure>

<h3 class="relative group">SAML
    <div id="saml" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#saml" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The LDAP interface is limited in terms of features and user experience. Another option is to use LDAP. There is an open-source plugin called <a href="https://github.com/derricksmith/phpsaml" title="https://github.com/derricksmith/phpsaml" target="_blank" rel="noreferrer">phpsaml</a>. The installation is very easy:</p>
<ol>
<li>Download the zip file from <a href="https://github.com/derricksmith/phpsaml/releases"  target="_blank" rel="noreferrer"><strong>Releases · derricksmith/phpsaml</strong></a></li>
<li>Extract and copy in the folder <code>&lt;GLPI_ROOT&gt;/plugins/phpsaml</code> (if using my docker-compose, that’s the folder <code>www/plugins/phpsaml</code>)</li>
<li>Enable and configure it from the web interface of GLPI (Setup / Plugins</li>
</ol>

<h4 class="relative group">Configuration - Okta Side
    <div id="configuration---okta-side" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuration---okta-side" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>On Okta, the configuration is very easy, just a custom SAML application.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-10/saml1_hu_37ecdf1ae91df6f5.webp  330w,
      /howto/okta-glpi-10/saml1_hu_961362a3b4a32ca8.webp  660w,
      /howto/okta-glpi-10/saml1_hu_6bcdeb5d471c7ac5.webp  960w,
      /howto/okta-glpi-10/saml1_hu_f6608961f1185c48.webp 1280w,
      /howto/okta-glpi-10/saml1_hu_413b64c8f8fe82d7.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-10/saml1.png"
    src="/howto/okta-glpi-10/saml1.png">


  
</figure>

<h4 class="relative group">Configuration - GLPI Side
    <div id="configuration---glpi-side" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#configuration---glpi-side" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>For a simple demo perspective and to make the configuration easier, do not enable Strict and Single logout, so there is no need to generate an SP certificate.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-10/saml2_hu_9ef9b84260cccf3a.webp  330w,
      /howto/okta-glpi-10/saml2_hu_a6cdf524c7cd870d.webp  660w,
      /howto/okta-glpi-10/saml2_hu_f1812a99451d14ff.webp  960w,
      /howto/okta-glpi-10/saml2_hu_7a3b24b41e3517f.webp 1280w,
      /howto/okta-glpi-10/saml2_hu_862a139ad3bef630.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-10/saml2.png"
    src="/howto/okta-glpi-10/saml2.png">


  
</figure>
<p>If the Plugin Enforced option is set to Yes, then it will be mandatory for the user to log in using Okta. Otherwise, there is an option “Sign In with SSO” on the login page.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-10/saml3_hu_fc984d88404b931e.webp  330w,
      /howto/okta-glpi-10/saml3_hu_d85788bd35d19ae1.webp  660w,
      /howto/okta-glpi-10/saml3_hu_a698e45b623c4feb.webp  960w,
      /howto/okta-glpi-10/saml3_hu_6046ea034eec3ea3.webp 1280w,
      /howto/okta-glpi-10/saml3_hu_3da8f5d9d89f7aea.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-10/saml3.png"
    src="/howto/okta-glpi-10/saml3.png">


  
</figure>

<h3 class="relative group">SAML - SLO
    <div id="saml---slo" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#saml---slo" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Sample configuration with Single Logout:</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-10/saml4_hu_7915275dd3646359.webp  330w,
      /howto/okta-glpi-10/saml4_hu_12660de4b186f19.webp  660w,
      /howto/okta-glpi-10/saml4_hu_e40b3020c9e527ee.webp  960w,
      /howto/okta-glpi-10/saml4_hu_bb25670e2ed27ebd.webp 1280w,
      /howto/okta-glpi-10/saml4_hu_adb7e951a9e97433.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-10/saml4.png"
    src="/howto/okta-glpi-10/saml4.png">


  
</figure>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-glpi-10/saml5_hu_171bccb37529e096.webp  330w,
      /howto/okta-glpi-10/saml5_hu_60a5b106559a2547.webp  660w,
      /howto/okta-glpi-10/saml5_hu_c320aaf16cc1eec5.webp  960w,
      /howto/okta-glpi-10/saml5_hu_d552a9469ba1f2d6.webp 1280w,
      /howto/okta-glpi-10/saml5_hu_5abfb84a0c8710d2.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-glpi-10/saml5.png"
    src="/howto/okta-glpi-10/saml5.png">


  
</figure>

<h3 class="relative group">OAuth / OIDC
    <div id="oauth--oidc" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#oauth--oidc" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The last option for federate GLPI is to use OAuth.</p>
<p>There is a plugin <a href="https://services.glpi-network.com/documentation/1731/file/README.md" title="https://services.glpi-network.com/documentation/1731/file/README.md" target="_blank" rel="noreferrer">Oauth SSO client for GLPI</a> included in the <a href="https://services.glpi-network.com/"  target="_blank" rel="noreferrer"><strong>GLPI Network</strong></a> subscription (BASIC or higher).</p>
<p>In this case, is not included in the Open Source project and can be used only by customers with an active (paid) subscription.</p>
<p>I have not tested it, but they <a href="https://glpi-plugins.readthedocs.io/fr/latest/oauthsso/okta.html"  target="_blank" rel="noreferrer">explicitly mention Okta in their documentation</a>.</p>
<p>With the GLPI Network subscription you can also leverage on the <a href="https://glpi-plugins.readthedocs.io/en/latest/scim/index.html"  target="_blank" rel="noreferrer">SCIM connector for GLPI</a>.</p>
]]></content:encoded>
      <category>Okta</category>
      <category>GLPI</category>
      <category>SSO</category>
      <category>LDAP</category>
      <category>SAML</category>
      <category>OAuth</category>
      <category>OIDC</category>
      <category>SCIM</category>
      <category>Integration</category>
      <category>Docker</category>
    </item>
    <item>
      <title>Base64 Header in Okta Access Gateway</title>
      <link>https://iam.fabiograsso.net/howto/base64-header-oag/</link>
      <pubDate>Fri, 11 Aug 2023 15:46:00 +0200</pubDate>
      <guid>https://iam.fabiograsso.net/howto/base64-header-oag/</guid>
      <description>This guide explains how to send Base64-encoded HTTP headers with Okta Access Gateway (OAG) using nginx configuration extensions. It covers internal-only app setup, usage of OpenResty modules, and examples for encoding user data in headers. Solutions include native nginx directives and Lua scripting, supporting common legacy integration needs for secure internal communication and custom header enrichment.</description>
      <content:encoded>&lt;![CDATA[
<h2 class="relative group">Use case
    <div id="use-case" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#use-case" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>OAG - Okta Access Gateway, doesn&rsquo;t provide an out of the box way to encode in Base64 the header to be sent to an internal application. However, it is quite common for legacy applications to require Base64-encoded data (e.g., the username of the authenticated user). This guide takes advantage of the ability to edit the OAG nginx configuration to create a simple function that allows you to manage Base64 encoding.</p>

<h2 class="relative group">Instructions
    <div id="instructions" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#instructions" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>To make it work, we need to create an &ldquo;internal-only&rdquo; application in  OAG.</p>
<p>In this case, Public Domain must be an internal-only address not exposed to the internet (i.e. <code>testapp.domain.local</code>)</p>

<h3 class="relative group">Internal Application
    <div id="internal-application" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#internal-application" aria-label="Anchor">#</a>
    </span>
    
</h3>

<h4 class="relative group">Settings
    <div id="settings" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#settings" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li>Type: <code>No-Auth</code></li>
<li>Name: <code>TestApp - 1 Internal</code></li>
<li>Public Domain: <code>https://testapp.domain.local/</code></li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="eager"
    decoding="async"
    fetchpriority="high"
    alt=""
    srcset="
      /howto/base64-header-oag/1_hu_23718e7fcf4ac0df.webp  330w,
      /howto/base64-header-oag/1_hu_d64034453612671b.webp  660w,
      /howto/base64-header-oag/1_hu_d5bafac1ca7ad923.webp  960w,
      /howto/base64-header-oag/1_hu_996d29b0bb0c4532.webp 1280w,
      /howto/base64-header-oag/1_hu_e81e1eefa37d6aee.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/base64-header-oag/1.png"
    src="/howto/base64-header-oag/1.png">


  
</figure>

<h4 class="relative group">Attributes - None
    <div id="attributes---none" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#attributes---none" aria-label="Anchor">#</a>
    </span>
    
</h4>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/base64-header-oag/2_hu_b1481b7e4cdd2700.webp  330w,
      /howto/base64-header-oag/2_hu_d37e55cd4aca5388.webp  660w,
      /howto/base64-header-oag/2_hu_8c2002bec8e84597.webp  960w,
      /howto/base64-header-oag/2_hu_c00589fbedba9c33.webp 1280w,
      /howto/base64-header-oag/2_hu_9099f1c293595cf1.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/base64-header-oag/2.png"
    src="/howto/base64-header-oag/2.png">


  
</figure>

<h4 class="relative group">Policies
    <div id="policies" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#policies" aria-label="Anchor">#</a>
    </span>
    
</h4>
<ul>
<li>Policy Type: <code>Not Protected</code></li>
<li>Advanced: copy the following custom code&hellip;</li>
</ul>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/base64-header-oag/3_hu_d4b2b1e32e9a9e78.webp  330w,
      /howto/base64-header-oag/3_hu_cea9d431a91f8eb0.webp  660w,
      /howto/base64-header-oag/3_hu_74e42a7ef67eebb9.webp  960w,
      /howto/base64-header-oag/3_hu_c6bcff9946ca8cd8.webp 1280w,
      /howto/base64-header-oag/3_hu_384671e5ad41d109.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/base64-header-oag/3.png"
    src="/howto/base64-header-oag/3.png">


  
</figure>

<h4 class="relative group">Custom Code
    <div id="custom-code" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#custom-code" aria-label="Anchor">#</a>
    </span>
    
</h4>
<p>⚠️ Important: Start with this code to deny access from internet. This host must be reachable only by the OAG.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">allow</span> <span class="n">127.0.0.1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">deny</span> <span class="s">all</span><span class="p">;</span></span></span></code></pre></div></div>
<p>Sample code using LUA for one field (i.e. Username) in the AUTHORIZATION header:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">set_encode_base64</span> <span class="nv">$authzHeader</span> <span class="nv">$http_oag_username</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">proxy_set_header</span> <span class="s">AUTHORIZATION</span> <span class="nv">$authzHeader</span><span class="p">;</span></span></span></code></pre></div></div>
<p>Alternative code combining two fields (i.e. First Name + Last Name) in the AUTHORIZATION header:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">set</span> <span class="nv">$val</span> <span class="s">&#34;</span><span class="nv">${http_FirstName}${http_FirstLastName}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">set_encode_base64</span> <span class="nv">$authzHeader</span> <span class="nv">$val</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">proxy_set_header</span> <span class="s">AUTHORIZATION</span> <span class="nv">$authzHeader</span><span class="p">;</span></span></span></code></pre></div></div>
<p>As you noted, we are using the <a href="https://github.com/openresty/set-misc-nginx-module?tab=readme-ov-file#set_encode_base64"  target="_blank" rel="noreferrer">set_encode_base64</a> function of nginx. This function is included in the OpenResty module <a href="https://github.com/openresty/set-misc-nginx-module"  target="_blank" rel="noreferrer">set-misc</a></p>

<h3 class="relative group">OAG Configuration
    <div id="oag-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#oag-configuration" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>
<p>Open an SSH session</p>
</li>
<li>
<p>Select <kbd><kbd>1</kbd></kbd>  <code>Network</code> and then <kbd><kbd>3</kbd></kbd> <code>Edit /etc/hosts</code>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/base64-header-oag/4_hu_15c045b0e6097ad4.webp  330w,
      /howto/base64-header-oag/4_hu_bf85b285c07a73c2.webp  660w,
      /howto/base64-header-oag/4_hu_1d98bb2df179967e.webp  960w,
      /howto/base64-header-oag/4_hu_583f0e2ea74525f8.webp 1280w,
      /howto/base64-header-oag/4_hu_9ea4a4b4779df1c2.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/base64-header-oag/4.png"
    src="/howto/base64-header-oag/4.png">


  
</figure>










<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/base64-header-oag/5_hu_6f89ff46c1e87b99.webp  330w,
      /howto/base64-header-oag/5_hu_854f54484dc36985.webp  660w,
      /howto/base64-header-oag/5_hu_ccf24d0e388e1410.webp  960w,
      /howto/base64-header-oag/5_hu_ef8a7ce5a6db0b31.webp 1280w,
      /howto/base64-header-oag/5_hu_34424636938a27aa.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/base64-header-oag/5.png"
    src="/howto/base64-header-oag/5.png">


  
</figure>
</p>
</li>
<li>
<p>Select <kbd><kbd>a</kbd></kbd>  (<code>[a]dd entry</code>)</p>
</li>
<li>
<p>Enter IP address: <code>127.0.0.1</code></p>
</li>
<li>
<p>Enter Host(s): <code>testapp.domain.local</code> (the “Public Domain” of the internal application)









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/base64-header-oag/6_hu_c5380645f51aeccb.webp  330w,
      /howto/base64-header-oag/6_hu_48d62e04650067bb.webp  660w,
      /howto/base64-header-oag/6_hu_65e19d110769b93c.webp  960w,
      /howto/base64-header-oag/6_hu_721da2d57dcf37cb.webp 1280w,
      /howto/base64-header-oag/6_hu_dee600bb86f206d7.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/base64-header-oag/6.png"
    src="/howto/base64-header-oag/6.png">


  
</figure>
</p>
</li>
<li>
<p>Confirm by pressing key <kbd><kbd>c</kbd></kbd> (<code>[c]ommit changes</code>)</p>
<p>Then press <kbd><kbd>x</kbd></kbd> (<code>e[x]it</code>)</p>
<p>Press again <kbd><kbd>c</kbd></kbd> (<code>c - commit changes to the system</code>)</p>
<p>And finally <kbd><kbd>x</kbd></kbd> (<code>exit</code>)</p>
</li>
</ol>

<h3 class="relative group">Test the configuration
    <div id="test-the-configuration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-the-configuration" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>[TODO]</p>

<h3 class="relative group">Lua alternative
    <div id="lua-alternative" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#lua-alternative" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>ℹ️ Note: It is also possible to use <a href="https://www.lua.org/"  target="_blank" rel="noreferrer">Lua</a>. In fact, OAG uses <a href="https://openresty.org/"  target="_blank" rel="noreferrer">OpenResty</a> (an extension of nginx, which supports <a href="https://luajit.org/"  target="_blank" rel="noreferrer">LuaJIT</a>, a Just-In-Time Compiler for <a href="https://www.lua.org/"  target="_blank" rel="noreferrer">Lua</a>). However, for this simple use case, it is sufficient to use the native nginx functions.</p>
<p>As a reference example, here is the Lua version for Base64 encoding:</p>
<p>Single field (username):</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">set_by_lua_block</span> <span class="nv">$authzHeader</span>  <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kn">local</span> <span class="s">val</span> <span class="p">=</span>  <span class="s">ngx.req.get_headers()[&#39;oag_username&#39;]</span>
</span></span><span class="line"><span class="cl"> <span class="s">return</span> <span class="s">ngx.encode_base64(val)</span>
</span></span><span class="line"><span class="cl"><span class="err">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="s">proxy_set_header</span> <span class="s">AUTHORIZATION</span> <span class="nv">$authzHeader</span><span class="p">;</span></span></span></code></pre></div></div>
<p>Joint of two fields (First Name + Last Name):</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">set_by_lua_block</span> <span class="nv">$authzHeader</span>  <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kn">local</span> <span class="s">firstname</span> <span class="p">=</span>  <span class="s">ngx.req.get_headers()[&#39;FirstName&#39;]</span>
</span></span><span class="line"><span class="cl"> <span class="s">local</span> <span class="s">laststname</span> <span class="p">=</span>  <span class="s">ngx.req.get_headers()[&#39;LastName&#39;]</span>
</span></span><span class="line"><span class="cl"> <span class="s">local</span> <span class="s">val</span> <span class="p">=</span>  <span class="s">firstname..lastname</span> 
</span></span><span class="line"><span class="cl"> <span class="s">return</span> <span class="s">ngx.encode_base64(val)</span>
</span></span><span class="line"><span class="cl"><span class="err">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="s">proxy_set_header</span> <span class="s">AUTHORIZATION</span> <span class="nv">$authzHeader</span><span class="p">;</span></span></span></code></pre></div></div>
]]></content:encoded>
      <category>Okta</category>
      <category>OAG</category>
      <category>Okta Access Gateway</category>
      <category>nginx</category>
      <category>Base64</category>
      <category>OpenResty</category>
      <category>Legacy Integration</category>
      <category>Headers</category>
    </item>
    <item>
      <title>Okta Flask SCIM Server with Docker Compose</title>
      <link>https://iam.fabiograsso.net/howto/okta-flask-scim-server-docker-compose/</link>
      <pubDate>Wed, 09 Aug 2023 12:25:00 +0000</pubDate>
      <guid>https://iam.fabiograsso.net/howto/okta-flask-scim-server-docker-compose/</guid>
      <description>Enable rapid SCIM server testing with Okta using Flask, Docker Compose, and ngrok tunnels. This guide details a working starter solution with persistent PostgreSQL data, Makefile commands for easy management, and public access via ngrok. Ideal for demo and development, it supports Okta provisioning but implements no production-grade security. Sample endpoints, troubleshooting notes, and port references included for quick setup.</description>
      <content:encoded>&lt;![CDATA[<p>This project is based on <a href="https://github.com/oktadev/okta-scim-flask-example"  target="_blank" rel="noreferrer">okta-scim-flask-example</a> and the guidance of the Okta blog post <a href="https://developer.okta.com/blog/2021/09/01/flask-scim-server"  target="_blank" rel="noreferrer">How to Build a Flask SCIM Server Configured for Use with Okta</a>.</p>
<p>There are some issues with running the Flesk server in an environment with Python &gt;3.10. As it&rsquo;s not easy to prepare an environment with the right version of Python and the relative dependencies, I used a Docker Compose in order to make very quick the startup of the SCIM Server.</p>
<ul>
<li>Quick Instruction
Clone  the repository at <a href="https://github.com/fabiograsso/okta-lab-scim-server"  target="_blank" rel="noreferrer">GitHub - fabiograsso/okta-lab-scim-server</a>: Okta SCIM Server with Docker Compose  (or download <a href="https://github.com/fabiograsso/lab-okta-scim-server-docker-compose/archive/refs/heads/main.zip"  target="_blank" rel="noreferrer">https://github.com/fabiograsso/okta-lab-scim-server/archive/refs/heads/main.zip</a>)</li>
<li>⚠️ Before running the server, edit file <code>config/ngrok.yml</code> with the ngrok Authtoken (otherwise the ngrok session will be limited to 2 hours)</li>
<li>To start the SCIM Server, execute the following:
`make start`
The ngrok URL is outputted in the console after the startup. Then you can follow the instruction of the blog post, <a href="https://developer.okta.com/blog/2021/09/01/flask-scim-server"  target="_blank" rel="noreferrer">How to Build a Flask SCIM Server Configured for Use with Okta</a>, to create and configure the application in Okta.</li>
</ul>
<p>Remember that the free version of ngrok uses dynamic URLs, so at every execution of the server, the public URL will change and must be updated in the Okta configuration.</p>
<p>Note: <a href="https://developer.apple.com/forums/thread/700989"  target="_blank" rel="noreferrer">Port 5000 is already in use on OSX</a>, so I used the following ports:</p>
<table>
  <thead>
      <tr>
          <th>URL/PORT</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="http://localhost:5001/"  target="_blank" rel="noreferrer">http://localhost:5001</a></td>
          <td>ngrok console (useful to see the SCIM commands)</td>
      </tr>
      <tr>
          <td><a href="http://localhost:5002/"  target="_blank" rel="noreferrer">http://localhost:5002</a></td>
          <td>Adminer (for managing the PostgreSQL content)</td>
      </tr>
      <tr>
          <td><a href="http://localhost:5003/"  target="_blank" rel="noreferrer">http://localhost:5003</a></td>
          <td>Okta SCIM Server (for local debugging and testing)</td>
      </tr>
      <tr>
          <td>postgresql://localhost:5004</td>
          <td>PostgreSQL Database</td>
      </tr>
  </tbody>
</table>
<p>The Postgres data folder is saved in <code>data/postgresql</code> in order to make the db persistent. To start from scratch, delete the folder.</p>
<p>You can test the app by running:</p>
<p><code>curl -XGET -H 'Authorization: Bearer 123456789' -H &quot;Content-type: application/json&quot; 'http://localhost:5003/scim/v2/Users'</code></p>
<p>for localhost access, and:</p>
<p><code>curl -XGET -H 'Authorization: Bearer 123456789' -H &quot;Content-type: application/json&quot; 'https://xyz.ngrok-free.app/scim/v2/Users'</code></p>
<p>for public access (remember to change the ngrok URL with your)</p>
<p>Additional info is in the <a href="https://github.com/fabiograsso/okta-scim-server-docker-compose#readme"  target="_blank" rel="noreferrer">GitHub repository readme file</a>.</p>









<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt=""
    srcset="
      /howto/okta-flask-scim-server-docker-compose/scim_hu_4515a7179d6a02b.webp  330w,
      /howto/okta-flask-scim-server-docker-compose/scim_hu_b4a964aba9da3cdb.webp  660w,
      /howto/okta-flask-scim-server-docker-compose/scim_hu_431836833e6fc80e.webp  960w,
      /howto/okta-flask-scim-server-docker-compose/scim_hu_20bf9561a0f12a3f.webp 1280w,
      /howto/okta-flask-scim-server-docker-compose/scim_hu_b17f65f861dacedb.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-flask-scim-server-docker-compose/scim.png"
    src="/howto/okta-flask-scim-server-docker-compose/scim.png">


  
</figure>

<h2 class="relative group">Prerequisites
    <div id="prerequisites" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#prerequisites" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Docker and Docker Compose</p>

<h2 class="relative group">Usage
    <div id="usage" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#usage" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>A Makefile is present in order to create some shortcuts for common operations.</p>
<p>Usage:</p>
<table>
  <thead>
      <tr>
          <th>Command</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>make start</code></td>
          <td>Start docker-compose (in background)</td>
      </tr>
      <tr>
          <td><code>make stop</code></td>
          <td>Stop docker-compose</td>
      </tr>
      <tr>
          <td><code>make restart</code></td>
          <td>Restart docker-compose</td>
      </tr>
      <tr>
          <td><code>make logs</code></td>
          <td>Show the last 500 logs and start tail -f</td>
      </tr>
      <tr>
          <td><code>make start-logs</code></td>
          <td>Start docker-compose with logs</td>
      </tr>
      <tr>
          <td><code>make restart-logs</code></td>
          <td>Restart docker-compose with logs</td>
      </tr>
      <tr>
          <td><code>make build</code></td>
          <td>Rebuild all docker images</td>
      </tr>
  </tbody>
</table>

<h2 class="relative group">Notes
    <div id="notes" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#notes" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>This project is only for testing purposes. No kind of security is implemented (i.e. PostgreSQL runs with trust authentication enabled).</p>

<h2 class="relative group">Custom Images
    <div id="custom-images" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#custom-images" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">scim-server
    <div id="scim-server" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#scim-server" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Python 3.10 image with a copy of <a href="https://github.com/oktadev/okta-scim-flask-example"  target="_blank" rel="noreferrer">okta-scim-flask-example</a>.</p>
<p>I changed the DB hostname from <code>localhost</code> to <code>db</code> and created a <code>startup.sh</code> file, to run the database preparation scripts.<a href="https://github.com/fabiograsso/okta-lab-scim-server#scim-server"  target="_blank" rel="noreferrer"></a></p>

<h2 class="relative group">Useful links
    <div id="useful-links" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#useful-links" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p><a href="https://www.okta.com/video/scim-course-introduction/"  target="_blank" rel="noreferrer">https://www.okta.com/video/scim-course-introduction/</a>
<a href="https://developer.okta.com/blog/2023/07/28/scim-workshop"  target="_blank" rel="noreferrer">https://developer.okta.com/blog/2023/07/28/scim-workshop</a>
<a href="https://help.okta.com/oie/en-us/content/topics/apps/apps-about-scim.htm"  target="_blank" rel="noreferrer">https://help.okta.com/oie/en-us/content/topics/apps/apps-about-scim.htm</a>
<a href="https://developer.okta.com/docs/concepts/scim/"  target="_blank" rel="noreferrer">https://developer.okta.com/docs/concepts/scim/</a></p>

<h2 class="relative group">Thanks to
    <div id="thanks-to" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#thanks-to" aria-label="Anchor">#</a>
    </span>
    
</h2>
<ul>
<li>@Cale Switzer for the original project and source code</li>
<li>@Pascale Kik for the idea &amp; beta test</li>
</ul>

<h2 class="relative group">Screenshot
    <div id="screenshot" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#screenshot" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>








<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Docker-compose startup"
    srcset="
      /howto/okta-flask-scim-server-docker-compose/1_hu_8e2d6f5cc8208813.webp  330w,
      /howto/okta-flask-scim-server-docker-compose/1_hu_d71a9129e7659449.webp  660w,
      /howto/okta-flask-scim-server-docker-compose/1_hu_fc7396f437c2c8a0.webp  960w,
      /howto/okta-flask-scim-server-docker-compose/1_hu_90619e5f3d27f95a.webp 1280w,
      /howto/okta-flask-scim-server-docker-compose/1_hu_4a6f75b403ba56ea.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-flask-scim-server-docker-compose/1.png"
    src="/howto/okta-flask-scim-server-docker-compose/1.png">


  
</figure>










<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="Docker Desktop"
    srcset="
      /howto/okta-flask-scim-server-docker-compose/2_hu_5d402b39661ded12.webp  330w,
      /howto/okta-flask-scim-server-docker-compose/2_hu_7a2b81212811e4cb.webp  660w,
      /howto/okta-flask-scim-server-docker-compose/2_hu_6e944a7b8f061a28.webp  960w,
      /howto/okta-flask-scim-server-docker-compose/2_hu_8d8f3ea9c60c8afd.webp 1280w,
      /howto/okta-flask-scim-server-docker-compose/2_hu_d2d3fe2793939cdc.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-flask-scim-server-docker-compose/2.png"
    src="/howto/okta-flask-scim-server-docker-compose/2.png">


  
</figure>










<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="PostgreSQL Login"
    srcset="
      /howto/okta-flask-scim-server-docker-compose/3_hu_38366ee20f5f1029.webp  330w,
      /howto/okta-flask-scim-server-docker-compose/3_hu_680399a9e7646495.webp  660w,
      /howto/okta-flask-scim-server-docker-compose/3_hu_a39d9eafff1f174e.webp  960w,
      /howto/okta-flask-scim-server-docker-compose/3_hu_ef59bf4f9ec8fa38.webp 1280w,
      /howto/okta-flask-scim-server-docker-compose/3_hu_59b2bf9015cc6ea3.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-flask-scim-server-docker-compose/3.png"
    src="/howto/okta-flask-scim-server-docker-compose/3.png">


  
</figure>










<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="PostgreSQL DB"
    srcset="
      /howto/okta-flask-scim-server-docker-compose/4_hu_53c8da7539f17538.webp  330w,
      /howto/okta-flask-scim-server-docker-compose/4_hu_d424fc6b079a1775.webp  660w,
      /howto/okta-flask-scim-server-docker-compose/4_hu_b274fdd9c6e8a865.webp  960w,
      /howto/okta-flask-scim-server-docker-compose/4_hu_9b2bb087e121435c.webp 1280w,
      /howto/okta-flask-scim-server-docker-compose/4_hu_ced93fc9d197010e.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-flask-scim-server-docker-compose/4.png"
    src="/howto/okta-flask-scim-server-docker-compose/4.png">


  
</figure>










<figure>
      
  <img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="low"
    alt="ngrok logs"
    srcset="
      /howto/okta-flask-scim-server-docker-compose/5_hu_48b8610d77e792f.webp  330w,
      /howto/okta-flask-scim-server-docker-compose/5_hu_99267e2ae7591ec6.webp  660w,
      /howto/okta-flask-scim-server-docker-compose/5_hu_1f576a08d61bdd58.webp  960w,
      /howto/okta-flask-scim-server-docker-compose/5_hu_22fb1bd109841ee2.webp 1280w,
      /howto/okta-flask-scim-server-docker-compose/5_hu_e011cb1ee216f533.webp 1920w
    "
    sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw"
    data-zoom-src="/howto/okta-flask-scim-server-docker-compose/5.png"
    src="/howto/okta-flask-scim-server-docker-compose/5.png">


  
</figure>
</p>

  <div class="github-card-wrapper">
    <a id="github-d2bfdf70f94018acdcfd96d7e3bbcde1" target="_blank" href="https://github.com/fabiograsso/okta-lab-scim-server" class="cursor-pointer">
      <div
        class="w-full md:w-auto p-0 m-0 border border-neutral-200 dark:border-neutral-700 border rounded-md shadow-2xl">
        <div class="w-full md:w-auto pt-3 p-5">
          <div class="flex items-center">
            <span class="text-2xl text-neutral-800 dark:text-neutral me-2">
              <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
</span>
            </span>
            <div
              id="github-d2bfdf70f94018acdcfd96d7e3bbcde1-full_name"
              class="m-0 font-bold text-xl text-neutral-800 decoration-primary-500 hover:underline hover:underline-offset-2 dark:text-neutral">
              fabiograsso/okta-lab-scim-server
            </div>
          </div>
          <p id="github-d2bfdf70f94018acdcfd96d7e3bbcde1-description" class="m-0 mt-2 text-md text-neutral-800 dark:text-neutral">
            View this repository on GitHub
          </p>
        </div>
      </div>
      
      
      <script
        async
        type="text/javascript"
        src="/js/fetch-repo.min.dc5533c50cefd50405344b235937142271f26229fe39cbee27fd4960e8bb897a0beebfad77a1091ca91cd0d1fb14e70fc37cc114dd9674fb2c32e0ab512ec8a4.js"
        integrity="sha512-3FUzxQzv1QQFNEsjWTcUInHyYin&#43;OcvuJ/1JYOi7iXoL7r&#43;td6EJHKkc0NH7FOcPw3zBFN2WdPssMuCrUS7IpA=="
        data-repo-url="https://api.github.com/repos/fabiograsso/okta-lab-scim-server"
        data-repo-id="github-d2bfdf70f94018acdcfd96d7e3bbcde1"></script>
    </a>
  </div>
]]></content:encoded>
      <category>Okta</category>
      <category>SCIM</category>
      <category>Flask</category>
      <category>Docker</category>
      <category>Provisioning</category>
      <category>Python</category>
      <category>ngrok</category>
      <category>PostgreSQL</category>
      <category>Adminer</category>
    </item>
  </channel>
</rss>