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:
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…:
🛠 In the window that appears, make sure that the iOS tab is selected. Select Swift File, then click Next:
🛠 Name the file Profile.swift
and click Create to create the 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:
- 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. This code defines
Profile
, thestruct
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 identifiername
: The user’s full nameemail
: The user’s email addressemailVerified
:true
if the user verified their email address with Auth0,false
otherwisepicture
: The URL for the user’s pictureupdatedAt
: 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
:
- An
AsyncImage
, a view that will download the user’s picture asynchronously and displays it when the download is complete. It uses theplaceholder
option to display the camera icon from iOS’s built-in icon collection while downloading the user’s picture. - A
VStack
contains twoText
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:
- SwiftUI by Example
- Your First SwiftUI App
- Stanford University’s “Developing Applications for iOS” course for Spring 2021
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!