OAuth2 frameworks for OS X and iOS written in Swift 2.0.
Technical documentation is available at p2.github.io/OAuth2. Take a look at the OS X sample app for basic usage of this framework.
The code in this repo requires Xcode 7, the built framework can be used on OS X 10.9 or iOS 8 and later. To use on iOS 7 you'll have to include the source files in your main project.
Since the Swift language is constantly evolving I have adopted a versioning scheme mirroring Swift versions: the framework version's first two digits are always the Swift version the library is compatible with, see releases. Code compatible with brand new Swift versions are to be found on a separate feature branch named after their swift version.
To use OAuth2 in your own code, start with import OAuth2
(use p2_OAuth2
if you installed via CocoaPods) in your source files.
For a typical code grant flow you want to perform the following steps.
The steps for other flows are mostly the same short of instantiating a different subclass and using different client settings.
If you need to provide additional parameters to the authorize URL take a look at authorizeURLWithRedirect(redirect:scope:params:)
.
let settings = [
"client_id": "my_swift_app",
"client_secret": "C7447242-A0CF-47C5-BAC7-B38BA91970A9",
"authorize_uri": "https://authorize.smartplatforms.org/authorize",
"token_uri": "https://authorize.smartplatforms.org/token",
"scope": "profile email",
"redirect_uris": ["myapp://oauth/callback"], // don't forget to register this scheme
"keychain": false, // if you DON'T want keychain integration
"title": "My Service" // optional title to show in views
] as OAuth2JSON // the "as" part may or may not be needed
Create an OAuth2CodeGrant
Instance.
Optionally, set the onAuthorize
and onFailure
closures or the afterAuthorizeOrFailure
closure to keep informed about the status.
let oauth2 = OAuth2CodeGrant(settings: settings)
oauth2.onAuthorize = { parameters in
print("Did authorize with parameters: \(parameters)")
}
oauth2.onFailure = { error in // `error` is nil on cancel
if nil != error {
print("Authorization went wrong: \(error!.localizedDescription)")
}
}
By default the OS browser will be used for authorization if there is no access token present in the keychain.
If you want to use the embedded web-view, change authorizeEmbedded
to true
and set a root view controller, from which to present the login screen if needed, as authorizeContext
.
Starting with iOS 9, SFSafariViewController
will be used when enabling embedded authorization, meaning you must intercept the callback in your app delegate or explicitly use the old login screen (see below).
To start authorization call authorize()
:
oauth2.authConfig.authorizeEmbedded = true
oauth2.authConfig.authorizeContext = <# presenting view controller #>
// see **Advanced Settings** for more options
oauth2.authorize()
When using the OS browser or the iOS 9 Safari view controller, you will need to intercept the callback in your app delegate. Let the OAuth2 instance handle the full URL:
func application(application: UIApplication,
openURL url: NSURL,
sourceApplication: String?,
annotation: AnyObject) -> Bool {
// you should probably first check if this is your URL being opened
if <# check #> {
oauth2.handleRedirectURL(url)
}
}
See Manually Performing Authentication below for details on how to do this on the Mac.
After everything completes either the onAuthorize
or the onFailure
closure will be called, and after that the afterAuthorizeOrFailure
closure if it has been set.
Hence, unless you have a reason to, you don't need to set all three callbacks, you can use any of those.
You can now obtain an OAuth2Request
, which is an already signed NSMutableURLRequest
, to retrieve data from your server.
let req = oauth2.request(forURL: <# resource URL #>)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(req) { data, response, error in
if nil != error {
// something went wrong
}
else {
// check the response and the data
// you have just received data with an OAuth2-signed request!
}
}
task.resume()
It is safe to always call oauth2.authorize()
before performing a request.
You can also perform the authorization before the first request after your app became active again.
Or you can always intercept 401s in your requests and call authorize again before re-attempting the request.
The authorize()
method will:
- Check if an access token that has not yet expired is in the keychain, if not
- Check if a refresh token is in the keychain, if found
- Try to use the refresh token to get a new access token, if it fails
- Start the OAuth2 dance by using the
authConfig
settings to determine how to display an authorize screen to the user
If you do not wish this kind of automation, the manual steps to show the authorize screens are:
Embedded (iOS only):
let vc = <# presenting view controller #>
let web = oauth2.authorizeEmbeddedFrom(vc, params: nil)
oauth2.afterAuthorizeOrFailure = { wasFailure, error in
web.dismissViewControllerAnimated(true, completion: nil)
}
iOS/OS X browser:
if !oauth2.openAuthorizeURLInBrowser() {
fatalError("Cannot open authorize URL")
}
In case you're using the OS browser or the new Safari view controller, you will need to intercept the callback in your app delegate.
iOS
func application(application: UIApplication!,
openURL url: NSURL!,
sourceApplication: String!,
annotation: AnyObject!) -> Bool {
// you should probably first check if this is your URL being opened
if <# check #> {
oauth2.handleRedirectURL(url)
}
}
OS X
See the OAuth2 Sample App's AppDelegate class on how to receive the callback URL in your Mac app.
Based on which OAuth2 flow that you need you will want to use the correct subclass. For a very nice explanation of OAuth's basics: The OAuth Bible.
For a full OAuth 2 code grant flow (response_type=code
) you want to use the OAuth2CodeGrant
class.
This flow is typically used by applications that can guard their secrets, like server-side apps, and not in distributed binaries.
In case an application cannot guard its secret, such as a distributed iOS app, you would use the implicit grant or, in some cases, still a code grant but omitting the client secret.
It has however become common practice to still use code grants from mobile devices, including a client secret.
This class fully supports those flows, it automatically creates a “Basic” Authorization header if the client has a client secret.
An implicit grant (response_type=token
) is suitable for apps that are not capable of guarding their secret, such as distributed binaries or client-side web apps.
Use the OAuth2ImplicitGrant
class to receive a token and perform requests.
Would be nice to add another code example here, but it's pretty much the same as for the code grant.
A 2-legged flow that lets an app authenticate itself via its client id and secret.
Instantiate OAuth2ClientCredentials
, as usual supplying client_id
but also a client_secret
– plus your other configurations – in the settings dict, and you should be good to go.
Some sites might not strictly adhere to the OAuth2 flow. The framework deals with those deviations by creating site-specific subclasses.
- Facebook:
OAuth2CodeGrantFacebook
to deal with the URL-query-style response instead of the expected JSON dictionary. - GitHub:
OAuth2CodeGrant
automatically puts the client-key/client-secret into an “Authorization: Basic” header. GitHub however needs those two in the POSTed body; you need to set thesecretInBody
setting to true, either directly in code or via thesecret_in_body
key in the settings dictionary. - Reddit:
OAuth2CodeGrant
automatically adds a Basic authorization header when a client secret is set. This means that you must specify a client_secret; if there is none (like for Reddit) specify the empty string. There is a RedditLoader example in the OAuth2App sample app for a basic usage example. - Google: If you authorize against Google with a
OAuth2CodeGrant
, the built-in iOS web view will intercept thehttp://localhost
as well as theurn:ietf:wg:oauth:2.0:oob
(with or without:auto
) callbacks.
There is preliminary support for dynamic client registration.
The registerIfNeeded()
immediately calls the callback if there are client credentials in the keychain, otherwise a registration is attempted.
Note: currently only user-interaction-free registrations are supported.
let oauth2 = OAuth2...()
let dynreg = OAuth2DynReg(settings: [
"client_name": "<# app-name #>",
"redirect_uris": ["<# redirect-uri #>"],
"registration_uri": "<# registration-url-string #>,
])
dynreg.registerIfNeeded() { json, error in
if let id = dynreg.clientId where !id.isEmpty {
oauth2.clientId = id
oauth2.clientSecret = dynreg.clientSecret
}
else {
// failed to register
}
}
This framework can transparently use the iOS and OS X keychain.
It is controlled by the useKeychain
property, which can be disabled during initialization with the "keychain" setting.
Since this is enabled by default, if you do not turn it off during initialization, the keychain will be queried for tokens related to the authorization URL.
If you turn it off after initialization, the keychain will be queried for existing tokens, but new tokens will not be written to the keychain.
If you want to delete the tokens from keychain, i.e. log the user out completely, call forgetTokens()
.
Ideally, access tokens get delivered with an "expires_in" parameter that tells you how long the token is valid.
If it is missing the framework will still use those tokens if one is found in the keychain and not re-perform the OAuth dance.
You will need to intercept 401s and re-authenticate if an access token has expired but the framework has still pulled it from the keychain.
This behavior can be turned off by supplying "token_assume_unexpired": false in settings or setting accessTokenAssumeUnexpired
to false.
The main configuration you'll use with oauth2.authConfig
is whether or not to use an embedded login:
oauth2.authConfig.authorizeEmbedded = true
Starting with version 2.0.1 on iOS 9, SFSafariViewController
will be used for embedded authorization.
To revert to the old custom OAuth2WebViewController
:
oauth2.authConfig.ui.useSafariView = false
To customize the go back button when using OAuth2WebViewController
:
oauth2.authConfig.ui.backButton = <# UIBarButtonItem(...) #>
You can use git or CocoaPods to install the framework.
Add a Podfile
that contains at least the following information to the root of your app project, then do pod install
.
If you're unfamiliar with CocoaPods, read using CocoaPods.
platform :ios, '8.0' # or platform :osx, '10.9'
pod 'p2.OAuth2'
use_frameworks!
Using Terminal.app, clone the OAuth2 repository, best into a subdirectory of your app project:
$ cd path/to/your/app
$ git clone --recursive https://github.com/p2/OAuth2.git
If you're using git you'll want to add it as a submodule.
Once cloning completes, open your app project in Xcode and add OAuth2.xcodeproj
to your app:
Now link the framework to your app:
These three steps are needed to:
- Make your App also build the framework
- Link the framework into your app
- Embed the framework in your app when distributing
NOTE that as of Xcode 6.2, the "embed" step happens in the "General" tab. You may want to perform step 2 and 3 from the "General" tab. Also make sure you select the framework for the platform (OS X vs. iOS). This is currently a bit tricky since Xcode shows both as OAuth2.framework; I've filed a bug report with Apple so that it also shows the target name, fingers crossed.
This code is released under the Apache 2.0 license, which means that you can use it in open as well as closed source projects.
Since there is no NOTICE
file there is nothing that you have to include in your product.