Packages/kandi-login/iOS (Swift)

kandi-login / iOS (Swift)

Coming Soon

Native iOS and macOS authentication using ASWebAuthenticationSession for OAuth and Keychain for secure token storage. Works with SwiftUI and UIKit.

Download Example Project

Quick Start

1

Clone the example project

git clone https://github.com/KandiForge/kandi-packages.git
cd kandi-packages/examples/ios
2

Open in Xcode

open KandiLoginExample.xcodeproj
3

Build and run on a simulator or device

4

Tap "Login" — ASWebAuthenticationSession handles the OAuth flow natively

Auth Configuration

KandiAuthConfig.swift
// KandiAuthConfig.swift
import KandiLogin

let authConfig = KandiAuthConfig(
    authServerUrl: "https://api.packages.kandiforge.com",
    authBasePath: "/api/auth",
    providers: [.google, .github],
    // Tokens stored in Keychain
    keychainAccessGroup: "com.yourapp.shared"
)

Switch to Your Server

The example app points to the KandiForge reference server by default. Once you have your own server running:

// Change authServerUrl to your own server: authServerUrl: "https://your-api.example.com"

Run the conformance validator against your server to confirm it implements all required endpoints.

Test Personas for Automated Testing

Use the test persona system to authenticate in XCTest, and XCUITest tests without launching a browser OAuth flow.

Tests/AuthTestSetup.swiftNo browser needed
// Tests/AuthTestSetup.swift
import XCTest
@testable import KandiLogin

class AuthTestSetup: XCTestCase {
    func authenticateAsPersona(_ personaId: String) async throws -> AuthTokens {
        let baseURL = URL(string: "https://your-api.example.com/api/auth")!

        // Seed personas (idempotent)
        var seedReq = URLRequest(url: baseURL.appendingPathComponent("test/seed"))
        seedReq.httpMethod = "POST"
        let _ = try await URLSession.shared.data(for: seedReq)

        // Get real JWT tokens — no ASWebAuthenticationSession needed
        var loginReq = URLRequest(url: baseURL.appendingPathComponent("test/login-as"))
        loginReq.httpMethod = "POST"
        loginReq.setValue("application/json", forHTTPHeaderField: "Content-Type")
        loginReq.httpBody = try JSONEncoder().encode(["personaId": personaId])

        let (data, _) = try await URLSession.shared.data(for: loginReq)
        return try JSONDecoder().decode(AuthTokens.self, from: data)
    }
}

Default personas: admin-alex, designer-dana, viewer-val, new-user-naya. See all personas