OAuth PKCE Generator Guide: Secure Mobile and App Logins
Logging in with an external account, such as signing into an app with a larger provider, is convenient for users and risky to implement badly. The standard flow for this is OAuth, in which the app redirects the user to the provider, the user approves, and the provider returns an authorization code that the app exchanges for tokens. The weakness in that flow, for apps that cannot keep a secret, is that the authorization code travels through places an attacker might observe, and anyone who captures it could exchange it for tokens themselves. PKCE, pronounced "pixie" and short for Proof Key for Code Exchange, closes that hole, and the OAuth PKCE Generator creates the pair of values the flow needs. It generates values for you to use in your own implementation; it does not perform logins itself, and generated values should be treated as test material unless your implementation handles them securely.
PKCE works with two values: a code verifier and a code challenge. The code verifier is a long random string that the app creates and keeps private, never sending it anywhere except in the final token exchange, over a secure connection. The code challenge is derived from the verifier by a one-way transformation, usually a SHA-256 hash encoded in a URL-safe form, and it is the value the app sends at the start of the flow, along with the authorization request. When the app later presents the authorization code together with the original verifier, the provider recomputes the challenge from the verifier and checks that it matches the challenge sent at the start. An attacker who captures the authorization code but not the verifier cannot pass that check, because the verifier never travelled through the exposed channel, and the one-way transformation means the challenge cannot be reversed to recover the verifier. The whole mechanism rests on one rule: the verifier stays secret until the token exchange, and the challenge is the only value exposed early.
The verifier itself must meet specific requirements, and a generator handles them so you do not have to memorize the specification. It must be long enough, typically 43 to 128 characters, drawn from an alphabet of letters, digits, and a few safe punctuation characters, and it must be random, generated from a proper random source rather than from a predictable pattern such as a timestamp or a username. A short or predictable verifier defeats the mechanism, because an attacker who can guess it can pass the check. The challenge method also matters: the secure method hashes the verifier, while a plain method sends the verifier unchanged as the challenge, which offers no protection if the challenge is observed and should only be used when hashing is genuinely unavailable. A generator that offers both methods should default to the hashed method, and an implementation that accepts the plain method without a good reason is worth questioning.
Worked example
Take a sample flow, with values clearly labelled as an illustration and simplified for readability. The app generates a code verifier, a random string of 64 characters, and keeps it in memory only, never storing it in a log or a URL. It computes the challenge by hashing the verifier with SHA-256 and encoding the result in URL-safe base64, producing a challenge string of 43 characters. The app sends the authorization request with that 43-character challenge and the method name for the hashed variant. The user approves, and the provider returns an authorization code. The app then sends the code together with the original 64-character verifier to the token endpoint over a secure connection. The provider hashes the verifier, obtains the same 43-character challenge, compares it with the stored challenge, and finds a match, so it issues the tokens. Now consider an attacker who captured the authorization code in transit: without the 64-character verifier, the attacker cannot reproduce the challenge, and the provider refuses the exchange. The attacker's chance of guessing a random 64-character verifier from the allowed alphabet is negligible, which is the quantitative basis for the protection. The flow adds one generation step and one comparison, and in return it removes a whole class of code-interception attacks from apps that cannot hold a client secret.
Mistakes to avoid
The first mistake is generating a short or predictable verifier, such as a timestamp or a short word, which an attacker can guess. The second is using the plain challenge method when the hashed method is available, which exposes the verifier in the challenge itself. The third is logging the verifier, storing it where it can leak, or placing it in a URL that gets recorded in history or server logs; the verifier should live in memory and be discarded after the exchange. The fourth is reusing one verifier across multiple login attempts, which means a single captured value compromises more than one flow. The fifth is treating generated test values as production secrets: values produced while experimenting should never be pasted into a live application, and any verifier, token, or secret that has been shared in a test, a screenshot, or a chat should be considered exposed and replaced.
Putting it into practice
Generate a fresh verifier for every login attempt, use the hashed challenge method, keep the verifier in memory only, and discard it after the token exchange. Test the flow with the generated pair before shipping, including the failure case where a wrong verifier is presented, to confirm the provider rejects it. For related tools, the JWT Decoder inspects the tokens such a flow returns, the Password Generator creates strong random secrets for other purposes, and the companion guide on the JWT Decoder explains token structure in depth. PKCE is a small addition to an OAuth flow, but for apps without a client secret, it is the difference between an authorization code that anyone can use and one that only its rightful owner can redeem.
Key points
- PKCE protects OAuth authorization codes by requiring a secret verifier that only the legitimate app holds at token exchange
- The verifier is a long random string kept private, while the challenge is its one-way hash sent at the start of the flow
- Use the hashed challenge method and a fresh, properly random verifier of 43 to 128 characters for every login attempt
- Never log, store, or share a verifier, and treat any test value that leaked as exposed and replace it
In short: generate a fresh random verifier per attempt, send only its hash as the challenge, present the verifier once at token exchange, and discard it.