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:
- In the Admin Console, go to Applications → Applications → Create App Integration.
- Same setup as Phase 1: OIDC — OpenID Connect, Web Application.
- Name it
TaskFlow Production. - Sign-in redirect URIs: your real production domain, e.g.
https://taskflow.yourdomain.com/api/auth/callback. - Sign-out redirect URIs:
https://taskflow.yourdomain.com. - Save, and note this Application's own separate Client ID and Client Secret — these are different from your development ones.
- Repeat the Phase 1 checklist for this new app too: confirm the
defaultAuthorization 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
- Go to
vercel.com, sign in, click Add New → Project. - Select your GitHub repository.
- 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