iOS

Get Started with iOS Authentication using SwiftUI, Part 2: User Profiles

Learn how to use Auth0 and user profiles to basic information about logged-in users in iOS apps built with SwiftUI.

March 30, 2022

iOS

Get Started with iOS Authentication using SwiftUI, Part 2: User Profiles

Learn how to use Auth0 and user profiles to basic information about logged-in users in iOS apps built with SwiftUI.

March 30, 2022

In the previous part of this tutorial, you created a SwiftUI-based iOS app that uses Auth0 for basic login and logout functionality. In this part, you’ll enhance your app by giving it the ability to read the user’s profile to display their photo, name, and email address when they’re logged in.

Display the User’s Profile Information

The app only “knows” whether the user is logged in or logged out in its current state. It also has information about the user’s identity, which is encoded in the ID token. Let’s extract this user information — the user profile — and use it to display the user’s name, email address, and picture when the user is logged in.

When you’ve completed this section, the app’s “logged in” screen will look something like this:

The app’s “logged in” screen, now displaying the user’s picture, name, and email address.

Create a data structure for the user profile

We'll need to build a data structure to store it before we can extract the user’s profile information from the ID token. Let’s create a struct for that purpose.

🛠 Create a new Swift file for the struct by right-clicking (or control-clicking) the SwiftUI Login Demo folder in Project Explorer and selecting New File…:

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

🛠 In the window that appears, make sure that the iOS tab is selected. Select Swift File, then click Next:

Xcode screenshot, showing the selection of the “Swift File” file template..

🛠 Name the file Profile.swift and click Create to create the file:

Xcode screenshot, showing the “Save” dialog box for the Swift file.

Now that there’s a Profile struct, let’s implement it a little bit at a time.

🛠 Replace the contents of Profile with the following:

// 1
import JWTDecode


// 2
struct Profile {
  let id: String
  let name: String
  let email: String
  let emailVerified: String
  let picture: String
  let updatedAt: String
}

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

  1. The ID token’s format is JWT, or JSON Web Token. The JWTDecode library allows you to decode the token to extract the user profile information it contains.
  2. This code defines Profile, the struct that will decode and store the user profile information in the ID token. It has the following properties, all of which are strings:

    • id: The user’s unique identifier
    • name: The user’s full name
    • email: The user’s email address
    • emailVerified: true if the user verified their email address with Auth0, false otherwise
    • picture: The URL for the user’s picture
    • updatedAt: The date and time when the user profile was last updated

🛠 Add the following after the definition of Profile:

extension Profile {

}

Once again, we’re using an extension to break a struct into smaller parts that are easier to read and update. This extension will contain methods and variables that act like methods.

🛠 Add the following variable declaration inside the newly-created extension:

  static var empty: Self {
    return Profile(
      id: "",
      name: "",
      email: "",
      emailVerified: "",
      picture: "",
      updatedAt: ""
    )
  }

This defines empty, a static variable that acts more like a function. It creates a new Profile instance whose properties are all set to empty strings.

🛠 Add this function to the extension immediately after the declaration for empty:

static func from(_ idToken: String) -> Self {
  guard 
    let jwt = try? decode(jwt: idToken),
        let id = jwt.subject,
        let name = jwt.claim(name: "name").string,
        let email = jwt.claim(name: "email").string,
        let emailVerified = jwt.claim(name: "email_verified").boolean,
        let picture = jwt.claim(name: "picture").string,
        let updatedAt = jwt.claim(name: "updated_at").string
  else {
    return .empty
  }
  
  return Profile(
    id: id,
    name: name,
    email: email,
    emailVerified: String(describing: emailVerified),
    picture: picture,
    updatedAt: updatedAt
  )
}

Given an ID token string, the from() function creates a Profile instance. If from() is able to extract claims — values about the user’s identity, which include their name, email address, and the URL for their picture — from the ID token, it returns a Profile instance with that information in its properties. Otherwise, it returns a Profile instance with empty properties.

Update the user interface

Now that there’s a way to extract information from an ID token, let’s put it to use. In this step, we’ll make changes to the user interface that will allow it to display the logged-in user’s name, email address, and picture.

🛠 Open ContentView and add a new property immediately after the one for isAuthenticated. Note where the comments tell you to add the new code:

  @State private var isAuthenticated = false
  // 👇🏽👇🏽👇🏽 New line of code here!
  @State var userProfile = Profile.empty
  // 👆🏽👆🏽👆🏽

This creates userProfile, an instance of Profile that will contain information about the user when they log in. The app will use its properties to display the user’s name, email address, and picture.

Since userProfile is marked with the @State attribute, it defines the user interface’s state. Whenever userProfile or one of its properties changes, the app redraws the user interface to reflect the change.

🛠 Add user interface elements to the body property to display the user’s information. Once again, the comments will tell you where to add new code:

  var body: some View {

    if isAuthenticated {
      
      // 3
      VStack {
        
        // 4
        Text("Logged in")
          .padding()
        
        // 👇🏽👇🏽👇🏽 New lines of code here!
        AsyncImage(url: URL(string: userProfile.picture)) { image in
          image
            .frame(maxWidth: 128)
        } placeholder: {
          Image(systemName: "photo.circle.fill")
            .resizable()
            .scaledToFit()
            .frame(maxWidth: 128)
            .foregroundColor(.gray)
            .opacity(0.5)
        }
        .padding(40)
        
        VStack {
          Text("Name: \(userProfile.name)")
          Text("Email: \(userProfile.email)")
        }
        .padding()
        // 👆🏽👆🏽👆🏽
        
        // 5
        Button("Log out") {
          logout()
        }
        .padding()
        
      }
      
    } else {
    
    // The rest of the “body” property goes here...

The new code adds two new views to ContentView:

  1. An AsyncImage, a view that will download the user’s picture asynchronously and displays it when the download is complete. It uses the placeholder option to display the camera icon from iOS’s built-in icon collection while downloading the user’s picture.
  2. A VStack contains two Text views: one to display the user’s name and one for the user’s email address.

🛠 Scroll to the extension where the methods for ContentView are defined and update login() to extract the user’s profile information from the ID token. The comments in the code below will tell you where to add the new line:

  func login() {
    Auth0 // 1
      .webAuth() // 2
      .start { result in // 3
        switch result {
          // 4
          case .failure(let error):
            print("Failed with: \(error)")
          // 5
          case .success(let credentials):
            self.isAuthenticated = true
            // 👇🏽👇🏽👇🏽 New line of code here!
            self.userProfile = Profile.from(credentials.idToken)
            // 👆🏽👆🏽👆🏽
            print("Credentials: \(credentials)")
            print("ID token: \(credentials.idToken)")
        }
      }
  }

The newly-added line of code runs after the user logs in and the app receives the ID token. It creates a new Profile instance from the data in the ID token and sets ContentView’s userProfile property to that instance.

Run the app

Run the app and confirm that the changes work. When you log in, you should see the user’s name, email address, and picture.

Next Steps

You’ve covered a lot of ground in this exercise! Not only did you build an iOS app using the SwiftUI framework, but you also gave it the ability to authenticate users with Auth0.

You can download a completed project in the get-started-ios-authentication-swiftui repository on the Auth0 Blog Samples GitHub account.

SwiftUI is an expansive topic, and we only scratched its surface in this tutorial. For your next steps in exploring it, you might want to look at these useful resources:

This is the second in a new series of articles on iOS development with Auth0. Future articles will feature 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