How to Build an iOS App With OAuth2 Authentication Flow — GitHub Example(Part 1)

iacopo.pazzaglia
The Startup
Published in
8 min readNov 2, 2020

Step by step journey on how to build a flexible Swift app using a hexagonal architecture that uses an authentication method OAuth2.

Introduction

Let’s imagine that you are asked to create an app that is able to list all the Github repo of un user, public and private.

So you start taking a look at the Github API and you discover that the API that you need requires an access token.

Backend Configuration

To obtain the token you must create a Github OAuth App and use it to authenticate the user.

The app configuration requires an “Authorization callback URL”. This URL will be used by Github to notify the result of the authentication process. So, to be able to be notified inside the app, you need to specify a URL that has a schema that is unique for your app. I’ll use as URL it.iacopo.github://authentication since the bundle Id of the app that I’ll create is it.iacopo.github.

Authorization callback URL
Authorization callback URL

After the creation is done you’ll obtain a “Client ID” and a “Client Secret” that are used later to configure the iOS app.

iOS App

(In this article I will add for each step the commit reference inside square brackets of a sample project, so you can also follow the evolution of the codebase from there 🙂)

Coming back to the Github OAuth documentation, and specifically the Web application flow, we see that we need to handle the following steps:

  1. Users are redirected to request their GitHub identity
  2. Users are redirected back to your site by GitHub
  3. Your app accesses the API with the user’s access token

1. Request a user’s GitHub identity

To request a Github identity we need to show a web page where the user can login using her credentials and give access to the Github App we created before.

So we need a UIViewController with a button that presents a SFSafariViewController initialized with that URL. I’m not a fan of the Storyboard approach to build the UI because it doesn’t allow you to use constructor injection to set the UIViewController dependencies so my first step is to delete the default Storyboard and its view controller. To properly remove it you need to delete these two lines from the info.plist

and delete the files Main.storyboard and ViewController.swift [bc48224]. Then I create a new view controller, called LoginViewController[03054ed], with the following design

Login Screen

Now let me explain briefly the architecture of the project that I’ll follow.

App Architecture

The idea is to distinguish between UI components, Domain components, and Infrastructure components. The Domain components don’t depend on any other kind of components, while the UI and the Infrastructure components depend on Domain. This kind of architecture allows us to clearly separate responsibility and easily switch between implementations. Using a composition root and a constructor injection approach the project is also highly testable

App architecture

Composition Root

All these components are created and wired up together by a Composition root that lives in Main. These components hold the long live dependency of the project and provide to each component the concrete implementation of its own dependencies. This object is created directly on the Scene Delegate and is used to instantiate the LoginViewController created before, and set it as the rootViewController [38f5225]

OAuthService

Now, coming back to OAuth flow, we need to generate the proper URL to open on the LoginViewController when the user clicks on the button. To do so we will use a Domain object that provides this URL to the view controller called OAuthService that will be injected as constructor parameter

This object depend on OAuthClient, a protocol defined in the Domain layer and implemented on the in Infrastructure one. Let’s use for now a very basic implementation of the client that returns a hardcoded URL. The diagram now looks like this(I removed the <<create>> relationship for clarity).

And this is the dependency wiring up happening on the AppDependencyContainer

At this point, we are able to open the web page where the user could authenticate itself but we didn’t handle yet the deep link that we will receive when the user successfully authenticates [f0bddd6]. So let’s move to the next step

2. Users are redirected back to your site by GitHub

Deeplink configuration

To be able to react to the URL you specify on the Github app creation we need to add a new entry on the URL Typestab section of the info tab in the iOS target like in the image below.

Now the app will react to the custom URL schema and we need to handle it properly. To be notified when such interaction happen while the app is in the foreground we need to override this method of the Scene delegate

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {

We’ll create an enum to specify the kind of deeplink the app support and right now we have just the OAuth one

We also create a class that contains a map of which action to execute for each deeplink. This class is used on the callback method discussed before

The Github documentation says that the deeplink URL contains two params: code and state

If the user accepts your request, GitHub redirects back to your site with a temporary code in a code parameter as well as the state you provided in the previous step in a state parameter.

And the app should be able to exchange that code for an access code. Let’s see how we can do it 💪🏻

Exchange code for an access token

I think that is the OAuthService responsibility to be able to extract the code and state parameters from the deeplink and it can forward the exchange message to the OAUthClient to obtain an access token. So I’ll add an additional method to the OAuthClient protocol

and add a method to the OAuthService to extract data from the URL and perform the code exchange

The last thing to do is to actually execute this method when a deeplink is received since right now the map that contains the callback to execute for each deeplink is empty. We can add the proper callback directly in the AppDependencyContainer

At this point[a5763f0] we are able to receive a deeplink and exchange it for an access token but the LoginViewController , that start the authentication process, is not aware of the result. So we need a way fromOAuthService to notify backLoginViewController . To do so I decide to add a callback in OAuthService

var onAuthenticationResult: ((Result<TokenBag, Error>) -> Void)?

that will be called by OAuthService inside exchangeCodeForToken

This callback will be wired up in viewDidLoad of the LoginViewController , using property injection

In this example, I just show an alert with the token received [f130a54].

Let’s try it in the simulator now, using as URL a static page with a link to the deeplink defined before. It works! 🎉

Authentication simulation

OAuth Client

The current implementation of the OAuthClient return just static values

Now it’s time to integrate the app with the real API. To do this we’ll create an OAuthClient implementation that is able to interact with the Github API. To be able to perform and describe network request we create as well two small abstractions called HTTPRequest andHTTPClient [021c8ea]. We can see the relationship between these objects in the updated architecture diagram

Moving to Github API documentation we see that to request a user identity we should use this URL

GET https://github.com/login/oauth/authorize 

with the following parameters:

So we can think at the following struct to contains the needed parameters

Let’s look now at the API we should use to exchange the code for an access token. The URL is

POST https://github.com/login/oauth/access_token

with the following parameters:

based on that, the complete OAuthConfig looks like this:

Using this information the RemoteOAuthClient can create the proper Codable request and response to interact with the API.

The last thing to do is to useRemoteOAuthClient instead of LocalOAuthClient. Thanks to the architecture used we can easily exchange it in the composition root

of course, you must insert here the “Client ID” and a “Client Secret” that you obtained in the first step of this tutorial and change as well the redirectUri [e4c4463]

Conclusion

We saw so far how is possible to use the Github OAuth flow in an iOS app. As a disclaimer, I’d like to say that this implementation can not be used as a complete OAuth library since for example doesn’t handle the PKCE flow nor the possible expiration of the access token, but it gives you an idea of how it is possible to implement it from scratch 🏗

We saw as well how this kind of architecture lets you switch the implementation of a concrete class just in the composition root with no impact on the rest of the app.

If you have any questions, critics, suggestion feel free to leave your comment below and let me know 💪🏻

In the second part of the article, we’ll see how we can use the access token to make an authenticated request, how we can store it in the Keychain, how separate the presentation logic from the UI, and much more!

Resources

--

--

iacopo.pazzaglia
The Startup

🇮🇹 Frontend developer(Swift, React native, React)