PKCE Generator

Generate an RFC 7636 code_verifier and S256 code_challenge, see why a challenge fails, and copy the authorize URL and token cURL. Nothing leaves your browser.

  • Runs in your browser
  • Your data never leaves your browser
  • Free · No Sign-Up
Generate creates a new verifier, state and nonce. Length accepts whole numbers from 43 to 128; changing it generates a new set. Ctrl/⌘+Enter also generates a new set. The default 43 characters encode 32 random bytes (256 bits).
S256 hashes the verifier string with SHA-256 and encodes the digest as base64url without padding. plain uses the verifier unchanged.
Paste 43–128 ASCII letters, digits or - . _ ~. Surrounding whitespace is ignored. The challenge updates as you edit. Ctrl/⌘+L clears the fields and results.
Check a code_challengefind why the token request fails Paste a challenge to check the pair. The result identifies padding, standard Base64, hex and hashing the decoded bytes instead of the verifier string.

Authorization request URLredirect the user here The endpoint must start with http:// or https:// and contain no fragment. Parameters are URL-encoded; existing parameters of the same name are replaced. nonce is included only for the openid scope.

state and nonce get new random values with every Generate. Compare state when the user returns to redirect_uri. nonce is sent only when scope contains openid.

Token request (cURL)send code_verifier with the code This preview builds a POST command with form-encoded fields and the verifier. It does not send the request. An empty code uses AUTHORIZATION_CODE.

Your challenge and request previews will appear here.

Random values, SHA-256 and the URLs are computed in this tab. Nothing is sent or saved; reloading the page discards the verifier.

Read the full guide PKCE Generator: Wire Up OAuth Authorization Code Flow Without Mistakes
Examples, details and FAQ Worked examples, how it compares with other tools, and answers to common questions.

The verifier and challenge follow RFC 7636 §4.1 and §4.2: code_challenge = BASE64URL(SHA256(ASCII(code_verifier))), base64url without = padding. The generator turns n random bytes into base64url and keeps the first characters you asked for; 43 characters use exactly 32 bytes (256 bits), the amount RFC 7636 §7.1 recommends. The SHA-256 here is the same function as in the Hash Generator; the difference is the base64url encoding of the raw 32 digest bytes instead of hex.

Examples You Can Reproduce

RFC 7636 Appendix B. Paste dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk into the verifier field. The tool shows E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM, the value in Appendix B. The verifier is the base64url form of the 32 octets listed there, starting 116, 24, 223, 180. If a library gives you a different challenge for this verifier, the library is wrong.

An authorization URL. With endpoint https://auth.example.com/authorize, client_id s6BhdRkqt3, redirect_uri https://client.example.org/cb, scope openid profile, state xyz and nonce n-0S6_WzA2Mj, the tool builds:

https://auth.example.com/authorize?response_type=code&client_id=s6BhdRkqt3&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb&scope=openid+profile&state=xyz&nonce=n-0S6_WzA2Mj&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256

Values are form-encoded as RFC 6749 Appendix B requires, so the space in the scope becomes +. code_challenge_method is always written out, because RFC 7636 §4.3 makes a missing method mean plain. To read a callback URL back into its parts, use the URL Parser.

A verifier copied from standard Base64. Paste dBjftJeZ4CVP+mB92K27uhbUJU1p1r/wW1gFWFOEjXk= and the tool stops with: Character 13 ”+” is not allowed. A verifier may only contain A–Z, a–z, 0–9 and - . _ ~ (RFC 7636 §4.1). followed by how to convert it to base64url. Spaces, line breaks and non-ASCII characters get their own hint, and lengths outside 43–128 are reported with the actual count.

Finding a Challenge That Does Not Match

When the token endpoint answers invalid_grant, the usual cause is a challenge built the wrong way. Open Check a code_challenge and paste what your app sent. For the RFC verifier above, these are the answers:

Pasted code_challengeResult
E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cMMatch
E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM=Padding: remove the =
E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw+cM=Standard Base64 instead of base64url
13d31e961a1ad8ec2f16b10c4c982e0876a878ad6df144566ee1894acb70f9c3Hex digest instead of base64url
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkThe verifier itself (plain)
38v3YOi6zQgk1xkqk6Y5dvSDoBHqZrTh3mmWHxxWvykSHA-256 of the decoded random bytes, not of the verifier string

The last row is a real bug pattern: code that keeps the 32 random bytes and hashes them, instead of hashing the 43 ASCII characters it sent as the verifier. With plain selected, the check also recognizes the S256 value and tells you to send code_challenge_method=S256. For the Base64 side of these errors, base64url is RFC 4648 §5: - and _ instead of + and /, and PKCE drops the padding.

The Same Calculation in Code

These functions give the RFC challenge above. The tests for this page run all three.

// Browsers and Node.js 20+ (Web Crypto)
const base64url = (bytes) =>
  btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

function createVerifier() {
  return base64url(crypto.getRandomValues(new Uint8Array(32))); // 43 characters
}

async function createChallenge(verifier) {
  const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
  return base64url(new Uint8Array(digest));
}
import base64
import hashlib
import secrets


def create_verifier() -> str:
    return secrets.token_urlsafe(32)  # 32 random bytes -> 43 characters


def create_challenge(verifier: str) -> str:
    digest = hashlib.sha256(verifier.encode("ascii")).digest()
    return base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
package main

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
)

func createVerifier() string {
	b := make([]byte, 32)
	rand.Read(b)
	return base64.RawURLEncoding.EncodeToString(b) // 43 characters
}

func createChallenge(verifier string) string {
	sum := sha256.Sum256([]byte(verifier))
	return base64.RawURLEncoding.EncodeToString(sum[:])
}

In Go, golang.org/x/oauth2 also has GenerateVerifier, S256ChallengeOption and VerifierOption. After login, the JWT Decoder shows the claims of the ID token, including the nonce.

How It Differs from Other Online PKCE Generators

Tested on 2026-10-01 in desktop Chromium, with the page’s fetch, XMLHttpRequest and sendBeacon recorded while generating and checking:

  • tonyxu-io.github.io/pkce-generator (first on Bing for “pkce generator”) runs locally and gets the RFC example right, but it does not validate input: a 42-character verifier, a verifier with +, / and =, Chinese text and an empty field all produce a challenge. The empty field gives 47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU, the SHA-256 of nothing.
  • Ping Identity’s PKCE Code Generator generates locally, but the verifier field is read-only, so you cannot check a verifier from your own app.
  • Elysia Tools posts the mode, the verifier and the challenge as JSON to api.elysiatools.com and shows the result the server returns, for generating as well as checking.
  • AuthAction, Found Tools, encode64 and ToolBada generate locally but cannot check an existing verifier or challenge.

This tool checks a pasted verifier against the RFC grammar and points at the character that breaks it, and tells you why a challenge does not match instead of only that it does not. The verifier never leaves the tab.

Limits

  • Methods are S256 and plain, the two that RFC 7636 defines. Generated verifiers use the 64 base64url characters, so . and ~ (valid in a verifier) never appear in them.
  • The entropy figure is shown only for verifiers this page generated. For a pasted verifier the tool can check the format, not how random it is.
  • The tool does not contact your authorization server. It cannot exchange the code for you; run the cURL command, which is written for a public client. Confidential clients add their client authentication.
  • Provider rules beyond RFC 7636 are not checked. For example, LINE Login accepts only S256, and Kakao and WeChat separate scopes with commas (the nonce is still added when such a scope contains openid).
  • A verifier belongs to one authorization request. Generate a new one for every login; reusing a fixed verifier defeats the protection, and RFC 9700 §2.1.1 encourages servers to detect and refuse constant values.

FAQ

Is the verifier sent anywhere or saved?

No. Random bytes come from crypto.getRandomValues and SHA-256 from crypto.subtle in this tab. The tool makes no network requests, does not write the verifier to localStorage, sessionStorage, cookies or the URL, and the page loads no analytics or ad scripts. Reloading the page discards the verifier.

How long should the code_verifier be?

43 characters from 32 random bytes is the recipe in RFC 7636 sections 4.1 and 7.1, and it gives the 256 bits of entropy the RFC asks for. Longer verifiers (up to 128 characters) are allowed; 256 bits is already the target the RFC sets. A verifier you type by hand is valid if it has 43 to 128 characters from A-Z, a-z, 0-9 and - . _ ~, but its strength depends on how it was made.

Should I use S256 or plain?

S256. RFC 7636 section 4.2 says a client that can compute SHA-256 must use S256, RFC 9700 says S256 is currently the only method that does not expose the verifier in the authorization request, and the OAuth 2.1 draft removes plain. Some providers, such as LINE Login, accept only S256.

Why does my token request fail with invalid_grant?

Paste the code_challenge your app sent into Check a code_challenge. The tool tells you whether it matches the verifier or was built in a known wrong way: Base64 with + / =, hex, trailing padding, the verifier itself, or a hash of the decoded random bytes. If it matches, check that code_challenge_method=S256 was sent (without it the server assumes plain), that the verifier comes from the same login attempt, and that the code was used only once.

Do confidential clients need PKCE too?

RFC 9700 (OAuth 2.0 Security Best Current Practice, 2025) requires PKCE for public clients and recommends it for confidential clients, because it also stops authorization code injection. A confidential client still authenticates at the token endpoint; add its client secret or other credentials to the cURL command yourself.