Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.prelude.so/llms.txt

Use this file to discover all available pages before exploring further.

Log in a user

Submit the user’s email and password. On success, the SDK caches the access token and attaches it to subsequent protected requests.
import PreludeSession

do {
    let user = try await client.loginWithPassword(
        LoginWithPasswordOptions(
            emailAddress: "user@example.com",
            password: "SecureP@ssw0rd!"
        )
    )
    // User is now authenticated.
} catch PreludeSessionError.unauthorized {
    // Invalid credentials
} catch PreludeSessionError.invalidPassword {
    // Password does not meet the policy
} catch PreludeSessionError.badRequest {
    // Invalid email format
} catch PreludeSessionError.rateLimited {
    // Too many login attempts
}
The password is wrapped in RedactedString internally — LoginWithPasswordOptions is safe to print or dump, and the SDK never persists the plaintext.
Replace ContentView.swift with:
ContentView.swift
import SwiftUI
import PreludeSession

private let appID = "YOUR_APP_ID"

@MainActor
final class LoginModel: ObservableObject {
    let client = try! PreludeSessionClient(
        endpoint: .custom("https://\(appID).session.prelude.dev")
    )

    @Published var user: PreludeUser?
    @Published var error: String?

    func login(email: String, password: String) async {
        error = nil
        do {
            user = try await client.loginWithPassword(
                LoginWithPasswordOptions(emailAddress: email, password: password)
            )
        } catch PreludeSessionError.unauthorized {
            error = "Invalid email or password."
        } catch PreludeSessionError.invalidPassword {
            error = "Password doesn't meet the requirements."
        } catch PreludeSessionError.badRequest {
            error = "Invalid email format."
        } catch PreludeSessionError.rateLimited {
            error = "Too many attempts. Please try again later."
        } catch {
            self.error = "Something went wrong. Please try again."
        }
    }
}

struct ContentView: View {
    @StateObject private var model = LoginModel()
    @State private var email = ""
    @State private var password = ""

    var body: some View {
        VStack(spacing: 12) {
            if let user = model.user {
                Text("Logged in").font(.headline)
                Text(user.profile.userID ?? "—").font(.caption.monospaced())
            } else {
                TextField("Email", text: $email)
                    .keyboardType(.emailAddress)
                    .textInputAutocapitalization(.never)
                SecureField("Password", text: $password)
                if let error = model.error {
                    Text(error).foregroundStyle(.red).font(.caption)
                }
                Button("Log In") {
                    Task { await model.login(email: email, password: password) }
                }
                .buttonStyle(.borderedProminent)
            }
        }
        .textFieldStyle(.roundedBorder)
        .padding()
    }
}

Validate a password (optional)

Your application can require passwords to meet certain rules — for example, a minimum length and a mix of uppercase, lowercase, number, or symbol characters. These rules are configured per application and enforced on both the client and the server. Before submitting a sign-up form, validate the password against the configured rules. The SDK fetches the rules from the server and checks the password locally:
let results = try await client.validatePassword("SecureP@ssw0rd!")

if !results.valid {
    for result in results.results where !result.valid {
        print("\(result.criterion): expected \(result.expected), got \(result.actual)")
    }
}
If you already have the rules in hand — for example, fetched once and cached — you can validate without an extra round-trip:
let compliancy = try await client.passwordCompliancy()
let results = PreludeSessionClient.validate(
    password: "SecureP@ssw0rd!",
    against: compliancy
)
Character classification uses Unicode generalCategory and counts code points, so passwords containing emoji or combining sequences classify consistently.
Replace ContentView.swift with:
ContentView.swift
import SwiftUI
import PreludeSession

private let appID = "YOUR_APP_ID"

@MainActor
final class ValidatorModel: ObservableObject {
    let client = try! PreludeSessionClient(
        endpoint: .custom("https://\(appID).session.prelude.dev")
    )

    @Published var results: PreludePasswordCompliancyResults?

    func validate(_ password: String) async {
        guard !password.isEmpty else { results = nil; return }
        results = try? await client.validatePassword(password)
    }
}

struct ContentView: View {
    @StateObject private var model = ValidatorModel()
    @State private var password = ""

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Password Validator").font(.title3)
            SecureField("Type a password…", text: $password)
                .textFieldStyle(.roundedBorder)
                .onChange(of: password) { _, new in
                    Task { await model.validate(new) }
                }
            if let results = model.results {
                ForEach(results.results, id: \.criterion) { r in
                    HStack {
                        Image(systemName: r.valid ? "checkmark.circle.fill" : "xmark.circle.fill")
                            .foregroundStyle(r.valid ? .green : .red)
                        Text("\(r.criterion.rawValue): \(r.actual)/\(r.expected)")
                            .font(.callout.monospaced())
                    }
                }
                Text(results.valid ? "Password is valid" : "Password does not meet requirements")
                    .font(.headline)
                    .foregroundStyle(results.valid ? .green : .red)
            }
        }
        .padding()
    }
}