Clever Instant Login makes it easy for students to log in to their learning applications, saving valuable instructional time. By using the widely-deployed OAuth 2 protocol, our team tries to save valuable development time and make it easy for our app customers to create integrations. OAuth 2 has been a fairly smooth road, but we encountered some obstacles when we decided to build support for authentication into native iOS applications as part of the release of our iOS application.
Unlike web applications, native applications don’t have an HTTP-based redirect URI to which you can direct an OAuth user with a code. So native applications can’t effectively protect a secret that is required for the code exchange in an OAuth 2 flow. Additionally, native mobile apps that want to allow OAuth-based authentication might attempt to use an embedded webview in their apps, which is not recommended from a security standpoint. These webviews don’t have the same basic user protections against phishing and isolation from the calling app that a standalone browser does.
We were fortunate to encounter a contemporaneous effort to establish standards for OAuth 2.0 for native applications in an RFC. This document was incredibly helpful in establishing our basic principles and architecture for a secure OAuth flow for native clients, which are now included in our developer documentation and implemented by our SDK. Based on that standard, as well as our research of other implementations, we made the following design decisions for our OAuth 2.0 implementation for iOS.
Use the authorization code grant flow
Previously, we had considered using the implicit grant flow for iOS OAuth flows, since iOS applications can’t effectively maintain client secrets. The RFC doesn’t recommend the implicit grant flow because it doesn’t support refresh tokens, and it prohibits the use of PKCE, an under-review standard for preventing intercepting of authorization codes. While we don’t currently use refresh tokens or PKCE in the Clever Instant Login API, we wanted to build something that allowed us to evolve in the direction of secure standards. We built an authorization code grant flow where people can create iOS-specific client IDs in their dashboard, and send us only these client IDs to exchange the code for the token.
Use SFSafariViewController, when available
We had seen some apps attempt to build “Login with Clever” functionality in their apps using UIWebView controls, which are used to embed web content in an iOS application. According to the aforementioned RFC, and now mandated by Google, UIWebViews (and its successor, WKWebView) should not be used for OAuth flows. Per recommendations, we built our SDK to use SFSafariViewController, and as a backup for older versions of iOS, Mobile Safari. We did find an issue with cookies not syncing between SFSafariViewController and Mobile Safari, which we’re hoping is resolved in a future version of iOS.
Use redirect URIs with a custom, unique per-app protocol
We considered the use of custom-protocol based redirect URIs that could be defined by the user (e.g. myapp://oauth) but worried about the potential for collision. We also considered the use of Universal Links, but decided against it for two reasons. One is that it wasn’t supported in older versions of iOS, but more importantly Universal Links don’t appear to be designed to support OAuth’s use cases. As one example of this bad fit, Universal Links can be easily and accidentally “disabled” from being opened in a native app by a user simply pressing the “back to previous app” button after clicking one. Given the brittle nature of these links into native apps, we decided to go with the more explicit app-opening semantics of custom protocols, namespaced by client ID (e.g. clever-<client-id>://oauth) to ensure global uniqueness.
Require the state parameter
In the past, the state parameter has been an optional part of Clever’s Instant Login API. Starting with the iOS Instant Login API, we’re making it required. While not all apps are affected by the security threats introduced with the lack of a state parameter, we want to ensure that the default experience for users on Clever is secure, and so we’re starting with the right defaults for new integrations on iOS.
There are several other articles that discuss native applications and OAuth 2.0, and the 12 drafts of the RFC suggest this problem is not easy to solve. We hope describing our experience and the tradeoffs we made help elucidate the problem others face. Let us know, in the comments or on Twitter: @clever