This walkthrough covers SwiftUI Login with a Node.js API. Xcode, an iOS simulator and an HTTPS backend with a documented mobile authentication contract. The backend must issue, expire and revoke tokens; the app cannot create trusted identity by itself.
The video is an illustrated code walkthrough with AI narration. Code blocks are integration excerpts, not a complete tested application. Helper names such as stores, middleware and clients describe responsibilities you must implement in your existing project. No live server, payment provider or device execution is claimed.
Before you begin
Use a separate development environment, fictional data and test credentials. Record dependency versions and follow the official references below for your installed versions. Keep a working baseline and add one workflow at a time.
1. Agree on the mobile contract
Define the authentication contract before drawing the login screen. The backend validates credentials and controls token lifetime and revocation. A mobile client can use a short-lived access token with a refresh session designed for rotation. This is a different contract from a browser-only cookie flow. Document error responses and decide how the application behaves when the refresh session is no longer valid.
POST /api/mobile/login -> access token + refresh token
POST /api/mobile/refresh -> rotated tokens
POST /api/mobile/logout -> revoke refresh session
GET /api/me -> authenticated profile2. Model responses explicitly
Create small decodable models that match the server response. Check the web status before decoding success data so an error response doesn't become a confusing model failure. Validate the expected content and never print tokens during debugging. If the backend returns a different naming convention, configure coding keys deliberately. Keep authentication transport concerns separate from the view's form state.
struct LoginResponse: Decodable {
let accessToken: String
let refreshToken: String
let expiresIn: Int
}
struct Profile: Decodable { let id: Int; let email: String }3. Submit over HTTPS
Send credentials over a trusted secure connection and keep the platform's certificate validation enabled. Set an appropriate timeout and handle cancellation. Don't disable transport security to fix a development hostname problem. Disable duplicate submission while the request is running, show a general authentication failure message, and clear the password from the form when it is no longer needed.
var request = URLRequest(url: baseURL.appendingPathComponent("api/mobile/login"))
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(credentials)
let (data, response) = try await URLSession.shared.data(for: request)4. Store refresh credentials in Keychain
Use Keychain for the refresh credential rather than ordinary preferences or a plain file. Choose an accessibility policy that matches the app's foreground or background requirements. This excerpt shows insertion; the complete wrapper must handle existing items, update, lookup, deletion, and returned status codes. Keep the short-lived access token in memory where practical, and avoid exposing either token through diagnostic logs.
let item: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "CodeKits.Auth",
kSecAttrAccount as String: "refresh",
kSecValueData as String: Data(token.utf8),
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
let status = SecItemAdd(item as CFDictionary, nil)5. Coordinate authenticated state
Let a dedicated store coordinate authentication state on the main actor. Views can then render signed-out, loading, signed-in, and recovery states without each making independent token decisions. At startup, attempt the documented session restoration flow and load the authoritative profile. A cached email address isn't proof of a valid session. Handle an unavailable network differently from credentials that the server has explicitly rejected.
@MainActor final class AuthStore: ObservableObject {
@Published var profile: Profile?
@Published var isLoading = false
@Published var errorMessage: String?
// login(), restoreSession(), logout() coordinate the client.
}6. Refresh without a request storm
When several requests discover expiry together, coordinate one refresh operation rather than sending many competing rotations. Persist the newly rotated refresh credential safely before discarding the old one according to the protocol. Retry a failed authenticated request only within a bounded policy. For mutations, account for idempotency and uncertain completion. A rejected refresh session should return the interface to a clear signed-out state.
Authenticated request -> expired access response
Single shared refresh task -> rotated credentials
Retry original request once
Refresh rejected -> clear session and show login7. Revoke on logout
Logout clears the local credentials and asks the server to revoke the refresh session. Offline logout can remove local access immediately, but it can't guarantee remote revocation until the server is contacted. Design token lifetimes around that distinction. Also clear account-specific caches and pending requests so a second user doesn't see the first user's data. Test switching accounts as well as a simple sign-out screen.
await api.revokeRefreshSessionIfReachable()
keychain.deleteRefreshCredential()
accessToken = nil
profile = nil
// Server expiry limits exposure when offline revocation fails.Integration checks
Test wrong credentials, Keychain errors, expiry, concurrent refresh, revoked sessions, offline startup, logout and switching accounts. Compile and run the integrated app in Xcode against your backend.
These are verification instructions, not reported test results. The excerpts have not been executed as a complete integrated application. Review authentication, database schema, dependency versions and operational settings before deployment.
Official references
Explore CodeKits development services.

