Phase 6: Backend & Admin Operations — Part 2 (Inline Hooks & Event Hooks)

Everything so far in this phase has been TaskFlow's backend calling Okta. This lecture flips that direction: Okta calling TaskFlow's backend, at specific points during its own processes, to run your custom logic. This is what Inline Hooks and Event Hooks are for, and they solve two genuinely different problems, so it's worth being clear on the distinction before building either.

Inline Hooks vs. Event Hooks — The Core Difference

Inline Hooks are synchronous and blocking. At a specific point in an Okta process — like a user completing self-service registration — Okta pauses, calls your external service, and waits for a response before continuing. Your response can actually change what happens next (e.g., "set this new user's profile field to X before the account is created"). Because Okta waits on your response, inline hooks must respond quickly and reliably.

Event Hooks are asynchronous and one-way. After something has already happened (a user was deactivated, a password was reset), Okta notifies your external service as a fire-and-forget notification. Your response doesn't change anything in Okta — it's purely "for your information," useful for logging, syncing to another system, or triggering a downstream workflow.

A simple way to remember it: Inline Hooks influence what Okta does next. Event Hooks tell you what Okta already did.

Step 1: Set Up ngrok — Required Before Either Hook Type Works

Both hook types require your endpoint to be reachable over HTTPS. Okta will not deliver to a plain HTTP URL, and your Next.js app running on localhost:3000 isn't reachable from the internet at all during development. ngrok solves this by creating a temporary public HTTPS tunnel that forwards to your local server.

Install ngrok

On Windows, the simplest method is winget:


    winget install ngrok.ngrok

Close and reopen your terminal afterward so it picks up the new PATH entry.

If winget isn't available, download it directly from https://ngrok.com/download, extract ngrok.exe somewhere permanent (e.g. C:\ngrok\), and either add that folder to your PATH or run it using its full path.

Create a Free Account and Connect Your Auth Token

Modern ngrok requires a free account before it'll run:

  1. Sign up at https://dashboard.ngrok.com/signup.
  2. Once logged in, go to https://dashboard.ngrok.com/get-started/your-authtoken and copy the token shown.
  3. In your terminal, run once:

    ngrok config add-authtoken YOUR_TOKEN_HERE

Run It

With your Next.js dev server already running in one terminal (npm run dev), open a second terminal window and run:


    ngrok http 3000

You'll see a Forwarding line like:

Forwarding    https://random-string.ngrok-free.app -> http://localhost:3000

That https://random-string.ngrok-free.app URL is what you'll paste into Okta's hook settings below, followed by your actual route path.

Two Things That Will Cause Silent Failures If You Miss Them

  • Both your Next.js dev server and ngrok must be running at the same time, in two separate terminal windows, whenever Okta tries to reach your endpoint — including during the one-time verification step below. If either one isn't running, Okta's request simply fails to connect, and you'll see a generic error with no useful detail.
  • The free ngrok tier generates a new random URL every time you restart it. If you stop ngrok and start it again later, the URL changes, and you must go back into Okta's hook settings and update the URL — otherwise Okta will keep trying to reach a tunnel that no longer exists.

Building an Inline Hook: Modifying a User's Profile at Registration

This connects directly to Phase 4's self-service registration. We'll add a Registration Inline Hook that runs the moment before Okta actually creates the new account, letting TaskFlow's backend inspect and modify the profile first.

Step 2: Build the Endpoint in Next.js

Create src/app/api/hooks/registration/route.ts:


    import { NextRequest, NextResponse } from "next/server";

    export async function POST(request: NextRequest) {
        // Verify the shared secret Okta sends, so random internet traffic
        // can't trigger this endpoint and manipulate registrations.
        const authHeader = request.headers.get("authorization");
        if (authHeader !== process.env.OKTA_HOOK_SECRET) {
            return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
        }

        const body = await request.json();
        const email: string = body.data?.userProfile?.email ?? "";

        // Example logic: assign a default role based on the email domain.
        const role = email.endsWith("@taskflow-internal.com") ? "admin" : "member";

        // This specific response shape is what Okta's inline hook contract expects —
        // a "commands" array telling Okta what to change before creating the user.
        return NextResponse.json({
            commands: [
                {
                    type: "com.okta.user.profile.update",
                    value: { role },
                },
            ],
        });
    }

Two things worth understanding here. First, the authorization check matters more than it might seem — Okta does not sign these requests with a verifiable signature by default, so a shared secret header, checked on every request, is the actual security boundary protecting this endpoint. Second, the response shape (commands array, com.okta.user.profile.update type) is a fixed contract Okta expects — this isn't arbitrary JSON, it's how you tell Okta what to actually change.

Add the secret to .env.local:


    OKTA_HOOK_SECRET=some-long-random-string-you-generate

Step 3: Register the Inline Hook in Okta

  1. In the Admin Console, go to Workflow → Inline Hooks.
  2. Click Add Inline Hook.
  3. Select Registration as the hook type.
  4. Name: TaskFlow Registration Hook.
  5. URL: your ngrok URL plus the route, e.g. https://random-string.ngrok-free.app/api/hooks/registration.
  6. Authentication field: Authorization.
  7. Authentication secret: the same value you put in OKTA_HOOK_SECRET.
  8. Save.

You should now see it listed with Status: Active.

Step 4: Attach It to Your Registration Policy

Registering the hook and attaching it to your registration flow are two separate steps — creating the hook alone doesn't make Okta use it anywhere.

  1. Go to Security → User Profile Policies → TaskFlow Registration Policy (the one built in Phase 4).
  2. On the Enrollment tab, you'll see the full Profile Enrollment card — this single card controls Self-service registration, Progressive Profiling, Password, Email verification, group assignment, and the Inline hook setting all together.
  3. Click Edit at the top of this card — this is the one click that matters. Without it, every field on the card (including the inline hook dropdown) is shown as plain read-only text, which is easy to mistake for there being nothing to select.
  4. Scroll down to the Inline hook section. It currently shows "Use the following inline hook: None (disabled)" — with the card in edit mode, this is now an actual dropdown.
  5. Select TaskFlow Registration Hook.
  6. Scroll down and click Save.

Reload the page afterward and confirm the Inline hook section now shows TaskFlow Registration Hook instead of "None (disabled)."

Try It

With your Next.js dev server and ngrok both running, register a new user through TaskFlow's /login → Sign up flow. Before the account is finalized, Okta calls your endpoint, which decides the role value — check the new user's profile in Directory → People afterward to confirm role was set based on the logic in your Route Handler.

Building an Event Hook: Reacting to User Deactivation

Now something asynchronous — notifying TaskFlow's backend whenever an admin deactivates a user in Okta, so you could, for example, clean up related data in your own database.

Step 5: Build the Endpoint

Create src/app/api/hooks/events/route.ts:


    import { NextRequest, NextResponse } from "next/server";

    // Okta sends a one-time GET request to verify you control this endpoint,
    // before it will ever deliver real events here.
    export async function GET(request: NextRequest) {
        const verificationChallenge = request.headers.get("x-okta-verification-challenge");
        if (!verificationChallenge) {
            return NextResponse.json({ error: "Missing verification challenge" }, { status: 400 });
        }
        return NextResponse.json({ verification: verificationChallenge });
    }

    export async function POST(request: NextRequest) {
        const authHeader = request.headers.get("authorization");
        if (authHeader !== process.env.OKTA_HOOK_SECRET) {
            return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
        }

        const body = await request.json();
        const events = body.data?.events ?? [];

        for (const event of events) {
            if (event.eventType === "user.lifecycle.deactivate") {
                const email = event.target?.find((t: any) => t.type === "User")?.alternateId;
                console.log(`User deactivated in Okta: ${email} — clean up related TaskFlow data here.`);
                // e.g., await db.tasks.archiveForUser(email);
            }
        }

        // Okta doesn't wait on this response to continue anything (it already happened) —
        // just acknowledge receipt.
        return NextResponse.json({ received: true });
    }

The GET handler exists specifically for the one-time verification step described below — Okta calls your endpoint once with a random challenge value, and your endpoint must echo it back, proving you genuinely control this URL.

Step 6: Register the Event Hook

  1. Go to Workflow → Event Hooks.
  2. Click Create Event Hook.
  3. Name: TaskFlow Deactivation Sync.
  4. URL: your endpoint, e.g. https://random-string.ngrok-free.app/api/hooks/events.
  5. Authentication field: Authorization, Authentication secret: same OKTA_HOOK_SECRET.
  6. Under Subscribe to events, search for and select User Deactivated (user.lifecycle.deactivate).
  7. Save.

Step 7: Verify the Endpoint

Back on the Event Hooks list, your new hook will show status UNVERIFIED. Click its Actions menu and select Verify.

Before clicking Verify, make sure both your Next.js dev server (npm run dev) and ngrok are actually running at this exact moment — this verification is a real, live network call from Okta's servers straight to your ngrok URL, forwarded to your local machine. If either one isn't running, or if ngrok was restarted since you registered the hook (giving it a new URL that no longer matches what's saved in Okta), this step will simply fail to connect, usually with a vague timeout-style error rather than anything pointing you back to "your server isn't running."

If it succeeds, the status changes to VERIFIED — only verified hooks actually receive live events afterward.

Try It

With everything still running, deactivate a test user in Directory → People. Check your terminal (where npm run dev is running) — you should see the console log from your Route Handler confirming the event was received.

A Note on SCIM Provisioning

SCIM (System for Cross-domain Identity Management) is a standardized protocol for automatically syncing users between two systems — for example, if TaskFlow needed to receive user provisioning from an enterprise customer's own identity provider, rather than users signing up through Okta directly. Implementing a full SCIM server is a substantial undertaking on its own and is genuinely outside what a single lecture can build hands-on. The concept worth carrying forward: SCIM matters when TaskFlow becomes the receiving end of automated provisioning from an enterprise customer's own directory — a common requirement once selling to businesses with their own IT-managed user directories, rather than something needed for TaskFlow's current, individual-signup model.


Where TaskFlow Stands

TaskFlow's backend can now react to Okta in both directions this phase covers: influencing what Okta does during a process (Inline Hooks, via a registration profile enrichment example), and reacting to what Okta already did (Event Hooks, via a deactivation-sync example) — both reachable from Okta's servers through ngrok during development, and both properly authenticated with a shared secret since Okta hooks aren't cryptographically signed by default.

That completes Phase 6.

Next is phase 7 — the final phase, covering environment/secrets hygiene, testing authentication flows, monitoring with Okta's System Log, and deploying TaskFlow to production.

No comments:

Post a Comment

Phase 7: Production Readiness — Final (Capstone Review)

This is the last lecture. No new Okta features here — just tying every phase together into a single, coherent picture of what TaskFlow actua...