Essay2 min read

OAuth 2.0 Authorization Code Flow with PKCE for Native Apps

Why native and other public OAuth clients use the authorization code flow with PKCE, with a browser-based S256 implementation.

Illustration of a lock and redirect arrows protecting a mobile PKCE flow

The original insecure random verifier has been replaced with crypto.getRandomValues and Web Crypto SHA-256. Provider details can still change, so verify current OAuth and PKCE documentation before using the examples.

If you are building a native application and need OAuth 2.0, PKCE is the recommended authorization-code flow for public clients. This article explains why.

Native application is often used to refer to native application that has no back-end, and running totally in user’s devices such as Windows, Mac, iOS, and Android, where securely storing secrets is impossible.

What are the OAuth 2 options for those native applications and what are the pros and cons for them?

  1. Implicit Flow: Implicit flow will pass Access Token to Redirect URL。Assume your native application is redirecting to third-party browsers to perform OAuth2 authorization request, then your application either obtain the Access Token back by listening to app-name://?access_token= request or start a HTTP server to listen to http://localhost:{port}/?access_token= request. These two approaches both have the risk of Access Token being intercepted by malicious party who occupy URL Scheme or localhost port. Also access token can’t be renewed after expiration. Thus implicit flow is not recommended.
  2. Authorization Code Flow: Access Token of this flow will not be passed to Redirect URL directly. Redirect URL will only receive an authorization code,which need to be used along with Client ID,and Client Secret to exchange for Access Token. Since native application can’t securely store Client Secret, malicious party can decode it from native code/binary, and intercept Authorization Code to exchange for Access Token. Thus authorization code flow is not recommended either. Even some OAuth provider doesn’t require Client Secret, Access Token can still be exchanged by malicious party.

Now that two options are excluded, how can native application securely implement OAuth2? The answer is PKCE OAuth 2.0.

PKCE (Proof Key for Code Exchange), is using cryptography method to prevent malicious party to be able to exchange access token with the information they can intercept.

PKCE flow steps:

  1. Generate random string and encode with URL-Safe Base64, and used as code_verifier
  2. Do SHA256 hash,and URL-Safe Base64,and used as code_challenge
  3. Redirect to OAuth provider with code_challenge and receive Authorization Code
  4. Exchange for Access Token with code_verifier

Since malicious party can’t infer code_verifier by code_challenge, only the native application itself knows about the two value. In this way, even if malicious party intercepted code_challenge, it will not be able to exchange Access Token.

Generate PKCE Code Verifier and Code Challenge Online

Use the compact PKCE code verifier and challenge generator to create a verifier and its S256 challenge locally in your browser.

Implementation (JavaScript)

<!doctype html>
<html>
  <head>
    <script>
      function base64URL(bytes) {
        var binary = "";
        bytes.forEach(function (byte) {
          binary += String.fromCharCode(byte);
        });
        return btoa(binary)
          .replace(/=/g, "")
          .replace(/\+/g, "-")
          .replace(/\//g, "_");
      }
      function generateCodeVerifier() {
        var randomBytes = new Uint8Array(32);
        crypto.getRandomValues(randomBytes);
        var code_verifier = base64URL(randomBytes);
        document.getElementById("code_verifier").value = code_verifier;
      }
      async function generateCodeChallenge(code_verifier) {
        var data = new TextEncoder().encode(code_verifier);
        var digest = await crypto.subtle.digest("SHA-256", data);
        return base64URL(new Uint8Array(digest));
      }
      async function submit() {
        var code_verifier = document.getElementById("code_verifier").value;
        var code_challenge = await generateCodeChallenge(code_verifier);
        document.getElementById("code_challenge").textContent = code_challenge;
        document.getElementById("code_challenge_div").style.display = "block";
      }
    </script>
  </head>

  <body>
    <div>
      <label for="code_verifier">Code Verifier: </label>
      <input type="text" id="code_verifier" name="code_verifier" size="50" />
    </div>
    <br />
    <div style="display:none" id="code_challenge_div">
      Code Challenge:
      <span id="code_challenge"> </span>
    </div>
    <br />
    <div>
      <button onclick="generateCodeVerifier()">Generate Code Verifier</button>
      <button onclick="submit()">Generate Code Challenge</button>
    </div>
  </body>
</html>

OAuth2 providers that support PKCE


Reference:

↑ Back to top

Comments are hosted on GitHub for reliable, searchable threads.

Open comments