OnlineWithAuthentication: Swift Tutorial
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.
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.
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:
- Gets the access token from Auth0;
- Starts the Ditto instance; and
- Creates a liveQuery
The provider name given to the Ditto Client must match a provider name in the Portal (e.g., replit-auth).
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:
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:
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
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.
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.