Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Saving Authentication Credentials #2

Closed
wants to merge 3 commits into from

2 participants

@dlinsin

This introduces saving the Authentication Credentials in iOS's Keychain.

It uses a submodule which simplifies the Keychain access tremendously: UICKeyChainStore. Unfortunately, it's not ARC compatible so I had to add the flag -fno-objc-arc to the two source files.

The credentials are stored once the authorization succeeded and used as soon as an TPTentHTTPClientinstance is created with the same URL.

Unfortunately, I had to change AccountViewController a little bit to adjust the flow.

Note: there is no error handling or logging out! You'd basically have to login with a different entity in order to overwrite the Keychain entries!

@followben
Owner

Thanks for this @dlinsin.

I'm quite keen to reduce dependencies on external libraries to just AFNetworking (had planned to deprecate the need for SSToolkit shortly).

With that in mind, I've implemented something functionally similar using Apple's Keychain API: 545c7c0. Doing it direct also means creds are more correctly stored using the kSecClass for crypto keys rather than user passwords.

Thanks heaps for the pull request though, and let me know if you think there's any issues or shortcomings in the current implementation.

@followben followben closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 6, 2012
  1. @dlinsin

    Added git ignore file

    dlinsin authored
Commits on Oct 7, 2012
  1. @dlinsin
  2. @dlinsin
This page is out of date. Refresh to see the latest.
View
10 .gitignore
@@ -0,0 +1,10 @@
+/build
+*/*.xcodeproj/*.mode1v3
+*/*.xcodeproj/*.pbxuser
+.DS_Store
+*.orig
+*.pbxuser
+*.perspective*
+*.xcworkspace
+*xcuserdata
+*/*.idea/*
View
3  .gitmodules
@@ -4,3 +4,6 @@
[submodule "Example/Vendor/AFNetworking"]
path = Example/Vendor/AFNetworking
url = git://github.com/AFNetworking/AFNetworking.git
+[submodule "Example/Vendor/UIKeyChainStore"]
+ path = Example/Vendor/UIKeyChainStore
+ url = git://github.com/kishikawakatsumi/UICKeyChainStore.git
View
18 Example/TentStatusExample.xcodeproj/project.pbxproj
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
+ 6835E53C24D5C71A51E4DAA5 /* UICKeyChainStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 6835E44BB15E7EA0EF875D7D /* UICKeyChainStore.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ 6835E8330BFA285809387AD7 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6835E38390B0F179EBB33DFA /* Security.framework */; };
9F24FC86161AAC0100A93048 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F24FC85161AAC0100A93048 /* UIKit.framework */; };
9F24FC88161AAC0100A93048 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F24FC87161AAC0100A93048 /* Foundation.framework */; };
9F24FC8A161AAC0100A93048 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F24FC89161AAC0100A93048 /* CoreGraphics.framework */; };
@@ -42,6 +44,9 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ 6835E38390B0F179EBB33DFA /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
+ 6835E44BB15E7EA0EF875D7D /* UICKeyChainStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = UICKeyChainStore.m; path = Vendor/UIKeyChainStore/UICKeyChainStore.m; sourceTree = "<group>"; };
+ 6835E54D48C3DC2610453879 /* UICKeyChainStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UICKeyChainStore.h; path = Vendor/UIKeyChainStore/UICKeyChainStore.h; sourceTree = "<group>"; };
9F24FC81161AAC0100A93048 /* TentStatusExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TentStatusExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
9F24FC85161AAC0100A93048 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
9F24FC87161AAC0100A93048 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
@@ -111,12 +116,22 @@
9F24FC86161AAC0100A93048 /* UIKit.framework in Frameworks */,
9F24FC88161AAC0100A93048 /* Foundation.framework in Frameworks */,
9F24FC8A161AAC0100A93048 /* CoreGraphics.framework in Frameworks */,
+ 6835E8330BFA285809387AD7 /* Security.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 6835E0D8F2F3C53868C1F412 /* UIKeyChainStore */ = {
+ isa = PBXGroup;
+ children = (
+ 6835E44BB15E7EA0EF875D7D /* UICKeyChainStore.m */,
+ 6835E54D48C3DC2610453879 /* UICKeyChainStore.h */,
+ );
+ name = UIKeyChainStore;
+ sourceTree = "<group>";
+ };
9F24FC76161AAC0100A93048 = {
isa = PBXGroup;
children = (
@@ -138,6 +153,7 @@
9F24FC84161AAC0100A93048 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 6835E38390B0F179EBB33DFA /* Security.framework */,
9F24FC85161AAC0100A93048 /* UIKit.framework */,
9F24FC87161AAC0100A93048 /* Foundation.framework */,
9F24FC89161AAC0100A93048 /* CoreGraphics.framework */,
@@ -180,6 +196,7 @@
9F24FCB2161AB09500A93048 /* TPTentClient */,
9F24FED7161AB0C500A93048 /* AFNetworking */,
9F24FE3C161AB0BE00A93048 /* SSToolkit */,
+ 6835E0D8F2F3C53868C1F412 /* UIKeyChainStore */,
);
name = Vendor;
sourceTree = "<group>";
@@ -378,6 +395,7 @@
9F83DF20161BC0AC0078F22C /* AccountViewController.m in Sources */,
9F859A84161BD4D300CE3388 /* NSURL+TPEquivalence.m in Sources */,
9F859A8C161D0C0700CE3388 /* NewStatusPostViewController.m in Sources */,
+ 6835E53C24D5C71A51E4DAA5 /* UICKeyChainStore.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
39 Example/TentStatusExample/AccountViewController.m
@@ -25,6 +25,7 @@
#import "AccountViewController.h"
#import "TentStatusClient.h"
#import "NSURL+TPEquivalence.h"
+#import "UICKeyChainStore.h"
@interface AccountViewController () <UITextFieldDelegate>
@@ -39,7 +40,15 @@ @implementation AccountViewController
- (void)viewDidLoad
{
[super viewDidLoad];
-
+
+ // comment this in for resetting the keychain values
+// [UICKeyChainStore removeItemForKey:BASE_URL];
+// [UICKeyChainStore removeItemForKey:ACCESS_TOKEN];
+// [UICKeyChainStore removeItemForKey:MAC_KEY];
+// [UICKeyChainStore removeItemForKey:MAC_KEY_ID];
+// [UICKeyChainStore removeItemForKey:MAC_ALGORITHM];
+// [UICKeyChainStore removeItemForKey:CLIENT_ID];
+
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didAuthorizeWithEntity:)
name:TPTentClientDidAuthorizeWithTentServerNotification
@@ -49,6 +58,12 @@ - (void)viewDidLoad
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
+
+ if ([UICKeyChainStore stringForKey:ENTITY] && [UICKeyChainStore stringForKey:BASE_URL]) {
+ self.entityURIField.text = [UICKeyChainStore stringForKey:ENTITY];
+ self.entityURL = [NSURL URLWithString:self.entityURIField.text];
+ self.tentServerURL = [NSURL URLWithString:[UICKeyChainStore stringForKey:BASE_URL]];
+ }
if (self.entityURIField.text.length == 0) {
self.entityURIField.text = @"https://";
@@ -65,7 +80,7 @@ - (BOOL)textFieldShouldReturn:(UITextField *)textField
}
[textField resignFirstResponder];
-
+
if ([self.entityURL isEquivalent:entityURL] &&
[[TentStatusClient sharedClient] isAuthorizedForTentServer:self.tentServerURL]) {
[self showTimeline];
@@ -73,19 +88,25 @@ - (BOOL)textFieldShouldReturn:(UITextField *)textField
}
self.entityURL = entityURL;
-
+
+ [self doAuthorize];
+
+ return YES;
+}
+
+- (void)doAuthorize
+{
[[TentStatusClient sharedClient] discoverTentServerForEntityURL:self.entityURL success:^(NSURL *canonicalServerURL, NSURL *canonicalEntityURL) {
+ [UICKeyChainStore setString:self.entityURIField.text forKey:ENTITY];
self.entityURL = canonicalEntityURL;
if ([self.tentServerURL isEquivalent:canonicalServerURL] &&
- [[TentStatusClient sharedClient] isAuthorizedForTentServer:self.tentServerURL]) {
+ [[TentStatusClient sharedClient] isAuthorizedForTentServer:self.tentServerURL]) {
[self showTimeline];
} else {
self.tentServerURL = canonicalServerURL;
- [[TentStatusClient sharedClient] authorizeForTentServerURL:self.tentServerURL];
- }
- } failure:nil];
-
- return YES;
+ [[TentStatusClient sharedClient] authorizeForTentServerURL:self.tentServerURL];
+ }
+ } failure:nil];
}
- (void)didAuthorizeWithEntity:(NSDictionary *)notification
1  Example/Vendor/UIKeyChainStore
@@ -0,0 +1 @@
+Subproject commit 6745ef5ce7527074fdf94ac4413c1e60279645ad
View
22 TPTentClient/TPTentClient.m
@@ -78,11 +78,22 @@ - (void)discoverTentServerForEntityURL:(NSURL *)url
[self headTentServerWithDiscoveryHTTPClient:discoveryHTTPClient success:success failure:failure];
}
+
#pragma mark OAuth
+- (void)initHttpClient:(NSURL *)url
+{
+ if (![self.httpClient.baseURL isEquivalent:url]) {
+ self.httpClient = [[TPTentHTTPClient alloc] initWithBaseURL:url];
+ self.httpClient.delegate = self;
+ }
+}
+
- (BOOL)isAuthorizedForTentServer:(NSURL *)url
{
- if (self.httpClient && [self.httpClient.baseURL isEquivalent:url] && [self.httpClient isRegisteredWithBaseURL]) {
+ [self initHttpClient:url];
+
+ if ([self.httpClient isRegisteredWithBaseURL]) {
return YES;
}
@@ -101,12 +112,9 @@ - (void)authorizeForTentServerURL:(NSURL *)url
if (self.httpClient.isRegisteredWithBaseURL && [self.httpClient.baseURL isEqual:url]) {
return;
}
-
- if (![self.httpClient.baseURL isEqual:url]) {
- self.httpClient = [[TPTentHTTPClient alloc] initWithBaseURL:url];
- self.httpClient.delegate = self;
- }
-
+
+ [self initHttpClient:url];
+
[[NSNotificationCenter defaultCenter] postNotificationName:TPTentClientWillAuthorizeWithTentServerNotification
object:nil
userInfo:@{TPTentClientAuthorizingWithTentServerURLKey: url}];
View
8 TPTentClient/TPTentHTTPClient.h
@@ -25,6 +25,14 @@
@protocol TPTentHTTPDelegate;
+static NSString *const BASE_URL = @"base_url";
+static NSString *const ENTITY = @"entity";
+static NSString *const ACCESS_TOKEN = @"access_token";
+static NSString *const MAC_KEY = @"mac_key";
+static NSString *const MAC_ALGORITHM = @"mac_algorithm";
+static NSString *const MAC_KEY_ID = @"mac_key_id";
+static NSString *const CLIENT_ID = @"client_id";
+
@interface TPTentHTTPClient : AFHTTPClient
@property (nonatomic, weak) id<TPTentHTTPDelegate> delegate;
View
36 TPTentClient/TPTentHTTPClient.m
@@ -24,6 +24,8 @@
#import "TPTentHTTPClient.h"
#import "AFJSONRequestOperation.h"
#import "NSURL+SSToolkitAdditions.h"
+#import "UICKeyChainStore.h"
+#import "NSURL+TPEquivalence.h"
#import <CommonCrypto/CommonHMAC.h>
#pragma mark - Functions and constants
@@ -112,7 +114,15 @@ - (id)initWithBaseURL:(NSURL *)url {
[self setParameterEncoding:AFJSONParameterEncoding];
[self setDefaultHeader:@"Accept" value:kTPTentContentType];
[AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:kTPTentContentType]];
-
+
+ if ([url isEquivalent:[NSURL URLWithString:[UICKeyChainStore stringForKey:BASE_URL]]]) {
+ self.tentAccessToken = [UICKeyChainStore stringForKey:ACCESS_TOKEN];
+ self.tentMacKey = [UICKeyChainStore stringForKey:MAC_KEY];
+ self.tentMacAlgorithm = [UICKeyChainStore stringForKey:MAC_ALGORITHM];
+ self.tentMacKeyId = [UICKeyChainStore stringForKey:MAC_KEY_ID];
+ self.tentClientId = [UICKeyChainStore stringForKey:CLIENT_ID];
+ }
+
return self;
}
@@ -178,12 +188,13 @@ - (void)registerForBaseURLWithSuccess:(void (^)())success failure:(void (^)(NSEr
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
- self.tentMacAlgorithm = JSON[@"mac_algorithm"];
- self.tentMacKey = JSON[@"mac_key"];
- self.tentMacKeyId = JSON[@"mac_key_id"];
+ self.tentMacAlgorithm = JSON[MAC_ALGORITHM];
+ self.tentMacKey = JSON[MAC_KEY];
+ self.tentMacKeyId = JSON[MAC_KEY_ID];
self.tentClientId = JSON[@"id"];
- NSDictionary *authRequestParams = @{@"client_id": self.tentClientId,
+ NSDictionary *authRequestParams = @{
+ CLIENT_ID: self.tentClientId,
@"tent_profile_info_types": @"all",
@"tent_post_types": @"all",
@"redirect_uri": [NSString stringWithFormat:@"%@://oauth", self.delegate.customURLScheme],
@@ -220,11 +231,18 @@ - (BOOL)handleOpenURL:(NSURL *)url
__weak TPTentHTTPClient *weakSelf = self;
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
- if (JSON[@"access_token"]) {
-
- weakSelf.tentAccessToken = JSON[@"access_token"];
- weakSelf.tentMacKey = JSON[@"mac_key"];
+ if (JSON[ACCESS_TOKEN]) {
+ weakSelf.tentAccessToken = JSON[ACCESS_TOKEN];
+ weakSelf.tentMacKey = JSON[MAC_KEY];
+
+ [UICKeyChainStore setString:[weakSelf.baseURL absoluteString] forKey:BASE_URL];
+ [UICKeyChainStore setString:weakSelf.tentAccessToken forKey:ACCESS_TOKEN];
+ [UICKeyChainStore setString:weakSelf.tentMacKey forKey:MAC_KEY];
+ [UICKeyChainStore setString:weakSelf.tentMacAlgorithm forKey:MAC_ALGORITHM];
+ [UICKeyChainStore setString:weakSelf.tentMacKeyId forKey:MAC_KEY_ID];
+ [UICKeyChainStore setString:weakSelf.tentClientId forKey:CLIENT_ID];
+
if ([weakSelf.delegate respondsToSelector:@selector(httpClientDidRegisterWithBaseURL:)]) {
[weakSelf.delegate httpClientDidRegisterWithBaseURL:self];
}
Something went wrong with that request. Please try again.