Discover and enable the integrations you need to solve identityAuth0 Marketplace
iOS

Get Started with iOS Authentication using Swift and UIKit

A step-by-step tutorial on using Auth0 and Swift to implement login/logout and use user metadata in iOS apps.

Last Updated On: December 21, 2021

iOS

Get Started with iOS Authentication using Swift and UIKit

A step-by-step tutorial on using Auth0 and Swift to implement login/logout and use user metadata in iOS apps.

Last Updated On: December 21, 2021

If you’re planning to build a native iOS app that requires users to log in, you could implement authentication yourself — or you can use Auth0 and focus your time and effort on your app’s actual functionality.

Building a user login/logout system looks simple. It often turns into its own project. Just handling the many ways users want to log in can easily take a lot of time. You’d also have to deal with issues such as user management, scaling, and security, each of which has dozens of considerations, risks, issues, and edge cases.

Auth0 solves this problem. With Auth0 and a few lines of code, your app can have a full-featured system that supports logging in with a username/password combination, single sign-on and social accounts, passwordless login, biometrics, and more. You won’t have to handle the “behind the scenes” issues, either! Instead, you can focus on what your app does.

This tutorial covers the basics of using Auth0 to implement login/logout in an iOS app written using Swift and the UIKit framework. It also provides an introduction to reading and writing information into a user account’s metadata so that your app can “remember” each user’s status, configurations, and preferences. Along the way, you’ll become familiar with the Auth0 dashboard and learn how to use it to register applications and manage users and their metadata.

Look for the 🛠 emoji if you’d like to skim through the content while focusing on the build and execution steps.

What You’ll Build

You’ll build a simple, single-screen iOS app that will allow the user to log in and log out using Auth0. While logged in, the user will be able to see the following information from their user profile, namely:

  • The following basic information from the user account:
    • The user’s name
    • The user’s email address
  • User metadata, specifically the user’s favorite color, which they will be able to edit in the app
  • An announcement if a flag in the user’s app metadata has been set

A quick tour of the app

When you launch the completed app, you’ll see a greeting, a Log In button, and a disabled Log Out button:

The app’s “Welcome” screen. The “Log In” button is enabled, and the “Log In” button is disabled.

Tapping the Log In button takes the user to the Auth0 Universal Login screen, which appears in a custom browser window:

The default Auth0 Universal Login web page, with Auth0 logo and “email address” and “password” fields.

When you use Auth0 to add login/logout capability to your apps, you delegate authentication to a centralized login page in the same way that Google properties such as Gmail and YouTube redirect you to accounts.google.com when you log in. In this exercise, you’ll use the default “look and feel” for the login page, but you can customize it to match your app or organization’s branding.

If the user logs in successfully, the Universal Login screen and its browser window disappear, and the user returns to the app.

Immediately after login, the app checks the app metadata associated with the user’s account for a “make an announcement” flag. If this flag is set, the app shows this alert box:

The app displays an alert that tells the user if the alert is visible, then the “make_announcement” flag in “app_metadata” is set to “true”.

This alert box doesn’t appear if the flag isn’t set.

Here’s what the app looks like once the user is logged in and has dismissed the alert box (if it appeared):

The app in its “logged in” state. The “Log In” button is disabled, “Log Out” button is enabled, and the app’s other controls are visible.

Here’s what changed after login:

  • The greeting will have changed to a notification telling you that you’re now logged into the app.
  • The Log In button will now be disabled, and the Log Out button will now be enabled.
  • The name and email address for the user account that you used to log in will be displayed.
  • There will now be a section for metadata, complete with an editable text field and Get and Set buttons that will allow you to read and write to the favorite_color field of the metadata for the user account.

As you might expect, logging out is done by tapping the Log Out button. Once logged out, the user will see this:

The app in its “logged out” state. The “Log In” button is enabled, “Log Out” button is disabled, and the app’s other controls are hidden.

Note that:

  • The notification at the top of the screen will now inform you that you’re logged out of the app.
  • Logging out enables the Log in button and disables the Log out button.
  • The other controls that were visible when you were logged in will no longer be visible.

This is a UIKit-based app

We’re at an interesting time for iOS developers. There are currently two very different UI frameworks for iOS development:

  1. SwiftUI, which is also known as “the new one”. With SwiftUI, you build user interfaces using Swift code with a declarative approach, which means that you specify how the UI should look and what it should do in different states. You don’t have to specify how the UI moves between those states — it takes care of that for you. If you know React programming, you’ll find SwiftUI familiar.
  2. UIKit, also known as “the old one” or “the original one”. With UIKit, you build user interfaces using Interface Builder, a drag-and-drop layout tool to build the app’s views graphically. You then connect UI elements to variables and methods in the app’s view controllers using outlets and actions. You program UIKit apps imperatively, which means that you define how the program moves between states as well as how the UI should look and what it should do in different states.

Most iOS apps are written using UIKit. Since it’s been around since the beginning of iOS, it’s the one that most iOS developers know, and it’s the better-documented framework. For this reason, this tutorial will focus on UIKit and feature a UIKit-based app. We’ll publish a similar article focused on SwiftUI soon.

Prerequisites

An Auth0 account

The app uses Auth0 to provide login, logout, and user metadata functionality, which means that as the developer, you need an Auth0 account. You can sign up for a free account, which lets you add authentication for enough apps and users for prototyping, development, and testing.

An iOS development setup

To develop applications for any Apple hardware, you need a Mac computer running Xcode, Apple’s integrated development environment:

  • The computer can be any Mac — MacBook, MacBook Air, MacBook Pro, iMac, iMac Pro, Mac Mini, or Mac Pro — from mid-2012 or later with at least 8 GB RAM.
  • Xcode version 11.0 (September 2019) or later. I used the current version when writing this article: version 13.1 (build 13A1030d), which was released on October 25, 2021.

An iOS device, virtual or real

Xcode 13.1 comes with the Simulator, an application that creates virtual devices that simulate recent models of the iPhone, iPad, and iPod Touch, all of which run iOS 15.

To install earlier versions of iOS for the Simulator, select Preferences… from the Xcode menu, followed by the Components tab. You’ll see a list of older versions of not just iOS, but watchOS and tvOS as well.

One of the benefits of using the Simulator is that you don’t need an Apple Developer account — free or paid — to use it.

While the Simulator is convenient, there’s no substitute for an actual physical device. It provides a more realistic testing experience, and there are certain things that can’t be done on the Simulator (such as motion/tilt sensing and augmented reality).

You need a free Apple Developer account to deploy an app directly to a device for testing (you need the paid one to deploy an app to the App Store), and you need to register the device in your developer account to deploy apps to it. For more details, see Apple’s article, Distributing Your App to Registered Devices.

To find out more about running apps on virtual and real iOS devices, start with Apple’s Running Your App in the Simulator or on a Device.

What might be useful

While not completely necessary, it would help if you were familiar with the Swift programming language and iOS development. If these are new to you, the Swift programming language, or the UIKit framework, try this Apple tutorial: Getting Started with Today,.

Download and Run the Starter Project

To keep this tutorial focused on implementing Auth0 authentication in an iOS app, I created a starter project that you can download. It contains a view with controls already laid out and connected to corresponding methods in the view controller. By starting with this project, you’ll be able to focus on logging in, logging out, and reading and writing user information without the distraction of building a user interface.

🛠 Download the .zip file containing the starter project (225 KB) and uncompress it. This will create a folder named iOS Auth (starter) on your local drive. Open this folder, then launch Xcode by opening the project file, iOS Auth.xcodeproj.

🛠 Confirm that the starter project works. Select a simulator from the device menu at the top of the Xcode window (see the picture below), then click the ▶️ button to run the app:

Screenshot of Xcode with instructions to choose a simulator and run the app.

The Simulator will launch, and after a moment, the app will run within the Simulator. It should look like this:

The starter app’s initial screen, with all the UI, controls in their default states.

Right now, the app simply draws a user interface on the screen. You can tap on the buttons, but they’ll do nothing. As you go through the steps in this tutorial, you’ll add the following functionality to the app:

  1. Login
  2. Logout
  3. Retrieving and displaying the current user’s name and email address
  4. Getting and setting the current user’s favorite color
  5. Displaying a message if the make_announcement flag in the app metadata is set

There’s one more task to perform before we set things up on the Auth0 side: you need to make a note of the app’s bundle identifier, which is a string that uniquely identifies the app.

🛠 Find the bundle identifier by opening the Project Explorer (click on the folder icon at the top of Xcode’s left column), then select the iOS Auth project. Then under Targets, select iOS Auth:

Xcode screenshot showing the selection of the project target.

🛠 Select the General tab in Xcode’s center pane and look for the Bundle Identifier field value:

Xcode screenshot showing the display of the Bundle Identifier.

You can keep the bundle identifier currently defined in the starter project — com.example.iOS-Auth — or you can define your own.

Apple encourages developers to use reverse domain name notation for application bundle identifiers. For example, if you own the domain examplesoftwarecompany.com and the name of the app is “Example App”, the recommended bundle identifier is com.examplesoftwarecompany.exampleapp.

Remember the bundle identifier — you’ll use it in the next step!

Register the App in the Auth0 Dashboard

The next step is to register the app in your Auth0 dashboard. In this process, you will:

  1. Add the app to your Auth0 dashboard’s list of registered applications.
  2. Gather two pieces of information that the app will need to delegate login/logout to Auth0: your tenant’s domain and the client ID that Auth0 will assign to the app.
  3. Provide Auth0 with the necessary callback URLs to contact the app: one to call at the end of the login process and the other to call at the end of the logout process.

To perform this step, you’ll need an Auth0 account.

🛠 If you already have an Auth0 account, log in, skip the next section, and proceed to the part titled Add the app to the Applications list.

If you don’t have an Auth0 account yet...

🛠 ...go ahead and sign up for one! It’s free, and we’ve taken great care to make the process as painless as possible.

Add the app to the Applications list

🛠 In the left side menu of the Auth0 dashboard, click on Applications:

The main page of the Auth0 dashboard. The reader is directed to expand the “Applications” menu.

🛠 This will expand the Applications menu. Select the first item in that menu, which also has the name Applications:

The main page of the Auth0 dashboard, with the “Applications” menu expanded. The reader is directed to select the “Applications” menu item.

You will now be on the Applications page. It lists all the applications that you have registered so that Auth0 can handle their login/logout processes.

🛠 Let’s create a new registration for the app. Do this by clicking the Create application button near the top right of the page:

The “Applications” page. The reader is directed to click the “Create Application” button.

You’ll see this dialog appear:

The “Create application” dialog. The application’s name is set to “iOS Auth”, and the selected application type is “Native”.

🛠 You’ll need to provide two pieces of information to continue:

  • Enter a name for the app in the name field. It might be simplest to use the same name as your Xcode project (if you’ve been following my example, use the name iOS Auth).
  • Specify the application type, which in this case is Native.

Click Create. The Quick Start page for the app will appear:

The “Quick Start” page. It contains several icons, each one representing an operating system or platform.

This page provides you with ready-made projects for several different platforms that you can use as the basis for an application that delegates login/logout to Auth0. You won’t be using any of them in this exercise; instead, you’ll make use of a couple of Auth0 libraries and write the code yourself. It’s more educational — and more importantly, fun — that way.

🛠 Click the Settings tab, which will take you to this page:

The “Application” page’s “Settings” tab.

You’re going to do two critical things on this page:

  1. Get information that the app needs to know about Auth0, and
  2. Provide information that Auth0 needs to know about the app.

Let’s take care of the first one: Getting the information that the app needs, namely:

  • The domain, which you need to build the URL that the app will use to contact Auth0. It uniquely identifies your Auth0 tenant, which is a collection of applications, users, and other information that you have registered with your Auth0 account.
  • The client ID, which is the identifier that Auth0 assigned to the app. It’s how Auth0 knows which app of yours it’s working with.

🛠 Get this information by copying the contents of the Domain and Client ID fields for later reference:

The “Application” page’s “Settings” tab. The reader is directed to copy the values of the “Domain” and “Client ID” text fields, then to scroll down the page.

You’ll enter them into your Xcode project as strings in a property list soon.

🛠 Scroll down to the Applications URIs section. This is where you provide two pieces of information that Auth0 needs to know about the app, which are:

  1. A callback URL: the URL that Auth0 will redirect to after the user successfully logs in. There can be more than one of these.
  2. A logout URL: the URL that Auth0 will redirect to after the user logs out. There can be more than one of these.

At this point, you’re probably thinking: “Wait a minute — I’m writing an iOS app. It doesn’t have web pages that you navigate to using URLs, but Views with underlying code in View Controllers!”

You’re absolutely right. In the case of native applications, the callback and logout URLs are the same string, and Auth0 sends that string to the app to inform it that a user has logged in or logged out.

The string that native iOS apps use for both the callback URL and the logout URL follow this format:

{BUNDLE_IDENTIFIER}://{YOUR_DOMAIN}/ios/{BUNDLE_IDENTIFIER}/callback

🛠 To construct the string, do the following:

  • Replace {BUNDLE_IDENTIFIER} with the app’s bundle identifier. If you didn’t change the bundle identifier in the starter project, this value is com.example.login. Note that {BUNDLE_IDENTIFIER} appears twice in the URL; you’ll need to replace it twice.
  • Replace {YOUR_DOMAIN} with the value from the Domain field that you saw earlier on this page.

🛠 Enter the URL you just constructed into both the Allowed Callback URLs and Allowed Login URLs fields. Remember, the same URL goes into both fields.

The “Application URIs” section of the page. The reader is directed to enter the callback URL into “Allowed Callback URLs” and “Allowed Logout URLs”.

🛠 You’ve done everything you need to do on this page. Scroll down to the bottom of the page and click the Save Changes button:

The bottom of the page, which features the “Save Changes” button. An arrow directs the reader to click the button.

Create a user if your tenant doesn’t have any

If you just created an Auth0 account, your tenant is brand new, so it won’t have any user accounts. This means that there won’t be any way to log in to the app. If this is the case, follow these steps to create a user.

🛠 In the menu on the left side of the Auth0 dashboard, click on User Management:

The bottom of the page. An arrow directs the reader to expand the “User Management” menu.

🛠 This will expand the User Management menu. Select the Users item in that menu:

The bottom of the page now featuring an expanded “User Management” menu. An arrow directs the reader to expand the “Users” menu item.

The Users page will appear. It lists all the users registered to your tenant. If there are no users, you’ll see the “You don’t have any users yet” message.

The “Users” page. The page says, “You don’t have any users yet”. An arrow directs the reader to click the “Create User” button.

🛠 Click the Create User button to create a new user, which will make this dialog box appear:

The “Create User” dialog. It has fields for email and password, as well as a drop-down menu displaying “Username-Password-Authentication”.

🛠 Enter an email address and password for the user. The only option for the Connection will be Username-Password-Authentication, so leave it as it is. Make a note of that email address and password — you’ll be using them to log in to the app.

🛠 Click the Create button to create the user. The user’s Details page will appear:

The user’s “Details” page.

That takes care of all the setup you need to do within the Auth0 dashboard. It’s time to go back to Xcode and the app.

Set Up the App to Connect to Auth0

There are a few steps that you’ll need to perform before you can start coding:

  1. Store the domain and client ID values from the Auth0 dashboard in the app
  2. Configure the app’s callback URLs
  3. Install the Auth0.swift package

Store the app’s domain and client ID in the app

Just as Auth0 needs to know information about the app — namely, its bundle identifier and callback URL — the app needs information about Auth0. It needs to know the domain and client ID, both of which you noted previously.

The domain identifies the Auth0 tenant, and the client ID identifies the app. Neither of these values is secret, but we still recommend that you store them in a separate file that you can explicitly exclude from version control. For iOS projects, we recommend using a property list (plist) file to store the domain and client ID.

🛠 Create a new plist file. Right-click (or control-click) on the Login folder in Project Explorer and select New File… from the menu that appears:

Xcode screenshot, showing the user selecting “File -> New File...”

🛠 In the window that appears, make sure that the iOS tab is selected. Scroll down the list of icons until you see the Property List icon. Select it, then click Next:

Xcode screenshot, showing the selection of the “Property List” file template.

🛠 Name the file Auth0.plist and click Create to create the file:

Xcode screenshot, showing the “Save” dialog box for the property list.

Xcode will display the new Auth0.plist file in a table format. Let’s add two rows to the table, which will hold the domain and client ID values.

🛠 Move the cursor over the Root row to reveal the + button. Click on the + button twice to add two rows:

Xcode screenshot, showing the creation of two new rows in the property list.

🛠 Enter the following values into the two rows:

  • Row 1:
    • Key: Domain
    • Type: String
    • Value: The app’s domain from the Auth0 dashboard
  • Row 2:
    • Key: ClientId
    • Type: String
    • Value: The app’s client ID from the Auth0 dashboard

Your Auth0 plist file should look like this:

Xcode screenshot, showing the completed property list.

Configure the app’s callback URLs

When an application delegates login and logout to Auth0, it navigates to an URL for an authentication server through a dedicated browser window. Once the login or logout process finishes, the authentication server needs a way to return control to the application.

In a web application, you would create a route and provide the URL for this route to Auth0. When Auth0 finishes logging in or logging out, it calls this URL, returning control to the web application.

iOS native apps aren’t web applications, and they don’t use URLs. For Auth0 to return control to an iOS app, you need to provide the app with some configuration information to allow Auth0 to use the callback URL to return control to the app.

You’ll provide this configuration information by adding it to Info.plist, a file included in every default iOS project that stores configuration information. Since the configuration information isn’t sensitive, you can store it in Info.plist without compromising security.

Unlike the information you entered into Auth0.plist, the configuration information that you need to add to Info.plist is more complex. It will be easier to enter as XML.

🛠 Open Info.plist as an XML file. Right-click (or control-click) on the Info in Project Explorer, select Open As from the menu that appears, then select Source Code from the sub-menu that appears:

Xcode screenshot, showing the opening of Info.plist as XML (source code).

Xcode will display the contents of Info.plist in XML form. It will look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>

... (about 80 lines of configuration)

</dict>
</plist>

🛠 Add the following XML to Info.plist immediately after the first <dict> tag in the file:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleTypeRole</key>
    <string>None</string>
    <key>CFBundleURLName</key>
    <string>auth0</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    </array>
  </dict>
</array>

The XML above defines a type of URL named auth0 and whose scheme is the app’s bundle identifier. In other words, the part of the URL that comes before :// isn’t http or https, but com.example.login (or whatever value you entered for the app’s bundle identifier). This matches the values that you entered as the callback and logout URLs in the Auth0 dashboard.

Once you’ve added the additional XML, Info.plist should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleURLTypes</key>
  <array>
    <dict>
      <key>CFBundleTypeRole</key>
      <string>None</string>
      <key>CFBundleURLName</key>
      <string>auth0</string>
      <key>CFBundleURLSchemes</key>
      <array>
        <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
      </array>
    </dict>
  </array>
  
... (all the other configuration info)

</dict>
</plist>

Install the Auth0.swift package

The Auth0.swift package is a collection of libraries that allow applications written in Swift to make use of Auth0”s authorization and authentication functionality. It contains these Swift libraries:

There are three ways to install Auth0.swift:

  1. Swift Package Manager, Apple’s dependency manager
  2. Cocoapods, a long-standing third-party package manager first released in 2011 used in many projects
  3. Carthage, another third-party package manager

You’ll use Swift Package Manager in this tutorial since it’s built into Xcode and doesn’t require additional software.

This page contains instructions for installing Auth0.swift using Cocoapods and Carthage.

🛠 Open the File menu, and select Add Packages.... This window will appear:

Xcode screenshot: The “Add Packages” window.

🛠 Enter this URL into the window’s search field:

This URL points to the *Auth0.swift* package repository.
Xcode will search online and display any packages it finds at the URL.

🛠 Select the **Auth0.swift** package when it appears in the list and click **Add Package**:

![Xcode screenshot: Select “Auth0.swift”, then click “Add Package”.](https://images.ctfassets.net/23aumh6u8s0i/5LaFAbYZsPaPHxVJf8SW6J/61b5a70b5316f5e72ca8f8c0306bc177/install_auth0_package_2.png)

🛠 You’ll be asked to confirm that you want to add the *Auth0.swift* package. Click **Add Package** to reassure Xcode that yes, you want *Auth0.swift*:

![Xcode screenshot: Confirming the installation of the Auth0 package.](https://images.ctfassets.net/23aumh6u8s0i/15ix1wqGd0rOcSPlRqaURz/9dfd040fe82c97a15650bfb4a06cdf9c/install_auth0_package_3.png)

When *Auth0.swift* finishes installing, its package will appear in the list under the **Package Dependencies** tab of the **iOS Auth** project screen. You’ll see *Auth0.swift’s* libraries listed in the Project Explorer pane: 

![Xcode screenshot: The “Package Dependencies” tab of the “iOS Auth” project screen.](https://images.ctfassets.net/23aumh6u8s0i/6SU2YRslnRnIBnwGOVxoAq/aa044e9219a2e9c829523f86a59f62e3/install_auth0_package_4.png)

You’ve done all the setup for connecting Auth0 to the app and the app to Auth0. It’s time to start coding!


## Implement Login

The first feature you’ll implement is login. Once implemented, the user will be able to perform the following steps in the order given:

1. The user launches the app. The app displays a greeting message as its title, an enabled **Log In** button, and a disabled **Log Out** button.
2. The user taps the **Log In** button, taking them to Auth0’s web-based Universal Login screen, which appears in a dedicated browser window.
3. If the user enters a valid username/password combination, they are logged in, which means:
4. The app receives a `Credentials` object from Auth0’s authorization server. This object’s properties include the user’s [ID token](https://auth0.com/docs/security/tokens/id-tokens) and [access token](https://auth0.com/docs/security/tokens/access-tokens). This app will use the access token to retrieve more data about the user.
5. The user will be returned to the app, which displays “You’re logged in” as its title, a disabled **Log In** button, an enabled **Log Out** button, and the previously-hidden **User Info** and **User Metadata** areas of the screen.


This is a simple single-screen app, so you’ll do all your coding in the `ViewController` class, located in the file named `ViewController.swift`.

🛠 Open the view controller class by clicking on **ViewController** in the Project Explorer:

![Xcode screenshot: Opening the VieController file.](https://images.ctfassets.net/23aumh6u8s0i/66VfmkMOJUhW8vdDw0JOS1/1d3e28c446cf1971305560da7ab5409d/open_viewcontroller.png)


### Import the Auth0 library

🛠 You’ll be writing code that uses functionality provided by the `Auth0` library. Import it by adding the following after the `import UIKit` statement near the start of the file:

// ViewController.swift

import Auth0 ```

Add properties to ViewController

The Properties section of the ViewController class is indicated by this set of comments:

// MARK: Properties
// ================

It contains these subsections:

  1. On-screen controls: These variables are references to the user elements on the app’s single screen. To save time, I’ve already declared these variables and connected them to their matching UI elements in the view.
  2. App and user status: This empty subsection will contain variables to keep track of the app’s and user’s state.
  3. Auth0 data: This empty subsection will contain variables to store data received from Auth0’s authentication server — credentials, user information, and user metadata.

🛠 Update the App status and Auth0 data subsections with the following code:

// App and user status
private var appJustLaunched = true
private var userIsAuthenticated = false
private var userIsNewlyAuthenticated = false

// Auth0 data
private var cachedCredentials: Credentials? = nil
private var cachedUserInfo: UserInfo? = nil
private var cachedUserMetadata: [String : Any]? = nil
private var cachedAppMetadata: [String : Any]? = nil

Here’s an overview of the properties you just added to ViewController:

  • The App status properties:
    • appJustLaunched: This property will be true only immediately after the app is launched. We’ll use its value to determine the contents of titleLabel, which should display a greeting at the start, but the user’s logged in/logged out status afterward.
    • userIsAuthenticated: This property will be true if the user is logged in and false otherwise.
    • userIsNewlyAuthenticated: This property will be true only immediately after the user has logged in and will be set to false shortly afterward. When the user is newly authenticated, the app checks the user’s app metadata to see if its make_announcement property is true. If this is the case, the app will display an alert.
  • The Auth0 data properties:

    • cachedCredentials: Stores the Credentials object that the app gets from Auth0, which contains authentication and authorization information such as the user’s ID token and access token.
    • cachedUserInfo: Stores the UserInfo object that the app gets from Auth0, which contains basic identity information about the user, including their name and email address.
    • cachedUserMetadata: Stores the user_metadata information that the app gets from Auth0. This is a [String: Any] dictionary containing values about the user’s preferences that the user can edit because they do not affect the core functionality available to the user. The app can both read and update user metadata. We’ll take a closer look at user metadata when we add the functionality to edit and retrieve the user’s favorite color.
    • cachedAppMetadata: Stores the app_metadata information that the app gets from Auth0. This is a [String: Any] dictionary containing values about the user’s status or privileges that the user cannot edit because they do affect the core functionality available to the user. The app can read app metadata but cannot update it. We’ll take a closer look at app metadata when we add the functionality to display an announcement when the user logs in and a specific condition is met.

    Add the login() method

Let’s write the first method for the login process: login(). It will be calling the Auth0 authentication server and take the appropriate actions for both a successful login and a failed one. We’ll implement it a little bit at a time.

Skip the login process if Auth0.plist is missing

Let’s start the function with a test to see if the app contains Auth0.plist, the property list with the domain and client ID, and skips the login process if it isn’t there:

🛠 Add a new method, login(), to the Login, logout, and user information section. It should look like this:

// MARK: Login, logout, and user information
// =========================================

func login() {
  guard
    let clientInfo = plistValues(bundle: Bundle.main)
  else {
    return
  }
}

The guard statement ensures that the app has property lists before starting the login process. Property lists are the preferred place to store configuration data in iOS apps, and the method that starts the authentication process requires the tenant’s domain and the app’s client ID to be stored in a property list.

Authenticate the user

Now that you have the tenant’s domain authenticate the user using Universal Login.

🛠 Update login() to the following:

// This first part is the code you’ve already added:

func login() {
  guard
    let clientInfo = plistValues(bundle: Bundle.main)
  else {
    return
  }


  // This second part is new code:
  
  Auth0
    .webAuth()
    .scope("openid profile email read:current_user update:current_user_metadata")
    .audience("https://\(clientInfo.domain)/api/v2/")
    .start { result in
        
      switch result {
              
        case .failure(let error):
          print("Error: Couldn’t retrieve user credentials.\n\(error.localizedDescription)")
          
        case .success(let credentials):
          self.cachedCredentials = credentials
          self.userIsAuthenticated = true
          self.userIsNewlyAuthenticated = true
          self.printTokens()
          
          // TODO: Get the user’s profile information
        
          DispatchQueue.main.async {
            self.updateTitle()
            self.updateButtonsAndStack()
          }
          
      } // switch
      
    } // start()
    
}

After you enter the code above, Xcode will very quickly show error messages informing you that ViewCredentials doesn’t have methods named printTokens(), updateTitle(), or updateButtonsAndStack(). Don’t worry; you’ll implement them soon.

Although this method is formatted to span several lines, it’s just a single line of code. This line calls a chain of the Auth0 class’ methods starting with webAuth(). If you ignore all the comments and parameters, the chain of methods looks like this:

Auth0
  .webAuth()
  .scope()
  .audience()
  .start()

This is the Builder design pattern in action. From webAuth() to audience(), each method in the chain takes an argument that provides additional information about the login, using that information to create a WebAuth object that it passes to the next method in the chain.

The final method in the chain, start(), takes the resulting WebAuth object as its argument and uses it to display the login page and define what should happen when the login succeeds and when it fails.

Let’s take a look at what each of the methods in the chain does.

webAuth() starts the web-based authentication process. It expects to find key-value pairs named domain and clientid in a property list.

scope() lists the names of scopes which the app will request. Scopes define the specific actions that an application can be allowed to do on a user's behalf. In response to these requests for scopes, Auth0 will return claims, which are assertions that an entity has some attribute. We request these scopes in order to get claims for attributes that provide information about the user, such as their various names, email address, and additional metadata.

In the code you just added to login(), the scope() method requests the following scopes from Auth0:

  • openid: Required for the app to use OpenID Connect (OIDC) to authenticate the user. This is the only scope required in scope()’s argument; all other scopes are optional.
  • profile: Information stored in the user’s profile. The profile stores a limited set of information — the user’s various names (full name, family name, given name, middle name, nickname), the URL for the user’s picture, and when the user profile was last updated.
  • email: Information about the user’s email, namely their email address and whether the email address was verified.
  • read:current_user: Read-only access to the user’s profile.
  • update:current_user_metadata: Read and write access to the user’s metadata. This allows the app to get and set the favorite color value in the user’s metadata.

audience() specifies the API that the app will use: the Auth0 Management API, which we’ll use to get information about the user.

Finally, start() takes the WebAuth object constructed by all the previous methods in the chain and opens the browser window to display the login page. It has a single parameter, a closure that takes an argument containing one of two possible values: .failure and .success.

Handle the login outcomes

Let’s take a closer look at the switch statement in login(), which handles the two possible outcomes of the login process:

switch result {
      
  case .failure(let error):
    print("Error: Couldn’t retrieve user credentials.\n\(error.localizedDescription)")
      
  case .success(let credentials):
    self.cachedCredentials = credentials
    self.userIsAuthenticated = true
    self.userIsNewlyAuthenticated = true
    self.printTokens()

    self.getUserInfo()

    DispatchQueue.main.async {
      self.updateTitle()
      self.updateButtonsAndStack()
    }

} // switch

The switch statement does the following:

  • If the login fails, the app prints an error message to the console.
  • If the login succeeds, the app stores the credentials that it received from the server, sets the self.userIsAuthenticated and self.userIsNewlyAuthenticated properties to true, and updates the user interface.

Since this code is inside a closure, I put the calls to the UI methods — updateTitle() and updateButtonsAndStack() — inside a Dispatch.main.async block. This ensures that they ensure they execute in the main thread instead of the closure’s thread.

With a successful login, the app receives a Credentials object. This object’s properties hold information about the user’s authentication, including the access token, which you’ll need to retrieve the user’s profile information and metadata.

Add a token debugger

The .success case of login()’s switch statement makes a call to a method named printTokens(). It’s a debugging tool that prints the values of the ID token and access token to the Xcode console.

🛠 Add the printTokens() function, shown below, just after the login() method:

func printTokens() {
  if let credentials = cachedCredentials {
    print("\nTOKENS\n======")
    print("ID token: \(credentials.idToken ?? "[No ID token found.]")")
    print("Access token: \(credentials.accessToken ?? "[No access token found.]")")
  }
}

🚨Important notice:🚨 printTokens() is a useful function for debugging while you’re developing the app. You should remove it — along with any print() statements, which might give away sensitive information — when you move the app to production!

Update the user interface

Now that you’ve implemented the underlying login code, it’s time to take care of the user interface. The first step is to initialize the user interface when the app launches. The viewDidLoad() method is the place to do this.

🛠 Update the viewDidLoad() method, located in the View events section so that it looks like this:

// MARK: View events
// =================

override func viewDidLoad() {
  super.viewDidLoad()

  updateTitle()
  userInfoMetadataStack.isHidden = true
  loginButton.isEnabled = true
  logoutButton.isEnabled = false
}

The next step is to make the Log In button call the login() method — but only if the user isn’t already logged in.

🛠 Update loginButtonPressed(), located in the Actions section, to the following:

// MARK: Actions
// =============

@IBAction func loginButtonPressed(_ sender: UIButton) {
  if !userIsAuthenticated {
    login()
  }
}

Finally, the app needs methods to update the user interface based on the login status. These are the unimplemented updateTitle() and updateButtonsAndStack() methods referred to in the login() method.

🛠 Add updateTitle() and updateButtonsAndStack() to the UI updaters section so that it looks like this:

// MARK: UI updaters
// =================

func updateTitle() {
  if appJustLaunched {
    titleLabel.text = "Welcome to the app!"
    appJustLaunched = false
  } else {
    if userIsAuthenticated {
      titleLabel.text = "You’re logged in."
    } else {
      titleLabel.text = "You’re logged out."
    }
  }
}

func updateButtonsAndStack() {
  loginButton.isEnabled = !userIsAuthenticated
  logoutButton.isEnabled = userIsAuthenticated
  userInfoMetadataStack.isHidden = !userIsAuthenticated
}

🛠 Run the app and log in. The first time you log in, the browser window that displayed the login page will display this “Authorize App” page:

The “Authorize App” page.

The page makes it clear to the user that the app is asking for permission to access the user’s profile information and metadata.

🛠 Tap the Accept button to allow this access. Once you’ve accepted, this page will not appear again.

You’ll now be logged in, with the User Profile Info and User Metadata parts of the screen now visible.

Display the User’s Name and Email Address

When the user logs in successfully, the app receives a Credentials object containing authentication and authorization credentials, including an access token. This token provides the app with the authorization for the scopes requested during the login process.

One of these scopes is profile, which refers to the user information profile, a collection of basic information about the user. The application’s access token authorizes it to use this scope. This means that it can request the user's profile from Auth0, which will provide it as a UserInfo object. The app can then get the user’s name and email address from this object and display it onscreen.

Retrieve the user information profile

We’ll do this in a method called getUserInfo().

🛠 Add the following after the login() method:

func getUserInfo() {
  // 1
  guard
    let accessToken = cachedCredentials?.accessToken
  else {
    return
  }

  // 2
  Auth0
    .authentication()
    .userInfo(withAccessToken: accessToken)
    // 3
    .start { result in
     
      switch result {
          
        // 4
        case .failure(let error):
          print("Error: Couldn’t retrieve user info.\n\(error.localizedDescription)")
        
        // 5
        case .success(let userInfo):
          self.cachedUserInfo = userInfo
          
          self.getMetadata()
          
          DispatchQueue.main.async {
            self.updateNameAndEmailUI()
          }
          
      } // switch
          
    } // start()
}

Here’s what’s happening at each of the numbered comments in the code above:

  1. This guard statement allows the function to continue executing only if the app has retrieved a credentials object from Auth0 and that credentials object contains an access token.
  2. This is the Builder design pattern again, this time building an Authentication object which is then used to request the user’s profile information. Once again, we pass the resulting object to a method named start().
  3. start() takes the Authentication object constructed by all the previous methods in the chain and makes the request for the user profile information. This method has a single parameter — a closure that takes an argument containing one of two possible values: .failure and .success.
  4. If the attempt to retrieve the user profile information fails, the app prints an error message to the console.
  5. If the attempt succeeds, the app stores the user profile information that it received and uses it to update the user interface with the user’s name and email address. Since this code is inside a closure, I put the calls to the UI method inside a Dispatch.main.async block to ensure they execute in the main thread.

Update the user interface

Now that we have the user information profile let’s use it to update the user interface.

🛠 Add this method into the UI updaters section of View Controller, just after the updateButtonsAndStack() method:

func updateNameAndEmailUI() {
  userNameLabel.text = cachedUserInfo?.name ?? "[Name unknown]"
  userEmailLabel.text = cachedUserInfo?.email ?? "[Email unknown]"
}

Update login()

Now that the app has methods to retrieve the user profile information and update the display based on its contents, we need to update login() to call on them.

🛠 Find the // TODO: Get the user’s profile information comment inside the login() method and replace it with this line:

self.getUserInfo()

🛠 Run the app. It will now display the user’s name and email address.

Retrieve the User Account’s User Metadata and App Metadata

The user profile information contained within the UserProfile object contains only information that applies to generic user accounts. It’s centered around who the user is and how to contact them, with fields for names, photo, profile URL, real-world and email addresses, phone number, and so on.

You’ll probably want to store more than just this basic information about each user. Your application may need to store the user’s preferred application settings, record their role or rank, or track if they have performed certain required actions. Users might want to add or edit information about themselves that gets shared with other app users or customize their experiences.

You could put this additional information in a storage system such as a database, but you also have the option of using the two key-value data structures that come with every Auth0 user account:

  1. User metadata: This is for storing user-related attributes that don’t affect the app’s core functionality for the user. It’s typically the sort of data that the user would be able to edit. Examples of user metadata include things such as their preferences, a bio, their social networking handles, or their favorite color.
  2. App metadata: This is for storing user-related attributes that do affect the app’s core functionality for the user. It’s typically the sort of data that the user would not edit; the user might not even be aware that this information exists. The app can only read this information. Examples of app metadata include things such as the last screen the user was viewing during the previous session, the user’s high score, or a flag or value indicating that the user has or hasn’t performed a required action.

Let’s write a method that retrieves both kinds of metadata from the user’s account and stores them in local variables.

🛠 Add the following after the getUserInfo() method:

func getMetadata() {
  // 1
  guard
    let accessToken = cachedCredentials?.accessToken,
    let userInfo = cachedUserInfo
  else {
    return
  }
  
  // 2
  Auth0
    .users(token: accessToken)
    .get(userInfo.sub, fields: ["user_metadata", "app_metadata"], include: true)
    // 3
    .start { result in
      
      switch result {
      
        // 4
        case .failure(let error):
          print("Error: Couldn’t retrieve metadata.\n\(error.localizedDescription)")
        
        // 5
        case .success(let user):
          let userMetadata = user["user_metadata"] as? [String: Any]
          self.cachedUserMetadata = userMetadata
        
          let appMetadata = user["app_metadata"] as? [String: Any]
          self.cachedAppMetadata = appMetadata
          
          DispatchQueue.main.async {

            // TODO: Update the UI based on ***USER*** metadata
            
            // TODO: Update the UI based on ***APP*** metadata

          }
          
      } // switch
      
    } // start()
}

Here’s what’s happening at each of the numbered comments in the code above. By this point in the exercise, you’re probably seeing a pattern in the way that the app sends a user-related request to Auth0 and handles the response:

  1. This guard statement allows the function to continue executing only if two conditions are met:
    • The app has retrieved a credentials object from Auth0, and that credentials object contains an access token.
    • The app has also retrieved a user profile information object from Auth0.
  2. This chain of methods does the following:
    • The users() method takes an access token — which comes from cachedCredentials — and returns an object representing the current user.
    • The get() method specifies which information about the user should be retrieved. It takes the identifier for the user (the sub property of the user’s profile information object), an array containing the names of the user metadata and app metadata fields of the user object, and a boolean that specifies if the names of the fields should be included or excluded from the data that this method returns. It returns an object representing a request for information from Auth0.
  3. start() takes the request object constructed by the previous methods in the chain and makes the request for the user’s user metadata and app metadata. This method has a single parameter — a closure that takes an argument containing one of two possible values: .failure and .success.
  4. If the attempt to retrieve the metadata fails, the app prints an error message to the console.
  5. If the attempt succeeds, the app stores a local copy of the user metadata in the cachedUserMetadata property and a local copy of the app metadata in the cachedAppMetadata property. The app will use the data in these properties to update the user interface, as indicated by the comment // TODO: Update the UI based on the metadata.

Now that the app has both the user and app metadata, it can do something with them. Let’s start with the user metadata.

Update and Display the User’s Favorite Color Using the User Metadata

I described the user metadata as a place that stores user-related information that the user can edit. We’ll use it to store the name of the user’s favorite color.

Immediately after launching or when the user presses the Get favorite color button, the app should try to retrieve the user metadata value for the key named favorite_color. If such a value exists, the app fills the favoriteColorTextField control with that value.

The user should be able to update their existing favorite color (or add their favorite color, if it hasn’t yet been defined) by entering its name into the favoriteColorTextField control and pressing the Set favorite color button. When this happens, the app updates the user metadata by setting the value for the key favorite_color to the value contained in favoriteColorTextField.

Update the user’s favorite color in their user metadata

Let’s first create the method that takes the contents of favoriteColorTextField and writes it to the user’s user metadata.

🛠 Add the following immediately after the getMetadata() method:

func updateFavoriteColor() {
  // 1
  guard
    let accessToken = cachedCredentials?.accessToken,
    let userInfo = cachedUserInfo
  else {
    return
  }
  
  // 2
  let updatedFavoriteColor = favoriteColorTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines)
  
  Auth0
    // 3
    .users(token: accessToken)
    .patch(userInfo.sub, userMetadata: ["favorite_color": updatedFavoriteColor ?? ""])
    // 4
    .start { result in
      
      switch result {
        
        // 5
        case .failure(let error):
          print("Error: Couldn’t update 'favorite_color' in the user metadata.\n\(error.localizedDescription)")
        
        // 6
        case .success(let updatedUserMetadata):
          self.cachedUserMetadata = updatedUserMetadata
        
        } // switch
        
      } // start()
}

This new method, updateFavoriteColor() is different from the other methods in the app that call Auth0. Instead of reading current information about the user from Auth0, it writes updated information about the user — the user’s favorite color — to Auth0.

Here’s an explanation of each of the numbered comments in the code above.

  1. This guard statement allows the function to continue executing only if two conditions are met:
    • The app has retrieved a credentials object from Auth0, and that credentials object contains an access token.
    • The app has also retrieved a user profile information object from Auth0.
  2. Before storing the contents of favoriteColorTextField, this line strips it of any leading or trailing whitespace characters.
  3. This chain of methods does the following:
    • The users() method takes an access token — which comes from cachedCredentials — and returns an object representing the current user.
    • The patch() method specifies which user metadata should be updated. It takes the identifier for the user (the sub property of the user’s profile information object) and a dictionary of key-value pairs representing the updated metadata. It returns an object representing a request to Auth0 to update user metadata.
  4. start() takes the request object constructed by the previous methods in the chain and makes the request to update the user metadata. This method has a single parameter — a closure that takes an argument containing one of two possible values: .failure and .success.
  5. If the attempt to update the metadata fails, the app prints an error message to the console.
  6. If the attempt succeeds, the app stores a local copy of the newly-updated user metadata.

Enable the UI elements that update and display the user’s favorite color

Let’s connect the Get favorite color and Set favorite color buttons to the methods they call, then update favoriteColorTextField.

🛠 Update getFavoriteColorButtonPressed() and setFavoriteColorButtonPressed, located in the Actions section, to the following:

@IBAction func getFavoriteColorButtonPressed(_ sender: UIButton) {
  getMetadata()
}

@IBAction func setFavoriteColorButtonPressed(_ sender: UIButton) {
  updateFavoriteColor()
}

🛠 Add the following to the end of the UI Updaters section, immediately after the updateNameAndEmailUI() method:

func updateFavoriteColorUI() {
  favoriteColorTextField.text = cachedUserMetadata?["favorite_color"] as? String ?? ""
}

🛠 Replace the // TODO: Update the UI based on ***USER*** metadata comment inside getMetadata() so that the DispatchQueue.main.async block looks like this:

DispatchQueue.main.async {
  self.updateFavoriteColorUI()
              
  // TODO: Update the UI based on ***APP*** metadata
}

🛠 Run the app, enter a color into the text field, and tap the Set favorite color button:

Setting a favorite color in the app.

Let’s confirm that the user’s favorite color was stored in the user’s user metadata. You can do this by clearing the text field and then tapping the Get favorite color button, but there’s a more convincing way.

🛠 Go to the Auth0 dashboard and look at the user by selecting User Management in the left column menu, followed by Users, and then selecting the user currently logged into the app. You will be on that user’s page. Scroll down to the Metadata section of the page and look at the user_metadata field. You’ll see that it contains a JSON dictionary with a single entry with the key favorite_color and whatever value you entered for the favorite color:

The “user_metadata” section of the “User” page in the Auth0 dashboard.

🛠 While still on the user’s page, change the value for favorite_color, then click the Save button to save the change. Make sure that the “Metadata saved successfully!” notification appears:

Editing the “favorite_color” value in the user’s “user_metadata” section.

🛠 Keep the “User” page of the Auth0 dashboard open — you’ll need it for the next step.

🛠 Go back to the app and tap the Get favorite color button. The text field will show the new “favorite color” value:

Getting the favorite color in the app.

Display an Alert Box if the User’s App Metadata Contains a Specific Value

I described the app metadata as a place that stores user-related information that the user cannot edit; in fact, they might not even know it exists. The app can only read the user’s app metadata; they can’t write to it. Typically, a back-end application updates users’ app metadata.

This app will display an alert box with a short message at launch if the user’s app metadata contains a key named make_announcement and if that key’s value is set to true.

First, let’s build a method to check the user’s app metadata and display the alert box if the conditions are met.

🛠 Add the following to the end of the UI Updaters section, immediately after the updateFavoriteColorUI() method:

func showOptionalAnnouncement() {
  if let makeAnnouncement = cachedAppMetadata?["make_announcement"] as? Int {
    if makeAnnouncement != 0 {
      let title = "Announcement"
      let message = "If you’re seeing this, it means that the ’make_announcement’ " +
                    "flag in ’app_data’ is set to ’true’."
      let alert = UIAlertController(
        title: title, message: message, preferredStyle: .alert)
      alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
      present(alert, animated: true, completion: nil)
    }
  }
}

🛠 Replace the // TODO: Update the UI based on ***APP*** metadata comment inside getMetadata() so that the DispatchQueue.main.async block looks like this:

DispatchQueue.main.async {
  self.updateFavoriteColorUI()
              
  if self.userIsNewlyAuthenticated {
    self.showOptionalAnnouncement()
    self.userIsNewlyAuthenticated = false
  }
}

The app can now check the user’s app metadata for the presence of the make_announcement key and its value.

🛠 Go back to the user’s “User” page in the Auth0 dashboard. Change the contents of the app_metadata field in the Metadata section to the following:

{
  "make_announcement": true
}

🛠 When you have updated the app metadata, click the Save button. Make sure that the “Metadata saved successfully!” notification appears:

The “app_metadata” field of the “User” page, with the updated metadata.

🛠 Restart the app and log in. It will display the announcement:

The app, displaying the alert.

Implement Logout

There’s only one thing left to do: implement logout!

🛠 Add this method to the end of the Login, logout, and user information section, just after the updateFavoriteColor() method:

func logout() {
  Auth0
    .webAuth()
    .clearSession(federated: false) { result in
      
      if result {
        print("Session cleared!")
        self.userIsAuthenticated = false
        self.cachedCredentials = nil
        self.cachedUserInfo = nil
        self.cachedUserMetadata = nil
        self.cachedAppMetadata = nil
          
        DispatchQueue.main.async {
          self.updateTitle()
          self.updateButtonsAndStack()
        }
      } else {
        print("Session NOT cleared -- time to start debugging.")
      }
      
    } // clearSession()
}

The call to webAuth() should seem familiar. That’s because it’s the same method you used for the login process.

As with the login() method, webAuth() returns a WebAuth object. Unlike login(), logout() uses WebAuth’s clearSession() method, which logs the user out.

clearSession()’s final parameter is a closure that takes a boolean argument. This argument is true if the user has successfully logged out. When this happens, we set the userIsAuthenticated property to false, clear all the cached values received from Auth0, and update the user interface.

🛠 The final step is to connect the Log Out button to the logout() method. Update the logoutButtonPressed() method so that it looks like this:

@IBAction func logoutButtonPressed(_ sender: UIButton) {
  if userIsAuthenticated {
    logout()
  }
}

🛠 Run the app. The Log Out button should work now.

Conclusion

You’ve just built a simple iOS app that features basic username/password authentication — the ability to identify a known user. In addition to logging a user in and out, you can also:

  • Retrieve basic user information from their user profile,
  • read and update additional information from their user metadata, and
  • read application-specific information from the user’s app metadata.

In addition to the .zip file of the starter project (225 KB), you can also download a .zip file of the completed project (234 KB). Just remember that the best way to understand how the app integrates with Auth0 is to start with the starter project and build it following the steps above!

You can also find the starter and completed projects in the get-started-ios-authentication-swift-uikit repository on the Auth0 Blog Samples GitHub account.

This is the first in a new series of articles on iOS development with Auth0. Future articles will cover an implementation of this app using SwiftUI, as well as deeper dives into authentication and authorization with iOS and Auth0 and alternatives to the standard username-and-password approach. Watch this space!

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon