• Home
  • About Us
  • Contact Us
  • Disclaimer
  • Privacy Policy
  • Terms & Conditions
Flyy Tech
  • Home
  • Apple
  • Applications
    • Computers
    • Laptop
    • Microsoft
  • Security
  • Smartphone
  • Gaming
  • Entertainment
    • Literature
    • Cooking
    • Fitness
    • lifestyle
    • Music
    • Nature
    • Podcasts
    • Travel
    • Vlogs
  • Camera
  • Audio
No Result
View All Result
  • Home
  • Apple
  • Applications
    • Computers
    • Laptop
    • Microsoft
  • Security
  • Smartphone
  • Gaming
  • Entertainment
    • Literature
    • Cooking
    • Fitness
    • lifestyle
    • Music
    • Nature
    • Podcasts
    • Travel
    • Vlogs
  • Camera
  • Audio
No Result
View All Result
Flyy Tech
No Result
View All Result

Dissect the PKCE Authorization Code Grant Flow on iOS

flyytech by flyytech
November 19, 2022
Home Applications
Share on FacebookShare on Twitter


Learn how to use Proof Key for Code Exchange (PKCE) authentication flow to access APIs with your Swift iOS apps.

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.

MyGoogleInfo Login screen.

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.

MyGoogleInfo Profile screen.

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:

OAuth 2.0 Authorization Flow.

  1. The user starts the login flow by tapping the MyGoogleInfo Login button.
  2. 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.
  3. 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.
  4. The user logs in and approves the request.
  5. If the user approves the access, the authorization server returns a grant code to the client.
  6. The client requests a token to the authorization server, passing its client_id and the received grant code.
  7. In response, the authorization server emits a token after verifying the client_id and the grant code.
  8. 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:

OAuth Authorization Flow with PKCE.

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 a code_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 matches code_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:

  1. Get a 32-byte random sequence.
  2. 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.

Generating HTTP Requests

In addition, the standard specifies two different endpoints on the Authorization server for the two authorization phases.

Open PKCERequestBuilder.swift and note the properties for each of these endpoints at the top:

  • Authorization endpoint at /authorize is in charge of emitting the authorization code grant.
  • Token endpoint at /token-generation, to emit and refresh tokens.

According to the RFC, the client should communicate with these two endpoints with two different HTTP request types:

  • Using a GET with all the required parameters passed as URL parameters, for the authorization endpoint.
  • Sending a POST with the parameters passed in the request’s body, encoded as URL parameters, for the token endpoint.

PKCERequestBuilder already contains everything you need to generate the two requests.

  • createAuthorizationRequestURL(codeChallenge:) generates a URL with the required parameters.
  • createTokenExchangeURLRequest(code:codeVerifier:) generates a URLRequest for the token exchange.

Preparing Server Side (Google Cloud Platform)

Before proceeding with the client implementation, you have to set up the backend service.

This setup process allows you to register your application and its redirection URI used throughout the authorization flow and receive the clientID.

In this specific example, since Google already offers a service for user authentication with OAuth, you can use their service.

The service setup process consists of the following steps:

  • Creating a new project.
  • Enabling the specific APIs your app intend to use.
  • Generating the authorization credentials for the app (the client ID).

You’ll need a Google account to register an app.

Creating a New Project

First, open the Google API Console and click Create Project.

Project creation screen.

If you’ve previously created a project, you might need to click the name of the project in the blue bar to bring up a dialog with a New Project button.

Screenshot showing how to create a new project when there is an existing one

You might be asked to enroll in the Google Cloud Developer program. If you’re not already in, don’t worry — it’s as simple as accepting their terms and conditions.

Enter MyGoogleInfo in the project’s name. Then, Google assigns you a client ID that you’ll need once you generate the authorization requests from the app.

Click CREATE.

Project screen.

Enabling the Required API

Now, it’s time to tell Google what kind of API your app will use.

Declaring the required APIs is twofold.

First, it affects the kind of permission Google presents to the user during the authorization phase.

And, more important, it allows Google to enforce the data scope when your app requests data. Each token has a scope that defines which API the token grants access to.

For instance, in the case of MyGoogleInfo, you need to enable the Google People API to allow the app to query the user information.

From the project page, click ENABLE APIS AND SERVICES.

Enable APIs screen.

Then, search for Google People API and click ENABLE.

Google People API screen.

Generating the Authorization Credentials

Finally, you need to create the authorization credentials before you can use the API.

Click Credentials in the sidebar.

If you see a prompt to configure a consent screen, select external user type and fill out the registration form for the required fields. Then, click Credentials in the sidebar again.

Click CREATE CREDENTIALS, then choose OAuth Client ID.

Add credentials menu.

These credentials let you specify which access level and to which API your users’ tokens have access.

Fill in the required fields as shown in the figure below.

Most importantly, the Bundle ID should have the same value as the one set in Xcode for your app. For example, in the example below, it’s com.alessandrodn.MyGoogleInfo. In your case, it’s your app bundle ID.

OAuth client ID screen.

Finally, click CREATE. You should have an OAuth client definition for iOS as in the picture below:

OAuth client screen.

Replace REPLACE_WITH_CLIENTID_FROM_GOOGLE_APP in the definition below with the Client ID from your Google app in PKCERequestBuilder.

PKCERequestBuilder for MyGoogleInfo.

It took a while to prepare, but you’re now ready to implement the PKCE client in Swift!

Implementing PKCE Client in Swift

After all that theory, it’s now time to get your hands dirty in Xcode :]

Authenticating the User

First, implement the first phase of the authorization flow, asking the authorization endpoint to verify the user identity.

Open PKCEAuthenticationService.swift. Add the following code to the end of startAuthentication():


// 1
let codeVerifier = PKCECodeGenerator.generateCodeVerifier()
guard
  let codeChallenge = PKCECodeGenerator.generateCodeChallenge(
    codeVerifier: codeVerifier
  ),
  // 2
  let authenticationURL = requestBuilder.createAuthorizationRequestURL(
    codeChallenge: codeChallenge
  )
else {
  print("[Error] Can't build authentication URL!")
  status = .error(error: .internalError)
  return
}
print("[Debug] Authentication with: \(authenticationURL.absoluteString)")
guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
  print("[Error] Bundle Identifier is nil!")
  status = .error(error: .internalError)
  return
}
// 3
let session = ASWebAuthenticationSession(
  url: authenticationURL,
  callbackURLScheme: bundleIdentifier
) { callbackURL, error in
  // 4
  self.handleAuthenticationResponse(
    callbackURL: callbackURL,
    error: error,
    codeVerifier: codeVerifier
  )
}
// 5
session.presentationContextProvider = self
// 6
session.start()

The code above implements the first part of the authorization flow:

  1. Generates the code verifier and derives the code challenge from it.
  2. Prepare the authorization endpoint URL with all the required parameters.
  3. Instantiate ASWebAuthenticationSession to perform the authentication, passing authenticationURL generated before.
  4. In its completion handler, ASWebAuthenticationSession returns the parameters received from the server as callbackURL.
  5. Tell the browser instance that your class is its presentation context provider. So, iOS instantiates the system browser window on top of the app’s main window.
  6. Finally, start the session.

ASWebAuthenticationSession gives you back an optional callback URL and an optional error.

For now, handleAuthenticationResponse(callbackURL:error:codeVerifier:) parses the error and prints the callback URL.

Build and run. Tap the Login button, and you’ll see an alert saying MyGoogleInfo wants to use google.com to sign in.

Dialog asking permission to use google.com to sign in

Tap Continue and you’ll see the Google login screen.

Note the Google request to share the user’s profile information.

MyGoogleInfo login screen

Enter your credentials, authorize the app and check the logs.
Check the app’s log for the callback URL returned from Google with the authorization response parameters.

Received callback URL log.

Parsing the Callback URL

To proceed with the authorization flow, you now need to do two things.

First, in PKCEAuthenticationService.swift, add the function getToken(code:codeVerifier:) as follows.


private func getToken(code: String, codeVerifier: String) async {
  guard let tokenURLRequest = requestBuilder.createTokenExchangeURLRequest(
    code: code,
    codeVerifier: codeVerifier
  ) else {
    print("[Error] Can't build token exchange URL!")
    status = .error(error: .internalError)
    return
  }
  let tokenURLRequestBody = tokenURLRequest.httpBody ?? Data()
  print("[Debug] Get token parameters: \(String(data: tokenURLRequestBody, encoding: .utf8) ?? "")")
  //TODO: make request
}

createTokenExchangeURLRequest() generates the HTTP request, given the grant code and code_verifier.

Note: The function getToken(code:codeVerifier:) is async, as it’ll return immediately and complete the network call in the background. Since you invoke it from a synchronous context, you use a Task.

Then, replace the implementation of handleAuthenticationResponse(callbackURL:error:codeVerifier:) with the following.


if let error = error {
  print("[Error] Authentication failed with: \(error.localizedDescription)")
  status = .error(error: .authenticationFailed)
  return
}
guard let code = extractCodeFromCallbackURL(callbackURL) else {
  status = .error(error: .authenticationFailed)
  return
}
Task {
  await getToken(code: code, codeVerifier: codeVerifier)
}

The code above extracts the code parameter value in the callback URL and passes it to getToken(code:codeVerifier:).

Build and run, then log in with your credentials. Verify the log now contains the parameters for the credential exchange.

Get Token Parameters log.

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!



Source_link

flyytech

flyytech

Next Post
TouchArcade Game of the Week: ‘Wreckfest’ – TouchArcade

TouchArcade Game of the Week: ‘Wreckfest’ – TouchArcade

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recommended.

AWS SSO Timeout Causes Incorrect Behavior throughout AWS Console | by Teri Radichel | Bugs That Bite | Sep, 2022

Template format error: Every Parameters member must be an object. | by Teri Radichel | Bugs That Bite | Sep, 2022

September 11, 2022
Automatic Updates Deliver Malicious 3CX ‘Upgrades’ to Enterprises

Automatic Updates Deliver Malicious 3CX ‘Upgrades’ to Enterprises

March 30, 2023

Trending.

Shop now. Pay later. on the App Store

Shop now. Pay later. on the App Store

February 25, 2023
USIU student team qualifies for Microsoft Imagine Cup World Championship

USIU student team qualifies for Microsoft Imagine Cup World Championship

April 5, 2023
Volla Phone 22 review

Volla Phone 22 review

March 26, 2023
Thermalright Peerless Assassin 120 SE Review: Incredible, Affordable Air Cooling Performance

Thermalright Peerless Assassin 120 SE Review: Incredible, Affordable Air Cooling Performance

September 27, 2022
Light Lens Lab 50mm f/2 Review: The Classic Speed Panchro II Reborn

Light Lens Lab 50mm f/2 Review: The Classic Speed Panchro II Reborn

March 22, 2023

Flyy Tech

Welcome to Flyy Tech The goal of Flyy Tech is to give you the absolute best news sources for any topic! Our topics are carefully curated and constantly updated as we know the web moves fast so we try to as well.

Follow Us

Categories

  • Apple
  • Applications
  • Audio
  • Camera
  • Computers
  • Cooking
  • Entertainment
  • Fitness
  • Gaming
  • Laptop
  • lifestyle
  • Literature
  • Microsoft
  • Music
  • Podcasts
  • Review
  • Security
  • Smartphone
  • Travel
  • Uncategorized
  • Vlogs

Site Links

  • Home
  • About Us
  • Contact Us
  • Disclaimer
  • Privacy Policy
  • Terms & Conditions

Recent News

Magento, WooCommerce, WordPress, and Shopify Exploited in Web Skimmer Attack

Magento, WooCommerce, WordPress, and Shopify Exploited in Web Skimmer Attack

June 5, 2023
Rolling out new features for Windows 11, version 22H2 in the Release Preview Channel

Updated Paint app for Windows 11 begins rolling out to Windows Insiders with dark mode and zoom improvements

June 5, 2023

Copyright © 2022 Flyytech.com | All Rights Reserved.

No Result
View All Result
  • Home
  • Apple
  • Applications
    • Computers
    • Laptop
    • Microsoft
  • Security
  • Smartphone
  • Gaming
  • Entertainment
    • Literature
    • Cooking
    • Fitness
    • lifestyle
    • Music
    • Nature
    • Podcasts
    • Travel
    • Vlogs

Copyright © 2022 Flyytech.com | All Rights Reserved.

What Are Cookies
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept All”, you consent to the use of ALL the cookies. However, you may visit "Cookie Settings" to provide a controlled consent.
Cookie SettingsAccept All
Manage consent

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary
Always Enabled
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
CookieDurationDescription
cookielawinfo-checkbox-analytics11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional11 monthsThe cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
viewed_cookie_policy11 monthsThe cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Performance
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytics
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Advertisement
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.
Others
Other uncategorized cookies are those that are being analyzed and have not been classified into a category as yet.
SAVE & ACCEPT