Phase 7: Production Readiness — Part 2 (Testing Authentication Flows & Deploying to Vercel)

Testing Authentication Flows

Testing an OAuth/OIDC flow is genuinely trickier than testing typical app logic, because the "real" flow involves a redirect to Okta's own hosted page, filling in credentials, and possibly an MFA challenge — none of which you control or want your test suite depending on. The right approach is a mix of three layers, each testing a different part of what TaskFlow actually does.

Layer 1: Unit Tests for the Pieces You Wrote Yourself

The PKCE helper and token verification logic from Phase 2 are pure functions — no network calls, no Okta involved — and are the easiest, highest-value things to test directly.

Install a test runner:


    npm install -D vitest

Create src/lib/pkce.test.ts:


    import { describe, it, expect } from "vitest";
    import { generateCodeVerifier, generateCodeChallenge } from "./pkce";

    describe("PKCE helpers", () => {
        it("generates a code verifier of sufficient length", () => {
            const verifier = generateCodeVerifier();
            expect(verifier.length).toBeGreaterThanOrEqual(43);
        });

        it("produces a consistent, deterministic challenge for the same verifier", () => {
            const verifier = "test-verifier-value";
            const challenge1 = generateCodeChallenge(verifier);
            const challenge2 = generateCodeChallenge(verifier);
            expect(challenge1).toBe(challenge2);
        });

        it("produces different challenges for different verifiers", () => {
            const challengeA = generateCodeChallenge("verifier-a");
            const challengeB = generateCodeChallenge("verifier-b");
            expect(challengeA).not.toBe(challengeB);
        });
    });

That length check (>= 43) isn't arbitrary — it's a specific requirement from the PKCE specification itself, and it's exactly the kind of subtle correctness bug (a code_verifier that's technically too short) that passes in a quick manual test but fails against a real provider, as flagged directly in current guidance on this exact topic.

Layer 2: Integration Tests for Your Route Handlers, With Okta Mocked

You don't want tests hitting your real Okta org on every run — it's slow, and it depends on network access and real credentials. Instead, mock the fetch calls to Okta's /token endpoint and test that your callback route handles both success and failure correctly.


    import { describe, it, expect, vi } from "vitest";

    describe("auth callback route", () => {
        it("rejects a request when state does not match", async () => {
            // Simulate a request with a state parameter that doesn't match
            // the one stored in cookies — this should always be rejected,
            // regardless of whether the authorization code itself is valid.
            // ... construct a mock NextRequest with mismatched state ...
            // expect(response.status).toBe(307); // redirect to /login?error=...
        });

        it("rejects tokens that fail signature verification", async () => {
            vi.mock("@/lib/verifyToken", () => ({
                verifyIdToken: vi.fn().mockRejectedValue(new Error("invalid signature")),
            }));
            // ... exercise the callback route and confirm it redirects to an error state
            // rather than setting session cookies with an unverified token.
        });
    });

The specific value of this layer: it catches exactly the kind of bug that's invisible when you're manually clicking through the login flow yourself (since you're always providing a correctly formed request) — a missing state check, or a code path that sets session cookies before verification completes.

Layer 3: One Real End-to-End Smoke Test

This is the layer that actually drives a browser through the real flow, and it's the one genuine current guidance is clear about: don't skip this layer entirely in favor of mocking everything — a misconfigured redirect URI, a broken PKCE implementation, or a missing code_challenge_method=S256 parameter will pass every mocked test and only fail against the real provider.

Install Playwright:


    npm install -D @playwright/test
    npx playwright install

Automating Okta's hosted login page directly is realistic but adds friction if MFA is in the way (typing a real TOTP code in an automated test is painful). The practical pattern: create a dedicated QA test user, and scope an Authentication Policy rule (using the same rule-priority technique from Phase 4) so that user can log in with a single factor — for example, reusing the passwordless email rule, or simplest of all, a rule allowing just Password for that one specific test account. This isn't a security compromise for production (the rule can be scoped narrowly to just that one QA account), and it keeps the E2E test deterministic.


    import { test, expect } from "@playwright/test";

    test("a user can log in and reach the dashboard", async ({ page }) => {
        await page.goto("/login");
        await page.click("text=Log in with Okta");

        // Now on Okta's hosted page
        await page.fill('input[name="identifier"]', process.env.QA_TEST_USER_EMAIL!);
        await page.click("text=Next");
        await page.fill('input[name="credentials.passcode"]', process.env.QA_TEST_USER_PASSWORD!);
        await page.click("text=Verify");

        // Back on TaskFlow
        await expect(page).toHaveURL(/\/dashboard/);
        await expect(page.locator("h1")).toContainText("Welcome");
    });

Store QA_TEST_USER_EMAIL and QA_TEST_USER_PASSWORD as CI secrets, never committed — same discipline as every other credential in this course. Run this test on a schedule (not on every single commit, since it depends on a live external service and is inherently slower and more fragile than the mocked layers above) to catch exactly the class of bug — real redirect URI misconfigurations, real PKCE issues — that only a genuine round-trip through Okta can reveal.


Deploying TaskFlow to Vercel

Step 1: Create a Separate Okta Application for Production

Reusing your development Application (from Phase 1) for production is a common shortcut that causes real problems — development redirect URIs, test users, and looser policies end up live. Instead:

  1. In the Admin Console, go to Applications → Applications → Create App Integration.
  2. Same setup as Phase 1: OIDC — OpenID Connect, Web Application.
  3. Name it TaskFlow Production.
  4. Sign-in redirect URIs: your real production domain, e.g. https://taskflow.yourdomain.com/api/auth/callback.
  5. Sign-out redirect URIs: https://taskflow.yourdomain.com.
  6. Save, and note this Application's own separate Client ID and Client Secret — these are different from your development ones.
  7. Repeat the Phase 1 checklist for this new app too: confirm the default Authorization Server's Access Policy still has an active rule allowing Authorization Code for this client (it will, since it's the same shared authorization server — but double-check the client is actually covered).

Step 2: Push TaskFlow to GitHub

If it isn't already:


    git init
    git add .
    git commit -m "Initial TaskFlow commit"

Push to a new GitHub repository. Before this step, do one final check of Step 2/3 from Part 1 of this phase — confirm .env.local truly isn't tracked.

Step 3: Import the Project Into Vercel

  1. Go to vercel.com, sign in, click Add New → Project.
  2. Select your GitHub repository.
  3. Vercel auto-detects Next.js — leave the build settings at their defaults.

Step 4: Set Production Environment Variables

Before deploying, go to Project Settings → Environment Variables, and add every variable TaskFlow needs, using the production Okta Application's credentials from Step 1:


    OKTA_ORG_URL=https://integrator-1393295.okta.com
    OKTA_CLIENT_ID=<production Client ID>
    OKTA_CLIENT_SECRET=<production Client Secret>
    OKTA_SERVICE_CLIENT_ID=<Service App Client ID from Phase 6>
    OKTA_SERVICE_PRIVATE_KEY=<Service App private key JSON>
    OKTA_HOOK_SECRET=<hook secret>

Scope each to the Production environment specifically (Vercel lets you set different values per environment — Production, Preview, Development — which is exactly how you'd eventually let Preview deployments point at a separate staging Okta app if TaskFlow grows to need one).

Step 5: Update Hook URLs

The Inline and Event Hooks built in Phase 6 currently point at your ngrok tunnel — that's a development-only address. Once deployed, go back to Workflow → Inline Hooks and Workflow → Event Hooks in Okta, and update each URL to point at your real production domain (https://taskflow.yourdomain.com/api/hooks/registration, etc.), then re-run the Event Hook's verification step against the live production URL.

Step 6: Deploy

Click Deploy in Vercel. Once it finishes, visit your production URL and run through the full login flow — the same smoke test Layer 3 above automates, but worth doing manually once by hand the first time.


Where TaskFlow Stands

TaskFlow now has a genuine, layered test strategy — fast unit tests for the logic you wrote by hand, mocked integration tests for your Route Handlers, and a real end-to-end smoke test that actually round-trips through Okta — plus a live production deployment on Vercel, using its own separate Okta Application and properly scoped environment variables.

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...