OAuth 2.0: Authorization Code Grant Flow with PKCE for Web Applications By Example
Exploring the use of OAuth 2.0: Authorization Code Grant Flow with PKCE for Web Applications through a concrete example; React front-end and Python backend.

The Problem
I was recently working on a proof-of-concept web application and went to incorporate Amazon Cognito User Pools for authentication; needless to say, it did not go well even though I was using it as intended.
A user pool is a user directory in Amazon Cognito. With a user pool, your users can sign in to your web or mobile app through Amazon Cognito. Your users can also sign in through social identity providers like Google, Facebook, Amazon, or Apple, and through SAML identity providers.
— AWS — Amazon Cognito User Pools
Setting up the User Pool itself was straightforward; it was when I tried to integrate it with my existing web application (happened to be built with React) that I struggled.
The first thing that I discovered is that the principle AWS documentation on the topic, Integrating Amazon Cognito With Web and Mobile Apps, builds upon AWS Amplify.
AWS Amplify consists of a development framework and developer services that provide the fastest and easiest way to build mobile and web applications on AWS. The open source Amplify Framework provides an opinionated set of libraries, UI components, and a command line interface to build an app backend and integrate it with your iOS, Android, Web, and React Native apps. The Amplify Framework leverages a core set of AWS Cloud Services to offer capabilities including offline data, authentication, analytics, push notifications, bots, and AR/VR at high scale. The AWS Amplify Developer Tools services include the AWS Amplify Console for building, deploying, and hosting web apps and AWS Device Farm for testing mobile apps on real iOS and Android devices.
— AWS — AWS Amplify FAQ
While AWS Amplify works with the React framework, the developer experience is more like you are building an AWS Amplify application that has an option to use React. I was looking for something different, I was simply looking to incorporate Amazon Cognito User Pools in an existing React application.
Researching the Topic
Knowing that Amazon Cognito User Pools uses OAuth 2.0 under the hood, I read up on the topic from Configuring a User Pool App Client. The documentation suggests that one must pick between one of three flows for a web application:
The Authorization code grant flow initiates a code grant flow, which provides an authorization code as the response. This code can be exchanged for access tokens with the TOKEN Endpoint. Because the tokens are never exposed directly to an end user, they are less likely to become compromised. However, a custom application is required on the backend to exchange the authorization code for user pool tokens.
…
The Implicit grant flow allows the client to get the access token (and, optionally, ID token, based on scopes) directly from the AUTHORIZATION Endpoint. Choose this flow if your app cannot initiate the Authorization code grant flow. For more information, see the OAuth 2.0 specification.
…
The Client credentials flow is used in machine-to-machine communications. With it you can request an access token to access your own resources. Use this flow when your app is requesting the token on its own behalf, not on behalf of a user.
— AWS — Configuring a User Pool App Client
note: I did notice that for mobile (iOS and Android) they documented another flow; Authorization Code Grant Flow with PKCE (foreshadowing….)
The Client credentials flow did not fit my requirement of authenticating users and knew to not to use the Implicit Grant Flow.
The Implicit flow was a simplified OAuth flow previously recommended for native apps and JavaScript apps where the access token was returned immediately without an extra authorization code exchange step.
It is not recommended to use the implicit flow (and some servers prohibit this flow entirely) due to the inherent risks of returning access tokens in an HTTP redirect without any confirmation that it has been received by the client.
— OAuth 2.0 — OAuth 2.0 Implicit Grant
After a bit of head-spinning research on how to implement the Authorization Code Grant Flow using a Python backend, I went back to watch the official (from OAuth 2.0) video on what the precisely the problem was with the Implicit Grant flow. Take the time to watch the video; it is super instructive.
Researching Authorization Code Grant Flow with PKCE
If you watched the video, you would have learned that Authorization Code Grant Flow with PKCE is now (as of mid-2019) a recommended solution for the web (not just mobile iOS and Android). Also, notice that because we are using the Authorization Code Grant Flow, we get a refresh token; so we can have the user remain authenticated for weeks (not hours).
With little luck finding any AWS documentation about if and how to implement the Authorization Code Grant Flow with PKCE for web application for Amazon Cognito User Pools, I read through the documentation provided by Auth0; Execute an Authorization Code Grant Flow with PKCE.
The key observation in the Auth0 documentation is that the authorization URL, the one that returns the hosted UI, uses the code_challenge and code_challenge_method query parameters to support the Authorization Code Grant Flow with PKCE.
https://YOUR_DOMAIN/authorize?
audience=API_AUDIENCE&
scope=SCOPE&
response_type=code&
client_id=YOUR_CLIENT_ID&
code_challenge=CODE_CHALLENGE&
code_challenge_method=S256&
redirect_uri=https://YOUR_APP/callback
Looking at the Amazon Cognito User Pools AUTHORIZATION endpoint documentation we see that it too supports these parameters; things are looking good.
Implementing the Authorization Code Grant Flow with PKCE Using Amazon Cognito User Pools
The first challenge that I ran into is the Auth0 example used methods on crypto, e.g., randomBytes and createHash, that I could not find in the built-in Window.crypto object.
Not knowing enough about cryptography, I turned to another library that provided the exact documented methods; crypto-browserify:
The goal of this module is to reimplement node’s crypto module, in pure javascript so that it can run in the browser.
— crypto-browserify — crypto-browserify
note: What is interesting is that this library only has 413 stars (of this writing) but is used by 3.6 million projects; yes that is million.
Using this library, it was fairly straightforward (albeit tedious) to implement the following endpoints:
- AUTHORIZATION Endpoint: In loginUrl export
- TOKEN Endpoint: In login and refreshTokens exports
- LOGOUT Endpoint: In logoutUrl export
Could not figure out when to use the LOGIN Endpoint and chose not to use the USERINFO Endpoint (but can see how this would be useful; say you want to show the user’s name in the UI).
The entire solution is also available for download; but it includes other code that is not relevant to this article.
Authenticating Users on a Resource Server (Your Back-End)
The good news is that here, AWS provides detailed instructions on Verifying a JSON Web Token, e.g., say you want to authenticate users on your back-end. The bad news is that the steps are somewhat technical and anything with cryptography can be challenging.
note: It is important to note that you do not want to have the front-end supply the authorization information directly, i.e., pass in the user’s identifier in the API payload itself.
Another bit of good news is that I remembered I wrote a series of articles, Authentication Made Easy with Auth0: Part 1, that performed essentially the same operations.
The bit of bad news is that article was written using Node.js and my new project uses a Python backend (trying to get better at writing Python).
After researching a number of Python JOSE (The JavaScript Object Signing and Encryption) libraries, I ended up using python-jose with a surprisingly simple solution:
The entire solution is available for download; as with the React project, this includes other code unrelated to this article.
While the solution is simple, it took some time to find a library that made it easy to work with a JSON Web Key Set (JWKS).
Wrap Up
Hope you find this useful.