Standard OAuth 2.0 Flow
In the standard OAuth 2.0 flow, there are four roles:
- Resource owner: Me, the owner of user information.
- Client: The web application trying to access the user’s information.
- Authorization server: Google, the server that grants permission.
- Resource server: Google Contact API, the server storing our resources (sometimes the Resource Server is the same as the Authorization Server).
Here are some other terms explained:
- Authorization grant: Proof that the user clicked “Yes.”
- Redirect URI: The place where the Authorization Server sends back the response to the Client.
- Access Token: The key that the Client uses to access resources.
The flow is as follows:
- Client sends Authorization Server a Redirect URI and Resource Type: Code.
- Client logs in to the Authorization Server and grants permission.
- Authorization Server sends back an Authorization Code through the Callback URL (redirect URL).
- Client connects to the Callback URL and passes the relevant information (Authorization Code) to the application.
- Client sends the Authorization Code back to the Authorization Server to exchange for an Access Token.
- Client can happily use the Access Token to get data from the Resource Server.
Why PKCE is Needed
This flow looks safe and useful, right? Using the Authorization Code to exchange for an Access Token instead of doing it in the Front Channel (the path where data can be known by the user) seems okay!
So why do we need another protocol?
Because this flow assumes the Client is a Backend Application. In real-world scenarios, we often need to develop Desktop Applications, meaning there’s no Back Channel, and the entire system’s data flow can be accessed by the user, giving hackers a chance to steal the Authorization Code and exchange it for an Access Token!
Additionally, when exchanging the Authorization Code for an Access Token, you need to include the Client Secret. For a Desktop Application, embedding the Client Secret in the application is very unwise!
Therefore, we need a new protocol to protect our Access Token!
Introducing today’s main character: “Proof Key for Code Exchange” (PKCE, pronounced “pixy”).
PKCE solves the problem of exchanging information with the Resource Authorizer to get an Access Token without exposing the Client Secret on the user side, and importantly, it prevents interception attacks and other security issues!
PKCE Flow
First, let’s define some terms.
- Application: Desktop Application, the desktop software we commonly use, such as Slack or VSCode.
The complete PKCE flow is:
- Application Initialization
- The application generates a random string called “Code Verifier” in memory (InMemory).
- The application hashes the Code Verifier using SHA-256 to get a shorter string called “Code Challenge” in memory (InMemory).
- The Code Verifier is stored in memory (InMemory) for later verification.
- Authorization Request
- When the application gets the login page, it includes two additional parameters in the URL:
- code_challenge: the challenge code generated from the Code Verifier
- code_challenge_method: the method used to generate the challenge code, typically “S256” (representing SHA-256)
- User Authorization This step is the same as the regular OAuth 2.0 flow where the user grants permission.
- Authorization Code Returned
- The Authorization Code is returned to the user, with the possibility of being intercepted by a hacker.
- But, the next step is different!
- Token Request
- To exchange the Authorization Code for an Access Token, the application must include the “Code Verifier” for the Authorization Server to verify the source.
- The hacker doesn’t know the exact value of the Code Verifier stored in memory (InMemory), making it impossible to impersonate the user to get an Access Token.
- Authorization Server Verification
- The Authorization Server processes the Code Verifier with the
code_challenge_method
and compares it with the code_challenge sent earlier. - If successful, the Access Token is returned.
- The Authorization Server processes the Code Verifier with the
- Token Returned
- The application successfully obtains the Access Token.
Where is the Increased Security?
PKCE’s most important functions:
- Protecting the Authorization Code: Even if an attacker intercepts the Authorization Code, they cannot use it to obtain a token without the Code Verifier.
- Improved Security: PKCE provides stronger security for the Authorization Code flow even in environments without a confidential client, such as mobile and web applications.
If you compile the traditional OAuth 2.0 client secret into the application, these strings can easily be extracted using reverse engineering techniques! Compared to PKCE, PKCE uses random codes (one-time random values) and hashes (to verify the source) with all information temporarily stored in memory, making it much harder to steal.
Even if the code challenge is intercepted, the attacker cannot reverse-engineer it to determine the code verifier. Even if they could, the time sensitivity would have expired!
Perfect!
Summary
This article introduces our main character: PKCE, its overall flow, and the problems it solves.
In the next article, we’ll introduce how AWS Cognito User Pool supports PKCE!