Security Aspects of Offline Authentication Checks

Take the following security aspects into account when you do authentication checks while the user is offline

Authenticity

Do you need to protect the local user’s data on the device from whoever else may have access to the same device? If that is a requirement, you need to add a passcode in addition to the regular online login (or use fingerprint/face ID authentication through the WebAuthn API on the devices that support those). This is what many mobile bank apps do. It is to encrypt any user data that is cached locally for offline use. If you do go that route, most likely you will end up having a helper library that you can call to check if the user is logged in or not.

Arguably, not many apps would need to securely verify the user authenticity offline, and most can check if an online login happened in the not-so-distant past.

  • For that the simplest approach would be setting a local storage flag after a user has successfully logged in online (and clear it when the user logs out). During offline navigation, the user auth then would be checked from the local storage. The Offline Support for Authentication article describes how to do that. It is worth to classify that this as a UX improvement, not as a security feature, because it is easy to bypass through the browser developer tools.

  • And if you need to add a more reliable expiration mechanism, you could use signed tokens (for example, JWT) so that the client can strongly verify the timestamp of the last login. Though, it is still not bulletproof because the user may change the date on the device.

Authorization

When it comes to authorization (checking roles), in reality, it is only feasible on the backend. The reason is simple: the backend is the only place that can reliably guard the data the user is not authorized to view/edit. Once some data has reached the client, you have to assume it may be tampered with. When it comes to, for example, checking whether a user has a role to access a certain view, the client-side implementation is mostly about the UX. You would need a way to check if the user has the permissions to navigate to a view, but this way does not need to be a secure one. In case if the user tampers with the permission on their device, they may end up navigating to that view anyway, but they can not see any sensitive data because that would be securely guarded by the backend.

What you may end up doing in case when you want to keep more than a plain isLoggedIn flag, is keeping a list of user roles in plain text in the local storage, and checking against that list in offline navigation. If the user changes their roles through the developer tools, they could see the views they are not allowed to see (if the view templates are stored in the browser cache), but they can not see any data there.

Using secure tokens, like JWT, makes the list of roles secure. It is cryptographically signed and the client application may verify that it has not been tampered with. While such tokens are useful when accessing the backend because they allow the backend to be stateless, there are no particular benefits for client-side offline authentication.