Bummer! This is just a preview. You need to be signed in with a Basic account to view the entire video.
Start a free Basic trial
to watch this video
We don't watch to fetch the access token from Yelp's authorization server every time we want to make a request. In this video, we take a look at how we can use Keychain Services in iOS to save the token securely to disk
Resources
-
0:00
Now that we have an access token on hand, we need to save it to some place so
-
0:04
that we can retrieve it and use it every time we need to make a request.
-
0:08
There are two main places we can quickly save things to an iOS, but
-
0:12
which of those two we use depends on what we're trying to save.
-
0:16
The first place you might have read about is user defaults.
-
0:20
Every app has a default system that allows the application to store things like
-
0:25
preferences that allow app customization.
-
0:28
Any preferences that the user selects in settings, things like temperature units or
-
0:32
the text color in the app, are all saved as key-value pairs in a default database.
-
0:38
To get access to this database, we use the user default type,
-
0:41
or NS user defaults as the objective C class is called.
-
0:45
But over here, user defaults is not what we need.
-
0:48
We need to save our data securely,
-
0:50
because we really don't want access tokens being compromised.
-
0:54
Why go through all the trouble to make fetching the tokens secure,
-
0:57
if we're just going to drop the ball on our end when we store it?
-
1:01
To securely store data we're going to use the keychain API.
-
1:04
Keychain services is Apple's solution to security problems, particularly
-
1:09
those where users store passwords or other private information in plain sight.
-
1:14
The keychain does some really important stuff, so
-
1:17
rather than summarizing, I'm going to read a snippet from the docs.
-
1:20
The keychain secures data by encrypting it before storing it in the file system,
-
1:25
relieving you of the need to implement complicated encryption algorithms.
-
1:29
The system also carefully controls access to stored items.
-
1:33
The entire keychain can be locked, meaning no one can decrypt its protected
-
1:38
contents untill it is unlocked with a master password.
-
1:42
Even with an unlocked keychain, the system's key chain access policy ensures
-
1:46
that only authorized apps gain access to a given item in the keychain.
-
1:51
In the simplest case,
-
1:52
the app that created an item is the only one that can access it later.
-
1:56
However, if we need it,
-
1:58
keychain services also provides ways to share secrets among apps.
-
2:02
Now, there's only one minor problem.
-
2:04
The keychain API is kind of a pain to use.
-
2:08
So if we go to Help > Documentation and API Reference, and then start
-
2:13
searching for a keychain, you should get an iOS Keychain Services Tasks result.
-
2:19
Let's click here.
-
2:21
So this document describes how we add an item to a keychain,
-
2:25
find an item in a keychain, and so on.
-
2:27
If we scroll down a bit and click on the line that says SecItemAdd,
-
2:32
this is how we add an item to the keychain.
-
2:35
You'll see here on this page, that this is a global function, so right there,
-
2:40
and this is not a method on a type.
-
2:42
Now if you inspect the parameters a bit closely,
-
2:47
you'll see types like UnsafeMutablePointer and
-
2:51
then it's a generic type, CFTypeRef, that is optional, and then this is optional.
-
2:56
Basically, this is all old C code that is bridged over to Swift without
-
3:00
any sort of wrapper.
-
3:02
C code is unsafe as S, S being the word I can't say on here,
-
3:06
because some of you watching this might be a little too young.
-
3:10
So, what we're going to do is use another third party library that provides a much
-
3:14
better experience by creating Swift types around all the C code.
-
3:19
Before we do that though, a quick overview on how we use the Keychain API.
-
3:23
The Keychain is the actual encrypted storage our
-
3:26
app is going to use to store the data.
-
3:28
iOS controls this keychain, so
-
3:30
we don't have to worry about the actual security implementation.
-
3:34
We just need to correctly insert the data.
-
3:36
A keychain holds any number of keychain items.
-
3:40
A keychain item is a piece of sensitive data you want to store in the keychain.
-
3:45
Keychain items belong to an item class.
-
3:49
The item class determines what kind of information
-
3:52
we store in the keychain item by defining a set of attributes.
-
3:56
Okay, so what types of classes are we working with?
-
3:59
There's a generic password class that includes a service and
-
4:02
account name attributes, and then there's an Internet password item class,
-
4:07
which includes attributes for things such as the server, the security domain,
-
4:10
the protocol type, path, and so on.
-
4:13
Some attributes such as the creation date and
-
4:16
a label are common to all item classes.
-
4:19
Others are specific to the class.
-
4:21
For a keychain item that needs protection, such as a password, or
-
4:25
a private key, the data's encrypted and protected by the keychain.
-
4:29
For keychain items that do not need protection such as certificates,
-
4:33
the data isn't protected, or encrypted rather.
-
4:36
Encrypted data is inaccessible when the keychain is locked.
-
4:40
So in iOS the keychain is automatically unlocked when your device is unlocked.
-
4:44
So your application always has access to its own keychain item data while the user
-
4:49
is present, basically while the phone is unlocked.
-
4:52
Okay, so we're going to save our access token as a generic password item.
-
4:57
We're going to go back to Google to github.com, okay, so
-
5:01
we're going to go to github.com and
-
5:05
then in the search bar we're going to search for a swift locksmith.
-
5:11
This is the library we're going to using.
-
5:13
Now I made sure that this library passes all my sanity checks, but
-
5:18
I'd like you to go ahead and do the same.
-
5:20
So we're going to add this to our cart file, and run the script again.
-
5:24
So we're gonna go back to the terminal window, make sure we're in the correct
-
5:28
directory, and then we're going to open the cart file again, in Xcode,
-
5:31
by running the following command,.
-
5:33
So, open Cartfile ar a quick tip is if you're in terminal,
-
5:39
you can just keep hitting the up arrow and
-
5:41
it'll go through the commands you've typed before, and hit Enter.
-
5:44
Okay, in the read me for the repo, which we should find on the site.
-
5:49
By the way, if you're wondering what I mean when I keep saying repo,
-
5:53
that's a way github, this website, organizes a project.
-
5:57
So all of this is called a repository.
-
6:00
Anyway, if you look at the documentation, you'll see under installation there's
-
6:04
a Carthage section, and it includes what you need to include, or it specifies,
-
6:09
what you need to include in your cartfile.
-
6:11
Now pay close attention to the fact that there's no version number
-
6:14
specified over here.
-
6:16
So, if you pasted this line here into your cartfile as is,
-
6:20
then anytime you ran Carthage update you would get the very latest version.
-
6:25
So, right now, that's not a problem, but remember if there was a major change to
-
6:28
Swift that was incompatible with the current version, let's pretend Swift 4 is
-
6:32
going to be that way, and this library was migrated to swift 4,
-
6:36
but we hardly migrated our project over yet, at that point Carthage
-
6:40
would fetch the wrong library and you would have to figure out how to fix it.
-
6:44
Which it usually means you would have to post something on the forum and
-
6:47
wait till someone helps you, and that gonna take too long.
-
6:50
Since there's no version number mentioned here, what do we do?
-
6:54
Well at the top of the page, over here,
-
6:58
you should see the section labeled Releases.
-
7:02
Whenever a library author tags and
-
7:04
releases a new version it is listed on this page.
-
7:08
Now the official release is pointing at version 2.0.8 despite there
-
7:13
being a 3.0.0 above that, and if you dig through the code history for
-
7:18
this release, you'll see that 2.0.8 contains Swift 3 code.
-
7:23
Now one way you can do this is by clicking on this link right here,
-
7:27
which goes to the actual history, and then you can read through here, which I did,
-
7:32
and you'll find out that this contains Swift 3 code.
-
7:36
But we're not going to that, so we're gonna go back, and
-
7:39
then back to the main page.
-
7:40
Now on the read me,
-
7:42
right above that Carthage snippet, right over here under installation,
-
7:46
it actually says that Locksmith 3.0 and greater is Swift compatible.
-
7:52
So what we're going to do is add version specifier to our cartfile saying anything
-
7:57
above three is good, but up to the end of three basically,
-
8:00
not including version four.
-
8:02
So I'm going to copy this, Cmd+C to grab that.
-
8:07
We'll go back to my cartfile, hit paste, and
-
8:10
then we'll include the tilde and an open angle bracket,
-
8:15
or rather, that's a closing angle bracket, and then 3.0 as the version.
-
8:20
Okay, so hit save, go back to terminal, and again we're going to run.
-
8:24
Keep clicking the up arrow until you get carthage update platform iOS no
-
8:29
use binaries, and hit Enter.
-
8:33
That should finish fairly quickly, and once that's done, you're going to
-
8:37
type out, let me bring this up here so you can see it, that didn't help.
-
8:42
Okay, you're going to type out open.
-
8:45
with a space in between the two,
-
8:46
to open the current directory in the finder window.
-
8:49
Again, we're going to navigate into Carthage > Build > iOS,
-
8:56
and then this time, we're going to grab the Locksmith.framework and we're gonna
-
9:01
put that in the same spot as we did earlier, so in the general section of
-
9:05
the project settings, at the bottom where it says Linked Frameworks and Libraries.
-
9:10
We'll drop that in there.
-
9:12
Remember, we also need to edit our build phase script.
-
9:16
So we added this run script here.
-
9:18
The only thing we need to do now is,
-
9:19
we need to specify this new framework as an input file.
-
9:22
So we'll click + again, and it's the same thing as before,
-
9:28
so we'll say Carthage/Build/iOS/Locksmith.framework.
-
9:33
Okay, and then hit Cmd+B to build, and
-
9:36
make sure everything is more or less okay, cool.
-
9:40
So now that we have that locksmith framework in there,
-
9:44
I'm going to go back to the YelpAccount file, and
-
9:49
we're going to add a few methods in here to save and load our data.
-
9:52
So we'll do this in an extension of YelpAccount.
-
9:56
Now, the first method I want to add is a save method.
-
10:01
We'll make the save method a throwing one so
-
10:03
that if there are any issues saving the token,
-
10:06
we can deal with it in the permissions controller where we call this method.
-
10:10
So say fun save( ) throws.
-
10:13
In the body of the method we are going to use the locksmith
-
10:16
framework to do the actual saving.
-
10:18
Now there are two ways of using locksmith, and it's quite flexible, and
-
10:22
powerful powerful in its design.
-
10:24
If you go back to docs, and head down to quick start,
-
10:28
the first way to do it is to call the relevant save and
-
10:32
load methods directly and pass at some data.
-
10:35
The second way is to use the power of swift protocols.
-
10:39
So we use a protocol based approach, and
-
10:42
we would add protocol conformance to the YelpAccount struct.
-
10:46
So as you can see here they have a TwitterAccount struct, and
-
10:49
they've added conformance to specific protocols, CreateableSecureStorable,
-
10:53
GenericPasswordSecureStorable, and so on.
-
10:56
When you do that you have to specify a few properties, but in return you get save and
-
11:01
load methods for free.
-
11:02
So since this handles some of the magic for
-
11:04
you we're going to go with the first approach.
-
11:08
We'll start by importing, up at the top, the Locksmith framework.
-
11:14
So now inside the save method, we're going to call locksmith save data method.
-
11:19
The token information is going to be saved in a dictionary, which needs keys, so
-
11:24
let's create some static constants right here in the extension, to serve as keys.
-
11:29
So this is a struct Keys, and in here we'll say,
-
11:33
static let token = "token", and this, again, is just so
-
11:37
that we don't make any typos when typing these keys out everywhere.
-
11:41
We'll say, static let expirationPeriod = "expirationPeriod" and
-
11:48
static let grantDate = "grantDate".
-
11:56
Okay, so inside save, we're going to call locksmith save data method which is
-
12:01
a throwing one so we'll need to use the keyword try, and
-
12:04
since we're just throwing this error on we don't need a do catch statement,
-
12:08
or a try question mark, or anything like that.
-
12:10
So we'll say try Locksmith.saveData and
-
12:13
we're gonna use this first one that takes some data for a user account.
-
12:18
Okay, so let's define a dictionary now.
-
12:19
Here we're going to simply assign a set of keys for
-
12:22
the values contained in the instance.
-
12:24
So empty dictionary first, and
-
12:26
then first one is Keys.token: and then the value is accessToken.
-
12:31
We'll do Keys.expirationPeriod: and then the value is the expiration value,
-
12:37
and then finally we'll say Keys.grantDate and
-
12:40
the value is the grantDate, but we're gonna store it as a time interval.
-
12:46
So, we'll say timeIntervalSince1970.
-
12:49
The second parameter here asks for a user account associated with this data, and
-
12:54
typically this is where we specify a username for example.
-
12:57
Since our access token is from two machines talking to another,
-
13:01
we'll just specify the service instead, so we'll say YelpAccount.service, and
-
13:06
of course this is going to create an error.
-
13:09
So back in the type we'll say, static let service = "Yelp".
-
13:15
To load the data we're going to do the opposite.
-
13:18
We're going to fetch the dictionary, unpack the data, and
-
13:21
create an instance of the type.
-
13:22
We'll make this method a static method since we won't have an instance
-
13:26
at that point, to call it on, since we won't have any of the data.
-
13:30
So we'll say static func loadFromKeychain( ) and
-
13:37
this method will turn an optional value of YelpAccount making it easy for
-
13:42
us to determine whether we got a valid value back.
-
13:46
So inside the body we'll start by fetching the dictionary.
-
13:48
Say guard let dictionary =
-
13:51
Locksmith.loadDataForUserAccount (user
-
13:57
account is YelpAccount.service).
-
14:02
From here we'll just get the relevant values using the keys we created.
-
14:05
So, let token =, or we'll do this all in one guard statement.
-
14:11
So, let token = dictionary[Keys.token] and
-
14:17
we want the value to be a String.
-
14:22
For the expiration the key here, so we're gonna get this out of the dictionary,
-
14:27
the [Keys.expirationPeriod] and
-
14:30
this is going to be a TimeInterval value, so we're going to try and pass that.
-
14:35
Finally, we wanna say let grantDateValue
-
14:42
= dictionary[Keys.grantDate].
-
14:48
Remember we saved this as a time interval, so we're gonna cast it to that as well.
-
14:53
If this doesn't work, if we don't get any of these values back,
-
14:57
we're going to return nil.
-
14:59
So after we construct a date, for the grantDate, which we can do pretty easily.
-
15:04
We'll say, let grantDate = Date(timeIntervalSince1970:
-
15:10
grantDateValue).
-
15:12
Now we can return an instance of YelpAccount.
-
15:14
So we'll say return YelpAccount and we'll just use it's standard initializer.
-
15:20
So token, expiration, and grantDate.
-
15:26
Okay, this is quite a bit of code, but now back in the permissions controller we can
-
15:31
use these methods to finish up the implementation for authorization.
-
15:35
So if you navigate to PermissionsController,
-
15:38
inside the requestOAuthToken method,
-
15:41
we've now created a YelpAccount using the data we got back.
-
15:45
Let's pick up right where we left off.
-
15:47
So since this saving method, at this point we wanna save it, and
-
15:51
since this saving method is throwing, we need to wrap that in a do clause.
-
15:56
Inside we'll say, try?
-
15:57
account.save( ).
-
16:01
If this works, we're going to set the oauthTokenButton's text,
-
16:07
so setTitle("OAuth Token Granted", for: UIControlState.disabled),
-
16:15
and then we're gonna disable the button, so
-
16:19
self.oauthTokenButton.isEnabled = false.
-
16:24
Note, that I'm using try with a question mark here to let it silently fail just so
-
16:29
I can avoid error handling.
-
16:31
You really shouldn't do that, and instead you should get some practice with
-
16:34
cleaning up all your code and doing valid error handling.
-
16:37
Okay, if the save worked, we're gonna change the title of the button and
-
16:40
disable it.
-
16:41
So let's test this out.
-
16:41
We're gonna hit run.
-
16:45
Okay, so our permissions controller popped up, and in here,
-
16:48
we're going to hit the Request OAuth Token button.
-
16:51
If this works, in a second you should get a grayed out button
-
16:54
with a message that the OAuth token button was granted.
-
16:58
Perfect.
-
16:59
Now we have a way to communicate with the Yelp server.
-
17:02
In the next video, we're going to start tackling location permissions.
-
17:06
Before we get there, I want you to take a small break and work on some UI for me.
-
17:11
Right now when we hit this Request OAuth Token button,
-
17:14
there's really no indication that there's a request in progress.
-
17:18
I want you to show the user something,
-
17:20
preferably an activity indicator to show that work is happening.
-
17:24
Also, we haven't really accounted for
-
17:26
the failure case, so go ahead and try to fix that up too.
-
17:29
An easy way to test what happens is to go up to the top of the permissions
-
17:34
controller and omit a single character from your client's secret and
-
17:38
then run this flow.
-
17:40
The authorized method will call back with an error and
-
17:42
all we're doing now is logging, so you should try and clean that up for the user.
-
17:47
When you're done I'll see you in the next video.
You need to sign up for Treehouse in order to download course files.
Sign up