Dissect the PKCE Authorization Code Grant Flow on iOS
Learn how to use Proof Key for Code Exchange (PKCE) authentication flow to access APIs with your Swift iOS apps. By Alessandro Di Nepi.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Dissect the PKCE Authorization Code Grant Flow on iOS
20 mins
- Getting Started
- Introducing the OAuth 2.0 Authorization Framework
- Authorization Code Grant Flow
- Attacking the Authorization Code Grant Flow
- Introducing PKCE
- Generating Code Verifier and Challenge
- Generating HTTP Requests
- Preparing Server Side (Google Cloud Platform)
- Creating a New Project
- Enabling the Required API
- Generating the Authorization Credentials
- Implementing PKCE Client in Swift
- Authenticating the User
- Parsing the Callback URL
- Getting the Access Token
- Storing the Token
- Refreshing the Token
- Where to Go From Here?
Proof Key for Code Exchange (PKCE) is an addition to the OAuth authorization framework that protects the authorization flow from security attacks. As data owners adopt the protocol, it’s mandatory for applications using their APIs to authorize access using this new protocol.
In this tutorial, you’ll build an app called MyGoogleInfo. The app authenticates users with Google using the OAuth authorization flow with PKCE, and it uses the Google API to retrieve users’ profile names and pictures.
Here’s what you’ll learn:
- The OAuth 2.0 authorization code grant flow details and its vulnerability.
- What the PKCE authorization flow is and how it strengthens the OAuth flow.
- How to configure access to the Google API on the Google Cloud console.
- How to implement the PKCE authorization flow in Swift to authenticate the user.
- How to use the provided token to access the Google API.
If you have ever wondered how the authentication protocol works or if you’re thinking about using an API from one of the prominent providers for your next project, stay tuned. You’ll get all the details in this article.
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.
Open MyGoogleInfo.xcodeproj in the starter folder. Build and run. The Login screen will look like this.
The Login button doesn’t do anything yet. You’ll implement the PKCE flow with the Google OAuth service in PKCEAuthenticationService
.
Once that’s done, when the user logs in, MyGoogleInfo presents the user’s profile information.
Introducing the OAuth 2.0 Authorization Framework
The OAuth 2 Authorization framework is the standard protocol used for client authentication. The main idea behind OAuth authorization is the separation of roles. Specifically, the standard defines a protocol to allow data owners to delegate clients to access their data, without giving them their credentials.
Here are some terms to know:
- Resource Owner: This is the entity that owns the resources your app would like to access. Typically, this is you, holding your data.
- Client: The application that wants to access the data on the resource server, such as MyGoogleInfo in this case.
- Authorization server: The server in charge of authenticating the user and issuing the tokens to the client.
- Resource Server: The server hosting the data to access. An access token protects the access to the resource server.
Authorization Code Grant Flow
This diagram represents the OAuth 2.0 Authorization code grant flow that mobile applications implement:
- The user starts the login flow by tapping the MyGoogleInfo Login button.
- Consequently, the app asks the authorization server to identify the user and ask their consent to access the data. The request includes a
client_id
so that the server can identify the app requesting the access. - So, the authorization server redirects the user to its login screen (e.g. Google) and asks the user’s consent to give the app access to the API.
- The user logs in and approves the request.
- If the user approves the access, the authorization server returns a grant code to the client.
- The client requests a token to the authorization server, passing its
client_id
and the received grant code. - In response, the authorization server emits a token after verifying the
client_id
and the grant code. - Finally, the client accesses the data to the resource server, authenticating its requests with the token.
For all the details on this flow and the other ones defined in the standard, consult the RFC 6749: The OAuth 2.0 Authorization Framework (RFC 6749).
Attacking the Authorization Code Grant Flow
Although the authorization code grant flow is the way to go for mobile apps, it’s subject to client impersonation attacks. A malicious app can impersonate a legitimate client and receive a valid authentication token to access the user data.
For the flow diagram above, to receive a token the attacker should know these two parameters:
- The app’s
client_id
. - The code received in the callback URL from the authorization token.
Under certain circumstances, a malicious app can recover both. The app’s client ID is usually hardcoded, for example, and an attacker could find it by reverse-engineering the app. Or, by registering the malicious app as a legitimate invoker of the callback URL, the attacker can also sniff the callback URL.
Once the attacker knows the client ID and the grant code, they can request a token to the token endpoint. From that point forward, they use the access token to retrieve data illegally.
Introducing PKCE
Proof Key for Code Exchange (PKCE) is an addition to the Authorization Code Grant flow to mitigate the attack depicted above. In practice, it adds a code to each request that’s dynamically generated by the client so an attacker can’t guess or intercept it.
The following diagram depicts how PKCE strengthens the Authorization Code Grant flow in practice:
That is to say, PKCE introduces the following changes with respect to the plain flow:
- [1] This is where the login flow begins.
- [2] On each login request, the client generates a random code (
code_verifier
) and derives acode_challenge
from it. - [3] When starting the flow, the client includes the
code_challenge
in the request to the authorization server. On receiving the authorization request, the authorization server saves this code for later verification. - [7] The client sends the
code_verifier
when requesting an access token. - [8] Therefore, the authorization server verifies that
code_verifier
matchescode_challenge
. If these two codes match, the server knows the client is legit and emits the token.
With reference to the previous attack scenario, even if the attacker can intercept the authorization grant code and the code code_challenge
, it’s way more difficult — if not impossible — to intercept the code_verifier
.
PKCE is secure, and it’s the best way to implement OAuth authorization flow in mobile apps.
You can find all the PKCE details at the RFC 7636 – Proof Key for Code Exchange by OAuth Public Clients.
Now, you’ll look at the code verifier/challenge generation and how to transmit the PKCE parameters with the HTTP requests.
Generating Code Verifier and Challenge
The standard itself specifies how to generate the code_verifier
and code_challenge
.
Open PKCECodeGenerator.swift and replace the body of generateCodeVerifier()
with:
// 1
var buffer = [UInt8](repeating: 0, count: 32)
_ = SecRandomCopyBytes(kSecRandomDefault, buffer.count, &buffer)
// 2
return Data(buffer).base64URLEncodedString()
This generates the code_verifier
as follows:
- Get a 32-byte random sequence.
- Pass the 32 bytes sequence to base64 URL encoder to generate a 43 octet URL safe string.
Now, replace the body of generateCodeChallenge(codeVerifier:)
with:
guard let data = codeVerifier.data(using: .utf8) else { return nil }
let dataHash = SHA256.hash(data: data)
return Data(dataHash).base64URLEncodedString()
This derives the code_challenge
as the SHA256 hash of the code verifier and then base64 URL encodes it.