Implementing OAuth with ASWebAuthenticationSession

Learn about what OAuth is and how to implement it using ASWebAuthenticationSession. By Felipe Laso-Marsetti.

5 (8) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Understanding Tokens

Upon login, users can now acquire an authorization code. This, however, is not the final stopping point. This access code has a very short duration and can usually be used just once. Its purpose is for you to exchange it for an access token and a refresh token.

As you saw earlier, the access token is the one you’ll send with every request to authorize your requests. The refresh token is usually longer-lived. You can use it to acquire a new access token when it expires.

Handling Tokens

With the authorization code in hand, it’s time to exchange it for tokens. You’ll also add logic in your app to handle them properly.

Open NetworkRequest.swift.

Inside start(responseType:completionHandler:) and right below the following code block:

guard 
  error == nil,
  let data = data
else {
  DispatchQueue.main.async {
    let error = error ?? NetworkRequest.RequestError.otherError
    completionHandler(.failure(error))
  }
  return
}

Find the line:

if let object = try? JSONDecoder().decode(responseType, from: data) {

Replace this line with the following code:

// 1
if T.self == String.self,
  let responseString = String(data: data, encoding: .utf8) {
  // 2
  let components = responseString.components(separatedBy: "&")
  var dictionary: [String: String] = [:]
  // 3
  for component in components {
    let itemComponents = component.components(separatedBy: "=")
    if let key = itemComponents.first,
       let value = itemComponents.last {
      dictionary[key] = value
    }
  }
  // 4
  DispatchQueue.main.async {
    // 5
    NetworkRequest.accessToken = dictionary["access_token"]
    NetworkRequest.refreshToken = dictionary["refresh_token"]
    completionHandler(.success((response, "Success" as! T)))
  }
  return
} else if let object = try? JSONDecoder().decode(T.self, from: data) {

Whereas the other GitHub API responses are JSON formatted, the token exchange response comes back as a string. Here’s how you handle that case:

  1. By adding this statement, you first see if there’s a string response as its contents.
  2. This response string, with your tokens, is made of key-value pairs separated by the ampersand, which is why you break that string into an array of key-value pairs.
  3. You loop through each of the components to acquire a Swift dictionary of the response.
  4. Right now, everything is running in a background thread due to URLSession‘s default threading model. Therefore, you make a call to DispatchQueue to call the next code on the main thread. You need to do this because the completion handler will be updating the UI, which can only be done on the main thread.
  5. The code within the block stores the access and refresh tokens within two helper properties of NetworkRequest. It then proceeds to call the completion handler, indicating that the process was successful.

How NetworkRequest Handles the Tokens

To see what NetworkRequest does with the tokens, switch over to NetworkRequest+User.swift.

These are properties of type String, but behind the scenes, they read and write the tokens to UserDefaults so they can persist across app launches.

These tokens are sensitive data, so in your apps, you want to ensure you securely store these in the keychain. This is an interesting topic which you can learn more about here in How To Secure iOS User Data: Keychain Services and Biometrics with SwiftUI.

Build and run. Sign in. While the modal may flash on screen again, you’ll now see the list of your repositories afterward:

Repository List

Fantastic!

Creating Ephemeral Sessions

The last topic to cover is ephemeral sessions, which are private authentication sessions. Ephemeral sessions don’t cache session-related data to disk but to RAM. Upon session invalidation, the ephemeral sessions’ data clears the session data. This conveniently gives users the added privacy and security.

Open. SignInViewModel.swift. In signInTapped and below:

authenticationSession.presentationContextProvider = self

Add the following code:

authenticationSession.prefersEphemeralWebBrowserSession = true

This mitigates the issue encountered earlier, where attempting to sign in immediately after running your app a second or third time results in no prompt to enter the user credentials. An ephemeral session means ASWebAuthenticationSession will not cache anything and will always ask the user for their credentials at the start of the session.

Build and run.

You’ll still be signed in since the app persists the access and refresh tokens. Tap Sign Out at the top to clear the tokens. Now, upon attempting to sign in, you’re asked to input your GitHub username and password. The app won’t cache anything from your previous login, as this is a private session.

Happy app character

Excellent! This might seem like a small issue, but security-wise, it’s of great benefit.

Where to Go From Here?

You can download the completed project by clicking the Download Materials button at the top or bottom of this tutorial.

Great work! In this tutorial, you got an introduction to OAuth and created your own third-party GitHub app using ASWebAuthenticationSession to authenticate. Some additional features you could try are enhancing the way you store the tokens by putting them in the keychain and adding more GitHub API requests.

If you’re interested in reading more about OAuth, visit IETF’s page, which contains the whole standard. It’ a bit dense, but a good reference.

For ASWebAuthenticationSession details and documentation, visit the Apple Developer Documentation.

For a comprehensive video course on networking, check out the Networking with URLSession.

If you have any questions or comments, don’t hesitate to reach out in the forum discussion below.