• Townie
    AI
  • Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
project logo

tijs

location-feed-generator

This is the Anchor AppView - location based feed generator
Public
Like
1
location-feed-generator
Home
Code
21
.claude
1
.github
1
backend
7
coverage
database
2
debug-oauth-sessions
docs
2
frontend
7
lexicons
2
scripts
2
tests
4
types
1
.gitignore
.vtignore
CLAUDE.md
README.md
SESSION_VALIDATION_PLAN.md
deno.json
deno.test.json
H
main.tsx
opinionated-val-town.md
Branches
1
Pull requests
Remixes
History
Environment variables
9
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
SESSION_VALIDATION_PLAN.md
Code
/
SESSION_VALIDATION_PLAN.md
Search
…
SESSION_VALIDATION_PLAN.md

iOS Session Validation Implementation Plan

Current State (After Phase 1)

✅ Completed - Backend (anchor-appview)

  • Updated @tijs/atproto-oauth-hono from 0.3.1 to 0.4.0
  • Created backend/services/auth.ts with kipclip-style auth helpers:
    • getAuthSession(req) - Cookie-based authentication with automatic token refresh
    • clearSessionCookie() - Generate cookie clear header
    • unauthorizedResponse(c) - Consistent 401 response
  • Updated createCheckin() and deleteCheckin() to use new helpers
  • Removed old authenticateUser() function

✅ Completed - Web Frontend

  • Created frontend/utils/api.ts with automatic 401 handling
  • All authenticated requests use apiFetch() wrapper
  • Automatic redirect to login on session expiry
  • No more confusing "Authentication required" errors
  • All 97 tests passing

❌ Current Issue - iOS App

  • iOS app is temporarily broken - cannot authenticate with current backend
  • Old Bearer token flow removed from backend
  • iOS needs to be updated to use new session validation pattern

Phase 2: iOS App Session Validation

Overview

Implement proper session validation in the iOS app following the kipclip pattern, adapted for native iOS architecture.

Backend Preparation (Already Complete)

  • /api/auth/session endpoint exists (provided by atproto-oauth-hono)
  • Automatically refreshes expired tokens via oauth.sessions.getOAuthSession()
  • Returns session data: { valid, did, handle, displayName, avatar, accessToken, refreshToken, expiresAt }
  • Supports both cookie (web) and Bearer token (mobile) authentication

iOS Implementation Steps

Step 1: Update Auth Service for Session Validation

File: AnchorKit/Sources/AnchorKit/Services/AnchorAuthService.swift

Changes:

  1. Implement real validateSession() that calls /api/auth/session endpoint
  2. Add validateSessionWithBackend() method for explicit validation
  3. Parse response and update credentials with new token data
  4. Handle 401 responses as session expired

Example 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 }

Step 2: Update AuthStore Lifecycle Validation

File: AnchorKit/Sources/AnchorKit/Stores/AuthStore.swift

Changes:

  1. validateSessionOnAppLaunch():

    • Call backend validation using updated AnchorAuthService
    • If validation fails → attempt token refresh
    • If refresh fails → sign out user and clear credentials
    • Update authentication state
  2. validateSessionOnAppResume():

    • Call lightweight session validation
    • Handle expired sessions gracefully
    • Update UI state to show login prompt

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() } } }

Step 3: Add Pre-Operation Validation in CheckInStore

File: AnchorKit/Sources/AnchorKit/Stores/CheckInStore.swift

Changes:

  1. Add requireValidSession() helper method
  2. Call before creating checkin
  3. Throw clear SessionExpiredError if validation fails
  4. UI layer catches this and shows "Sign in again" prompt

Example 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 }

Step 4: Improve UI Error Handling

File: AnchorMobile/Views/CheckInComposeView.swift

Changes:

  1. Catch SessionExpiredError specifically
  2. Show alert: "Your session has expired. Please sign in again."
  3. Provide "Sign In" button that navigates to SettingsView
  4. Clear any in-progress checkin data

Example 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.") }

Step 5: Update SettingsView Session Status

File: AnchorMobile/Views/SettingsView.swift

Changes:

  1. Display actual session validity (not just isAuthenticated)
  2. Show expiration time if available
  3. Add visual indicator for expired sessions
  4. Make re-authentication easy with clear CTA

Step 6: Add Centralized 401 Response Handling (Optional)

File: AnchorKit/Services/Auth/IronSessionAPIClient.swift

Changes:

  1. Detect 401 responses in API client
  2. Automatically clear credentials on persistent 401s
  3. Update AuthStore state to trigger login UI
  4. Prevent cascading authentication errors

Testing Strategy

Unit Tests

  • Mock /api/auth/session endpoint responses
  • Test validateSession() with valid/expired/invalid responses
  • Test validateSessionOnAppLaunch() with various session states
  • Test pre-operation validation in CheckInStore
  • Test error handling flows

Integration Tests

  1. Expired Session on Launch:

    • Launch app with expired session in Keychain
    • Verify session validation is attempted
    • Verify user is signed out if validation fails
  2. Expired Session During Use:

    • App in use, session expires in background
    • User attempts to create checkin
    • Verify clear error message and re-auth prompt
  3. Token Refresh Success:

    • Session near expiry
    • Validation triggers automatic refresh
    • User continues without interruption
  4. Token Refresh Failure:

    • Session expired and refresh tokens invalid
    • User is signed out gracefully
    • Clear path to re-authenticate

Models & Types Needed

// 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 }

Expected Behavior After Implementation

Scenario 1: App Launch with Expired Session

  1. App loads credentials from Keychain
  2. Calls /api/auth/session to validate
  3. Backend returns 401 (session expired)
  4. App clears credentials from Keychain
  5. Shows login screen
  6. ✅ No confusing error messages

Scenario 2: Creating Checkin with Expired Session

  1. User fills out checkin form
  2. Taps "Drop Anchor"
  3. Pre-operation validation detects expired session
  4. Shows "Session expired - please sign in again" alert
  5. User taps "Sign In" button
  6. Navigates to Settings view for re-authentication
  7. ✅ Clear recovery path

Scenario 3: Session Expires While App in Background

  1. App goes to background
  2. Session expires (30 days later)
  3. User brings app to foreground
  4. validateSessionOnAppResume() detects expiration
  5. Signs out user, shows login screen
  6. ✅ Proactive session management

Success Criteria

✅ 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

Files to Modify (Summary)

AnchorKit (Business Logic):

  • Services/AnchorAuthService.swift - Real session validation
  • Stores/AuthStore.swift - Lifecycle validation
  • Stores/CheckInStore.swift - Pre-operation validation
  • Services/Auth/IronSessionAPIClient.swift - Centralized 401 handling (optional)

AnchorMobile (UI Layer):

  • Views/CheckInComposeView.swift - Error handling
  • Views/SettingsView.swift - Session status display
  • Views/Authentication/* - Re-auth flow improvements

Tests:

  • AnchorKit/Tests/Services/AnchorAuthServiceTests.swift
  • AnchorKit/Tests/Stores/AuthStoreTests.swift
  • AnchorKit/Tests/Stores/CheckInStoreTests.swift

Notes

  • iOS app currently broken until Phase 2 implementation
  • Web app fully functional with new auth flow
  • Backend ready for iOS - /api/auth/session endpoint works
  • Pattern matches kipclip-appview (adapted for iOS native)
  • No backward compatibility needed - clean slate for new pattern

Timeline Estimate

  • Step 1 (Auth Service): ~2 hours
  • Step 2 (AuthStore): ~2 hours
  • Step 3 (CheckInStore): ~1 hour
  • Step 4 (UI Error Handling): ~2 hours
  • Step 5 (Settings View): ~1 hour
  • Step 6 (Optional 401 handling): ~1 hour
  • Testing: ~3 hours

Total: ~12 hours of development work

References

  • kipclip-appview auth pattern: /Users/tijs/projects/atproto/kipclip.com/kipclip-appview/backend/services/auth.ts
  • Anchor web frontend: /Users/tijs/projects/atproto/anchor-appview/frontend/utils/api.ts
  • Current iOS implementation: /Users/tijs/projects/atproto/Anchor/Anchor/AnchorKit/
FeaturesVersion controlCode intelligenceCLI
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.