iOS-Swift User Sessions

Sample Project

Download this sample project configured with your Auth0 API Keys.

System Requirements
  • CocoaPods 1.1.1
  • Version 8.2 (8C38)
  • iPhone 6 - iOS 10.2 (14C89)
Show requirements

Before Starting

This tutorial assumes you're using the Lock library for handling login. Make sure you've integrated this library into your project and you're familiar with it. If you're not sure, review the Login Tutorial first.

Add the SimpleKeychain Dependency

We're going to use the SimpleKeychain library to help us manage user credentials. Make sure you integrate it before proceeding.

a. Carthage

If you are using Carthage, add the following line to the Cartfile:

github "auth0/SimpleKeychain"

Then, run carthage bootstrap.

For more information about Carthage usage, check their official documentation.

b. Cocoapods

If you are using Cocoapods, add these lines to your Podfile:

use_frameworks!
pod 'SimpleKeychain', '~> 0.7'

Then, run pod install.

For further reference on Cocoapods, check their official documentation.

On Login: Store the user's token

The idToken is a string representing, basically, the user's JWT token.

We will store this idToken upon a successful login, in order to prevent the user from being asked for login credentials again every time the app is re-launched.

Once the user has logged in, you get a Credentials object, as follows:

Lock
    .classic()
    .onAuth { credentials in
        // Let's save our credentials.idToken value
    }
    .present(from: self)

We want to store the idToken string value, which is inside the Credentials instance that comes in credentials. To do so, we'll use an A0SimpleKeychain instance:

guard let idToken = credentials.idToken else { return }
let keychain = A0SimpleKeychain(service: "Auth0")
keychain.setString(idToken, forKey: "id_token")

As you can see, SimpleKeychain can be seen simply as a key-value storage.

You can also verify whether a JWT token is valid or not by decoding it locally, to check its expiration. For further reference, you can check out this JWT decoder for Swift.

On Startup: Check idToken existence

The main purpose of storing this token is to save the user from having to re-enter login credentials upon relaunch of the app. So, once the app has launched, we need to check for the existence of an idToken to see if we can automatically log the user in and redirect the user straight into the app's main flow, skipping any login screen.

To do so, first, we retrieve its value from the id_token key we used above, from the keychain:

let keychain = A0SimpleKeychain(service: "Auth0")
guard let idToken = keychain.string(forKey: "id_token") else {
    // idToken doesn't exist, user has to enter their credentials to log in
    // Present Lock
    return
}
// idToken exists
// We still need to validate it!

Validate an existent idToken

Then, if such a token exists, we need to check whether it's still valid, has expired, or is no longer valid for some other reason, such as being revoked. To do so, we'll use Auth0 to fetch the user's profile based on the current idToken we've got:

let keychain = A0SimpleKeychain(service: "Auth0")
guard let idToken = keychain.string(forKey: "id_token") else {
    // No idToken found, present Lock Login
    return
}

// Retrieve profile
Auth0
     .authentication()
     .tokenInfo(token: idToken)
     .start { result in
         switch(result) {
         case .success(let profile):
             // Our idToken is still valid and we have the user's profile
             // This would be a good time to store your profile somewhere
         case .failure(let error):
             // idToken has expired or no longer valid
         }
     }

Dealing with a non-valid idToken

How to deal with a non-valid idToken is up to you. You will normally choose between two scenarios: Either you ask users to re-enter theirs credentials, or you can use delegation with a [refresh_token((/refresh-token)) to obtain a new valid idToken again.

If you aim for the former scenario, make sure you clear all the keychain stored values by doing:

A0SimpleKeychain(service: "Auth0").clearAll()

However, in this tutorial, we'll focus on the latter scenario, where we still want to log users in without asking for their credentials again.

In this case, we're going to leverage the refreshToken. The refresh token is another token string contained within the Credentials object that comes upon a successful login, which doesn't expire, and whose main purpose is retrieving a new valid idToken.

It's recommended that you read and understand the refresh token documentation before proceeding. You got to keep in mind, for example, that, even though the refresh token cannot expire, it can be revoked.

Store the refreshToken

The refreshToken can be nil if offline_access is not sent in the scope parameter during authentication.

Besides storing the idToken, we need to store the refreshToken. Let's make a couple of changes:

Lock
    .classic()
    .withOptions {
        $0.scope = "openid offline_access"
        $0.parameters = ["device":"A_UNIQUE_ID"]
    }
    .onAuth {
      guard let idToken = credentials.idToken, let refreshToken = credentials.refreshToken else { return }
      let keychain = A0SimpleKeychain(service: "Auth0")
      keychain.setString(idToken, forKey: "id_token")
      keychain.setString(refreshToken, forKey: "refresh_token")
    }

Use the refreshToken to get a new idToken

We can use the func renew(withRefreshToken refreshToken: String) method in Auth0 to yield fresh user's credentials.

// ⚠️ idToken has expired or invalid
let keychain = A0SimpleKeychain(service: "Auth0")
guard let refreshToken = keychain.string(forKey: "refresh_token") else {
    keychain.clearAll()
    return
}
Auth0
    .authentication()
    .delegation(withParameters: ["refresh_token": refreshToken])
    .start { result in
        switch(result) {
        case .success(let credentials):
            // Just got a new idToken!
            // Don't forget to store it...
            guard let idToken = credentials["id_token"] as? String else { return }
            self.storeTokens(idToken)
            keychain.setString(idToken, forKey: "id_token")
            // At this point, you can log the user into your app. e.g. by navigating to the corresponding screen
        case .failure(let error):
            // refreshToken is no longer valid (e.g. it has been revoked)
            // Cleaning stored values since they are no longer valid
            keychain.clearAll()
            // At this point, you should ask the user to enter their credentials again!
        }
}

That's it! You've already dealt with the basic concepts of session handling in your app.

On Logout: Clear the Keychain

Whenever you need to log the user out, you just have to clear the keychain:

let keychain = A0SimpleKeychain(service: "Auth0")
keychain.clearAll()

Optional: Encapsulate session handling

As you have probably realized by now, session handling is not a straightforward process. All this token-related information and processes can be encapsulated into a class that separates its logic from the View Controller layer. We recommend that you download the sample project from this tutorial and take a look at its implementation, focusing on the SessionManager class, which is in charge of dealing with these processes.

Fetch the User Profile

The first step is to fetch the user profile. To do so, you need a valid idToken first.

You need to call a method from the Auth0 module that allows you to fetch the user profile given an idToken:

 // Retrieve profile
 Auth0
      .authentication()
      .tokenInfo(token: idToken)
      .start { result in
          switch(result) {
          case .success(let profile):
              // You've got the user profile here
              // Store it somewhere safe, you can see an example in this chapter's sample project.
          case .failure(let error):
              // Check this chapters sample project for an example of how to handle this.
          }
      }

Show User Profile's Data

Default info

Showing the information contained in the user profile is pretty simple. You only have to access its properties, for instance:

let name = profile.name
let avatarURL = profile.pictureURL

Check out the Profile class documentation to learn more about its properties.

Additional info

Besides the defaults, you can request more information than returned in the basic profile. Before we do this let's add some userMetadata to the profile.

Update the User Profile

You can store additional user information in the user metadata. In order to do so, you need to perform a patch:

let idToken = ... // You will need the idToken from your credentials instance 'credentials.idToken'
let profile = ... // the Profile instance you obtained before
Auth0
    .users(token: idToken)
    .patch(profile.id, userMetadata: ["first_name": "John", "last_name": "Appleseed", "country": "Canada"]
    .start { result in
        switch result {
          case .success(let ManagementObject):
              // deal with success
          case .failure(let error):
              // deal with failure
        }
}

Retrieving User Metadata

The user_metadata dictionary contains fields related to the user profile that can be added from client-side (e.g. when editing the profile). This is the one we're going to work with in this tutorial.

You can specify the fields to be retrieved, or use an empty array [] to pull back the complete user profile. Let's grab the user_metadata:

Auth0
    .users(token: idToken)
    .get(userId, fields: ["user_metadata"], include: true)
    .start { result in
        switch result {
        case .success(let user):
            guard let userMetadata = user["user_metadata"] as? [String: Any] else { return }
            // Access userMetadata
        case .failure(let error):
            // Deal with failure
        }
}

You can then access its fields as follows:

let firstName = userMetadata["first_name"] as? String
let lastName = userMetadata["last_name"] as? String
let country = userMetadata["country"] as? String
let isActive = userMetadata["active"] as? Bool

The strings you use for subscripting the userMetadata dictionary, and the variable types you handle, are up to you.

Previous Tutorial
3. Custom Login
Next Tutorial
5. Calling APIs
Use Auth0 for FREECreate free Account