JJWT aims to be the easiest to use and understand library for creating and verifying JSON Web Tokens (JWTs) and JSON Web Keys (JWKs) on the JVM and Android.
JJWT is a pure Java implementation based exclusively on the JOSE Working Group RFC specifications:
It was created by Les Hazlewood and is supported and maintained by a community of contributors.
JJWT is open source under the terms of the Apache 2.0 License.
- Features
- Community
- What is a JSON Web Token?
- Installation
- Quickstart
- Creating a JWT
- Reading a JWT
- Signed JWTs
- Encrypted JWTs
- JSON Web Keys (JWKs)
- JWK Sets
- Compression
- JSON Support
- Base64 Support
- Examples
- JWT Signed with HMAC
- JWT Signed with RSA
- JWT Signed with ECDSA
- JWT Signed with EdDSA
- JWT Encrypted Directly with a SecretKey
- JWT Encrypted with RSA
- JWT Encrypted with AES Key Wrap
- JWT Encrypted with ECDH-ES
- JWT Encrypted with a Password
- SecretKey JWK
- RSA Public JWK
- RSA Private JWK
- Elliptic Curve Public JWK
- Elliptic Curve Private JWK
- Edwards Elliptic Curve Public JWK
- Edwards Elliptic Curve Private JWK
- Learn More
- Author
- License
-
Fully functional on all Java 7+ JDKs and Android
-
Automatic security best practices and assertions
-
Easy to learn and read API
-
Convenient and readable fluent interfaces, great for IDE auto-completion to write code quickly
-
Fully RFC specification compliant on all implemented functionality, tested against RFC-specified test vectors
-
Stable implementation with almost 1,700 tests and enforced 100% test code coverage. Every single method, statement and conditional branch variant in the entire codebase is tested and required to pass on every build.
-
Creating, parsing and verifying digitally signed compact JWTs (aka JWSs) with all standard JWS algorithms:
Identifier Signature Algorithm HS256
HMAC using SHA-256
HS384
HMAC using SHA-384
HS512
HMAC using SHA-512
ES256
ECDSA using P-256 and SHA-256
ES384
ECDSA using P-384 and SHA-384
ES512
ECDSA using P-521 and SHA-512
RS256
RSASSA-PKCS-v1_5 using SHA-256
RS384
RSASSA-PKCS-v1_5 using SHA-384
RS512
RSASSA-PKCS-v1_5 using SHA-512
PS256
RSASSA-PSS using SHA-256 and MGF1 with SHA-2561
PS384
RSASSA-PSS using SHA-384 and MGF1 with SHA-3841
PS512
RSASSA-PSS using SHA-512 and MGF1 with SHA-5121
EdDSA
Edwards-curve Digital Signature Algorithm2
1. Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.
2. Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.
-
Creating, parsing and decrypting encrypted compact JWTs (aka JWEs) with all standard JWE encryption algorithms:
Identifier Encryption Algorithm A128CBC‑HS256
AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm
A192CBC-HS384
AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm
A256CBC-HS512
AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm
A128GCM
AES GCM using 128-bit key1
A192GCM
AES GCM using 192-bit key1
A256GCM
AES GCM using 256-bit key1
1. Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.
-
All Key Management Algorithms for obtaining JWE encryption and decryption keys:
Identifier Key Management Algorithm RSA1_5
RSAES-PKCS1-v1_5
RSA-OAEP
RSAES OAEP using default parameters
RSA-OAEP-256
RSAES OAEP using SHA-256 and MGF1 with SHA-256
A128KW
AES Key Wrap with default initial value using 128-bit key
A192KW
AES Key Wrap with default initial value using 192-bit key
A256KW
AES Key Wrap with default initial value using 256-bit key
dir
Direct use of a shared symmetric key as the CEK
ECDH-ES
Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF
ECDH-ES+A128KW
ECDH-ES using Concat KDF and CEK wrapped with "A128KW"
ECDH-ES+A192KW
ECDH-ES using Concat KDF and CEK wrapped with "A192KW"
ECDH-ES+A256KW
ECDH-ES using Concat KDF and CEK wrapped with "A256KW"
A128GCMKW
Key wrapping with AES GCM using 128-bit key1
A192GCMKW
Key wrapping with AES GCM using 192-bit key1
A256GCMKW
Key wrapping with AES GCM using 256-bit key1
PBES2-HS256+A128KW
PBES2 with HMAC SHA-256 and "A128KW" wrapping1
PBES2-HS384+A192KW
PBES2 with HMAC SHA-384 and "A192KW" wrapping1
PBES2‑HS512+A256KW
PBES2 with HMAC SHA-512 and "A256KW" wrapping1
1. Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.
-
Creating, parsing and verifying JSON Web Keys (JWKs) in all standard JWA key formats using native Java
Key
types:JWK Key Format Java Key
TypeJJWT Jwk
TypeSymmetric Key
SecretKey
SecretJwk
Elliptic Curve Public Key
ECPublicKey
EcPublicJwk
Elliptic Curve Private Key
ECPrivateKey
EcPrivateJwk
RSA Public Key
RSAPublicKey
RsaPublicJwk
RSA Private Key
RSAPrivateKey
RsaPrivateJwk
XDH Private Key
XECPublicKey
1OctetPublicJwk
XDH Private Key
XECPrivateKey
1OctetPrivateJwk
EdDSA Public Key
EdECPublicKey
2OctetPublicJwk
EdDSA Private Key
EdECPublicKey
2OctetPrivateJwk
1. Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.
2. Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.
-
Convenience enhancements beyond the specification such as
-
Payload compression for any large JWT, not just JWEs
-
Claims assertions (requiring specific values)
-
Claim POJO marshaling and unmarshalling when using a compatible JSON parser (e.g. Jackson)
-
Secure Key generation based on desired JWA algorithms
-
and more…
-
-
Non-compact serialization and parsing.
This feature may be implemented in a future release. Community contributions are welcome!
If you have trouble using JJWT, please first read the documentation on this page before asking questions. We try very hard to ensure JJWT’s documentation is robust, categorized with a table of contents, and up to date for each release.
If the documentation or the API JavaDoc isn’t sufficient, and you either have usability questions or are confused about something, please ask your question here. However:
Please do not create a GitHub issue to ask a question.
We use GitHub Issues to track actionable work that requires changes to JJWT’s design and/or codebase. If you have a usability question, instead please ask your question here, and we can convert that to an issue if necessary.
If a GitHub Issue is created that does not represent actionable work for JJWT’s codebase, it will be promptly closed.
If you do not have a usability question and believe you have a legitimate bug or feature request, please discuss it here FIRST. Please do a quick search first to see if an existing discussion related to yours exist already and join that existing discussion if necesary.
If you feel like you’d like to help fix a bug or implement the new feature yourself, please read the Contributing section next before starting any work.
Simple Pull Requests that fix anything other than JJWT core code (documentation, JavaDoc, typos, test cases, etc) are always appreciated and have a high likelihood of being merged quickly. Please send them!
However, if you want or feel the need to change JJWT’s functionality or core code, please do not issue a pull request without starting a new JJWT discussion and discussing your desired changes first, before you start working on it.
It would be a shame to reject your earnest and genuinely-appreciated pull request if it might not align with the project’s goals, design expectations or planned functionality. We’ve sadly had to reject large PRs in the past because they were out of sync with project or design expectations - all because the PR author didn’t first check in with the team first before working on a solution.
So, please create a new JJWT discussion first to discuss, and then we can see easily convert the discussion to an issue and then see if (or how) a PR is warranted. Thank you!
If you would like to help, but don’t know where to start, please visit the Help Wanted Issues page and pick any of the ones there, and we’ll be happy to discuss and answer questions in the issue comments.
If any of those don’t appeal to you, no worries! Any help you would like to offer would be appreciated based on the above caveats concerning contributing pull requests. Feel free to discuss or ask questions first if you’re not sure. :)
JSON Web Token (JWT) is a general-purpose text-based messaging format for transmitting information in a compact and secure way. Contrary to popular belief, JWT is not just useful for sending and receiving identity tokens on the web - even if that is the most common use case. JWTs can be used as messages for any type of data.
A JWT in its simplest form contains two parts:
-
The primary data within the JWT, called the
payload
, and -
A JSON
Object
with name/value pairs that represent metadata about thepayload
and the message itself, called theheader
.
A JWT payload
can be absolutely anything at all - anything that can be represented as a byte array, such as Strings,
images, documents, etc.
But because a JWT header
is a JSON Object
, it would make sense that a JWT payload
could also be a JSON
Object
as well. In many cases, developers like the payload
to be JSON that
represents data about a user or computer or similar identity concept. When used this way, the payload
is called a
JSON Claims
object, and each name/value pair within that object is called a claim
- each piece of information
within 'claims' something about an identity.
And while it is useful to 'claim' something about an identity, really anyone can do that. What’s important is that you trust the claims by verifying they come from a person or computer you trust.
A nice feature of JWTs is that they can be secured in various ways. A JWT can be cryptographically signed (making it what we call a JWS) or encrypted (making it a JWE). This adds a powerful layer of verifiability to the JWT - a JWS or JWE recipient can have a high degree of confidence it comes from someone they trust by verifying a signature or decrypting it. It is this feature of verifiability that makes JWT a good choice for sending and receiving secure information, like identity claims.
Finally, JSON with whitespace for human readability is nice, but it doesn’t make for a very efficient message format. Therefore, JWTs can be compacted (and even compressed) to a minimal representation - basically Base64URL-encoded strings - so they can be transmitted around the web more efficiently, such as in HTTP headers or URLs.
Once you have a payload
and header
, how are they compacted for web transmission, and what does the final JWT
actually look like? Let’s walk through a simplified version of the process with some pseudocode:
-
Assume we have a JWT with a JSON
header
and a simple text message payload:header
{ "alg": "none" }
payload
The true sign of intelligence is not knowledge but imagination.
-
Remove all unnecessary whitespace in the JSON:
String header = '{"alg":"none"}' String payload = 'The true sign of intelligence is not knowledge but imagination.'
-
Get the UTF-8 bytes and Base64URL-encode each:
String encodedHeader = base64URLEncode( header.getBytes("UTF-8") ) String encodedPayload = base64URLEncode( payload.getBytes("UTF-8") )
-
Join the encoded header and claims with period ('.') characters:
String compact = encodedHeader + '.' + encodedPayload + '.'
The final concatenated compact
JWT String looks like this:
eyJhbGciOiJub25lIn0.VGhlIHRydWUgc2lnbiBvZiBpbnRlbGxpZ2VuY2UgaXMgbm90IGtub3dsZWRnZSBidXQgaW1hZ2luYXRpb24u.
This is called an 'unprotected' JWT because no security was involved - no digital signatures or encryption to 'protect' the JWT to ensure it cannot be changed by 3rd parties.
If we wanted to digitally sign the compact form so that we could at least guarantee that no-one changes the data without us detecting it, we’d have to perform a few more steps, shown next.
Instead of a plain text payload, the next example will use probably the most common type of payload - a JSON claims
Object
containing information about a particular identity. We’ll also digitally sign the JWT to ensure it
cannot be changed by a 3rd party without us knowing.
-
Assume we have a JSON
header
and a claimspayload
:header
{ "alg": "HS256" }
payload
{ "sub": "Joe" }
In this case, the
header
indicates that theHS256
(HMAC using SHA-256) algorithm will be used to cryptographically sign the JWT. Also, thepayload
JSON object has a single claim,sub
with valueJoe
.There are a number of standard claims, called Registered Claims, in the specification and
sub
(for 'Subject') is one of them. -
Remove all unnecessary whitespace in both JSON objects:
String header = '{"alg":"HS256"}' String claims = '{"sub":"Joe"}'
-
Get their UTF-8 bytes and Base64URL-encode each:
String encodedHeader = base64URLEncode( header.getBytes("UTF-8") ) String encodedClaims = base64URLEncode( claims.getBytes("UTF-8") )
-
Concatenate the encoded header and claims with a period character '.' delimiter:
String concatenated = encodedHeader + '.' + encodedClaims
-
Use a sufficiently-strong cryptographic secret or private key, along with a signing algorithm of your choice (we’ll use HMAC-SHA-256 here), and sign the concatenated string:
SecretKey key = getMySecretKey() byte[] signature = hmacSha256( concatenated, key )
-
Because signatures are always byte arrays, Base64URL-encode the signature and join it to the
concatenated
string with a period character '.' delimiter:String compact = concatenated + '.' + base64URLEncode( signature )
And there you have it, the final compact
String looks like this:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4
This is called a 'JWS' - short for signed JWT.
Of course, no one would want to do this manually in code, and worse, if you get anything wrong, you could introduce serious security problems and weaknesses. As a result, JJWT was created to handle all of this for you: JJWT completely automates both the creation of JWSs and the parsing and verification of JWSs for you.
So far we have seen an unprotected JWT and a cryptographically signed JWT (called a 'JWS'). One of the things that is inherent to both of these two is that all the information within them can be seen by anyone - all the data in both the header and the payload is publicly visible. JWS just ensures the data hasn’t been changed by anyone - it doesn’t prevent anyone from seeing it. Many times, this is just fine because the data within them is not sensitive information.
But what if you needed to represent information in a JWT that is considered sensitive information - maybe someone’s postal address or social security number or bank account number?
In these cases, we’d want a fully-encrypted JWT, called a 'JWE' for short. A JWE uses cryptography to ensure that the payload remains fully encrypted and authenticated so unauthorized parties cannot see data within, nor change the data without being detected. Specifically, the JWE specification requires that Authenticated Encryption with Associated Data algorithms are used to fully encrypt and protect data.
A full overview of AEAD algorithms are out of scope for this documentation, but here’s an example of a final compact JWE that utilizes these algorithms (line breaks are for readability only):
eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0. 6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ. AxY8DCtDaGlsbGljb3RoZQ. KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY. U0m_YmjN04DJvceFICbCVQ
Next we’ll cover how to install JJWT in your project, and then we’ll see how to use JJWT’s nice fluent API instead of risky string manipulation to quickly and safely build JWTs, JWSs, and JWEs.
Use your favorite Maven-compatible build tool to pull the dependencies from Maven Central.
The dependencies could differ slightly if you are working with a JDK project or an Android project.
If you’re building a (non-Android) JDK project, you will want to define the following dependencies:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<!-- Uncomment this next dependency if you are using:
- JDK 10 or earlier, and you want to use RSASSA-PSS (PS256, PS384, PS512) signature algorithms.
- JDK 10 or earlier, and you want to use EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption.
- JDK 14 or earlier, and you want to use EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms.
It is unnecessary for these algorithms on JDK 15 or later.
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId> or bcprov-jdk15to18 on JDK 7
<version>1.76</version>
<scope>runtime</scope>
</dependency>
-->
dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' // or 'io.jsonwebtoken:jjwt-gson:0.12.6' for gson
/*
Uncomment this next dependency if you are using:
- JDK 10 or earlier, and you want to use RSASSA-PSS (PS256, PS384, PS512) signature algorithms.
- JDK 10 or earlier, and you want to use EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption.
- JDK 14 or earlier, and you want to use EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms.
It is unnecessary for these algorithms on JDK 15 or later.
*/
// runtimeOnly 'org.bouncycastle:bcprov-jdk18on:1.76' // or bcprov-jdk15to18 on JDK 7
}
Android projects will want to define the following dependencies and Proguard exclusions, and optional
BouncyCastle Provider
:
Add the dependencies to your project:
dependencies {
api('io.jsonwebtoken:jjwt-api:0.12.6')
runtimeOnly('io.jsonwebtoken:jjwt-impl:0.12.6')
runtimeOnly('io.jsonwebtoken:jjwt-orgjson:0.12.6') {
exclude(group: 'org.json', module: 'json') //provided by Android natively
}
/*
Uncomment this next dependency if you want to use:
- RSASSA-PSS (PS256, PS384, PS512) signature algorithms.
- EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption.
- EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms.
** AND ALSO ensure you enable the BouncyCastle provider as shown below **
*/
//implementation('org.bouncycastle:bcprov-jdk18on:1.76') // or bcprov-jdk15to18 for JDK 7
}
You can use the following Android Proguard exclusion rules:
-keepattributes InnerClasses -keep class io.jsonwebtoken.** { *; } -keepnames class io.jsonwebtoken.* { *; } -keepnames interface io.jsonwebtoken.* { *; } -keep class org.bouncycastle.** { *; } -keepnames class org.bouncycastle.** { *; } -dontwarn org.bouncycastle.**
If you want to use JWT RSASSA-PSS algorithms (i.e. PS256
, PS384
, and PS512
), EdECDH (X25512
or X448
)
Elliptic Curve Diffie-Hellman encryption, EdDSA (Ed25519
or Ed448
) signature algorithms, or you just want to
ensure your Android application is running an updated version of BouncyCastle, you will need to:
-
Uncomment the BouncyCastle dependency as commented above in the dependencies section.
-
Replace the legacy Android custom
BC
provider with the updated one.
Provider registration needs to be done early in the application’s lifecycle, preferably in your application’s
main Activity
class as a static initialization block. For example:
class MainActivity : AppCompatActivity() {
companion object {
init {
Security.removeProvider("BC") //remove old/legacy Android-provided BC provider
Security.addProvider(BouncyCastleProvider()) // add 'real'/correct BC provider
}
}
// ... etc ...
}
Notice the above JJWT dependency declarations all have only one compile-time dependency and the rest are declared as runtime dependencies.
This is because JJWT is designed so you only depend on the APIs that are explicitly designed for you to use in your applications and all other internal implementation details - that can change without warning - are relegated to runtime-only dependencies. This is an extremely important point if you want to ensure stable JJWT usage and upgrades over time:
|
JJWT guarantees semantic versioning compatibility for all of its artifacts except the |
This is done to benefit you: great care goes into curating the jjwt-api
.jar and ensuring it contains what you need
and remains backwards compatible as much as is possible so you can depend on that safely with compile scope. The
runtime jjwt-impl
.jar strategy affords the JJWT developers the flexibility to change the internal packages and
implementations whenever and however necessary. This helps us implement features, fix bugs, and ship new releases to
you more quickly and efficiently.
Most complexity is hidden behind a convenient and readable builder-based fluent interface, great for relying on IDE auto-completion to write code quickly. Here’s an example:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
// We need a signing key, so we'll create one just for this example. Usually
// the key would be read from your application configuration instead.
SecretKey key = Jwts.SIG.HS256.key().build();
String jws = Jwts.builder().subject("Joe").signWith(key).compact();
How easy was that!?
In this case, we are:
-
building a JWT that will have the registered claim
sub
(Subject) set toJoe
. We are then -
signing the JWT using a key suitable for the HMAC-SHA-256 algorithm. Finally, we are
-
compacting it into its final
String
form. A signed JWT is called a 'JWS'.
The resultant jws
String looks like this:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4
Now let’s verify the JWT (you should always discard JWTs that don’t match an expected signature):
assert Jwts.parser().verifyWith(key).build().parseSignedClaims(jws).getPayload().getSubject().equals("Joe");
There are two things going on here. The key
from before is being used to verify the signature of the JWT. If it
fails to verify the JWT, a SignatureException
(which extends JwtException
) is thrown. Assuming the JWT is
verified, we parse the claims and assert that that subject is set to Joe
. You have to love code one-liners
that pack a punch!
ℹ️ NOTE
|
Type-safe JWTs: To get a type-safe |
But what if parsing or signature validation failed? You can catch JwtException
and react accordingly:
try {
Jwts.parser().verifyWith(key).build().parseSignedClaims(compactJws);
//OK, we can trust this JWT
} catch (JwtException e) {
//don't trust the JWT!
}
Now that we’ve had a quickstart 'taste' of how to create and parse JWTs, let’s cover JJWT’s API in-depth.
You create a JWT as follows:
-
Use the
Jwts.builder()
method to create aJwtBuilder
instance. -
Optionally set any
header
parameters as desired. -
Optionally call
signWith
orencryptWith
methods if you want to digitally sign or encrypt the JWT. -
Call the
compact()
method to produce the resulting compact JWT string.
For example:
String jwt = Jwts.builder() // (1)
.header() // (2) optional
.keyId("aKeyId")
.and()
.subject("Bob") // (3) JSON Claims, or
//.content(aByteArray, "text/plain") // any byte[] content, with media type
.signWith(signingKey) // (4) if signing, or
//.encryptWith(key, keyAlg, encryptionAlg) // if encrypting
.compact(); // (5)
-
The JWT
payload
may be eitherbyte[]
content (viacontent
) or JSON Claims (such assubject
,claims
, etc), but not both. -
Either digital signatures (
signWith
) or encryption (encryptWith
) may be used, but not both.
|
Unprotected JWTs: If you do not use the |
A JWT header is a JSON Object
that provides metadata about the contents, format, and any cryptographic operations
relevant to the JWT payload
. JJWT provides a number of ways of setting the entire header and/or multiple individual
header parameters (name/value pairs).
The easiest and recommended way to set one or more JWT header parameters (name/value pairs) is to use the
JwtBuilder
's header()
builder as desired, and then call its and()
method to return back
to the JwtBuilder
for further configuration. For example:
String jwt = Jwts.builder()
.header() // <----
.keyId("aKeyId")
.x509Url(aUri)
.add("someName", anyValue)
.add(mapValues)
// ... etc ...
.and() // go back to the JwtBuilder
.subject("Joe") // resume JwtBuilder calls...
// ... etc ...
.compact();
The JwtBuilder
header()
builder also supports automatically calculating X.509 thumbprints and other builder-style benefits that
a simple property getter/setter object would not do.
ℹ️ NOTE
|
Automatic Headers: You do not need to set the |