Endurance

In 2018 I set a goal to workout for at least 30 minutes every day of the year. By December 31st I had learned an incredible amount about the physical and mental benefits of consistent training, whether it was running a marathon or the 30 minutes of yoga I did the next day.

I started building Endurance based on that experience of setting a consistent training plan and sticking to it. It’s an iOS app that allows you to set weekly goals, plan future workouts and encourages you to create streaks of consistent training. The app has support for multiple different types of activities, starting with running, cycling, lifting and yoga and allows you to enter upcoming races to plan for.

I’ll be iterating and improving this app over this next year and would love feedback on what others are looking for in a training application focused on endurance activities. Next I’m planning to work on the ability to share training plans, syncing from Apple Health and logging nutrition goals.

Setting up Firebase Auth for iOS backed by a Rails API

Recently I integrated Firebase Authentication in an iOS application and used it to authenticate against a Rails API. I hadn’t found a complete resource that documents this end to end and wanted to share the steps I went through along with code samples to give others a head start. This specifically explores adding Firebase’s email and password authentication with a custom user interface.

Here are the steps:

1. Set up Firebase iOS SDK

Follow the instructions in this doc: https://firebase.google.com/docs/ios/setup In this step you’ll set up a new Firebase project, add the Firebase/Auth library and configure Firebase in your app delegate.

2. Create a class to manage authentication on the client

Next you’ll want to set up a class in the client for interacting with the Firebase Auth API. You can create functions for the basic auth operations that accept success and error handlers. The Firebase docs here are a great place to read more about these API calls: https://firebase.google.com/docs/auth/ios/manage-users

This class includes the getUserState function, which allows you to check whether the user is currently signed in. This is an async operation and you’re able to pass in a handler to execute when the state has been evaluated. This is particularly useful when starting the application and determining if an auth view should be displayed. You can read more about the state change listener here: https://firebase.google.com/docs/auth/ios/start#sign_in_existing_users

// AuthenticationManager.swift

class AuthenticationManager {
  func getUserState(
    handler: @escaping (_: AuthenticationState) -> Void
  ) {
    var state: AuthenticationState = .signedOut
    Auth.auth().addStateDidChangeListener { (auth, user) in
      if user != nil { state = .signedIn }
      handler(state)
    }
  }
  
  func signUp(
    email: String,
    password: String,
    successHandler: @escaping () -> Void,
    errorHandler: @escaping (_ message: String) -> Void
  ) {
    Auth.auth().createUser(withEmail: email, password: password) { _, error in
      self.handleAuthResult(
        error: error,
        successHandler: successHandler,
        errorHandler: errorHandler
      )
    }
  }

  func signIn(
    email: String,
    password: String,
    successHandler: @escaping () -> Void,
    errorHandler: @escaping (_ message: String) -> Void
  ) {
    Auth.auth().signIn(withEmail: email, password: password) { _, error in
      self.handleAuthResult(
        error: error,
        successHandler: successHandler,
        errorHandler: errorHandler
      )
    }
  }
  
  func passwordReset(
    email: String,
    successHandler: @escaping () -> Void,
    errorHandler: @escaping (_ message: String) -> Void
  ) {
    Auth.auth().sendPasswordReset(withEmail: email) { error in
      self.handleAuthResult(
        error: error,
        successHandler: successHandler,
        errorHandler: errorHandler
      )
    }
  }
  
  func signOut(
    successHandler: @escaping () -> Void,
    errorHandler: @escaping (_ message: String) -> Void
  ) {
    do {
      try Auth.auth().signOut()
      successHandler()
    } catch let error as NSError {
      errorHandler(error.localizedDescription)
    }
  }
  
  func handleAuthResult(
    error: Error?,
    successHandler: @escaping () -> Void,
    errorHandler: @escaping (_ message: String) -> Void
  ) {
    if let error = error {
      DispatchQueue.main.async {
        errorHandler(error.localizedDescription)
      }
    } else {
      DispatchQueue.main.async {
        successHandler()
      }
    }
  }
}

enum AuthenticationState {
  case signedIn, signedOut
}

3. Configure requests to send Firebase token

Setup an authenticatedRequest function that retrieves the user’s current token and sets as a header for requests. This will appear as HTTP_FIREBASE_TOKEN in the request headers.

// API.swift

func authenticatedRequest(url: String,
                          method: HTTPMethod = .get,
                          parameters: [String: Any] = [:],
                          successHandler: @escaping (_: Data?) -> Void,
                          errorHandler: @escaping (_: String) -> Void) {
  let currentUser = Auth.auth().currentUser
  currentUser?.getIDTokenForcingRefresh(true) { token, error in
    guard let token = token, error == nil else {
      errorHandler("Error completing request.")
      return
    }

    let headers: HTTPHeaders = ["Firebase-Token": token]

    Alamofire.request(url,
                      method: method,
                      parameters: parameters,
                      headers: headers).validate().responseData { response in
      switch response.result {
      case .success:
        successHandler(response.data)
      case .failure:
        guard let data = response.data,
          let errors = try? JSON(data: data)["errors"].stringValue else {
            errorHandler("Error completing request.")
            return
        }
        errorHandler(errors)
      }
    }
  }
}

4. Build authentication forms on client

At this stage you’ll want to add your custom sign in, sign up, and reset password views/controllers and wire them up to the AuthenticationManager functions. You’ll also want to include a link within the user’s profile or settings to sign out.

The reset password flow works by sending a link to the users email that leads them to change their password in the browser. It’s also possible to deep link back to the app when this process is completed. You can read more about this here: https://firebase.google.com/docs/auth/ios/manage-users#send_a_password_reset_email

5. Setup token verification on the server

Next we’ll turn to the work needed within Rails. Firebase does not have an Admin SDK for Ruby so we’ll be using a gem called firebase_id_token to verify the token. Follow the instructions within the gem readme to set up token verification. More details about token verification can be found here: https://firebase.google.com/docs/auth/admin/verify-id-tokens

Within application.rb I’ve also added the following to trigger the rake task for updating the certs on start up of the server. You’ll also want to setup the rake task you’ve created on a scheduler as detailed in the gem readme.

# application.rb

class Application < Rails::Application
  config.after_initialize do
    Rails.application.load_tasks
   Rake::Task['firebase:certificates:request'].invoke
  end
end

6. Update the user model

Run a migration on your user model to add email and firebase_external_id attributes.

7. Setup an AuthenticationManager on the server

Add an AuthenticationManager on the server to verify the Firebase token and use it to find the current user or authenticate an endpoint.

# AuthenticationManager.rb

class AuthenticationManager
  def initialize(token:)
    @token = FirebaseIdToken::Signature.verify(token)
  end

  def find_or_create_user!
    User.find_or_create_by!(
      email: @token["email"],
      firebase_id: @token["user_id"]
    )
  end

  def current_user
    User.find_by(
      email: @token["email"],
      firebase_id: @token["user_id"]
    )
  end

  def authenticate_user
    current_user.present?
  end
end

Then add an authenticate_user method to your ApplicationController to be able to use as a before_action to restrict controller actions. The current_user method can also be used to scope resources to specific users within your controllers.

class ApplicationController < ActionController::Base
  def authenticate_user
    render json: {}, status: 401 unless authentication_manager.authenticate_user
  end

  def current_user
    authentication_manager.current_user
  end

  def authentication_manager
    AuthenticationManager.new(
      token: request.headers["HTTP_FIREBASE_TOKEN"]
    )
  end
end

8. Add an endpoint to create a user

With the AuthenticationManager in place on the server, the last step is to add a user creation endpoint and hit it after you’ve successfully created a user within Firebase on the client.

Create the user creation endpoint:

# AuthenticationController.rb

class AuthenticationController < ApplicationController
  def create
    authentication_manager.find_or_create_user!
  end
end

Next return to the client and add a AuthenticateAPI class that will handle posting the token to the client to create the user:

// AuthenticateAPI.swift

class AuthenticateAPI: API {
  func create(successHandler: @escaping () -> Void,
              errorHandler: @escaping (_ errors: String) -> Void) {
    let url = self.baseURL + "authentication.json"
    authenticatedRequest(
      url: url,
      method: .post,
      successHandler: { data in
        successHandler()
      }, errorHandler: { errors in
        errorHandler(errors)
      }
    )
  }
}

Then modify the success handler you’re passing to AuthenticationManager.signUp function to call the user creation endpoint. This success handler would be found wherever you are calling AuthenticationManager.signUp, in my case it’s in the SignUpViewController This is intended to be called after a user has successfully been created on Firebase.

// SignUpViewController.swift

private func successHandler() {
  AuthenticateAPI().create(
    successHandler: {
     Router.navigateToRoot(context: self)
   }, errorHandler: { error in
     self.alert(message: error)
   }
  )
}

That’s the basic steps to get auth working across the client and server. Hope that helps others get started in securing their APIs with Firebase Auth. Thanks and would love any feedback or questions at [email protected]

AWS Cognito for iOS is not ready for prime time

For an iOS project I’ve been building recently, I decided to start with AWS Cognito as the authentication solution. There’s a lot going for Cognito, including the free tier up to 50,000 active users, hosted UI libraries and the ability to leverage AWS’s security in storing passwords.

However, my experience with Cognito has given me a lot of pause around using it for future projects and the libraries need to evolve before I could recommend it for building iOS authentication flows. Here are the primary challenges that need to be solved in my experience:

1. Cognito does not automatically sign in the user after they’ve signed up

This results in a frustrating UX where the user has to go through a minimum of 3 screens (sign up, email confirmation and sign in) in order to create an account and is forced to re-enter their credentials. Furthermore this appears to be encouraging the practice of developers storing the user’s password client-side to pass through to the confirmation screen and sign in the user in the background. (Github issue)

2. The hosted UI requires the display of username and phone number fields on sign up

The username is harmless, I think the real issue is the phone number. Collecting a phone number, particularly without any explanation of why that phone number is being collected, is going to be very off-putting to potential users and will harm conversion. (Github issue)

3. There is no way to link to a privacy policy or terms of use on the hosted UI

This is something a company of almost any size will require within their sign up flow to comply with GDPR/legal requirements. (Github issue)

I wanted to post this to save fellow iOS devs the pain of integration if some of these items are not going to fly for their applications. Any feedback on docs I may have missed or additional context would be really helpful, but these all appear to be outstanding issues and it’s unclear where they fall on the Cognito roadmap.

Building an Order Ahead App

Wanted to link to Sean D’Auria’s great portfolio piece on designing Blue Bottle’s order ahead app. Had a chance to help build this app last year and I’m really proud of the product we were able to ship. It was great being able to work with Sean and see the app grow from early wireframes all the way to the app store.

Útila

In February, I spent 2 weeks on a little island off the coast of Honduras called Útila that’s known for its diving in the Mesoamerican barrier reef.

I started diving in the Sound back home and it was great to be back in the water. Had the chance to run through diver rescue scenarios, hit 40m during my deep dive spec, did dark dives where we turned off our lights and swam in the bioluminescence, learned how to dive plan using nitrox, and did an overhead wreck dive where we used a reel to enter a wreck called the Halliburton.

When not diving, I drank Salvas like water, paddleboarded under double rainbows and ate pastelitos until I got my fill.

2018 Goals
  • ✅ Post a Strava activity everyday (30+ minutes of activity, including weightlifting, running, cycling, swimming, diving, yoga)
  • Earn following specialities, up to 50 dives (18 -> 50 dives, currently at 33 dives)
    • ✅ Rescue Diver
    • ✅ Night Specialty
    • ✅ Deep Specialty
    • ✅ Wreck Specialty
    • ✅ Nitrox Specialty
  • Compete in Sprint Triathlon
    • ✅ Stanford Triathlon (March 3, registered)
  • Run half marathon
    • ✅ Oakland Half Marathon (March 25, registered)
  • ✅ Complete a crevasse rescue course (April 14)
  • ✅ Summit Mt Whitney (April 28th)
  • ✅ Finish Beer Mile (April 21st, time 13:55)
  • ✅ Summit Mt. Hood (May 19)
  • ✅ Compete in Olympic triathlon, Santa Rosa, NM (June 9, registered)
  • ✅ Double Dipsea (June 16)
  • ✅ Tahoe Ragnar (July 20-21)
  • ✅ RSVP (August 17, registered)
  • Compete in Half Ironman
  • Ironman 70.3 Santa Cruz (September 9)
  • Run marathon
    • ✅ California International Marathon (Dec 2)
  • Run a sub 5 minute mile
  • Complete various cycling routes
    • ✅ Summit Mt. Diablo
    • Circle Lake Tahoe
    • ✅ Summit Mt. Hamilton
    • Circle Lake Washington
    • Cycling distance PR
  • Lead sport outdoors 4 times
    • Pinnacles
    • Castle Rock
    • Yosemite
  • Lead a 10.c outdoors
  • Climb a 12.a in the gym
  • Climb a multipitch route
  • Intro to Multi-Pitch Climbing with Alpenglow (courses available in June)
  • Climb Rainier
  • Second Olympic Tri
Lessons learned building a prototype iOS donation app with ApplePay

On November 8th, I made the initial commit for Donate for iPhone, my first app for iOS. The idea was to build an app that enabled donors to give to a hand-selected list of non-profits through ApplePay.

I find that when learning a new language or platform, it’s best to start with a recommended set of lessons and then jump immediately into a project you’re interested in building. A project aiming to solve an interesting problem keeps a developer motivated and immediately thrusts you into real-world development challenges. I started with the series of lessons through Bitfountain’s The Complete iOS 8 Course with Swift, an up-to-date series using Swift and Xcode 6.

Behind the scenes, ApplePay requires a third-party payment processor to handle the actual charge and thankfully Stripe was a launch partner. When ApplePay is triggered on the device, the user uses TouchID to confirm the payment and then a token is generated. You take this token and pass it to an external API that you’ve set up to handle charges. After successfully charging or rejecting the card through Stripe’s API, you return a JSON response to the app, close ApplePay and display a relevent success or error message.

In the course of the project I learned:

  • IBOutlets and IBActions, using Assistant Editor with Storyboards
  • Autolayout, including autolayout within a scrollview
  • Static and dynamic tableviews
  • cellForRowAtIndexPath, didSelectRowAtIndexPath, numberOfRowsInSection
  • Passing data to controllers using seques
  • ApplePay + Stripe integration, including PaymentKit integration
  • Swift-Objective-C bridging header
  • Creating smooth tableview scrolling with SDWebImage for image calling and caching
  • Moving views up to enable seeing textfield and keyboard simultaneously with TPKeyboardAvoiding
  • In-app notifications with TSMessages
  • UIWebView, including passing and loading URL, back, forward, refresh and stop controls
  • Building a LaunchScreen.xib
  • Interacting with a Rails API using AFNetworking
  • Installing and using Cocoapods
  • Creating app archive and uploading build to iTunesConnect
  • Setting up screenshots, description, app info and submitting to TestFlight Beta Review
  • Adding pull-to-refresh with UIRefreshControl
  • Changing the UINavigationBar background color, text color, and transparency Creating round images with clipsToBounds
  • Creating field validations for email and dollar amounts
  • Using Images.xcassets with 1x,2x,3x assets