For the full source code, see the getditto > sample-authentication-permissions repository in GitHub.

Assuming you have a login button in your SwiftUI ContentView, we want to create a new ObservedObject that we can subscribe to for updates to the authentication status.

Swift
class ProfileViewModel: ObservableObject {
  // your authentication code will go here
}

struct ContentView: View {
    @ObservedObject var viewModel: ProfileViewModel = ProfileViewModel()

    var body: some View {
      Button("Login").padding()
    }
}

We attach a login function to the button.

Swift
class ProfileViewModel: ObservableObject {
  let credentialsManager = CredentialsManager(authentication: Auth0.authentication())

  func login () {
      Auth0
          .webAuth()
          .scope("openid profile")
          .audience("https://ENTER_YOUR_SCOPE_URL_HERE.auth0.com/userinfo")
          .start { result in
              switch result {
              case .success(let credentials):
                  print("Obtained credentials: \(credentials)")
                  self.credentialsManager.store(credentials: credentials)
                  self.startDitto()
              case .failure(let error):
                  print("Failed with: \(error)")
                  // Handle Error
              }
          }
  }
}

struct ContentView: View {
    @ObservedObject var viewModel: ProfileViewModel = ProfileViewModel()

    var body: some View {
      Button("Login", action: viewModel.login).padding()
    }
}

We can then create a startDitto function that:

  1. Gets the access token from Auth0;
  2. Starts the Ditto instance; and
  3. Creates a liveQuery

The provider name given to the Ditto Client must match a provider name in the Portal (e.g., replit-auth).

Swift
class ProfileViewModel: ObservableObject {
    @Published var ditto: Ditto?
    @Published var docs: [DittoDocument] = []
    ....

    func startDitto () {
      // 1. Get the access token from Auth0
        credentialsManager.credentials { error, credentials in
            guard error == nil, let credentials = credentials else {
                // Handle error
                return
            }

            guard let accessToken = credentials.accessToken else {
                // Handle Error
                return
            }
            self.authDelegate = AuthDelegate(token: accessToken)

            // 2. Start the Ditto instance
            let identity = DittoIdentity.onlineWithAuthentication(
                appID: "YOUR_APP_ID_HERE",
                authenticationDelegate: self.authDelegate
            )

            let ditto = Ditto(identity: identity)
            try! ditto.startSync()

            // 3. Create a liveQuery
            self.ditto = ditto
            let subscription = ditto.store.collection("cars").find("state == 'FOR_SALE'").subscribe()
            let liveQuery = ditto.store.collection("cars").find("name == 'Toyota'").observeLocal { docs, event in
                self.docs = docs
            }

        }
    }
}

To pass the token to your server route you created in the previous section, you need to create an AuthDelegate class that is passed to the Ditto constructor:

Swift
class AuthDelegate: DittoAuthenticationDelegate {
    var token: String

    init (token: String) {
        self.token = token
    }

    func authenticationRequired(authenticator: DittoAuthenticator) {
        authenticator.login(self.token, provider: "replit-auth") { clientInfo, err in
            print("Login request completed. Error? \(err)")
        }
    }

    func authenticationExpiringSoon(authenticator: DittoAuthenticator, secondsRemaining: Int64) {
        print("Auth token expiring in \(secondsRemaining)")
        authenticator.login(self.token, provider: "replit-auth") { clientInfo, err in
            print("Login request completed. Error? \(err)")
        }
    }
}

Our ContentView can now display the number of cars, and you can add a button for adding an item to the database:

Swift

struct ContentView: View {
    @ObservedObject var viewModel: ProfileViewModel = ProfileViewModel()

    var body: some View {
          Button("Login", action: viewModel.login)
              .padding()
        Text("Cars:" + String(viewModel.docs.count))
        // Bonus points: implement addItem button using Ditto's `upsert`
        Button("+1", viewModel.addItem)

    }
}

Log out​

Swift
class ProfileViewModel: ObservableObject {
  @Published private(set) var state = State.isLoading

  enum State {
      case isLoading
      case loaded(UserInfo)
  }

}

struct ContentView: View {
    @ObservedObject var viewModel: ProfileViewModel = ProfileViewModel()

    var body: some View {

        switch viewModel.state {
          case .isLoading:
              Button("Login", action: viewModel.login)
          case .loaded(let user):
              Text(user.name ?? "Anonymous Ditto User")
              Button("Logout", action: viewModel.logout)
          }

        Text("Cars:" + String(viewModel.docs.count))

    }
}

And then we can write the logout function and attach it to the button.

We also recommend calling ditto.auth.logout with a callback function that evicts any data from the local database.

Swift
class ProfileViewModel: ObservableObject {
  ...

  func logout () {
      Auth0
          .webAuth()
          .clearSession(federated: false) { result in
              if result {
                  if (self.ditto != nil) {
                    // Clean up the cars collection after logout
                      self.ditto!.auth?.logout(cleanup: { ditto in
                          ditto.store.collection("cars").findAll().evict()
                      })
                  }
                  self.state = State.isLoading
              }
          }
  }

}

To make this usable for real-world applications, you can retrieve the user’s profile details such as email, username, and full name. See the official Auth0 documentation for your platform to add that functionality to your application

Yay! You now have a fully functioning onlineWithAuthentication app. Build and run it on a device.