@tijs/atproto-oauth-hono from 0.3.1 to 0.4.0backend/services/auth.ts with kipclip-style auth helpers:
getAuthSession(req) - Cookie-based authentication with automatic token
refreshclearSessionCookie() - Generate cookie clear headerunauthorizedResponse(c) - Consistent 401 responsecreateCheckin() and deleteCheckin() to use new helpersauthenticateUser() functionfrontend/utils/api.ts with automatic 401 handlingapiFetch() wrapperImplement proper session validation in the iOS app following the kipclip pattern, adapted for native iOS architecture.
/api/auth/session endpoint exists (provided by atproto-oauth-hono)oauth.sessions.getOAuthSession(){ valid, did, handle, displayName, avatar, accessToken, refreshToken, expiresAt }File: AnchorKit/Sources/AnchorKit/Services/AnchorAuthService.swift
Changes:
validateSession() that calls /api/auth/session endpointvalidateSessionWithBackend() method for explicit validationExample Implementation:
public func validateSession(_ credentials: AuthCredentials) async throws -> AuthCredentials { let url = URL(string: "https://dropanchor.app/api/auth/session")! var request = URLRequest(url: url) request.setValue("Bearer \(credentials.sessionId ?? "")", forHTTPHeaderField: "Authorization") let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw AnchorAuthError.invalidResponse } if httpResponse.statusCode == 401 { throw AnchorAuthError.sessionExpired } let sessionData = try JSONDecoder().decode(SessionResponse.self, from: data) if !sessionData.valid { throw AnchorAuthError.sessionExpired } // Update credentials with new expiration var updatedCredentials = credentials updatedCredentials.expiresAt = Date(timeIntervalSince1970: sessionData.expiresAt) return updatedCredentials }
File: AnchorKit/Sources/AnchorKit/Stores/AuthStore.swift
Changes:
validateSessionOnAppLaunch():
AnchorAuthServicevalidateSessionOnAppResume():
Example Implementation:
public func validateSessionOnAppLaunch() async { guard let credentials = _credentials else { return } do { let validatedCredentials = try await authService.validateSession(credentials) _credentials = validatedCredentials await credentialsStorage.save(validatedCredentials) } catch { // Validation failed, try refresh do { try await refreshSession() } catch { // Refresh failed, sign out await signOut() } } }
File: AnchorKit/Sources/AnchorKit/Stores/CheckInStore.swift
Changes:
requireValidSession() helper methodSessionExpiredError if validation failsExample Implementation:
private func requireValidSession() async throws { guard let credentials = try await authStore.getValidCredentials() else { throw CheckInError.sessionExpired } } public func createCheckin(...) async throws { try await requireValidSession() // ... rest of checkin creation logic }
File: AnchorMobile/Views/CheckInComposeView.swift
Changes:
SessionExpiredError specificallyExample Implementation:
.alert("Session Expired", isPresented: $showSessionExpiredAlert) { Button("Sign In") { // Navigate to settings for re-authentication selectedTab = .settings } Button("Cancel", role: .cancel) { } } message: { Text("Your session has expired. Please sign in again to continue.") }
File: AnchorMobile/Views/SettingsView.swift
Changes:
isAuthenticated)File: AnchorKit/Services/Auth/IronSessionAPIClient.swift
Changes:
/api/auth/session endpoint responsesvalidateSession() with valid/expired/invalid responsesvalidateSessionOnAppLaunch() with various session statesExpired Session on Launch:
Expired Session During Use:
Token Refresh Success:
Token Refresh Failure:
// Session response from /api/auth/session struct SessionResponse: Codable { let valid: Bool let did: String? let handle: String? let displayName: String? let avatar: String? let accessToken: String? let refreshToken: String? let expiresAt: TimeInterval? } // New error types enum SessionError: Error { case expired case invalid case networkFailure case refreshFailed } enum CheckInError: Error { case sessionExpired case invalidData case networkFailure }
/api/auth/session to validatevalidateSessionOnAppResume() detects expiration✅ App validates session on launch and resume using backend endpoint ✅ Expired sessions detected before user attempts actions ✅ Clear "Sign in again" messaging when session expires ✅ No confusing "Authentication required" errors ✅ Consistent auth state across the app ✅ Smooth re-authentication flow ✅ Backend and iOS app fully aligned with kipclip pattern
AnchorKit (Business Logic):
Services/AnchorAuthService.swift - Real session validationStores/AuthStore.swift - Lifecycle validationStores/CheckInStore.swift - Pre-operation validationServices/Auth/IronSessionAPIClient.swift - Centralized 401 handling
(optional)AnchorMobile (UI Layer):
Views/CheckInComposeView.swift - Error handlingViews/SettingsView.swift - Session status displayViews/Authentication/* - Re-auth flow improvementsTests:
AnchorKit/Tests/Services/AnchorAuthServiceTests.swiftAnchorKit/Tests/Stores/AuthStoreTests.swiftAnchorKit/Tests/Stores/CheckInStoreTests.swift/api/auth/session endpoint worksTotal: ~12 hours of development work
/Users/tijs/projects/atproto/kipclip.com/kipclip-appview/backend/services/auth.ts/Users/tijs/projects/atproto/anchor-appview/frontend/utils/api.ts/Users/tijs/projects/atproto/Anchor/Anchor/AnchorKit/