HomeJournalThis post

Build an OAuth 2.0 client with PKCE

A secure authorization-code client binds each request to a one-time verifier, exact redirect state, S256 challenge, strict response validation, and safe token handling.

JP
JP Casabianca
Designer/Engineer · Bogotá

PKCE is not an extra hash parameter added to an otherwise unchanged sign-in redirect.

It binds one authorization request to the client instance that later exchanges the code. The binding only works when the verifier is unpredictable, the S256 challenge is correct, the authorization server enforces it, and the callback is matched to the transaction that created it.

RFC 9700, OAuth 2.0 Security Best Current Practice applies PKCE guidance to all kinds of OAuth clients, requires authorization-server support, and identifies S256 as the method that does not expose the verifier in the authorization request.

We will build the whole client transaction: validate provider metadata, create and store a short-lived verifier, redirect safely, validate the response, exchange once, and manage tokens without placing a client secret in public code.

Protocol security lives in the relationship between steps, not in any one URL parameter.

01 · CreateGenerate verifier and challenge

Use cryptographic randomness, derive base64url SHA-256 challenge, store verifier with issuer and transaction state, and set short expiry.

02 · AuthorizeSend challenge through the browser

Use an exact registered redirect URI, response type code, S256 method, scoped request, and CSRF or OIDC transaction binding.

03 · ExchangeProve possession of verifier

Validate callback and issuer, send code plus original verifier once to the token endpoint, then destroy transaction state.

Figure 1: PKCE binds the front-channel code to one client transaction.

Classify the OAuth client

Browser, native, desktop, server-rendered, and backend-for-frontend architectures have different abilities to protect credentials and tokens, so the client type must be explicit.

I would pressure-test that decision with four questions:

  • Can this client keep a secret?
  • Where does code exchange happen?
  • Which origin owns the callback?
  • Who stores refresh tokens?

The failure mode here is embedding a client secret in downloadable JavaScript. In web, mobile, desktop, and browser-based OAuth clients where authorization codes, redirect URIs, front-channel responses, malicious apps, open redirects, leaked logs, CSRF, token storage, refresh, and logout define the real security boundary, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be an OAuth client threat and deployment model. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is protocol choices matched to the actual trust boundary. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put an OAuth client threat and deployment model beside the question “Can this client keep a secret?” before the first implementation review. The next pass would use “Where does code exchange happen?” to test the boundary, then “Which origin owns the callback?” to expose the state most likely to be missed. I would keep “Who stores refresh tokens?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports protocol choices matched to the actual trust boundary.

Validate authorization-server metadata

Issuer, authorization endpoint, token endpoint, PKCE methods, revocation, and OIDC capabilities should come from trusted configuration or validated metadata.

The practical review starts here:

  • Is issuer exact?
  • Are endpoints HTTPS and expected?
  • Is S256 supported?
  • Can metadata be substituted?

Those questions keep accepting endpoints copied from an untrusted authorization response from becoming the default. I would capture the decision in a provider metadata validation rule, then use it while the work is still cheap to change. For OAuth authorization that resists code interception and flow confusion, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like one known authorization server for every transaction. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a provider metadata validation rule part of the working surface. I would use it to answer “Is issuer exact?” while scope is still flexible, and “Are endpoints HTTPS and expected?” before code or content becomes expensive to unwind. During QA, “Is S256 supported?” and “Can metadata be substituted?” become concrete checks rather than discussion prompts. That sequence turns OAuth authorization that resists code interception and flow confusion into something the team can operate and gives me a specific outcome to report: one known authorization server for every transaction.

  1. PendingAuthorization is in progress

    Transaction ID, verifier, issuer, redirect, state, optional nonce, creation time, and return target are bounded and correlated.

  2. ReturnedCallback is validated

    Error or code belongs to exactly one unused transaction; unexpected issuer, state, duplicate, or stale response fails closed.

  3. SettledTokens or actionable failure

    Exchange succeeds and transaction data is deleted, or failure preserves safe context and requires a fresh authorization attempt.

Figure 2: The client transaction should close every terminal path.

Generate a strong verifier

The verifier needs cryptographically secure randomness, RFC-compatible length and alphabet, one transaction scope, short retention, and no logging.

Before implementation, I would answer:

  • Which CSPRNG is used?
  • Is entropy sufficient?
  • Can values collide?
  • Where is the verifier retained?

The artifact is a verifier generation and storage helper. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is using UUID fragments or Math.random; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is an interception secret that cannot be feasibly guessed. That connects an Authorization Code flow with high-entropy PKCE verifier, S256 challenge, exact redirect handling, issuer-bound transaction state, metadata validation, one-time code exchange, and bounded token lifecycle to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “Which CSPRNG is used?” easy to answer. The boundary should force a decision about “Is entropy sufficient?” and “Can values collide?.” I would record both in a verifier generation and storage helper, including the part that stayed unresolved after the first pass. The final check, “Where is the verifier retained?,” is where the artifact earns its place: it either supports an interception secret that cannot be feasibly guessed, or it shows exactly why another iteration is needed.

Derive and encode S256 correctly

The challenge is base64url without padding of the SHA-256 bytes of the ASCII verifier, and the authorization request must declare code_challenge_method=S256.

I would use these prompts during the working review:

  • Are raw digest bytes encoded?
  • Is base64url used?
  • Is padding removed?
  • Can known vectors verify output?

If the team slips into base64-encoding a hexadecimal hash string, the product can still look complete while its operating rule stays ambiguous. I would make an S256 implementation with RFC test vector the shared reference and keep it small enough to update as evidence changes.

The standard is interoperable proof accepted without falling back to plain. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft an S256 implementation with RFC test vector, review it against “Are raw digest bytes encoded?,” implement the narrowest useful path, and then return with evidence for “Is base64url used?.” I would use “Is padding removed?” to inspect product consequence and “Can known vectors verify output?” to decide whether the result is stable enough to ship. This keeps base64-encoding a hexadecimal hash string visible as a known risk and makes interoperable proof accepted without falling back to plain the release receipt rather than a hopeful conclusion.

SignalDecisionWorking note
InterceptionCode is unusable without verifierAn attacker reading the authorization response cannot redeem the code unless they also hold the original high-entropy verifier.
CSRFTransaction still needs bindingUse supported PKCE-based protection correctly or state/nonce as required; never accept a callback detached from local intent.
Token theftStorage remains separatePKCE does not protect tokens after issuance; client architecture, sender constraints, lifetime, refresh, and XSS boundaries still matter.
Figure 3: PKCE solves one threat inside a larger flow.

Register exact redirect URIs

Redirect URIs should be pre-registered and compared exactly, use protected schemes, avoid open redirectors, and contain no attacker-controlled destination.

I would pressure-test that decision with four questions:

  • Which URI is registered?
  • Can query or path be varied?
  • Does a native loopback rule apply?
  • Where is return navigation stored safely?

The failure mode here is sending arbitrary next URLs through the redirect_uri parameter. In web, mobile, desktop, and browser-based OAuth clients where authorization codes, redirect URIs, front-channel responses, malicious apps, open redirects, leaked logs, CSRF, token storage, refresh, and logout define the real security boundary, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a redirect URI allowlist and return-target rule. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is callbacks that cannot be redirected into attacker control. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a redirect URI allowlist and return-target rule beside the question “Which URI is registered?” before the first implementation review. The next pass would use “Can query or path be varied?” to test the boundary, then “Does a native loopback rule apply?” to expose the state most likely to be missed. I would keep “Where is return navigation stored safely?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports callbacks that cannot be redirected into attacker control.

Bind callback to transaction

Issuer, state or nonce policy, redirect URI, verifier, start time, response type, and one-time transaction ID should be validated before code exchange.

The practical review starts here:

  • Which local request created this callback?
  • Has it expired or been used?
  • Does issuer match?
  • How are authorization errors displayed?

Those questions keep accepting any code that arrives at the callback route from becoming the default. I would capture the decision in a pending OAuth transaction record, then use it while the work is still cheap to change. For OAuth authorization that resists code interception and flow confusion, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like no login CSRF or cross-provider transaction confusion. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a pending OAuth transaction record part of the working surface. I would use it to answer “Which local request created this callback?” while scope is still flexible, and “Has it expired or been used?” before code or content becomes expensive to unwind. During QA, “Does issuer match?” and “How are authorization errors displayed?” become concrete checks rather than discussion prompts. That sequence turns OAuth authorization that resists code interception and flow confusion into something the team can operate and gives me a specific outcome to report: no login CSRF or cross-provider transaction confusion.

Exchange the code once

The token request should include the original verifier, use the registered client authentication method, handle ambiguous network outcomes, and never replay a code casually.

Before implementation, I would answer:

  • Where is the verifier read?
  • Does a public client omit secrets?
  • Can timeout hide success?
  • How are token errors classified?

The artifact is a one-shot token exchange function. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is retrying a spent authorization code without restarting safely; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is deterministic settlement of the authorization transaction. That connects an Authorization Code flow with high-entropy PKCE verifier, S256 challenge, exact redirect handling, issuer-bound transaction state, metadata validation, one-time code exchange, and bounded token lifecycle to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “Where is the verifier read?” easy to answer. The boundary should force a decision about “Does a public client omit secrets?” and “Can timeout hide success?.” I would record both in a one-shot token exchange function, including the part that stayed unresolved after the first pass. The final check, “How are token errors classified?,” is where the artifact earns its place: it either supports deterministic settlement of the authorization transaction, or it shows exactly why another iteration is needed.

Protect the token lifecycle

Access and refresh token storage, scope, audience, lifetime, rotation, revocation, sender constraints, and browser exposure remain separate decisions after PKCE succeeds.

I would use these prompts during the working review:

  • Where do tokens live?
  • Can script read them?
  • Are refresh tokens rotated?
  • How is revocation propagated?

If the team slips into claiming PKCE makes localStorage token theft safe, the product can still look complete while its operating rule stays ambiguous. I would make a token custody and renewal contract the shared reference and keep it small enough to update as evidence changes.

The standard is bounded authority after authorization completes. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a token custody and renewal contract, review it against “Where do tokens live?,” implement the narrowest useful path, and then return with evidence for “Can script read them?.” I would use “Are refresh tokens rotated?” to inspect product consequence and “How is revocation propagated?” to decide whether the result is stable enough to ship. This keeps claiming PKCE makes localStorage token theft safe visible as a known risk and makes bounded authority after authorization completes the release receipt rather than a hopeful conclusion.

Design recoverable product states

Consent denial, expired transaction, provider error, callback mismatch, offline exchange, account switch, and revoked session should preserve safe context and explain the next valid action.

I would pressure-test that decision with four questions:

  • Can the user restart safely?
  • Which message avoids protocol leakage?
  • Does Back create stale state?
  • What happens across several tabs?

The failure mode here is showing a generic login failed toast for every terminal state. In web, mobile, desktop, and browser-based OAuth clients where authorization codes, redirect URIs, front-channel responses, malicious apps, open redirects, leaked logs, CSRF, token storage, refresh, and logout define the real security boundary, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be an OAuth error-and-restart state matrix. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is clear recovery without weakening validation. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put an OAuth error-and-restart state matrix beside the question “Can the user restart safely?” before the first implementation review. The next pass would use “Which message avoids protocol leakage?” to test the boundary, then “Does Back create stale state?” to expose the state most likely to be missed. I would keep “What happens across several tabs?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports clear recovery without weakening validation.

Test adversarial flows

QA should intercept codes, alter state, reuse callbacks, downgrade PKCE, swap issuer, mutate redirect, expire transactions, race tabs, deny consent, time out exchange, and inspect logs.

The practical review starts here:

  • Can known PKCE vectors pass?
  • Does every mismatch fail closed?
  • Are secrets absent from telemetry?
  • Can one transaction settle twice?

Those questions keep testing only the provider's successful demo account from becoming the default. I would capture the decision in an OAuth attack-fixture suite, then use it while the work is still cheap to change. For OAuth authorization that resists code interception and flow confusion, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like evidence that the binding survives realistic flow manipulation. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make an OAuth attack-fixture suite part of the working surface. I would use it to answer “Can known PKCE vectors pass?” while scope is still flexible, and “Does every mismatch fail closed?” before code or content becomes expensive to unwind. During QA, “Are secrets absent from telemetry?” and “Can one transaction settle twice?” become concrete checks rather than discussion prompts. That sequence turns OAuth authorization that resists code interception and flow confusion into something the team can operate and gives me a specific outcome to report: evidence that the binding survives realistic flow manipulation.

What I would show in the work

The public version needs evidence from the work itself. For this topic, the first five artifacts I would reach for are:

  • an OAuth client threat and deployment model
  • a provider metadata validation rule
  • a verifier generation and storage helper
  • an S256 implementation with RFC test vector
  • a redirect URI allowlist and return-target rule

I would not publish all five at equal weight. One should orient the reader, one should reveal the hardest tradeoff, and one should prove the result. The others can live in a downloadable note or appear as supporting frames. That edit matters because an Authorization Code flow with high-entropy PKCE verifier, S256 challenge, exact redirect handling, issuer-bound transaction state, metadata validation, one-time code exchange, and bounded token lifecycle becomes harder to understand when every process detail is treated as equally important.

I would also show one rejected direction. The useful version is specific: which option looked attractive, which constraint made it wrong, and what evidence supported the narrower choice. That gives an engineering manager something real to question and keeps the case study from reading like the final answer was obvious from the beginning.

oauth-pkce-transaction.ts
# request
tx=K9… / issuer=https://id.example
Verifier 64 random chars, S256 challenge, exact redirect, state hash, nonce for OIDC, scopes, created time, and safe return path.

# callback code + state / no unexpected params Resolve one pending transaction, compare issuer and state in constant-time-capable library, enforce age and one-time use, reject mix-up.

# exchange POST token endpoint / verifier Confidential server authenticates as registered; public client sends no embedded secret; validate response, delete transaction, and record redacted receipt.

Figure 4: A transaction record makes callback validation deterministic.

Resource path

The practical follow-up I would build is an OAuth PKCE client starter with discovery validation, verifier generation, S256 challenge, transaction storage, state and nonce policy, exact redirect URI, callback validation, token exchange, error states, refresh rotation, revocation, logout, observability, and adversarial tests. I am treating that as a resource backlog item, not pretending the adjacent downloads below are the same artifact. The related cards cover useful pieces of the workflow today; this specific file should only be published when its examples, fields, and instructions are complete.

The first version should stay concise: context, constraint, decision, evidence, owner, and follow-up. Its value would come from helping someone repeat this exact review, not from adding another generic PDF to the site.

Review checklist

The article-specific review questions are:

  • Can this client keep a secret?
  • Is issuer exact?
  • Which CSPRNG is used?
  • Are raw digest bytes encoded?
  • Which URI is registered?
  • Which local request created this callback?
  • Where is the verifier read?
  • Where do tokens live?
  • Can the user restart safely?
  • Can known PKCE vectors pass?

I would add two editorial checks before publishing: can a recruiter find the point in the first minute, and can an engineer trace at least one claim to an implementation or production receipt? If either answer is no, the article needs another edit.

Implementation notes

For OAuth authorization that resists code interception and flow confusion, I would write the implementation note before polish. It would name the changed surface, source of truth, owner, failure boundary, and verification path. Those details prevent the principle from floating above the actual code or operational workflow.

The proof signals I care about are specific to this article:

  • no login CSRF or cross-provider transaction confusion
  • deterministic settlement of the authorization transaction
  • bounded authority after authorization completes
  • clear recovery without weakening validation
  • evidence that the binding survives realistic flow manipulation

I would choose two or three of those signals for the first release rather than instrumenting everything. The strongest pair usually combines one direct behavior check with one operating check: a route and a data query, a keyboard path and a support state, a handler replay and a reconciliation result, or a migration count and a rendered screen.

The follow-up belongs in the note before shipping. It should say what remains temporary, what evidence would trigger another pass, and who owns that decision. That is how the first version stays intentionally narrow without making the boundary invisible.

Case-study packaging

I would structure the case-study version around the four visual lessons already established:

  • PKCE binds the front-channel code to one client transaction.
  • The client transaction should close every terminal path.
  • PKCE solves one threat inside a larger flow.
  • A transaction record makes callback validation deterministic.

The opening frame explains the product pressure. The middle two show the decision moving through the system. The last frame is the receipt: what was checked, what held, and what remained unresolved. That order lets the reader move from product judgment into implementation detail without reconstructing the whole project first.

I would include one caveat tied to web, mobile, desktop, and browser-based OAuth clients where authorization codes, redirect URIs, front-channel responses, malicious apps, open redirects, leaked logs, CSRF, token storage, refresh, and logout define the real security boundary: a data limit, rollout boundary, unsupported state, external dependency, or result that is still directional. A precise caveat makes the evidence easier to trust because it shows where the claim stops.

The final test is whether the page creates a better conversation. If the artifact helps someone ask a sharper question about product judgment, implementation detail, or release proof in a live interview, it belongs in the story.

Interview angle

In an interview, I would explain this through an Authorization Code flow with high-entropy PKCE verifier, S256 challenge, exact redirect handling, issuer-bound transaction state, metadata validation, one-time code exchange, and bounded token lifecycle. The story should start with the product pressure, then move into the system constraint, the artifact, and the proof. That order keeps the answer grounded. It also gives the interviewer several places to go deeper: data, frontend architecture, design systems, support, migration, accessibility, or release process.

The strongest version of the answer includes a tradeoff. I want to be able to say what I chose, what I left alone, and how I knew the work helped. That is more credible than presenting every project as a clean win.

The hiring signal

A complete PKCE client is a hiring signal because it shows I can translate a security protocol into stateful product behavior, browser boundaries, failure recovery, and verifiable implementation details.

That is the level I want this site to communicate. The work should show taste, but it should also show operating judgment. It should make me look like someone who can enter a real product system, understand the messy middle, ship the useful version, and leave enough proof for the next person to trust it.

Companion artifacts

Use this after reading.

Practical downloads and templates that turn the article into something you can bring into a product review, implementation pass, or agent workflow.

RepoJun 2026

Agent-Ready API Spec Template

An OpenAPI and Postman starter template for APIs that AI agents can discover, call, and recover from safely.

OpenAPIPostmanAI agents
View details
TemplateJul 2026

Dependency Adoption Receipt

A reviewable receipt for package need, identity, provenance, permissions, supply-chain risk, verification, ownership, and removal.

Supply chainSecurityAI-assisted
View details
DownloadJun 2026

UI PR Risk Review Checklist

A merge-readiness checklist for product intent, states, accessibility, visual durability, and UI implementation risk.

UI reviewQAFrontend
View details