Sign Up
Hero

ChatGPT Tries to Add Auth0 Authentication to an iOS App, Round Two!

We asked ChatGPT to write code to integrate Auth0 into an iOS app after its initial release. The code didn’t work then. Will it work this time?

What Happened in Round One

Barely two weeks after the initial release of ChatGPT, I tidied up my home office/studio, put on my newest Aloha shirt, and started asking the newly-released AI some questions about OAuth and Auth0. The result was the video below, which you’ll find on the OktaDev YouTube channel: Chatting with ChatGPT About Auth0:

ChatGPT gave correct answers to these questions:

  • “What is OAuth?” and a follow-up question where I asked it for a more layperson-friendly version of its initial answer.
  • “What is OIDC?” and the same follow-up question as my previous one.
  • What is an ID token?
  • What is an access token?
  • What is a refresh token?
  • Are there security downsides to using refresh tokens with indefinite lifespans?

ChatGPT didn’t do as well when I asked it, “How do I write an iOS app that uses Auth0 for login?” The set of steps it suggested was correct, but it said that I should use the Auth0.authentication() method to make the Universal Login screen appear. No such method exists.

And Now, Round Two!

Half a year has passed since I asked ChatGPT about adding Auth0 authentication to an iOS app. Over that period, OpenAI’s developers added a newer model, GPT 4.0, which you can access only if you have a paid account. They also made adjustments and improvements to the original GPT 3.5 model, which powers the free version of ChatGPT.

I decided to see if the free version of ChatGPT could give me a working iOS authentication code this time. I asked it “How do I write an iOS app that uses Auth0 for login and logout?”

Here’s ChatGPT’s response, which I’ve condensed slightly for brevity:

  1. Set up an Auth0 account.
  2. Create an iOS project. Set it up with the desired UI and navigation flow for the login and logout functionality.
  3. Install the Auth0 SDK. You can use CocoaPods or Swift Package Manager to install the SDK dependencies.
  4. Configure your Auth0 application by providing the necessary settings, such as allowed callback URLs and logout URLs.
  5. Implement login functionality: Create a login view controller or integrate the login functionality into an existing view controller. Use the Auth0 SDK to initiate the login process by presenting the Auth0 login screen. The user will be able to enter their credentials or choose a social login option.
  6. Handle login callback: Once the user successfully logs in, Auth0 will redirect them back to your app. You need to implement the callback handling in your app to receive the authentication result. This typically involves handling a URL scheme or Universal Link that Auth0 uses to redirect back to your app.
  7. Store and manage tokens: When the login is successful, Auth0 provides an access token and, optionally, a refresh token. You'll need to store these tokens securely, such as using the iOS Keychain, and handle token refreshing when necessary.
  8. Implement logout functionality: Create a logout function that clears the stored tokens and logs out the user. You can use the Auth0 SDK to perform the logout operation.
  9. Secure API requests: To secure your app's API requests, include the access token in the authorization header when making requests to your backend services. Ensure that your backend verifies the authenticity of the token with Auth0.
  10. Test and refine: Thoroughly test your app's login and logout functionality to ensure it works as expected. Make any necessary refinements or enhancements based on user experience and feedback.

I then asked “Could you show me some Swift code that uses Auth0 for login and logout?”

Its response: “Certainly! Here's an example of Swift code that demonstrates how to use Auth0 for login and logout in an iOS app,” followed by this code:

import Auth0

class Auth0Manager {
    let auth0 = Auth0.webAuth(clientId: "YOUR_CLIENT_ID", domain: "YOUR_DOMAIN")

    func login(completion: @escaping (Error?) -> Void) {
        auth0
            .scope("openid profile")
            .audience("https://YOUR_API_IDENTIFIER")
            .start { result in
                switch result {
                case .success(let credentials):
                    // Successful login
                    let accessToken = credentials.accessToken
                    let idToken = credentials.idToken
                    // Store the tokens securely

                    completion(nil)

                case .failure(let error):
                    // Handle error
                    completion(error)
                }
            }
    }

    func logout() {
        auth0.clearSession(federated: true) { result in
            switch result {
            case .success:
                // Successful logout
                // Clear stored tokens and any other user data

            case .failure(let error):
                // Handle error
                print("Logout error: \(error)")
            }
        }
    }
}

My thought was probably what you’re thinking right now: Does this code work?

I opened a copy of the iOS project I use for demonstrating Auth0 authentication, which is based on the Auth0 iOS Quickstart app (you can download a version configured with your Auth0 tenant settings or get it from this Github repository). I added a new file to the project, Auth0Manager.swift, and pasted the class that ChatGPT generated there. I replaced these placeholder values...

  • YOUR_CLIENT_ID with my app’s client ID
  • YOUR_DOMAINwith my tenant’s domain
  • YOUR_API_IDENTIFIER with https://YOUR_DOMAIN/api/v2/, replacing YOUR_DOMAIN with my tenant’s domain

…and replaced my demo app’s calls to its original login() and logout() methods with calls to the versions that ChatGPT provided.

I ran the app...

...and it worked! It appears that this time, ChatGPT — and the free version at that — got it right.

A Closer Look At ChatGPT’s Code

Now that it’s clear that ChatGPT now knows how to write code to integrate Auth0 into an iOS app let’s take a look at the way it did this.

To make this exercise a little more fun, I’ll keep a running score where I’ll award points for what I thought were good things that ChatGPT did and deduct points for things that I thought were bad ones. Watch for the picture of the gavel for these scores.

The Auth0Manager class

This is different from a lot of tutorial code, which puts the Auth0 logic and data — thelogin() and logout() methods as well as the variables to hold the tokens that Auth0 returns — inside the view object that makes the call to display the Universal Login screen or log the user out. Many tutorials take this approach because it shows all the code in a single file, which is useful when you’re trying to explain something new to the reader.

In a real iOS app (as opposed to a tutorial app), you’d probably take the same approach as ChatGPT’s and put the authentication and authorization code in its own class. In fact, that’s what we do in tutorials showing how to integrate Auth0 into Android apps since they rely heavily on ViewModels that inherit from Android’s built-in ViewModel class.

I’ve decided to follow ChatGPT’s example and put Auth0 code in its own class in my tutorials from now on to make my tutorial code a little closer to real-world code. For this idea, I’ll award ChatGPT 1 point.

The WebAuth instance

ChatGPT’s Auth0Manager class has a single property, auth0, a WebAuth instance whose definition is as follows:

let auth0 = Auth0.webAuth(clientId: "YOUR_CLIENT_ID", domain: "YOUR_DOMAIN")

Auth0.webAuth() is a function in the Auth0.Swift library that creates an instance of the WebAuth class, which provides the necessary methods for logging a user in and out. Both the login() and logout() methods use this instance.

ChatGPT’s solution uses the version of Auth0.webAuth() that takes two arguments: the client ID and the domain. It’s the most straightforward way to show how you can use Auth0 to implement login and logout in an app, but you probably wouldn’t do this in a production application since you’d be hard-coding server credentials.

Fortunately, there’s another form of Auth0.webAuth() that doesn’t take arguments for the client ID and domain. In fact, it doesn’t take any arguments, and you’d use it like this:

let auth0 = Auth0.webAuth()

Instead of relying on arguments that you pass to it, this form of Auth0.webAuth() looks through the app’s property lists (a.k.a. plists) for values corresponding to the following keys:

  • ClientId for the client ID
  • Domain for the tenant domain

You would typically create a new property list — usually named Auth0.xml — and store the ClientID and Domain values there. Here’s what it would look like in an Xcode project if you prefer editing property lists visually...

...and here’s how it would look if you prefer editing property lists as source code:

<?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>Domain</key>
    <string>** Put your tenant’s domain here ***</string>
    <key>ClientId</key>
    <string>** Put your app’s client ID here ***</string>
</dict>
</plist>

Putting the client ID and domain values in their own file makes it easy to exclude them from version control, which makes it less likely that you’ll unintentionally share these values with people outside your organization.

ChatGPT’s implementation works when trying to explain how to implement something, which earns it 1 point. However, its implementation isn’t one that you should use in a production application, and it should have explained this fact, so I’m deducting the point I just gave it. ChatGPT’s current score is holding at 1 point.

The login() method

Let’s take a look at ChatGPT’s login() method:

func login(completion: @escaping (Error?) -> Void) {
    auth0
        .scope("openid profile")
        .audience("https://YOUR_API_IDENTIFIER")
        .start { result in
            switch result {
            case .success(let credentials):
                // Successful login
                let accessToken = credentials.accessToken
                let idToken = credentials.idToken
                // Store the tokens securely

                completion(nil)

            case .failure(let error):
                // Handle error
                completion(error)
            }
        }
}

Its login() implementation is mostly the same as the login() methods in our tutorials or our iOS Quickstart app. There’s the usual method chaining from the webAuth instance, where the methods are called in this order:

  • scope() to request the minimum amount of scopes necessary to authenticate a user:
    • openid to indicate that we want to use OpenID Connect (OIDC) to verify the user’s identity. Since Auth0 authentication is OIDC-based, Any application using Auth0 must request this scope.
    • profile to retrieve the user’s name.
    • The ChatGPT code doesn’t include a request for the email scope, which I would recommend, simply for access to the user’s email address, which is typically how an app would send out-of-band communications to the user.
  • audience() to specify one or more APIs that the access token that Auth0 returns should grant access to. In the rare case that you’re using Auth0 for authentication only, you can skip this method call. In my case, I passed it the string https://MY_DOMAIN/api/v2/, replacing MY_DOMAIN with my tenant’s domain. This is the URL for the Auth0 user management API, which is useful for accessing the user’s app and user metadata.
  • start() to indicate what the app should do in the cases where the user was able to log in and where they weren’t.

The big difference between ChatGPT’s implementation of login() and the ones we’ve been using here on the Auth0 blog and in our Quickstarts can be seen in the login() method’s signature:

func login(completion: @escaping (Error?) -> Void)

ChatGPT’s version of login() has a parameter, completion, that takes an argument of type (Error?) -> Void. This argument is a closure that takes an argument of type Error? (a value whose type is Error, a subclass of Error, or nil) and does not return a value. The @escaping annotation informs Swift that the closure provided to login() might outlive or go beyond login()’s scope.

The completion parameter allows the code that calls login() to perform additional actions upon successful login and also in case login fails:

  • If the user logged in successfully, login() calls completion(nil).
  • If the user’s attempt to log in failed, login() calls completion(error), where error is the Error instance returned by start() method of the WebAuth instance contained in the auth0 property.

The code calling login() must provide a closure argument that specifies what extra actions should happen whether the login succeeds or fails. For example, here’s the code in my demo app that calls Auth0NManager’s login() method:

auth0Manager.login() { error in
    if let error = error {
        print("Login error!")
        isAuthenticated = false
    } else {
        print("Login successful!")
        isAuthenticated = true
    }
}

I like ChatGPT’s addition of the completion parameter and feel no shame in stealing this idea (is it even possible to steal from an AI?). I’m granting ChatGPT 1 point, resulting in a total of 2 points so far.

The logout() method

Here’s ChatGPT’s logout() method:

func logout() {
    auth0.clearSession(federated: true) { result in
        switch result {
        case .success:
            // Successful logout
            // Clear stored tokens and any other user data
            print("Logged out!")
            
        case .failure(let error):
            // Handle error
            print("Logout error: \(error)")
        }
    }
}

ChatGPT’s implementation of the logout() method is pretty much the same as the logout() methods in our tutorials or our iOS Quickstart app.

I would steal ChatGPT’s trick from its login() method and add a completion parameter that takes a closure that performs additional actions for both successful and unsuccessful logouts. My implementation would look something like this:

func logout(completion: @escaping (Error?) -> Void) {
    auth0.clearSession(federated: true) { result in
        switch result {
        case .success:
            // Successful logout
            // Clear stored tokens and any other user data
            completion(nil)
            
        case .failure(let error):
            // Handle error
            completion(error)
        }
    }
}

And here’s how I’d call the logout() method:

auth0Manager.logout() { error in
    if let error = error {
        print("Logout error!")
    } else {
        print("OK")
        isAuthenticated = false
    }
}

ChatGPT didn’t provide a completion parameter for the logout() method as it did for the login() method. I won’t award it a point, but I won’t take one away, either. At the end of this exercise, ChatGPT has 2 points. Not bad!

Is Your Developer Job Safe?

Considering that it’s been only half a year since we last tested ChatGPT’s ability to write code that integrates Auth0 into iOS apps, this is an impressive improvement. You might be wondering what kinds of leaps it might make over the next couple of years if AIs keep advancing at the same pace, and more importantly, what this means for your career as a developer.

I don’t think of AIs like ChatGPT as a threat. Instead, I think of them as part of a long series of cognitive aids that include tally sticks, the abacus, writing, logarithmic tables, and slide rules, as well as their more modern counterparts such as the combination of search engines and Stack Overflow, code completion, and GitHub Copilot. I think that AIs can enhance what a human programmer can do and even provide ideas and inspiration that they might not think of. After all, ChatGPT’s answer provided me with a couple of ideas that I’m going to use in future code.

You might find it helpful to read this IEEE Spectrum article, How Coders Can Survive—and Thrive—in a ChatGPT World, which lists four ways in which developers can stay ahead of generative AIs. It captures my thinking about AI and programming in its final paragraph:

For programmers to survive in a generative AI world, they’ll need to embrace AI as a tool and incorporate AI into their workflow while recognizing the opportunities and limitations of these tools—and still relying on their human coding capabilities to thrive.