
This video is only available to subscribers. Start a subscription today to get access to this and 376 other videos.
Authentication with AFNetworking
Episode Links
- Episode source codeincludes server and client apps
- HTTP Client app - This is the app I used to test out the API
Creating a class to securely store the authentication token
<strong>CredentialStore.h</strong>
@interface CredentialStore : NSObject
- (BOOL)isLoggedIn;
- (void)clearSavedCredentials;
- (NSString *)authToken;
- (void)setAuthToken:(NSString *)authToken;
@end
<strong>CredentialStore.m</strong>
#import "CredentialStore.h"
#import "SSKeychain.h"
#define SERVICE_NAME @"NSScreencast-AuthClient"
#define AUTH_TOKEN_KEY @"auth_token"
@implementation CredentialStore
- (BOOL)isLoggedIn {
return [self authToken] != nil;
}
- (void)clearSavedCredentials {
[self setAuthToken:nil];
}
- (NSString *)authToken {
return [self secureValueForKey:AUTH_TOKEN_KEY];
}
- (void)setAuthToken:(NSString *)authToken {
[self setSecureValue:authToken forKey:AUTH_TOKEN_KEY];
[[NSNotificationCenter defaultCenter] postNotificationName:@"token-changed" object:self];
}
- (void)setSecureValue:(NSString *)value forKey:(NSString *)key {
if (value) {
[SSKeychain setPassword:value
forService:SERVICE_NAME
account:key];
} else {
[SSKeychain deletePasswordForService:SERVICE_NAME account:key];
}
}
- (NSString *)secureValueForKey:(NSString *)key {
return [SSKeychain passwordForService:SERVICE_NAME account:key];
}
Logging in using AFNetworking
- (void)login:(id)sender {
[SVProgressHUD show];
id params = @{
@"username": self.usernameField.text,
@"password": self.passwordField.text
};
[[AuthAPIClient sharedClient] postPath:@"/auth/login.json"
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *authToken = [responseObject objectForKey:@"auth_token"];
[self.credentialStore setAuthToken:authToken];
[SVProgressHUD dismiss];
[self dismissViewControllerAnimated:YES completion:nil];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (operation.response.statusCode == 500) {
[SVProgressHUD showErrorWithStatus:@"Something went wrong!"];
} else {
NSData *jsonData = [operation.responseString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData
options:0
error:nil];
NSString *errorMessage = [json objectForKey:@"error"];
[SVProgressHUD showErrorWithStatus:errorMessage];
}
}];
}
The API Client
The API client simply has the base URL to our API and the ability to attach the auth_token
header, if one is present. In order to allow the auth_token
to be updatable, we listen for a notification posted by CredentialStore
.
#import "AuthAPIClient.h"
#import "CredentialStore.h"
#define BASE_URL @"http://nsscreencast-auth-server.herokuapp.com"
@implementation AuthAPIClient
+ (id)sharedClient {
static AuthAPIClient *__instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURL *baseUrl = [NSURL URLWithString:BASE_URL];
__instance = [[AuthAPIClient alloc] initWithBaseURL:baseUrl];
});
return __instance;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (self) {
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setAuthTokenHeader];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(tokenChanged:)
name:@"token-changed"
object:nil];
}
return self;
}
- (void)setAuthTokenHeader {
CredentialStore *store = [[CredentialStore alloc] init];
NSString *authToken = [store authToken];
[self setDefaultHeader:@"auth_token" value:authToken];
}
- (void)tokenChanged:(NSNotification *)notification {
[self setAuthTokenHeader];
}
Fetching a regular authenticated endpoint
Now that we've logged in, it is trivial to request a protected endpoint, because the AuthAPIClient
will automatically include the auth_token
header if necessary.
[[AuthAPIClient sharedClient] getPath:@"/home/index.json"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[SVProgressHUD dismiss];
self.messageTextView.text = operation.responseString;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (operation.response.statusCode == 500) {
[SVProgressHUD showErrorWithStatus:@"Something went wrong!"];
} else {
NSData *jsonData = [operation.responseString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData
options:0
error:nil];
NSString *errorMessage = [json objectForKey:@"error"];
[SVProgressHUD showErrorWithStatus:errorMessage];
}
if (operation.response.statusCode == 401) {
// the auth token we have is no longer valid, clear it
[self.credentialStore setAuthToken:nil];
}
}];
Notes about the example server
You can use the example Rails app provided in this episode on your own (running it locally or deploying to somewhere like Heroku). Or you can use the server I already deployed.
The URL is http://nsscreencast-auth-server.herokuapp.com.
The endpoints:
/auth/login.json
(Parameters: username, password) Note: this endpoint accepts a?ttl=<seconds>
parameter that will set the expiration date of the token (only if the current token is already expired). This can be useful for testing the expiration./home/index.json
(Requires auth_token header)
I created 10 admin accounts for you to use: (admin, admin2, admin3, admin4, etc). All of them have a password of secret
.