PKCE code verifier / challenge generator
Save on GoogleFree PKCE generator. Create a code verifier and S256 code challenge, check your own values, and build the authorization and token requests. Everything runs in your browser. Nothing is uploaded.
Configure PKCE
Authorization request (optional)
Results & Output
The code verifier is a secret for a real login. Do not share a link or file that contains one.
Passkeys, OAuth and Identity
| Tool | What it does |
|---|---|
| WebAuthn options JSON builder / inspector | Build WebAuthn registration or sign-in options, or paste existing JSON to check it. It catches mistakes such as a padded challenge, a wrong rp.id, or an email used as the user ID, so your passkey setup works the first time. |
| OIDC discovery document validator | Paste the JSON from /.well-known/openid-configuration and see which required fields are missing, which URLs are wrong, and whether the issuer matches. The results are color-coded, so you spot problems fast. |
| JWKS viewer / JWK ↔ PEM converter | View the keys inside a JWKS, check their size and curve, and convert between JWK and PEM. It flags private key data, so you never publish it by mistake. |
| OAuth redirect URI and scope checker | Compare the redirect URI in your request with the ones registered for your app, and check your scopes for mistakes. It helps you fix redirect_uri_mismatch and invalid_scope errors. |
What does a PKCE code verifier and challenge generator do?
PKCE protects the OAuth 2.0 authorization code flow for apps that cannot keep a secret, such as single-page, mobile and desktop apps. This tool creates a random code_verifier, calculates the code_challenge with SHA-256, checks your own values against the rules in RFC 7636, and builds the authorization URL and token request body for you. Providers differ in small details, so check your provider’s documentation.
PKCE developer references :
Click Copy to copy a snippet. Snippets are examples, so replace placeholders such as CLIENT_ID, VERIFIER and auth.example.com with your own values. Providers differ, so check your provider’s documentation.
| Developer Question | Copy-ready Snippet | Explanation |
|---|---|---|
| What is PKCE? | code_challenge = BASE64URL(SHA256(code_verifier)) | PKCE (Proof Key for Code Exchange, RFC 7636) protects the OAuth authorization code flow. The client sends a hash of a secret first and the secret itself when it exchanges the code. |
| What does PKCE stand for? | // PKCE = Proof Key for Code Exchange (RFC 7636) | Proof Key for Code Exchange. It is usually pronounced "pixy". |
| Why do I need PKCE? | // attacker has the code but not the code_verifier | An attacker who steals the authorization code cannot exchange it, because they do not have the code verifier. |
| Do single-page apps need PKCE? | response_type=code | Yes. A single-page app is a public client and should use the authorization code flow with PKCE. |
| Do mobile and desktop apps need PKCE? | response_type=code&code_challenge_method=S256 | Yes. PKCE was designed for public native clients, and current guidance for native apps recommends it. |
| Do server-side apps need PKCE? | code_challenge_method=S256 | Current OAuth security guidance recommends PKCE for all clients, including confidential ones. |
| Is PKCE required in OAuth 2.1? | // OAuth 2.1 draft: authorization code flow requires PKCE | The OAuth 2.1 draft requires PKCE for the authorization code flow. It is still a draft, so check its current status. |
| PKCE vs client secret | -d "client_secret=SECRET" -d "code_verifier=VERIFIER" | Public clients cannot keep a secret, so PKCE uses a new secret for every request. Confidential clients can use both. |
| code_verifier vs code_challenge | code_verifier -> SHA-256 -> base64url -> code_challenge | The verifier is the random secret the client keeps. The challenge is its hash, sent in the authorization request. |
| What is code_challenge_method? | code_challenge_method=S256 | It tells the server how the challenge was made: S256 (SHA-256) or plain. Use S256. |
| S256 vs plain | code_challenge_method=S256 | With plain the challenge equals the verifier, so a request that is seen exposes the secret. Use plain only if S256 is impossible. |
| How long should a code_verifier be? | 43 <= verifier.length <= 128 | RFC 7636 requires 43 to 128 characters. |
| Which characters are allowed in a code_verifier? | /^[A-Za-z0-9\-._~]{43,128}$/.test(verifier) | Only unreserved characters: A-Z, a-z, 0-9, hyphen, period, underscore and tilde. |
| Generate a code_verifier in JavaScript | const verifier = base64url(crypto.getRandomValues(new Uint8Array(32))); | Use crypto.getRandomValues with 32 bytes. Base64url encoding gives 43 characters. |
| base64url helper in JavaScript | const base64url = (buf) => btoa(String.fromCharCode(...new Uint8Array(buf))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); | Use it for the verifier and for the challenge. |
| Generate a code_verifier in Node.js | const verifier = require("crypto").randomBytes(32).toString("base64url"); | Node.js supports base64url output directly. |
| Generate a code_verifier in Python | import secrets
verifier = secrets.token_urlsafe(32) | token_urlsafe(32) returns a 43-character URL-safe string. |
| Generate a code_verifier in Go | b := make([]byte, 32)
rand.Read(b)
verifier := base64.RawURLEncoding.EncodeToString(b) | Use crypto/rand and base64 RawURLEncoding. |
| Generate a code_verifier in Java | byte[] b = new byte[32];
new SecureRandom().nextBytes(b);
String verifier = Base64.getUrlEncoder().withoutPadding().encodeToString(b); | Use SecureRandom and an unpadded URL-safe encoder. |
| Generate a code_verifier in C# | var b = RandomNumberGenerator.GetBytes(32);
var verifier = Convert.ToBase64String(b).TrimEnd('=').Replace('+', '-').Replace('/', '_'); | Use RandomNumberGenerator and convert to base64url by hand. |
| Generate a code_verifier in PHP | $verifier = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '='); | Use random_bytes and convert to base64url. |
| Generate a code_verifier in Ruby | verifier = SecureRandom.urlsafe_base64(32) | SecureRandom.urlsafe_base64 returns unpadded base64url. |
| Generate a code_verifier in Kotlin for Android | val bytes = ByteArray(32).also { SecureRandom().nextBytes(it) }
val verifier = Base64.encodeToString(bytes, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING) | Use SecureRandom and the URL_SAFE, NO_WRAP and NO_PADDING flags. |
| Generate a code_verifier with openssl | openssl rand -base64 32 | tr '+/' '-_' | tr -d '=' | Make 32 random bytes and convert them to base64url. |
| Can I use Math.random for the verifier? | // Use crypto.getRandomValues(), never Math.random() | No. It is not cryptographically secure. Use crypto.getRandomValues. |
| Can I use a UUID as a code_verifier? | // UUID: 36 characters < 43 minimum | A UUID is 36 characters, which is below the 43 minimum, and is not designed as a secret. Use random bytes. |
| How much entropy should a verifier have? | crypto.randomBytes(32) | RFC 7636 recommends 256 bits, which is 32 random bytes. |
| Where do I keep the verifier during login? | sessionStorage.setItem("pkce_verifier", verifier); | Keep it only until the token exchange. In a browser redirect flow, use sessionStorage because the page reloads. |
| Can I reuse a code_verifier? | // new verifier for every login | No. Create a new verifier for every authorization request. |
| How is the code_challenge calculated? | code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) | Hash the ASCII verifier with SHA-256, then base64url encode the raw bytes without padding. |
| Calculate the S256 challenge in the browser | const data = new TextEncoder().encode(verifier);
const digest = await crypto.subtle.digest("SHA-256", data);
const challenge = base64url(digest); | Use crypto.subtle.digest, which needs a secure context. |
| Calculate the S256 challenge in Node.js | const challenge = require("crypto").createHash("sha256").update(verifier).digest("base64url"); | Hash the verifier and ask for base64url output. |
| Calculate the S256 challenge in Python | import base64, hashlib
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode() | Hash the bytes, then use urlsafe_b64encode and remove the padding. |
| Calculate the S256 challenge in Go | sum := sha256.Sum256([]byte(verifier))
challenge := base64.RawURLEncoding.EncodeToString(sum[:]) | Use sha256.Sum256 and RawURLEncoding. |
| Calculate the S256 challenge in Java | byte[] d = MessageDigest.getInstance("SHA-256").digest(verifier.getBytes(StandardCharsets.US_ASCII));
String challenge = Base64.getUrlEncoder().withoutPadding().encodeToString(d); | Digest the ASCII bytes, then encode without padding. |
| Calculate the S256 challenge in C# | var d = SHA256.HashData(Encoding.ASCII.GetBytes(verifier));
var challenge = Convert.ToBase64String(d).TrimEnd('=').Replace('+', '-').Replace('/', '_'); | Use SHA256.HashData and convert to base64url. |
| Calculate the S256 challenge in PHP | $challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '='); | Use hash with raw output, then convert to base64url. |
| Calculate the S256 challenge in Ruby | challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false) | Use Digest::SHA256.digest and an unpadded URL-safe encoder. |
| Calculate the S256 challenge in Kotlin | val d = MessageDigest.getInstance("SHA-256").digest(verifier.toByteArray(Charsets.US_ASCII))
val challenge = Base64.encodeToString(d, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING) | Digest the ASCII bytes and use URL-safe Base64 flags. |
| Calculate the S256 challenge with openssl | printf %s "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=' | Hash the verifier, base64 encode the raw digest, then make it URL-safe. |
| How long is an S256 code_challenge? | challenge.length === 43 | Always 43 characters, because a SHA-256 digest is 32 bytes. |
| RFC 7636 test vector | code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM | Use the example from RFC 7636 Appendix B to test your code. |
| Why is my code_challenge wrong? | // SHA-256 raw bytes -> base64url, with no "=" padding | Common causes: base64 padding, standard base64 characters, hex output, or hashing a different string than you send later. |
| Remove padding from the challenge | challenge.replace(/=+$/, "") | Strip trailing = characters after base64 encoding. |
| Should the challenge be hex or base64url? | // not digest("hex") | Base64url of the raw digest bytes, never a hex string. |
| Authorization request with PKCE | https://auth.example.com/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&scope=openid%20profile&state=STATE&code_challenge=CHALLENGE&code_challenge_method=S256 | Add code_challenge and code_challenge_method to the normal authorization request. |
| Which parameters does the authorization request need? | response_type=code&client_id=CLIENT_ID&code_challenge=CHALLENGE&code_challenge_method=S256 | response_type=code, client_id, code_challenge and code_challenge_method. Most servers also need redirect_uri, and scope. |
| Build the authorization URL in JavaScript | const url = new URL("https://auth.example.com/authorize");
url.search = new URLSearchParams({ response_type: "code", client_id, redirect_uri, scope, state, code_challenge: challenge, code_challenge_method: "S256" }); | URLSearchParams handles the encoding. |
| Redirect the user to the authorization server | window.location.assign(url.toString()); | Send the browser to the URL you built. |
| What is the state parameter? | const state = base64url(crypto.getRandomValues(new Uint8Array(16))); | A random value that ties the callback to your request and helps prevent CSRF. |
| state vs nonce | nonce=RANDOM_VALUE | state links the callback to the request. nonce links the ID token to the request in OpenID Connect. |
| redirect_uri must match | redirect_uri=https://app.example.com/callback | Most servers require an exact match with a registered redirect URI. |
| URL-encode the redirect_uri | encodeURIComponent("https://app.example.com/callback") | Encode it when you put it in a query string. |
| Request the openid scope | scope=openid profile email | Add openid to start an OpenID Connect login. |
| Request refresh tokens | scope=openid offline_access | Some providers need the offline_access scope or a client setting. Check your provider. |
| Do not send the verifier in the authorization request | // code_verifier belongs only in the token request | Only the challenge goes in the authorization request. |
| What does the callback URL look like? | https://app.example.com/callback?code=AUTH_CODE&state=STATE | The server redirects with the authorization code and your state. |
| Check state on the callback | if (params.get("state") !== sessionStorage.getItem("oauth_state")) throw new Error("Invalid state"); | Compare it with the stored value before you exchange the code. |
| Token request with PKCE | curl -X POST https://auth.example.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "redirect_uri=https://app.example.com/callback" \
-d "client_id=CLIENT_ID" \
-d "code_verifier=VERIFIER" | POST the code and the code_verifier to the token endpoint. |
| Token request with fetch | const res = await fetch(tokenEndpoint, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri, client_id, code_verifier: verifier })
}); | Send a form-encoded body. |
| Token request content type | Content-Type: application/x-www-form-urlencoded | The token endpoint expects application/x-www-form-urlencoded, not JSON. |
| Does the token request need a client_secret? | -d "client_secret=SECRET" | Public clients do not send one. Confidential clients follow their registered authentication method. |
| What does the token response look like? | { "access_token": "...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "...", "id_token": "..." } | A JSON object with the access token and, depending on scope, an ID token and refresh token. |
| How long does an authorization code last? | // exchange the code immediately | It should be short-lived and single-use. RFC 6749 recommends a maximum of 10 minutes. |
| Can I use an authorization code twice? | { "error": "invalid_grant" } | No. A reused code returns invalid_grant. |
| Refresh an access token | grant_type=refresh_token&refresh_token=REFRESH_TOKEN&client_id=CLIENT_ID | Send the refresh token to the token endpoint. |
| Clean up after the token exchange | sessionStorage.removeItem("pkce_verifier");
sessionStorage.removeItem("oauth_state"); | Remove the verifier and state so they cannot be reused. |
| invalid_grant at the token endpoint | { "error": "invalid_grant" } | Often the verifier does not match the challenge, or the code expired, was reused, or the redirect_uri differs. |
| invalid_request: code_verifier missing | -d "code_verifier=VERIFIER" | You sent a challenge but no verifier at the token endpoint. Add code_verifier to the body. |
| code_challenge_method not supported | curl https://auth.example.com/.well-known/openid-configuration | The server may not support your method. Check its metadata for code_challenge_methods_supported. |
| PKCE verification failed | sessionStorage.getItem("pkce_verifier") | Typical causes: a new verifier was generated on the callback page, the verifier was lost on reload, or a different string was hashed. |
| Verifier lost after the redirect | sessionStorage.setItem("pkce_verifier", verifier); | Memory is cleared when the page reloads. Store the verifier in sessionStorage before you redirect. |
| redirect_uri mismatch | redirect_uri=https://app.example.com/callback | The redirect_uri in the token request must match the one in the authorization request, character for character. |
| Check whether the server supports S256 | curl -s https://auth.example.com/.well-known/openid-configuration | Read the discovery document and look for S256 in code_challenge_methods_supported. |
| CORS error at the token endpoint | Access-Control-Allow-Origin: https://app.example.com | A browser app can only call a token endpoint that allows its origin. Check your provider’s CORS settings. |
| invalid_client at the token endpoint | { "error": "invalid_client" } | The client may be registered as confidential but sent as public, or the client ID is wrong. |
| unsupported_response_type | response_type=code | Use the authorization code flow, so response_type must be code. |
| access_denied on the callback | https://app.example.com/callback?error=access_denied&state=STATE | The user cancelled or the server refused the request. Show a message and let the user retry. |
| State mismatch on the callback | if (state !== stored) throw new Error("State mismatch"); | Possible causes are an attack, an expired session, or two login tabs sharing one stored state. |
| Where should a single-page app store the verifier? | sessionStorage.setItem("pkce_verifier", verifier); | In sessionStorage for the duration of the login. Remove it after the token exchange and avoid localStorage. |
| Should the verifier appear in a URL? | // never put code_verifier in a URL | Never. It is a secret and URLs end up in logs and history. |
| Always use S256 | code_challenge_method=S256 | Use S256 whenever the server supports it. |
| Implicit flow vs code flow with PKCE | response_type=code | Current guidance discourages the implicit flow. Use the authorization code flow with PKCE. |
| Where should I store tokens? | let accessToken = null; | Avoid localStorage for sensitive tokens. Many security guides suggest a backend-for-frontend or in-memory storage. |
| Mobile redirect URIs | https://app.example.com/oauth/callback | Native apps should prefer claimed HTTPS redirect URIs. Custom schemes are easier to hijack. |
| Desktop app redirect with loopback | http://127.0.0.1:53682/callback | A desktop app can listen on a loopback address and port. |
| Validate the ID token | // verify iss, aud, exp, nonce and signature | PKCE does not validate tokens. Check the signature, issuer, audience, expiry and nonce. |
| PKCE does not replace redirect_uri validation | redirect_uri=https://app.example.com/callback | The server must still compare the redirect URI with the registered values. |
| Server side: store the challenge with the code | store(code, { code_challenge, code_challenge_method, client_id, redirect_uri }); | An authorization server saves the challenge and method with each issued code. |
| Server side: verify the verifier | const expected = createHash("sha256").update(verifier).digest("base64url");
const ok = expected.length === stored.length && timingSafeEqual(Buffer.from(expected), Buffer.from(stored)); | At the token endpoint, recompute the challenge and compare it in constant time. |
| Prevent PKCE downgrade | if (stored.code_challenge && !verifier) return error("invalid_grant"); | Require PKCE for public clients and do not accept a token request without a verifier when a challenge was stored. |
| oidc-client-ts for single-page apps | npm install oidc-client-ts | This library supports the authorization code flow with PKCE for browser apps. |
| Auth0 SPA SDK | npm install @auth0/auth0-spa-js | Auth0’s single-page app SDK uses the authorization code flow with PKCE. |
| Microsoft MSAL for browsers | npm install @azure/msal-browser | MSAL Browser uses the authorization code flow with PKCE for single-page apps. |
| AppAuth for JavaScript | npm install @openid/appauth | AppAuth is an OAuth client library that supports PKCE. |
| Test PKCE in Postman | Grant Type: Authorization Code (With PKCE) / Code Challenge Method: SHA-256 | Choose the Authorization Code (With PKCE) grant type and the SHA-256 method. |
| Check a challenge from the command line | printf %s "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=' | Recompute the S256 challenge and compare it with the one you sent. |
| Do not log the verifier | // never console.log(verifier) in production | A verifier is a secret during a login. Keep it out of production logs. |
| Unit test with the RFC vector | expect(challenge).toBe("E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"); | Assert your function against the RFC 7636 example. |
| Check my verifier and challenge | code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM | Use the generator above to calculate a challenge and compare it with the one you have. |
FAQ
| Question | Answer |
|---|---|
| What is PKCE? | PKCE (Proof Key for Code Exchange, RFC 7636) is an extension of the OAuth 2.0 authorization code flow. The client creates a random code verifier, sends its SHA-256 hash (the code challenge) in the authorization request, and sends the verifier when it exchanges the code, so a stolen authorization code is useless to an attacker. |
| How do I generate a PKCE code verifier and challenge? | Create 32 to 96 random bytes and encode them as base64url to get a verifier of 43 to 128 characters. The challenge is BASE64URL(SHA-256(verifier)) without padding, and the method is S256. The generator above does this in your browser. |
| How long can a code_verifier be? | A code verifier must be 43 to 128 characters long and may only contain the characters A-Z, a-z, 0-9, hyphen, period, underscore and tilde. |
| Should I use S256 or plain? | Use S256. With plain, the challenge equals the verifier, so anyone who sees the authorization request learns the secret. Use plain only if the server cannot support S256. |
| Do I need PKCE if I already use a client secret? | Public clients such as single-page and mobile apps cannot keep a secret, so they need PKCE. Current OAuth security guidance also recommends PKCE for confidential clients, and the OAuth 2.1 draft requires it for the authorization code flow. |
| Is it safe to use this PKCE generator? | It runs in your browser and nothing is uploaded. Values made here are fine for testing, but in production your app should generate a new verifier for every login. The Share link button puts your verifier in the URL, so never share a link that contains a real verifier. |