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.
Quick Start
1
Clone the example project
git clone https://github.com/KandiForge/kandi-packages.git
cd kandi-packages/examples/ios2
Open in Xcode
open KandiLoginExample.xcodeproj3
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