Global Sign Out with AWS Cognito in iOS
In the previous articles, we implemented authentication, password recovery, and password change. We also have a «Sign Out» button that only deletes tokens locally. In this article, we’ll implement Global Sign Out, which invalidates tokens on ALL user devices simultaneously.
What are we building?
- globalSignOut method — Invalidate tokens on all devices
- Update HomeView — Offer two options: Local Sign Out vs Global
- Differentiate both flows — Local (fast) vs Global (secure)
- Error handling — Invalid token, expired session
- Testing — Test sign out in multiple scenarios
Difference: Local Sign Out vs Global Sign Out
| Aspect | Local Sign Out (Article 3) | Global Sign Out (this article) |
|---|---|---|
| Cognito API call | ❌ No | ✅ Yes — globalSignOut() |
| Tokens on other devices | ✅ Remain valid | ❌ Invalidated |
| Local Keychain tokens | ❌ Deleted | ❌ Deleted |
| Requires internet | ❌ No | ✅ Yes |
| Speed | ⚡ Instant | 🌐 API call |
| Security level | ⚠️ Medium | 🔒 High |
The Global Sign Out flow
User in HomeView → presses «Global Sign Out» → confirmation alert → confirms → globalSignOut(accessToken) called → Cognito invalidates tokens server-side → local tokens deleted from Keychain → returns to SignInView.
Use cases:
- Local Sign Out: User only wants to sign out on this device, continues using app on other devices, quick sign out
- Global Sign Out: User suspects account was compromised, lost a device with active session, wants maximum security, changing devices permanently
Prerequisites
- Have completed previous articles (especially Article 3: Login)
- Have CognitoAuthService implemented
- Have HomeView with «Sign Out» button
- User logged in with tokens in Keychain
Step 1: Add globalSignOut Method in CognitoAuthService
Add the method in CognitoAuthService.swift:
// MARK: - Global Sign Out
/// Signs out the user on ALL devices
/// Invalidates tokens on Cognito server and deletes local tokens
func globalSignOut() async throws {
guard let accessToken = KeychainService.shared.getAccessToken() else {
throw NSError(domain: "CognitoAuthService", code: 9001,
userInfo: [NSLocalizedDescriptionKey: "Invalid or expired session"])
}
guard let client = cognitoClient else {
throw NSError(domain: "CognitoAuthService", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Cognito client not initialized"])
}
let signOutInput = GlobalSignOutInput(accessToken: accessToken)
do {
_ = try await client.globalSignOut(input: signOutInput)
print("✅ Global Sign Out successful on server")
// Delete local tokens AFTER successful server invalidation
KeychainService.shared.deleteTokens()
print("✅ Local tokens deleted from Keychain")
print("✅ Session closed on all devices")
print("✅ Tokens invalidated on Cognito server")
} catch {
print("❌ Error in Global Sign Out: \(error)")
let errorMessage = error.localizedDescription
if errorMessage.contains("NotAuthorizedException") {
throw NSError(domain: "CognitoAuthService", code: 9002,
userInfo: [NSLocalizedDescriptionKey: "Invalid or expired session"])
} else {
throw NSError(domain: "CognitoAuthService", code: 9000,
userInfo: [NSLocalizedDescriptionKey: "Error signing out: \(errorMessage)"])
}
}
}
Key points:
- ✅ Requires Access Token
- ✅ Calls client.globalSignOut() — invalidates tokens on Cognito server
- ✅ FIRST invalidates on server, THEN deletes local tokens
- ✅ Uses error codes 9000-9002
Step 2: Update HomeView with Both Options
Update HomeView.swift to offer both types of sign out:
struct HomeView: View {
@Binding var isLoggedIn: Bool
@State private var showingSignOut = false
@State private var showingGlobalSignOutAlert = false
@State private var isSigningOut = false
@State private var errorMessage = ""
@State private var showError = false
private let authService = CognitoAuthService()
var body: some View {
VStack(spacing: 20) {
// ... welcome content ...
// Sign Out button (shows action sheet)
Button(action: { showingSignOut = true }) {
Text("Sign Out")
.fontWeight(.semibold)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
}
.background(Color.red)
.cornerRadius(10)
.padding(.horizontal, 30)
}
.confirmationDialog("Choose how you want to sign out",
isPresented: $showingSignOut,
titleVisibility: .visible) {
Button("Sign out on this device") {
localSignOut()
}
Button("Sign out on ALL devices", role: .destructive) {
Task { await performGlobalSignOut() }
}
Button("Cancel", role: .cancel) { }
}
.alert("Error", isPresented: $showError) {
Button("OK", role: .cancel) { }
} message: {
Text(errorMessage)
}
}
private func localSignOut() {
KeychainService.shared.deleteTokens()
print("✅ Tokens deleted locally. Session closed on this device.")
print("⚠️ Tokens remain valid on other devices")
isLoggedIn = false
}
private func performGlobalSignOut() async {
isSigningOut = true
do {
try await authService.globalSignOut()
await MainActor.run {
isSigningOut = false
isLoggedIn = false
}
} catch {
await MainActor.run {
isSigningOut = false
errorMessage = error.localizedDescription
showError = true
}
}
}
}
Key changes:
- ✅ Changed from .alert to .confirmationDialog — shows options in action sheet
- ✅ Two options: «Sign out on this device» (local) and «Sign out on ALL devices» (global, destructive role = red)
- ✅ localSignOut() — only deletes local Keychain tokens
- ✅ performGlobalSignOut() — calls Cognito API then deletes local tokens
- ✅ Error handling with separate alert

Step 3: Test the Complete Flow
Test 1: Successful Local Sign Out
Sign in → HomeView → press «Sign Out» → action sheet appears → select «Sign out on this device» → console: «✅ Tokens deleted locally. Session closed on this device.» → returns to SignInView. Note: tokens remain valid on other devices.
Test 2: Successful Global Sign Out
Sign in → HomeView → press «Sign Out» → action sheet → select «Sign out on ALL devices» → console: «✅ Global Sign Out successful on server → ✅ Local tokens deleted → ✅ Session closed on all devices → ✅ Tokens invalidated on Cognito server» → returns to SignInView. Tokens are now invalid on ALL devices.
Test 3: Cancel sign out
Press «Sign Out» → action sheet → press «Cancel» → action sheet closes → remains in HomeView → session stays active.
Test 4: Global Sign Out without internet
Turn off WiFi and mobile data → press «Sign Out» → select «Sign out on ALL devices» → network error → alert: «Error signing out: [network error]» → tokens NOT deleted locally (because server invalidation failed). Alternative: use «Sign out on this device» (works without internet).
Test 5: Global Sign Out with expired token
Wait for Access Token to expire (60 min) → press «Sign Out» → select «Sign out on ALL devices» → alert: «Invalid or expired session» → remains in HomeView → local tokens NOT deleted. Note: token refresh (Article 6) should prevent this scenario.
Test 6: Multi-device invalidation
Sign in on Device A and Device B → both have valid tokens → on Device A: select «Sign out on ALL devices» → Device A: returns to SignInView → Device B: tokens invalidated → Device B: next API call returns 401 → should redirect to sign in.
Test 7: Local Sign Out doesn’t affect other devices
Sign in on Device A and Device B → on Device A: select «Sign out on this device» → Device A: returns to login → Device B: session REMAINS ACTIVE, tokens still valid.
Detailed comparison: Local vs Global Sign Out
| Aspect | Local | Global |
|---|---|---|
| Cognito API | ❌ No | ✅ globalSignOut() |
| Tokens on server | ✅ Remain valid | ❌ Invalidated |
| Tokens in Keychain | ❌ Deleted | ❌ Deleted |
| Other devices | ✅ Stay logged in | ❌ Session closed |
| Requires internet | ❌ No | ✅ Yes |
| Speed | ⚡ Instant | 🌐 API call |
| Security | ⚠️ Medium | 🔒 High |
| Error codes | N/A | 9000-9002 |
Best practices
Offer both options: let user choose based on their need, use confirmationDialog for clear options.
Mark Global as destructive: use role: .destructive (red), indicates it’s a more critical action.
Handle network errors: global sign out can fail without internet, inform user and offer local sign out as alternative.
Order of operations: FIRST invalidate on server, THEN delete local tokens. If server fails, do NOT delete local tokens.
Common errors and solutions
Error 1: «Invalid or expired session»
Cause: Access Token expired. Solution: implement automatic token refresh (Article 6), or try local sign out as fallback.
Error 2: Global Sign Out fails without internet
Cause: requires Cognito API call. Solution: detect lack of connection, offer Local Sign Out as alternative.
Error 3: Local tokens not deleted after Global Sign Out
Cause: deleting before validating server response. Solution: always delete tokens AFTER receiving successful server response.
Error 4: User still receives push notifications after sign out
Cause: push tokens not deregistered. Solution: in Local/Global Sign Out, also deregister push tokens (UNUserNotificationCenter).
Summary
In this article, we implemented:
- ✅ globalSignOut method — calls Cognito API, deletes local tokens AFTER server invalidation, error codes 9000-9002
- ✅ HomeView update — confirmationDialog with two options (Local vs Global), localSignOut() and globalSignOut() functions
- ✅ Clear differentiation — Local: fast, this device, no internet needed. Global: secure, all devices, requires internet
- ✅ Error handling — network error, expired token, informative alerts
- ✅ Multi-device testing — Global invalidates everywhere, Local only affects one device
Key difference: Local Sign Out only deletes local tokens (does NOT call API). Global Sign Out calls globalSignOut() API and invalidates tokens on ALL devices.
Final reflection
I invite you to follow me on LinkedIn where I share content related to software development, best practices, and AWS technologies. You can also find me on Medium and explore my projects on GitHub. If you have questions or suggestions, don’t hesitate to connect.
Disclaimer
This article is for educational purposes. In production, you should: implement HTTPS for all API calls, validate tokens before using them, handle token expiration gracefully, implement push notification deregistration on sign out, comprehensive testing for multi-device scenarios.