{"id":22268177,"url":"https://github.com/curityio/pkce-javascript-example","last_synced_at":"2025-07-16T22:35:16.496Z","repository":{"id":38080337,"uuid":"243680736","full_name":"curityio/pkce-javascript-example","owner":"curityio","description":"JavaScript SPA showing how to handle PKCE in an OpenID Connect flow","archived":false,"fork":false,"pushed_at":"2023-01-04T08:15:29.000Z","size":28,"stargazers_count":60,"open_issues_count":0,"forks_count":15,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-07T03:35:48.543Z","etag":null,"topics":["code-example","oauth2","openid-connect","spa"],"latest_commit_sha":null,"homepage":"https://curity.io/resources/learn/javascript-pkce-client/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/curityio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-02-28T04:48:38.000Z","updated_at":"2024-10-18T02:16:43.000Z","dependencies_parsed_at":"2023-02-02T04:30:59.648Z","dependency_job_id":null,"html_url":"https://github.com/curityio/pkce-javascript-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curityio%2Fpkce-javascript-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curityio%2Fpkce-javascript-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curityio%2Fpkce-javascript-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curityio%2Fpkce-javascript-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/curityio","download_url":"https://codeload.github.com/curityio/pkce-javascript-example/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248464500,"owners_count":21108238,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-example","oauth2","openid-connect","spa"],"created_at":"2024-12-03T11:11:49.424Z","updated_at":"2025-04-11T18:53:25.209Z","avatar_url":"https://github.com/curityio.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# A Simple JavaScript PKCE Example\n\n[![Quality](https://img.shields.io/badge/quality-demo-red)](https://curity.io/resources/code-examples/status/)\n[![Availability](https://img.shields.io/badge/availability-source-blue)](https://curity.io/resources/code-examples/status/)\n\n## Introduction\n\nThe OAuth Code Flow is one of the more typical and flexible token flows, and, with that, one of the most popular. The details of this flow are not covered by this article, but can be found in the [code flow overview](https://curity.io/resources/learn/oauth-code-flow) article on the Curity Web site.\n\nProof Key for Code Exchange (PKCE) is a technique described in [RFC7636](https://www.rfc-editor.org/rfc/rfc7636), and is used to mitigate the risk of the authorization code being hijacked. More details on how to configure the Curity Identity Server to enable PKCE can be found in the [configuring PKCE](https://curity.io/resources/learn/pkce/) tutorial, and [further details on PKCE](https://curity.io/resources/learn/oauth-pkce/) can also be found on the same site.\n\nThe rest of this writeup explains how these technologies can be used in the JavaScript programming language. It is intentionally simple, so that the concepts are not obscured by superfluous details.\n\n## Configuration\n\n### Client\n\nThe client -- the HTML page -- needs to be configured with the client ID. In this example, the ID is `public-test-client`. If certain scopes are desired, these should be configured as well.\n\n```JavaScript\nconst clientId = \"public-test-client\";\n```\n\n#### Creating the Verifier\n\nThis function generates a random string (the verifier) that is later signed before it is sent to the authorization server, to Curity.\n\n```JavaScript\nfunction generateRandomString(length) {\n  var text = \"\";\n  var possible = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n\n  for (var i = 0; i \u003c length; i++) {\n    text += possible.charAt(Math.floor(Math.random() * possible.length));\n  }\n\n  return text;\n}\n```\n\n#### Hashing the Verifier\n\nThe Web crypto API is used to hash the verifier using SHA-256. This transformed version is called the code challenge.\n\n```JavaScript\nasync function generateCodeChallenge(codeVerifier) {\n  var digest = await crypto.subtle.digest(\"SHA-256\",\n    new TextEncoder().encode(codeVerifier));\n\n  return btoa(String.fromCharCode(...new Uint8Array(digest)))\n    .replace(/=/g, '').replace(/\\+/g, '-').replace(/\\//g, '_')\n}\n```\n\nNote that Javascript crypto services require that the `index.html` is served in a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) — either from **(*.)localhost** or via **HTTPS**.\nTo enable secure context do one of the following:\n- add an `/etc/hosts` entry like `127.0.0.1 public-test-client.localhost` and load the site from there, or\n- enable SSL using something like [letsencrypt](https://letsencrypt.org/), or\n- refer to this [stackoverflow article](https://stackoverflow.com/questions/46468104/how-to-use-subtlecrypto-in-chrome-window-crypto-subtle-is-undefined) for more alternatives.\n\nIf Javascript crypto is not available the script will fall back to using a plain-text code challenge.\n\n\n#### Storing the Verifier\n\nStore the verification key between requests (using session storage).\n\n```JavaScript\nwindow.sessionStorage.setItem(\"code_verifier\", codeVerifier);\n```\n\n#### Sending the Code Challenge in the Authorization Request\n\nThe code challenge (the transformed, temporary verification secret) is passed to the authorization server as part of the authorization request. The method (`S256`, in our case) used to transform the secret is also passed with the request.\n\n```JavaScript\nvar redirectUri = window.location.href.split('?')[0];\nvar args = new URLSearchParams({\n  response_type: \"code\",\n  client_id: clientId,\n  code_challenge_method: \"S256\",\n  code_challenge: codeChallenge,\n  redirect_uri: redirectUri\n});\nwindow.location = authorizeEndpoint + \"/?\" + args;\n```\n\n#### Call the Token Endpoint with the Code and Verifier\n\nThe authorization code is passed in the POST request to the token endpoint along with the secret verifier key (retrieved from the session storage).\n\n```JavaScript\nxhr.responseType = 'json';\nxhr.open(\"POST\", tokenEndpoint, true);\nxhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');\nxhr.send(new URLSearchParams({\n  client_id: clientId,\n  code_verifier: window.sessionStorage.getItem(\"code_verifier\"),\n  grant_type: \"authorization_code\",\n  redirect_uri: location.href.replace(location.search, ''),\n  code: code\n}));\n```\n\n### OAuth Server\n\nThe OAuth server needs to be configured with a client that matches the one configured [above](#Client). Also, the redirect should be set. When using `npx` (described below), this will be `http://localhost:8080` by default if no port is provided. Additionally, scopes may be configured.\n\nThis can be created in the Curity Identity Server by merging this XML with the current configuration:\n\n```xml\n\u003cconfig xmlns=\"http://tail-f.com/ns/config/1.0\"\u003e\n  \u003cprofiles xmlns=\"https://curity.se/ns/conf/base\"\u003e\n  \u003cprofile\u003e\n    \u003cid\u003emy-good-oauth-profile\u003c/id\u003e \u003c!-- Replace with the ID of your OAuth profile --\u003e\n    \u003ctype xmlns:as=\"https://curity.se/ns/conf/profile/oauth\"\u003eas:oauth-service\u003c/type\u003e\n      \u003csettings\u003e\n      \u003cauthorization-server xmlns=\"https://curity.se/ns/conf/profile/oauth\"\u003e\n      \u003cclient-store\u003e\n      \u003cconfig-backed\u003e\n      \u003cclient\u003e\n        \u003cid\u003epublic-test-client\u003c/id\u003e\n        \u003cno-authentication\u003etrue\u003c/no-authentication\u003e\n        \u003credirect-uris\u003ehttp://localhost:8080/\u003c/redirect-uris\u003e \u003c!-- Update with your URL --\u003e\n        \u003ccapabilities\u003e\n          \u003ccode/\u003e\n        \u003c/capabilities\u003e      \n        \u003cvalidate-port-on-loopback-interfaces\u003efalse\u003c/validate-port-on-loopback-interfaces\u003e\n      \u003c/client\u003e\n      \u003c/config-backed\u003e\n      \u003c/client-store\u003e\n      \u003c/authorization-server\u003e\n      \u003c/settings\u003e\n  \u003c/profile\u003e\n  \u003c/profiles\u003e\n\u003c/config\u003e\n```\n\n## Serving the Sample HTML File\n\nThe HTML needs to be served somehow from a Web server. Because the client is just a static HTML page, this can be done with a trivial server configuration. Below are a couple of ways to easily serve the static HTML page:\n\n```sh\n$ npx http-server -p \u003cport\u003e\n```\n\n```sh\n$ php -S \u003chost\u003e:\u003cport\u003e\n```\n\n```sh\n$ python -m SimpleHTTPServer \u003cport\u003e\n```\n\nThese will not use TLS, but are fast and easy ways to serve the HTML file without setting up any infrastructure.\n\n## License\n\nThe code and samples in this repository are licensed under the [Apache 2 license](LICENSE).\n\n## Questions\n\nFor questions and comments, contact Curity AB:\n\n\u003e info@curity.io\n\u003e https://curity.io\n\nCopyright (C) 2020 Curity AB.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcurityio%2Fpkce-javascript-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcurityio%2Fpkce-javascript-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcurityio%2Fpkce-javascript-example/lists"}