Popular searches
//

Self-issued JWT for mobile client authentication

4.2.2025 | 8 minutes reading time

Overview

Mobile applications frequently authenticate their backend calls via JWT. These tokens are frequently used in conjunction with OIDC to authenticate a user. Sometimes, particularly in high-assurance scenarios, it can be preferable to authenticate both a user and a device. This article describes a JWT-based scheme that produces user and device authentication without abandoning the JWT transfer format.

In particular, we provide a blueprint implementation of both client—and server-side code that achieves the goal of secure identification of both user and client devices, preventing potentially unauthorized spread of credentials by insecure or compromised client devices.

Our approach leverages the secure enclave (StrongBox) keychain implemented in recent Android operating systems. It offers tamperproof storage of cryptographic material, with even the device owner unable to extract private keys. We can also require the operating system to require user authentication before signing operations.

Background

Our use case is an application that allows collaborating groups to access shared resources with elaborate security requirements. The application includes end-to-end encrypted chat, secure file sharing, and server-agnostic state changes. Due to the high sensitivity of shared content (financial and reputational risks), we need to prevent simple attacks (such as replay attacks) and attempts of key theft on compromised client devices.

Onboarding and device verification are not part of this document. We assume that operating system integrity is maintained (i.e., no rooted devices) to establish a trusted path towards the StrongBox module. StrongBox and its HSM protect the key material even on a compromised device. However, deep compromises can potentially still introduce problems, such as misuse of accessibility features in order to fake user interactions.

JWTs are an industry standard for exchanging authentication information. They consist of base-64 encoded JSON documents. Our scheme exclusively uses ECDSA-signed JWTs instead of the more common RSA and HMAC-based signature algorithms. The backend system exposes a REST interface and transmits the token using the usual mechanism (an Authorization header with the Bearer-Scheme).

Elliptic curve cryptography is a variant of the discrete logarithm problem with several attractive properties for a signature scheme. It features short key sizes and has withstood the test of time for both mathematical and computational attacks.

EC cryptography is well-established and available for all current operating systems and development platforms. It is one of the signature schemes supported by StrongBox.

Protocol

In our scheme, we utilize several standard JWT claims to identify the user, the device, and other required metadata to authenticate a client request. These are:

Header claims

  • alg: Must be "ES256"
  • typ: Must be "JWT"

Body claims

  • sub - the standard subject claim. This claim contains the opaque user ID. Our application uses version 4 UUIDs to ensure that user IDs are distinct and not discoverable by iteration.
  • iss: The "issuer" of the token is the device that performed the signature (also identified by a UUID). Although this claim is frequently understood to refer to a service URL, in our case, it identifies the second factor (the authorized device)
  • aud: Identifies the service to authenticate against
  • iat, exp: All tokens must declare both an issuing timestamp and expiry. Used in conjunction with jti to prevent replay attacks (see below)
  • jti: The token identifier serves as a replay protection mechanism. Each token is single-use and "burned" afterward.

The server side imposes some constraints on top of the (client-controlled) claims. It ensures that iat within the interval of -5 to .1 seconds of the current server time. It ensures that exp is within the interval of -.1 to 5 seconds of current server time It matches the audience against a configured list of acceptable audiences It looks up a public key established for the iss/sub combination, and validates the signature against this key.

Serverside Implementation

As previously stated, signup and onboarding are out of scope for this document. For our example implementation, we assume a relational database backend comes preloaded with the following table:

In addition we maintain the following auxiliary table:

Our server is implemented in Rust and utilizes the Axum framework as an HTTP server. Access checking and authorization are implemented in middleware, where protected routes (which are all but a handful of liveness/metrics endpoints) require the injection of an AuthorizedUser structure.

Several potential storage mechanisms are supported, but in this document, we refer only to the relational DB storage introduced earlier.

With this abstraction, we can implement access checking as follows:

For completeness' sake, we use the following SQL commands to implement KeyStorage (slightly simplified):

Clientside implementation

For client authentication, we have selected the fusionauth-jwt library (https://github.com/FusionAuth/fusionauth-jwt). This library allows an implementation of the Signer interface, which we use to interact with the StrongBox secure enclave.

The Android Keystore API leverages the java standard library, and is easy to interact with. Therefore, the implementation required for fusionauth is not complex:

Due to this easy integration, we can fallback to the standard java APIs for signing, resulting in very ergonomic and easily-audited code.

We also include a simplified version of our signing key generator here for easy reference. This omits significant infrastructure code you would usually expect but showcases the general usage.

Further work

While this article only deals with the Android enclave implementation, similar facilities exist in iOS, and future work could easily achieve similar results using the primitives available on Apple devices. This work hasn't been relevant to our use case, but it is very probably possible with relatively little effort.

Similarly, smartcard/dongle implementations or using PC TPM features were outside the scope of our project but could probably be implemented.

Some features (such as token burning) could be omitted in lesser-assurance situations.

As with any security-relevant solution, further research, independent validation, and public scrutiny are essential, which is part of what motivates this article.

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.