Out of the box, Stacker comes with a pretty good example app that can serve as a basis for your own iPhone apps. But what about getting users to log in to your app?
I’m going to show you a very simple technique I like to use to show a “welcome screen” that asks a user to log in (or sign up). Even though the server code is written in Rails, the technique is simple enough that it can be applied to most other frameworks without a lot of effort. We’ll be building off of the example app, called GameRoom, so be sure to try to get the demo working by following the getting started documentation.
You can checkout the complete example below:
Rails app: Download
iOS app: Download
There are many ways to authenticate a user in an iOS app but since Stacker uses WebViews, signing in to your app in one WebView means you’ll be signed in on your other pages because your session and cookies are shared between pages in your app. This makes our lives a lot easier.
The first things we need to do is decide how we’re going to tell our iOS app that a user has signed in from the web app. We need to know this in order to show or hide the “sign in” page. The way we’re going to do this is by setting a cookie called “signed_in” (and delete it when you log out).
Apple gives us a nice way to monitor changes to our iOS app’s cookies using the NSNotificationCenter
. This means that all our server has to do is set a cookie called signed_in
after we’ve successfully signed in and we’ll know to hide the “sign in” screen. And visa versa: we’ll know when the cookie gets deleted so we’ll present the “sign in” page again.
We’re going to break this tutorial up into two sections: first the server (Rails) side, followed by the iOS app. I’ll try to explain what I’m doing on the server side in a manner that can be applied to any framework besides Rails.
Download GameRoom, the example Rails app that is designed to showcase Stacker. Make sure you’ve tried to run it at least once by following the Getting Started documentation.
We’re going to use Devise as our authentication solution because it’s easy to set up.
In your Gemfile
add the line
gem devise
Now install the library
bundle install
rails generate devise:install
rails generate devise User
rails generate devise:views
rake db:migrate
Great! What we’ve done is installed the library, created a User
model, generated all the neccessary views and updated our databse by adding the users
table.
Try running the app with the rails server
command in your terminal and visiting http://localhost:3000/users/sign_up
and see if you can create your first user.
In app/controllers/application_controller.rb
you should add a before_filter
to ensure the user is always authenticated - if they’re not they will be redirected to the sign in screen.
class ApplicationController < ActionController::Base
before_filter :authenticate_user!
end
Remember: the key thing we want to do is set a signed_in
cookie right after we’ve signed in and delete it when we log off. This way our iOS app can easily tell whether to display or hide the “sign in” screen.
In Rails we will do this using middleware callbacks, however if you’re using another framework you may choose to do it differently. What’s important is that the cookie is being set and deleted appropriately.
In config/initializers/devise.rb
add the following to configure Devise:
Devise.setup do |config|
... other code ...
Warden::Manager.after_set_user do |user,auth,opts|
auth.cookies[:signed_in] = 1
end
Warden::Manager.before_logout do |user,auth,opts|
auth.cookies.delete :signed_in
end
end
Awesome! Now we’ve got an app that sets a cookie that our iOS app can read to detect if we’re logged in.
At this point, it’s a good idea to add a “sign out” link on every page just to help us log out while we’re developing. We can remove it after we’re finished by placing it in on a different page where it might be more appropriate to have the link.
In app/views/layouts/application.html.erb
right after the <body>
add:
<%= link_to "Sign out", destroy_user_session_path, :method => :delete if current_user.present? %>
Since we’re relying heavily on sessions for authentication I’ve found using cookies to store your session information to be unreliable. Also, storing sensitive information in a cookie is generally a bad idea for several reasons, lack of encryption (or weak encryption) being only one of them. Instead, let’s use the database to store session information.
gem 'activerecord-session_store', github: 'rails/activerecord-session_store'
rails generate active_record:session_migration
rake db:migrate
in config/initializers/session-store.rb
Rails.application.config.session_store :active_record_store
Spin up the Rails server with the rails server
command in your terminal. Visit http://localhost:3000/users/sign_up
in Chrome
and try to create your first user. Open the inspector and visit the “resources” tab and see if you can see the signed_in
cookie being created and destroyed properly.
Great! What we’ve built is a simple authentication system that sets a cookie to indicate when we’re logged in. Next we’re going to build the iOS side and use that cookie to display or hide the sign in screen.
We’re going to be building off of the GameRoom app that comes with Stacker. Download Stacker and open the GameRoom
iOS workspace in Xcode (it’s in the Examples
folder).
$ git clone https://github.com/lokimeyburg/Stacker.git
$ cd Stacker/Example
$ open GameRoom.xcworkspace
We want the sign in page to be in a new StackerController
which we will call our welcomeController
In AppDelegate.m
, first add our welcomeController
variable
@implementation AppDelegate
NSString *welcomeController;
NSString *DOMAIN_URL;
Now let’s create our controller:
In AppDelegate.m
in the didFinishLaunchingWithOptions
method add:
welcomeController = [[LMStackerController alloc] initWithURL: @"http://localhost:3000/users/sign_in?x_page_title=Log+In"];
Apple lets us listen to changes made to our cookies using the Notification Center. The (very long) event name is NSHTTPCookieManagerCookiesChangedNotification
. What we’re going to do is run a method called checkLogInStatus
everytime a cookie gets created or destroyed.
In AppDelegate.m
just below our welcomeController
create the observer:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(checkLogInStatus)
name:NSHTTPCookieManagerCookiesChangedNotification
object:nil];
Great! Now let’s write the checkLogInStatus
method. What we’re going to do is iterate through our cookies and check for the signed_in
cookie. This will tell us if the user has signed in. Also, we’ll check if our welcomeController
is visible so that we now when to show or hide it.
-(void) checkLogInStatus
{
BOOL welcomeModalVisible = welcomeController.isViewLoaded && welcomeController.view.window;
BOOL isOnline = NO;
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
for (NSHTTPCookie *cookie in cookies) {
if([cookie.name isEqualToString:@"signed_in"] && [cookie.value isEqualToString:@"1"]){
isOnline = YES;
}
}
if (isOnline){
// You are online
if(welcomeModalVisible) {
[self closeWelcomeModal];
}
} else {
// You are offline
if(!welcomeModalVisible) {
[self showWelcomeModal];
}
}
}
-(void) closeWelcomeModal
{
[homeNavController clearStack];
[homeNavController refreshPage];
[self.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
}
-(void) showWelcomeModal
{
[welcomeController clearStack];
[welcomeController refreshPage];
[self.window.rootViewController presentViewController:welcomeController animated:YES completion:NULL];
}
Now run the app in your simulator to try it out. Your app should load and then the “Log In” screen should slide up. Log in and it should slide down and away. If everything is working then the rest is just polish!
Congratulations - you’ve built a super simple sign in page for your hybrid iOS app. You can checkout the example - which includes styling and a sign up page by visiting the example/authentication
branch for both the Rails app and the iOS app on Github.
You may notice that you have to log in again after killing the app. This is because we’re not remembering the user between sessions. To fix this we need to tell Devise to always remember a user.
In app/models/user.rb
add:
def remember_me
true
end
and then to remember the user for a very long time we need to edit config/initializers/devise.rb
# config.remember_for = 2.weeks
config.remember_for = 1.year
We can add an extra check on every request to delete the signed_in
cookie if the user is no longer signed in to the app. This ensures we’re always cleaning up our signed in
cookie if it’s no longer valid.
In app/controllers/application_controller.rb
before_filter :authenticate_user!
before_filter :delete_signed_in_token_if_logged_out
private
def delete_signed_in_token_if_logged_out
cookies.delete :signed_in unless current_user.present?
end
Here are the links to each screen and the styles associated with them: