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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Getting the Access Token

Finally, you’re ready to get the token.

Replace the //TODO: make request comment in getToken(code:codeVerifier:) with the following:

do {
  // 1
  let (data, response) = try await URLSession.shared.data(for: tokenURLRequest)
  // 2
  guard let response = response as? HTTPURLResponse else {
    print("[Error] HTTP response parsing failed!")
    status = .error(error: .tokenExchangeFailed)
    return
  }
  guard response.isOk else {
    let body = String(data: data, encoding: .utf8) ?? "EMPTY"
    print("[Error] Get token failed with status: \(response.statusCode), body: \(body)")
    status = .error(error: .tokenExchangeFailed)
    return
  }
  print("[Debug] Get token response: \(String(data: data, encoding: .utf8) ?? "EMPTY")")
  // 3
  let decoder = JSONDecoder()
  decoder.keyDecodingStrategy = .convertFromSnakeCase
  let token = try decoder.decode(GoogleToken.self, from: data)
  // TODO: Store the token in the Keychain
  // 4
  status = .authenticated(token: token)
} catch {
  print("[Error] Get token failed with: \(error.localizedDescription)")
  status = .error(error: .tokenExchangeFailed)
}

The function getToken(code:codeVerifier:) performs the following actions:

  1. Use the tokenURLRequest to start the token exchange session with the token endpoint. As a result, it receives back a URLResponse and an optional Data.
  2. Parse the server response status.
  3. If there are no errors, decode the result as a GoogleToken.
  4. Finally, set the status to authenticated, including the access token as a parameter.

Now you’re ready to start querying data. :]

Once you get the token, you can start using it to access the API.

The code in ViewModel listens to the authentication service status and passes the token to the GoogleProfileInfoService. Then, the profile info service uses the token to access your profile information.

Build and run. Log in one last time. Finally, you can see your Google profile information.

MyGoogleInfo showing user profile.

You can also see in the logs the token response from the server:

Get token response log.

Storing the Token

So far, you didn’t save the access token in persistent storage. In other words, every time the app starts, the user needs to log in again.

To make the user experience flawless, the app should do two more things.
First, it should save both the access and the refresh tokens in persistent storage, as soon as they’re received from the server.
Second, it should restore the token from the persistent storage when the app starts.

Since the tokens contain credential access, you should avoid UserDefaults and use the Apple keychain.

Refreshing the Token

The access token and the refresh token have a limited timeframe. In other words, they have a time expiration date enforced on the server.

Once the access token expires, your API calls will fail with error 401. In these cases, you need to trigger the token refresh flow with the token endpoint. The HTTP request body contains the client ID and the refresh token encoded as URL parameters.

For a reference, createRefreshTokenURLRequest(refreshToken:) in the final project generates the URLRequest for the token refresh.

Where to Go From Here?

Download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.

You dug into the details of the OAuth Authorization flow with PKCE, and now you’re ready to implement the authentication service for your next app. You can also experiment with things like token management and better error handling.

For all the details on how to store and retrieve the token in the keychain, check out How To Secure iOS User Data: Keychain Services and Biometrics with SwiftUI.

On the other hand, if you want to adopt one of the SDKs available for OAuth, you now know how PKCE works under the hood to totally control the package behavior.

For reference, here are some third-party SDKs that implement OAuth with PKCE.

  • AppAuth is an open-source SDK from the OpenID consortium; it supports native apps (iOS and Android) and all the other Apple OSs and Native JS.
  • Auth0 offers a complete solution for OpenID authentication and, as a part of it, they provide an SDK that supports both iOS and macOS.

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!