9123/22, 8:37 AM ISON Web Token for Java - OWASP Cheat Sheet Series
JSON Web Token Cheat Sheet for Java
Introduction
Many applications use JSON Web Tokens (JWT) to allow the dient to indicete its identity for
further exchange after authentication.
From JWT.O:
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-
contained way for securely transmitting information between parties as a JSON object. This
information can be verified and trusted because itis digitally signed. JWTs can be signed using
a secret (with the HMAC algorithm) or a public/private key pair using RSA.
JSON Web Token is used to carry information related to the identity and characteristics (claims) of
‘a client. This information is signed by the server in order for it to detect whether it was tampered
with after sending itt the client. This will prevent an attacker from changing the identity or any
characteristics (for example, changing the role from simple user to admin or change the client
login).
This token is created during authentication (is provided in case of successful authentication) and
is verified by the server before any processing. Itis used by an application to allow a client to
present a token representing the users “identity card! to the server and allow the server to verify
the validity and integrity of the token in a secure way, all of this in a stateless and portable
approach (portable in the way that client and server technologies can be different including also
the transport channel even if HTTP is the most often used).
Token Structure
Token structure example taken from JWT.I0:
[Base64 (HEADER) ] . [Base64 (PAYLOAD) ]..[Base64(SIGNATURE) |
eyJnbGei04 JTUZT INiTsINRSCCTSTkpXVCII
ey JzdNTi04TxHjMONTYSODKwTiwibaF tZST6TkpvaG4gRG91TimiVARtaWaionRydAV9.
TJVA9SOrH7E2cBab3BRMHrHDCEFxjo¥ZgeF ONFH7HgQ
Chunk 1: Header
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! ane9123122, 8:37 AM ISON Web Token for Java - OWASP Chest Sheet Series
4
“Hs256",
2 cuwr"
}
Chunk 2: Payload
{
sub": °1284567896",
john Doe",
true
admin":
+
Chunk: Signature
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), KEY )
Objective
This cheatsheet provides tips to prevent common security issues when using JSON Web Tokens
(JWT) with Java.
‘The tips presented in this article are part of a Java project that was created to show the corect
‘way to handle creation and validation of JSON Web Tokens.
You can find the Java project here, it uses the official JWT library.
In the rest of the article, the term token refers to the JSON Web Tokens (JWT).
Consideration about Using JWT
Even if a JWT token is"easy"to use and allow to expose services (mostly REST sty) in a stateless
way, i's not the solution that fits for all applications because it comes with some caveats, like for
‘example the question of the storage of the token (tackled in this cheatsheet) and cthers...
If your application does net need to be fully stateless, you can considerusing traditional session
system provided by all web frameworks and follow the advice from the dedicated session
management cheat sheet, However, for stateless applications, when well implemented, its a good
candidate.
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! ane9123122, 8:37 AM ISON Web Token for Java - OWASP Chest Sheet Series
Issues
None Hashing Algorithm
‘Symptom
This attack, described here occurs when an attacker alters the token and changes the hashing
algorithm to indicate, through, the none keyword, that the integrity of the token has already been
verified. As explained in the link above some libraries treated tokens signed with the none algorithm
as a valid token with a verified signature, so an attacker can alter the token claims and token will be
trusted by the application,
How to Prevent
First, use a JWT library that is not exposed to this vulnerability.
Last, during token validation, explicitly request that the expected algorithm was used.
Implementation Example
// ¥0C key ~ Block serialization and storage as String in JW# menory
private transient byte[] keyHMAC = ...;
/[Create a verification context for the token requesting
//explicitly the use of the HMAC-256 hashing algorithm
WIVerifier verifier = JWT. require(Algorithm.HYAC256(keyHMAC) ).build() ;
/Nerify the token, if the verification fail then a exception is throm
DecodedNT decodedToken = verifier .verify(token) ;
Token Sidejacking
symptom
This attack occurs when a token has been intercepted/stolen by an attacker and they use it to gain
access to the system using targeted user identity
How to Prevent
Away to prevent it is to add a “user context" in the token. A user context will be composed of the
following information:
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! ane9123/22, 8:37 AM ISON Web Token for Java - OWASP Cheat Sheet Series
‘+ Arandom tring thet will be generated during the authentication phase. it willbe sent to the
lient as an hardened cookie (fags: HitpOnly + Secure + Samesite + cookie prefixes).
+ ASHA256hash of the random string will be stored in the token (instead of the raw value) in
order to prevent any XSSissues allowing the attacker to read the random string value and
setting the expected cookie.
IP addresses should not be used because thete are some legitimate situations in which the IP
address can change during the same session. For example, when an user accesses an application
through their mobile device and the mobile operator changes during the exchange, then the IP
address mey (often) change. Moreover, using the IP address can potentially cause issues with
European GDPR compliance.
During token validation, if the received token does nct contain the right context (for example fit
has been replayed), then it must be rejected
Implementation example
Code to create the token after successful authentication.
// FIC key ~ Block serialization and storage as String in JVM menory
private transient byte[] keyHMAC = ...;
// Random data generator
private SecureRandom secureRandom = new SecureRandom() ;
//Generate a random string that will constitute the fingerprint for this user
byte[] randomFgp = new byte[5@];
secureRandon .nextBytes(randonF gp) ;
‘String userFingerprint = DatatypeConverter ,printHexBinary(randonFgp);
//hdd the fingerprint in a hardened cookie - Add cookie manually because
//SameSite attribute is not supported by javax.servlet .hhttp.Cookie class
String FingerprintCookie = "_Secure-Fgp=" + userFingerprint
; SameSite=Strict; HttpOnly; Secure”;
response. addHeacer( "Set-Cookie, fingerprintCookie);
//Compute 2 SHA256 hash of the fingerprint in order to store the
//fingerprint hash (instead of the raw value) in the token
//to prevent an XSS to be able to read the fingerprint and
//set the expected cookie itself
NessageDigest digest = MessageDigest .getInstance("SHA-256");
byte[] userFingerprintDigest = digest.digest(userFingerprint .getBytes(“utt-8"));
String userFingerprintHash
DatatypeConverter .printHexBinary(userFingerprintDigest);
/[oreate the token with @ validity of 15 minutes and client context (Fingerprint)
information
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! ana9123/22, 8:37 AM ISON Web Token for Java - OWASP Cheat Sheet Series
Calendar ¢ = Calendar.getInstance() ;
Date now = ¢.getTime();
e.add (Calendar .MINJTE, 15);
Date expirationDate = c.getTime()
Map headerClains = new HashNap<>();
headerClains.put("typ", "JNT");
String token = JWT create() .WwithStb ject (login)
«huithExpiresAt (expirationdate)
swithIssuer (this. issuerID)
cwithIssuedat(now)
-withNotBefore (now)
cwithClaim("userFingerprint”, userFingerprintHash)
«swithHeader (headerClaims)
sign Algorithm. HAC256 (this.keyHMAC) );
Code to validate the token.
71 HMAC key - Block serialization and storage as String in JV memory
private transient bytel] KeyHMAC = .».;
//Retrieve the user fingerprint from the dedicated cookie
String userFingerprint = null;
if (request .getCockies() != null && request.getCookies().length > @) {
List- cookies
Arrays.strean(request .getCookies()) «collect (Collectors.toList());
Optional cookie = cookies.stream().filter(c -> "_.Secure-Fgp"
sequals(¢.getNane())) -findFirst() ;
if (cookie.isPresent()) {
userFingerprint = cookie.get().getValue() ;
}
7 [Compute 2 SHA256 hash of the received fingerprint in cookie in order to compare
//it to the fingerprint hash stored in the token
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] userFingerprintDigest = digest. digest(userFingerprint .getBytes( "utf-8")) ;
String userFingerprintHlash =
DatatypeConverter .printHexBinary(userFingerprintDigest);
/[ereate a verification context for the token
JWIVerifier verifier = JNT.require(Algorithm .HYAC256(keyHMAC) )
swithTssuer(issuerID)
swithClaim("userFingerprint", userFingerprintHash)
sbuild();
/Nerify the token, if the verification fail then an exception is throm
DecodedWT decodedToken = verifier .verify( token);
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! sine9123122, 8:37 AM ISON Web Token for Java - OWASP Chest Sheet Series
No Built-In Token Revocation by the User
‘Symptom
This problem is inherent to JWT because a token only becomes invalid when it expires. The user
has no built-in feature to explicitly revoke the validity of a token. This means that if itis stolen, a
user cannot revoke the token itself thereby blocking the attacker.
How to Prevent
‘Away to protect against this is to implement a token block list that will be used to mimic the
“logout feature that exists with traditional session management system.
The block list will keep a digest (SHA-256 encoded in HEX) of the token with a revocation date.
This entry must endure at least until the expiration of the token.
When the user wants to “logout" then it call a dedicated service that will add the provided user
token to the block list resulting in an immediate invalidation of the token for further usage in the
application.
Implementation Example
BLOCK LIST STORAGE
A database table with the following structure will be used as the central block list storage.
create table if not exists revoked_token(jwt_token_digest varchar(258) prinary
key,
revocation_date tinestenp default now());
‘TOKEN REVOCATION MANAGEMENT
Code in charge of adding a token to the block list and checking if a token is revoked.
pe
% Handle the revocation of the token (logout).
% Use @ DB in order to allow miltiple instances to check for revoked token
* and allow cleanup at centralized DB level.
*/
public class TokenRevoker {
/** DB Comection */
@Resource(” jdbe/storeDS")
private DataSource storeDS;
pe
* Verify if a digest encoded in HEX of the ciphered token is present
* in the revocation table
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! ene9123/22, 8:37 AM ISON Web Token for Java - OWASP Cheat Sheet Series
* @param jwtInHex Token encoded in HEX
* ereturn Presence flag
* @throws Exception Tf any issue occur during conmunication with DB
*
public boolean isTokenRevoked(String jwtInHex) throws Exception {
boolean tokentsPresent = false;
Af (jwtInHex {= null @& !jwtInHex.trim().istimpty()) {
1/Decode the ciphered token
byte[] cipheredtoken = DatatypeConverter .parseHexBinary( jutInHex) ;
//Compute 2 S+A256 of the ciphered token
MessageDigest digest = MessageDigest .getInstance( "SHA-256");
byte[] cipheredTokenDigest = digest .digest(cipheredToken) ;
String jwtTokenDigestinex =
DatatypeConverter .printHexBinary(cipheredTokenDigest) ;
1/Search token digest in HEX in DB
‘try (Connection con = this.storeDS.getConnection()) {
String query = "select jwt_token_digest fron revoked_token where
Jwt_token.digest = 2°;
try (Preparedstatenent pStatenent = con.preparestatenent(query)) {
pStatenent.setString(1, jweTokenDigestInHex) ;
try (Resultset rSet = pStatenent .executecuery()) {
tokentsPresent = rSet.next() ;
)
+
return tokenTsPresent;
pe
% Add 2 digest encoded in HEX of the ciphered token to the revocation token
table
+ @param jwtInHex Token encoded in HEX
* @throws Exception If any issue occur during communication with DB
*7
public void revokeToken(String jwtInHex) throws Exception {
Af (jwtIniex != null && !wtInHex.trim().istinpty()) {
[Decode the ciphered token
byte[] cipheredToken = DatatypeConverter .parseHexBinary( jwtInHex) ;
1/ompute 2 $8256 of the ciphered token
Nessagedigest digest = MessageDigest .getInstance(“SHA-256");
byte[] cipheredtokenDigest = digest .digest(cipheredToken);
String jwtTokenDigestiniex =
DatatypeConverter.printHexBiinary(cipheredTokenDigest) ;
//Oheck if the token digest in HEX is already in the DB and add it if it
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! mma9123122, 8:37 AM ISON Web Token for Java - OWASP Chest Sheet Series
is absent
if (!this.isTokenRevoked(jwtInHex)) {
try (Connection con = this.storeDs.getconnection()) {
String query = “insert into revoked_token(jwt_token_ digest)
values(?)";
int insertedRecorécount;
try (PreparedStotenent pStatenent = con.prepareStaterent(query))
pStatement.cetString(1, jwtTokenDigestInHex) ;
insertedRecordCount = pStatement .executelpdate() ;
>
if (ansertedRecordcount = 1) {
‘hrow new TllegalStateException(“Nunber of inserted record
is invalid,” +
"1 expected but is " + insertedRecordCount) ;
)
; }
}
}
Token Information Disclosure
‘symptom
This attack occurs when an attacker has access to a token (or a set of tokens) and extracts
information stored in it (the contents cf JWT tokens are base64 encoded, but is not encrypted by
default) in order to obtain information about the system. Information can be for exarmple the
security roles, login format...
Howto Prevent
‘A way to protect against this attack is to cipher the token using, for example, a symmetric
algaithm,
It's also important to pretect the ciphered data against attack like Padding Oracle or any other
attack using ayptanalysis.
In order to achieve all these goals, the AES-GCIM algorithm is used which provides Authenticated
Encryption with Associated Data.
More details from here:
AEAD primitive (Authenticated Encryption with Associated Date) provides
functionality of symmetric
authenticated encryption
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! ane9123/22, 8:37 AM ISON Web Token for Java - OWASP Cheat Sheet Series
Implementations of this primitive are secure against adaptive chosen ciphertext
attacks
When encrypting a plaintext one can optionally provide associated data that
should be authenticated
but not encrypted.
That is, the encryption with associated data ensures authenticity (ie. who the
sender is) and
integrity (ie. da
secrecy
has not been tampered with) of that data, but not its
See RFCS116: https://tools. ietf.org/html/rfes116
Note:
Here ciphering is added mainly to hide intemal information butit’s very important to remember that
the frst protection against tampering of the JWT token is the signature. So, the token signature
andits verification must be always in place.
Implementation Example
TOKEN GPHERING
Code in charge of managing the ciphering. Google Tink dedicated crypto library is used to handle
ciphering operations in order to use built-in best practices provided by this library.
pe
+ Handle ciphering and deciphering of the token using AES-GOM.
*
+ @see "https: //ai thub.com/google/ tink /blob /master /docs /JAVA-HONTO.md"
*/
public class TokenCipher {
* Constructor - Register AEAD configuration
+ @throws Exception If any issue occur during AEAD configuration
registration
*/
public TokenCipher() throws Exception {
AeadConfig.register();
y
rn
* Cipher a JWT
* @param jut Token to cipher
* @param keysetHandle Pointer to the keyset handle
* @retumn The ciphered version of the token encoded in HEX
* @throws Exception If any issue occur during token ciphering operation
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! one9123/22, 8:37 AM ISON Web Token for Java - OWASP Cheat Sheet Series
*/
public String cipherToken(String jwt, KeysetHandle keysetHlandle) throws
Exception {
i Nerify paraneters
if (jut == null || jwt.isEmpty() || keysetHandle == null) {
‘throw new TllegalArgurentException("Both parameters must be
specified!");
//Get the primitive
head aead = AeadFactory.getPrimitive(keysetHandle) ;
W/Cipher the token
byte[] cipheredToken = aead.encrypt(jwt.getBytes(), nul);
return DatatypeConverter .printHexBinary(cipheredToken) ;
}
i
* Decipher a JNT
* @param jwtInHex Token to decipher encoded in HEX
* @param keysetHandle Pointer to the keyset handle
* @return The token in clear text
* @throvs Exception If any issue occur during token deciphering operation
*
public String decipherToken(String jwtInHex, KeysetHandle keysetHandle)
throws Exception {
/Nerify paraneters
if (jwtInHex = null || jwtInHex.isEmpty() || keysetHandle == null) {
‘throw new TllegalArgurentException("Both parameters must be specified
1");
)
/ [Decode the ciphered token
byte] eipheredToken = DatatypeConver ter.parseHexBinary(jwtTnHex) ;
//Get the prinitive
head aead = AeadFactory.getPrimitive(keysetHandle) ;
//Decipher the token
byte[] decipheredToken = aead.decrypt(cipheredToken, null);
return new String(decipheredToken) ;
>
}
CREATION / VAUDATION OF THE TOKEN
Use the token ciphering handler during the creation and the validation of the token.
Load keys (ciphering key was generated and storedusing Google Tink) and setup cipher.
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! sone9123/22, 8:37 AM ISON Web Token for Java - OWASP Cheat Sheet Series
//load keys from configuration text/json files in order to avoid to storing keys
as 9 String in JVM menory
privete transient byte[] keyHMAC = Files.readAlIBytes(Paths.get("sre",
conf*, "key-hmac. txt”);
private transient KeysetHandle keyCiphering
CleartextkeysetHandle .read(JsonkeysetReader.withFile(
Paths.get("src", “nain’, "conf", *key-ciphering. json”) toFile())) ;
main",
/finit token ciphering handler
TokenCipher tokenCipher = new TokenCipher();
Token creation.
//enerate the JIT token using the JNT API...
//Cipher the token (String JSON representation)
String cipheredToken = tokenCipher .cipherToken( token, this .keyCiphering) ;
//Send the ciphered token encoded in HEX to the client in HITP response...
Token validation.
//Retrieve the ciphered token encoded in HEX from the HTTP request...
/[ecipher the token
String token = tokenCipher .decipherToken(cipheredToken, this-keyCiphering) ;
/Nerify the token using the JWT APL...
/Nerify access...
Token Storage on Client Side
‘Symptom
This oocurs when an application stores the token in a menner exhibiting the following behavior:
‘+ Automatically sent by the browser (Cookie storage).
«+ Retrieved even ifthe browser is restarted (Use cf browser lacalStorage container).
«+ Retrieved in case of XSS issue (Cookie accessible to JavaScript code or Token stored in
broviserlocal/session storage).
Howto Prevent
11. Store the token using the browser sessionStarage container.
2. Addit asa Bearer HTTP Authentication header with JaveScript when calling services.
3. Add fingerprint information to the token,
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! sine9123122, 8:37 AM ISON Web Token for Java - OWASP Chest Sheet Series
By storing the token in browser sessionStorage container it exposes the token to being stolen
through a XSS attack. However, fngerprints added to the token prevent reuse cf the stolen token by
theattacker on their machine. To close a maximum of exploitation surfaces for an attacker, add a
browser Content Security Policy to harden the execution context,
Note:
+ Theremaining case is when an attacker uses the user's browsing context asa proxy touse the
‘target application through the legitimete user but the Content Security Policy can prevent
‘communication with non expected domains.
« Its also possible to implement the authentication service in a way that the token is issued
within a hardened cooke, but in this case, protection against a Cross-Site Request Forgery
attack must be implemented
Implementation Example
JavaScript code to store the token after authentication.
/* Wardle request for JWT token and local. storage*/
function authenticate() {
const login = §(*#login”
const postData = “logit
val();
+ encodeURIComponent (login) + “&password=test
$.post(“/services/authenticate", postdata, function (data) {
if (data.status = “Authentication successful!") {
sessionStorage.setItem("token", data.token)
)
else {
sessionStorage. renovelten("token”);
)
»
-feil(function (JqXHR, textStatus, error) {
sessionStorage.renoveTten("token") ;
»
JavaScript code to add the token as a Bearer HTTP Authentication header when calling a service,
for example a service to validate token here.
/* Handle request for JWT token validation */
function validateToken() {
var token = sessionStorage.getIten( token");
if (token
ndefined || token
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! rane9123122, 8:37 AM ISON Web Token for Java - OWASP Chest Sheet Series
HinfoZone").removeClass();
jinfozone").addClase(“alert alert-warning");
$(*#infozone).text("Obtein a JWT token first :)");
rewurn;
}
S.ajax({
url: "/https/www.scribd.com/services/velidate’,
type: "Post",
beforesend: function (xhr) {
xhr .setRequestHeader( "Authorization", "bearer ° + token);
by
success: function (data) {
»
error: function (jqXHR, textStatus, error) {
hb
Di;
+
Weak Token Secret
‘Symptom
\When the token is protected using an HMAC based algorithm, the security of the tokenis entirely
dependent on the strength of the secret used with the HMAC. If an attacker can obtain a valid JWT,
they can then carry out an offline attack and attempt to crack the secret using tools such as John
the Ripper or Hasheat,
If they are successful, they Would then be able to modify the token andre-sign it withthe key they
had obtained, This could let them escalate their privileges, compromise other users! accounts, or
perform cther actions depending on the contentsof the JWT.
There are anumber cf guides that document this processin greeter detail.
Howto Prevent
The simplest way to prevent this attackis to ensure that the sectet used to sign the JWTs is strong
and unique, in order to make it harder for an attacker to crack. As this secret would never need to
be typed by a human. it should be at least 64 characters, and generated using a secure source of
randomness.
Altematively, consider the use of tokens that are signed with RSA rather than using an HMAC and
secret key.
Further Reading
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm! sane9123122, 8:37 AM ISON Web Token for Java - OWASP Chest Sheet Series
‘* (WT) (Attack). Playbook -A projectdocuments the knownattacks and potential security
vuherabilties and misconfigurations of JSON Web Tokens.
¢ JWT Best Practices Internet Draft
Intps:ifcheatshectseries.owasp.orgicheatsheetsliSON_Web_Token_for_Java_Cheat_Sheethtm!