Handling "bad_authentication" with RealmSwift

When a refresh token has expired, the user must be logged out. This does not happen automatically yet: Log out user on token expiration · Issue #4882 · realm/realm-core · GitHub

How do I catch the bad_authentication error with Realm Swift? Can’t find anything in the docs…

In Java/Kotlin I use:

SyncConfiguration
.errorHandler { session, error ->
if (error.errorCode == ErrorCode.BAD_AUTHENTICATION) {
	app.currentUser()?.logOutAsync()
}

In my MainActivity, I implemented the AuthenticationListener so that users are immediately redirected to the StartActivity upon a logout event.

How can I handle this in Swift?

Hi @Annika,

The complete list of error codes regarding sync is not in the docs (yet), but you have it in this post.

Regarding how to catch those errors in Swift, it’s quite similar to Kotlin:

app.syncManager.errorHandler = { (error, session) in
    let error = error as NSError
    print("\(error.code)")
}

Here I’m casting from Swift’s Error into Cocoa’s NSError which will give you the error code

Hope this helps

Thanks, that helps!

So I guess this is the error code I am looking for:

ErrorCode = 203 // Bad user authentication

Unfortunately its not easy to test it, because I have to wait 30 days until the token actually expires. Or is there a way I can revoke the token manually (without logging the user out)?

My other question was if there is also something similiar to the AuthenticationListener?

Because my UI must respond to the logout event by navigating to the start screen to log the user back in.

@Diego_Freniche

It would be great if I could get an answer to my last question about listening to authentication events with swift. It is very important to handle the logout event, otherwise the app will stop syncing and might even crash. My app is already in production state and I need to deliver an update as soon as possible!

Hi @Annika

I happened to have a few expired users, and I’ve tried with the code available on our best practices Github repository: you can indeed setup an error handler in Swift like you do for Android, and actually you should, at the very least to be prepared to handle Client Resets. The relevant code snippet looks like:

		app.syncManager.errorHandler	= { [weak self] error, session in
			guard let self = self, let user = session?.parentUser() else { return }

			let syncError	= error as! SyncError
			
			switch syncError.code {
			case .clientResetError:
				// Handle client reset
			case .clientUserError, .underlyingAuthError:
				// Handle logout and other authentication errors
			default:
				// Other type of errors
				print("SyncManager Error: ", error.localizedDescription)
			}
		}

If the user is not Anonymous, you can try the Revoke all sessions or Disable user in the App User screen.

Hope it helps!

1 Like

Hi @Paolo_Manna

thanks for your answer. The best practices repository is very helpful.

However, I tried to test my code by revoking all session and/or disabling the user, but that doesn’t trigger the error handler and therefore the app crashes.

What type of users are you logging in with? There’s indeed an issue if you revoke the sessions for an anonymous user, as that is not supposed to happen (anonymous users are temporary, they cease to exist when the token expires), but for regular users that should work.

I am using JWT Authentication

Thanks for confirming that: do you happen to have a stack trace from Xcode? We have an open ticket for that, having an additional use case would certainly help.

Stacktrace:

HTTP/1.1 401 Unauthorized
cache-control: no-cache, no-store, must-revalidate
connection: close
content-length: 214
content-type: application/json
date: Tue, 05 Oct 2021 12:30:56 GMT
server: envoy
vary: Origin
x-envoy-max-retries: 0
x-frame-options: DENY

And then the app crashes with:

Sync: Connection[3]: Connection closed due to error
[connection] nw_endpoint_handler_set_adaptive_read_handler [C7.1 3.127.11.207:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, ipv6, dns)] unregister notification for read_timeout failed
[connection] nw_endpoint_handler_set_adaptive_write_handler [C7.1 3.127.11.207:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, ipv6, dns)] unregister notification for write_timeout failed
Sync: Connection[2]: Disconnected
Sync: Connection[1]: Disconnected
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]'
*** First throw call stack:
(0x184e1f25c 0x198bb4480 0x184e8aab4 0x184e95bf4 0x184d18dc8 0x184d0c118 0x1035072d8 0x103506f8c 0x103506ed0 0x103506e44 0x103505694 0x1038a8500 0x1038b2a70 0x1038705c4 0x10327571c 0x103272cd8 0x103272858 0x1033289d8 0x1855c64c8 0x104ba3ae8 0x104ba532c 0x104bac38c 0x104bad044 0x104bb8820 0x1cd3515bc 0x1cd35486c)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]'
terminating with uncaught exception of type NSException

That doesn’t look like a Realm crash, though, more like a piece of the code was waiting to have something to insert into a Dictionary, but the array was empty: have you tried to have breakpoints on Exceptions, and see from where in the code the exception is thrown? Unfortunately the stack is not clear, are you using a Debug build?

I just did and the exception is thrown in the RLMSyncConfiguration.mm

BOOL shouldMakeError = YES;
NSDictionary *custom = nil;
// Note that certain types of errors are 'interactive'; users have several options
// as to how to proceed after the error is reported.
auto errorClass = errorKindForSyncError(error);
switch (errorClass) {
	case RLMSyncSystemErrorKindClientReset: {
	custom = @{kRLMSyncPathOfRealmBackupCopyKey: recoveryPath,kRLMSyncErrorActionTokenKey: token};
	break;
	}
	case RLMSyncSystemErrorKindPermissionDenied: {
	custom = @{kRLMSyncErrorActionTokenKey: token};
	break;
	}
	case RLMSyncSystemErrorKindUser:
	case RLMSyncSystemErrorKindSession:
	break;
	case RLMSyncSystemErrorKindConnection:
	case RLMSyncSystemErrorKindClient:
	case RLMSyncSystemErrorKindUnknown:
	// Report the error. There's nothing the user can do about it, though.
	shouldMakeError = error.is_fatal;
	break;
}

At this line:
custom = @{kRLMSyncErrorActionTokenKey: token};

Error:
Thread 2: "*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]"

Thanks @Annika,
That’s very useful for us to track where the issue may be!